Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/frontend/config/redirects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const redirects = {
'/app-host/persistent-containers/': '/app-host/resource-lifetimes/',
'/ja/app-host/persistent-containers/': '/ja/app-host/resource-lifetimes/',
// Integration -client → -connect rename
'/integrations/ai/github-models/github-models-client/': '/integrations/ai/github-models/github-models-connect/',
'/integrations/ai/github-models/github-models-client/': '/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-connect/',
'/integrations/ai/ollama/ollama-client/': '/integrations/ai/ollama/ollama-connect/',
'/integrations/ai/openai/openai-client/': '/integrations/ai/openai/openai-connect/',
'/integrations/caching/garnet/garnet-client/': '/integrations/caching/garnet/garnet-connect/',
Expand Down Expand Up @@ -143,7 +143,11 @@ export const redirects = {
'/integrations/databases/efcore/postgresql/': '/integrations/databases/efcore/postgres/postgresql-get-started/',
'/integrations/databases/efcore/azure-postgresql/': '/integrations/databases/efcore/azure-postgresql/azure-postgresql-get-started/',
'/integrations/databases/efcore/azure-sql/': '/integrations/databases/efcore/azure-sql/azure-sql-get-started/',
'/integrations/ai/github-models/': '/integrations/ai/github-models/github-models-get-started/',
// GitHub Models was retired; its former pages redirect to the Microsoft Foundry integration.
'/integrations/ai/github-models/': '/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started/',
'/integrations/ai/github-models/github-models-get-started/': '/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-get-started/',
'/integrations/ai/github-models/github-models-host/': '/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-host/',
'/integrations/ai/github-models/github-models-connect/': '/integrations/cloud/azure/azure-ai-foundry/azure-ai-foundry-connect/',
'/integrations/ai/openai/': '/integrations/ai/openai/openai-get-started/',
'/integrations/caching/redis/': '/integrations/caching/redis/redis-get-started/',
'/integrations/caching/redis-distributed/': '/integrations/caching/redis-distributed/redis-distributed-get-started/',
Expand Down
18 changes: 0 additions & 18 deletions src/frontend/config/sidebar/integrations.topics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,24 +127,6 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
'zh-CN': '人工智能 (AI)',
},
items: [
{
label: 'GitHub Models',
collapsed: true,
items: [
{
label: 'Get started',
slug: 'integrations/ai/github-models/github-models-get-started',
},
{
label: 'Set up GitHub Models in the AppHost',
slug: 'integrations/ai/github-models/github-models-host',
},
{
label: 'Connect to GitHub Models',
slug: 'integrations/ai/github-models/github-models-connect',
},
],
},
{
label: 'Ollama',
collapsed: true,
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/scripts/update-integration-data.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,9 @@ function Get-VersionMap {
if ([string]::IsNullOrWhiteSpace($JsonText)) { return $map }
$entries = $JsonText | ConvertFrom-Json
foreach ($entry in @($entries)) {
if ($entry.PSObject.Properties.Name -contains 'apiReference' -and $entry.apiReference -eq $false) {
continue
}
if ($entry.PSObject.Properties.Name -contains 'title' -and
$entry.PSObject.Properties.Name -contains 'version') {
$title = [string]$entry.title
Expand Down
75 changes: 73 additions & 2 deletions src/frontend/scripts/update-integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,31 @@ import { normalizeAspireTerminology } from './aspire-terminology';
const OFFICIAL_NUGET_ORG_QUERIES = ['owner:aspire', 'Aspire.Hosting.'];
const OFFICIAL_RELEASE_FEED_QUERIES = ['Aspire.'];
const COMMUNITY_TOOLKIT_QUERIES = ['CommunityToolkit.Aspire'];
// Third-party packages whose IDs don't start with `Aspire.` or `CommunityToolkit.Aspire`.
// They are fetched by exact package ID and must still be verified and non-deprecated.
export const THIRD_PARTY_PACKAGES = ['Particular.Aspire.Hosting.ServicePlatform'];
const THIRD_PARTY_QUERIES = THIRD_PARTY_PACKAGES.map((id) => `packageid:${id}`);
const EXCLUDED_PACKAGES = [
'Aspire.Cli',
'Aspire.Hosting.Azure.AIFoundry',
'Aspire.Hosting.GitHub.Models',
'Aspire.Hosting.IncrementalMigration',
'Aspire.Hosting.NodeJs',
'Aspire.Hosting.Testing',
'Aspire.Microsoft.AspNetCore.SystemWebAdapters',
'Aspire.MongoDB.Driver.v3',
'Aspire.RabbitMQ.Client.v7',
'CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps',
'CommunityToolkit.Aspire.Hosting.Bun',
'CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis',
'CommunityToolkit.Aspire.Hosting.EventStore',
'CommunityToolkit.Aspire.Hosting.Golang',
'CommunityToolkit.Aspire.Hosting.Java',
'CommunityToolkit.Aspire.Hosting.Minio',
'CommunityToolkit.Aspire.Hosting.Python.Extensions',
'CommunityToolkit.Aspire.Hosting.Rust',
'CommunityToolkit.Aspire.EventStore',
'CommunityToolkit.Aspire.Minio.Client',
];
const OUTPUT_PATH = './src/data/aspire-integrations.json';
const DOCS_OUTPUT_PATH = './src/data/integration-docs.json';
Expand Down Expand Up @@ -119,6 +131,8 @@ export interface IntegrationOutput {
tags: string[];
downloads?: number;
version?: string;
// `false` for third-party packages, whose API reference is owned by their authors.
apiReference?: false;
}

interface IntegrationDoc {
Expand Down Expand Up @@ -304,14 +318,17 @@ export function reconcileIntegrationDocs(
});
}

function filterAndTransform(pkgs: PackageRecord[]): IntegrationOutput[] {
export function filterAndTransform(pkgs: PackageRecord[]): IntegrationOutput[] {
const excludedLower = EXCLUDED_PACKAGES.map((pkg) => pkg.toLowerCase());
const thirdPartyLower = THIRD_PARTY_PACKAGES.map((pkg) => pkg.toLowerCase());

return pkgs
.filter((pkg) => {
const id = pkg.id.toLowerCase();
return (
(id.startsWith('aspire.') || id.startsWith('communitytoolkit.aspire')) &&
(id.startsWith('aspire.') ||
id.startsWith('communitytoolkit.aspire') ||
thirdPartyLower.includes(id)) &&
(pkg.__trustedSource || pkg.verified === true) &&
pkg.deprecated !== true &&
!pkg.deprecation &&
Expand All @@ -328,6 +345,7 @@ function filterAndTransform(pkgs: PackageRecord[]): IntegrationOutput[] {
tags: pkg.tags?.map((tag) => tag.toLowerCase()) ?? [],
downloads: pkg.totalDownloads,
version: pkg.version,
...(thirdPartyLower.includes(pkg.id.toLowerCase()) && { apiReference: false as const }),
}));
}

Expand Down Expand Up @@ -738,6 +756,50 @@ export function reconcileReleaseBranchCatalog(
return [...reconciled, ...carriedForward].sort((a, b) => a.title.localeCompare(b.title));
}

// The nuget.org search replicas lag the package page's download stats, so third-party
// packages take their total from the package page's version history table instead.
export function parseNuGetPageDownloads(html: string): number | null {
const rows = html.match(/<tr[^>]*\bversion-row\b[\s\S]*?<\/tr>/g) ?? [];
let total = 0;
let counted = 0;

for (const row of rows) {
const cells = [...row.matchAll(/<td[^>]*>\s*([\d,]+)\s*<\/td>/g)];
if (cells.length > 0) {
total += Number(cells[0][1].replace(/,/g, ''));
counted++;
}
}

return counted > 0 ? total : null;
}

async function applyNuGetPageDownloads(output: IntegrationOutput[]): Promise<IntegrationOutput[]> {
const thirdPartyLower = new Set(THIRD_PARTY_PACKAGES.map((id) => id.toLowerCase()));

return Promise.all(
output.map(async (entry) => {
if (!thirdPartyLower.has(entry.title.toLowerCase())) {
return entry;
}

try {
const res = await fetch(`https://www.nuget.org/packages/${encodeURIComponent(entry.title)}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const downloads = parseNuGetPageDownloads(await res.text());
return downloads === null ? entry : { ...entry, downloads };
} catch (error: unknown) {
console.warn(
`⚠️ Could not read the nuget.org page download count for ${entry.title}: ${getErrorMessage(error)}`
);
return entry;
}
})
);
}

export async function updateIntegrations(): Promise<void> {
const officialSource = resolveOfficialAspirePackageSource();
const releaseVersion = getPinnedReleaseVersion(officialSource.branchName);
Expand Down Expand Up @@ -770,6 +832,14 @@ export async function updateIntegrations(): Promise<void> {
queries: COMMUNITY_TOOLKIT_QUERIES,
optional: true,
},
{
label: 'Third-party integrations (nuget.org)',
priority: 0,
trusted: false,
serviceIndex: NUGET_ORG_SERVICE_INDEX,
queries: THIRD_PARTY_QUERIES,
optional: true,
},
];

const results = await Promise.all(
Expand Down Expand Up @@ -832,6 +902,7 @@ export async function updateIntegrations(): Promise<void> {
if (officialSource.isReleaseBranch) {
output = reconcileReleaseBranchCatalog(output, fetchedPackageIds);
}
output = await applyNuGetPageDownloads(output);
const defaultIconPackages = getOfficialAspireDefaultIconPackages(output);
const releaseIconPackages = output.filter((pkg) => pkg.icon === ASPIRE_RELEASE_ICON_URL);
if (releaseIconPackages.length > 0) {
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/scripts/validate-generated-api-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { readEnumDeclarations } from './supplement-ats-enums';
interface CatalogEntry {
title: string;
version: string;
apiReference?: boolean;
}

interface PackageMetadata {
Expand Down Expand Up @@ -377,7 +378,7 @@ export function validateGeneratedApiData(input: ValidationInput): ValidationResu
);

for (const entry of input.catalog) {
if (!isPackageOutputExpected(entry.title)) continue;
if (!isPackageOutputExpected(entry.title) || entry.apiReference === false) continue;
const key = `${entry.title}@${entry.version}`;
if (!packageByIdentity.has(key)) {
errors.push(`Missing C# API output for catalog package ${key}.`);
Expand Down
Binary file removed src/frontend/src/assets/icons/github-icon.png
Binary file not shown.
Binary file not shown.
Binary file removed src/frontend/src/assets/icons/minio-icon.png
Binary file not shown.
13 changes: 6 additions & 7 deletions src/frontend/src/components/home/HomeIntegrationRail.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import PauseIcon from '@assets/icons/pause.svg';
import PlayIcon from '@assets/icons/play.svg';
import activeMq from '@assets/icons/activemq-icon.png';
import activeMqLight from '@assets/icons/activemq-light-icon.png';
import apacheKafka from '@assets/icons/apache-kafka-icon.svg';
import aws from '@assets/icons/aws-icon.png';
import awsLight from '@assets/icons/aws-light-icon.png';
import azureAiFoundry from '@assets/icons/azure-ai-foundry-icon.png';
import azureCosmosDb from '@assets/icons/azure-cosmosdb-icon.png';
import azure from '@assets/icons/azure-icon.png';
import azureServiceBus from '@assets/icons/azure-servicebus-icon.png';
import bun from '@assets/icons/bun-icon.png';
import clickhouse from '@assets/icons/clickhouse-icon.png';
import dapr from '@assets/icons/dapr-icon.png';
import daprLight from '@assets/icons/dapr-light-icon.png';
import deno from '@assets/icons/deno-icon.png';
Expand All @@ -19,16 +20,13 @@ import docker from '@assets/icons/docker.svg';
import elasticsearch from '@assets/icons/elastic-icon.png';
import go from '@assets/icons/go-icon.png';
import goLight from '@assets/icons/go-light-icon.png';
import github from '@assets/icons/github-icon.png';
import githubLight from '@assets/icons/github-light-icon.png';
import java from '@assets/icons/java-icon.png';
import keycloak from '@assets/icons/keycloak-icon.svg';
import kubernetes from '@assets/icons/kubernetes.svg';
import kurrentDb from '@assets/icons/kurrent-icon.png';
import kurrentDbLight from '@assets/icons/kurrent-light-icon.png';
import meilisearch from '@assets/icons/meilisearch-icon.png';
import milvus from '@assets/icons/milvus-icon.png';
import minio from '@assets/icons/minio-icon.png';
import mongodb from '@assets/icons/mongodb-icon.png';
import mysql from '@assets/icons/mysqlconnector-icon.png';
import nats from '@assets/icons/nats-icon.png';
Expand All @@ -38,6 +36,7 @@ import ollama from '@assets/icons/ollama-icon.png';
import openAi from '@assets/icons/openai-icon.png';
import openAiLight from '@assets/icons/openai-light-icon.png';
import postgresql from '@assets/icons/postgresql-icon.png';
import powershell from '@assets/icons/powershell-icon.png';
import python from '@assets/icons/python.svg';
import qdrant from '@assets/icons/qdrant-icon.svg';
import rabbitMq from '@assets/icons/rabbitmq-icon.svg';
Expand All @@ -60,7 +59,7 @@ const foundationIntegrations = [
{ name: 'RabbitMQ', icon: rabbitMq, search: 'rabbitmq' },
{ name: 'Azure', icon: azure, search: 'azure' },
{ name: 'AWS', icon: aws, lightIcon: awsLight, search: 'aws' },
{ name: 'GitHub', icon: github, lightIcon: githubLight, search: 'github' },
{ name: 'Apache Kafka', icon: apacheKafka, search: 'kafka' },
{ name: 'OpenAI', icon: openAi, lightIcon: openAiLight, search: 'openai' },
{ name: 'ActiveMQ', icon: activeMq, lightIcon: activeMqLight, search: 'activemq' },
] as const;
Expand All @@ -72,7 +71,7 @@ const dataIntegrations = [
{ name: 'NATS', icon: nats, search: 'nats' },
{ name: 'Dapr', icon: dapr, lightIcon: daprLight, search: 'dapr' },
{ name: 'Keycloak', icon: keycloak, search: 'keycloak' },
{ name: 'MinIO', icon: minio, search: 'minio' },
{ name: 'ClickHouse', icon: clickhouse, search: 'clickhouse' },
{ name: 'Meilisearch', icon: meilisearch, search: 'meilisearch' },
{ name: 'Elasticsearch', icon: elasticsearch, search: 'elasticsearch' },
{ name: 'Ollama', icon: ollama, search: 'ollama' },
Expand All @@ -82,7 +81,7 @@ const dataIntegrations = [

const runtimeIntegrations = [
{ name: 'Node.js', icon: nodeJs, lightIcon: nodeJsLight, search: 'nodejs' },
{ name: 'Bun', icon: bun, search: 'bun' },
{ name: 'PowerShell', icon: powershell, search: 'powershell' },
{ name: 'Deno', icon: deno, lightIcon: denoLight, search: 'deno' },
{ name: 'Go', icon: go, lightIcon: goLight, search: 'golang gofeature' },
{ name: 'Java', icon: java, search: 'java' },
Expand Down
Loading
Loading