Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-metadata"
---

Add `apiVersion` field to `LanguagePackageMetadata` resolved via TCGC's `createSdkContext`. Values: `"all"` when configured for all versions, `"multiple-versions"` for multi-service configs, the actual resolved version string, or `undefined` when unavailable. Also adds `api-version` as a passthrough emitter option.
10 changes: 9 additions & 1 deletion packages/typespec-metadata/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,19 @@
"yaml": "catalog:"
},
"peerDependencies": {
"@typespec/compiler": "workspace:^"
"@azure-tools/typespec-azure-core": "workspace:^",
"@azure-tools/typespec-client-generator-core": "workspace:^",
"@typespec/compiler": "workspace:^",
"@typespec/versioning": "workspace:^"
},
"devDependencies": {
"@azure-tools/typespec-azure-core": "workspace:^",
"@azure-tools/typespec-client-generator-core": "workspace:^",
"@types/node": "catalog:",
"@typespec/compiler": "workspace:^",
"@typespec/http": "workspace:^",
"@typespec/rest": "workspace:^",
"@typespec/versioning": "workspace:^",
"@vitest/coverage-v8": "catalog:",
"@vitest/ui": "catalog:",
"rimraf": "catalog:",
Expand Down
29 changes: 28 additions & 1 deletion packages/typespec-metadata/src/collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ export interface LanguageCollectionResult {
export async function collectLanguagePackages(
program: Program,
baseOutputDir: string,
fallbackApiVersion?: string,
resolvedSdkType?: "preview" | "stable",
): Promise<LanguageCollectionResult> {
const optionMap = program.compilerOptions.options ?? {};
const params = extractParameters(optionMap);
Expand All @@ -336,7 +338,14 @@ export async function collectLanguagePackages(
}

return {
languages: buildLanguageMetadata(optionMap, params, baseOutputDir, defaultServiceDir),
languages: buildLanguageMetadata(
optionMap,
params,
baseOutputDir,
defaultServiceDir,
fallbackApiVersion,
resolvedSdkType,
),
sourceConfigPath: program.compilerOptions.config,
};
}
Expand Down Expand Up @@ -483,6 +492,8 @@ export function buildLanguageMetadata(
params: Record<string, unknown>,
baseOutputDir: string,
defaultServiceDir?: string,
fallbackApiVersion?: string,
resolvedSdkType?: "preview" | "stable",
): Record<string, LanguagePackageMetadata[]> {
const languagesDict: Record<string, LanguagePackageMetadata[]> = {};

Expand All @@ -493,6 +504,8 @@ export function buildLanguageMetadata(
params,
baseOutputDir,
defaultServiceDir,
fallbackApiVersion,
resolvedSdkType,
);
const language = inferLanguageFromEmitterName(emitterName);
if (!languagesDict[language]) {
Expand All @@ -510,6 +523,8 @@ function createLanguageMetadata(
params: Record<string, unknown>,
baseOutputDir: string,
defaultServiceDir?: string,
fallbackApiVersion?: string,
resolvedSdkType?: "preview" | "stable",
): LanguagePackageMetadata {
const normalizedOptions = normalizeOptionsObject(emitterOptions);

Expand Down Expand Up @@ -578,13 +593,25 @@ function createLanguageMetadata(
}
}

// Read api-version from this emitter's options first; fall back to TCGC resolution.
// "multiple-versions" (from TCGC multi-service detection) always supersedes any per-emitter value.
const emitterApiVersion = normalizedOptions["api-version"];
const apiVersion =
fallbackApiVersion === "multiple-versions"
? "multiple-versions"
: typeof emitterApiVersion === "string"
? emitterApiVersion
: fallbackApiVersion;

return {
emitterName,
packageName,
namespace,
outputDir: relativeOutputDir,
flavor: flavor ? String(flavor) : undefined,
serviceDir,
apiVersion,
sdkType: resolvedSdkType,
};
}

Expand Down
98 changes: 96 additions & 2 deletions packages/typespec-metadata/src/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { emitFile, getDirectoryPath, resolvePath, type EmitContext } from "@typespec/compiler";
import { isPreviewVersion } from "@azure-tools/typespec-azure-core";
import type { SdkClientType, SdkHttpOperation } from "@azure-tools/typespec-client-generator-core";
import { createSdkContext } from "@azure-tools/typespec-client-generator-core";
import {
emitFile,
getDirectoryPath,
resolvePath,
type EmitContext,
type Program,
} from "@typespec/compiler";
import { getVersions } from "@typespec/versioning";
import { stringify as stringifyYaml } from "yaml";
import packageJson from "../package.json" with { type: "json" };
import { buildSpecMetadata, collectLanguagePackages } from "./collector.js";
Expand All @@ -18,7 +28,31 @@ export async function $onEmit(context: EmitContext<MetadataEmitterOptions>): Pro
// Get the common tsp-output directory (parent of this emitter's output dir)
const commonOutputDir = getDirectoryPath(getDirectoryPath(context.emitterOutputDir));

const languageResult = await collectLanguagePackages(context.program, commonOutputDir);
// Use TCGC for fallback API version resolution and SDK type detection
const sdkContext = await createSdkContext(context as any);
const apiVersionsMap = sdkContext.sdkPackage.metadata.apiVersions;
let fallbackApiVersion: string | undefined;
if (apiVersionsMap && apiVersionsMap.size > 0) {
if (apiVersionsMap.size > 1) {
fallbackApiVersion = "multiple-versions";
} else {
fallbackApiVersion = [...apiVersionsMap.values()][0];
}
}

// Resolve SDK type (preview/stable) by checking all client API versions
const resolvedSdkType = resolveSdkType(
context.program,
sdkContext.sdkPackage.clients,
sdkContext.previewStringRegex,
);

const languageResult = await collectLanguagePackages(
context.program,
commonOutputDir,
fallbackApiVersion,
resolvedSdkType,
);

const snapshot: MetadataSnapshot = {
emitterVersion: SNAPSHOT_VERSION,
Expand Down Expand Up @@ -48,3 +82,63 @@ async function writeSnapshot(
content: serialized,
});
}

/**
* Determine whether the SDK targets preview or stable API versions.
*
* Priority:
* 1. `@previewVersion` decorator on any version enum member (always wins)
* 2. Regex match against the `previewStringRegex` on client API version strings
*/
function resolveSdkType(
program: Program,
clients: SdkClientType<SdkHttpOperation>[],
previewStringRegex: RegExp,
): "preview" | "stable" | undefined {
// First check: @previewVersion decorator on version enum members
for (const client of clients) {
if (!client.__raw?.services) continue;
for (const serviceNs of client.__raw.services) {
const versionResult = getVersions(program, serviceNs);
if (versionResult.length === 2 && versionResult[1]) {
const versionMap = versionResult[1];
for (const version of versionMap.getVersions()) {
if (isPreviewVersion(program, version.enumMember)) {
return "preview";
}
}
}
}
}

// Second check: regex match on client API version strings
let hasAnyVersion = false;

function checkClient(client: SdkClientType<SdkHttpOperation>): boolean {
for (const version of client.apiVersions) {
hasAnyVersion = true;
if (previewStringRegex.test(version)) {
return true;
}
}

// Recurse into sub-clients
if (client.children) {
for (const child of client.children) {
if (checkClient(child)) {
return true;
}
}
}

return false;
}

for (const client of clients) {
if (checkClient(client)) {
return "preview";
}
}

return hasAnyVersion ? "stable" : undefined;
}
15 changes: 15 additions & 0 deletions packages/typespec-metadata/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ export interface LanguagePackageMetadata {
flavor?: string;
/** Service directory path for this language emitter. */
serviceDir?: string;
/**
* Resolved API version for this emitter.
* - `"all"` when configured for all versions
* - `"multiple-versions"` for multi-service configs with more than one API version entry
* - An actual resolved version string (e.g. `"2023-10-01"`) for a single version
* - `undefined` when no API version information is available
*/
apiVersion?: string;
/**
* Whether the SDK targets preview or stable API versions.
* - `"preview"` if any targeted API version is a preview version (matches `-preview` suffix or has `@previewVersion` decorator)
* - `"stable"` if all targeted API versions are stable
* - `undefined` when no API version information is available
*/
sdkType?: "preview" | "stable";
}

export interface MetadataSnapshot {
Expand Down
Loading
Loading