diff --git a/.chronus/changes/tcgc-add-ispreview-to-metadata-2026-7-27-15-15-0.md b/.chronus/changes/tcgc-add-ispreview-to-metadata-2026-7-27-15-15-0.md new file mode 100644 index 0000000000..0c8822c91f --- /dev/null +++ b/.chronus/changes/tcgc-add-ispreview-to-metadata-2026-7-27-15-15-0.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +Add `isPreview` boolean to `SdkPackageType.metadata` to indicate whether the SDK targets any preview API versions. diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index dd7ebf03d3..1e3ff3650f 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -1371,6 +1371,13 @@ export interface SdkPackage { * If value is a string, the service is versioned with the specified version. */ apiVersions?: Map; + /** + * Whether the package targets any preview API versions. + * `true` if any targeted API version has the `@previewVersion` decorator or matches the preview string regex. + * `false` if all versions are stable. + * `undefined` if no versioning is present. + */ + isPreview?: boolean; }; } diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index d5c71f5444..9668e5eed5 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -4,6 +4,7 @@ import { getNamespaceFullName, ignoreDiagnostics, } from "@typespec/compiler"; +import { isPreviewVersion } from "@azure-tools/typespec-azure-core"; import { prepareClientAndOperationCache } from "./cache.js"; import { createSdkClientType } from "./clients.js"; @@ -72,6 +73,7 @@ export async function createSdkPackage b.toString(16).padStart(2, "0")).join(""); return hex.substring(0, 12); } + +function computeIsPreview( + context: TCGCContext, + versions: Map, +): boolean | undefined { + const allVersions = [...versions.values()].flat(); + if (allVersions.length === 0) { + return undefined; + } + + // First check @previewVersion decorator on enum members + for (const versionEnum of context.getPackageVersionEnum().values()) { + if (!versionEnum) continue; + for (const member of versionEnum.members.values()) { + if (isPreviewVersion(context.program, member)) { + return true; + } + } + } + + // Fall back to regex check for backward compatibility + return allVersions.some((v) => context.previewStringRegex.test(v)); +} diff --git a/packages/typespec-client-generator-core/test/package/versioning.test.ts b/packages/typespec-client-generator-core/test/package/versioning.test.ts index 5c5ea80387..1e56d05a2a 100644 --- a/packages/typespec-client-generator-core/test/package/versioning.test.ts +++ b/packages/typespec-client-generator-core/test/package/versioning.test.ts @@ -1525,3 +1525,87 @@ it("multi-service client has no versionsEnum", async () => { ok(biClient.versionsEnum); strictEqual(biClient.versionsEnum.name, "VersionsB"); }); + +it("isPreview is false for stable versions", async () => { + const { program } = await SimpleTester.compile(` + @service + @versioned(Versions) + namespace TestService; + enum Versions { + v1, + v2, + v3, + } + op test(): void; + `); + + const context = await createSdkContextForTester(program); + const sdkPackage = context.sdkPackage; + strictEqual(sdkPackage.metadata.isPreview, false); +}); + +it("isPreview is undefined for unversioned service", async () => { + const { program } = await SimpleTester.compile(` + @service + namespace TestService; + op test(): void; + `); + + const context = await createSdkContextForTester(program); + const sdkPackage = context.sdkPackage; + strictEqual(sdkPackage.metadata.apiVersion, undefined); + strictEqual(sdkPackage.metadata.isPreview, undefined); +}); + +it("isPreview is true for service with preview suffix version", async () => { + const { program } = await SimpleTester.compile(` + @service + @versioned(Versions) + namespace TestService; + enum Versions { + v2024_10_01_preview: "2024-10-01-preview", + } + op test(): void; + `); + + const context = await createSdkContextForTester(program); + const sdkPackage = context.sdkPackage; + strictEqual(sdkPackage.metadata.isPreview, true); +}); + +it("isPreview is true for service with mixed stable and preview versions", async () => { + const { program } = await SimpleTester.compile(` + @service + @versioned(Versions) + namespace TestService; + enum Versions { + v2024_10_01: "2024-10-01", + v2024_11_01_preview: "2024-11-01-preview", + } + op test(): void; + `); + + const context = await createSdkContextForTester(program); + const sdkPackage = context.sdkPackage; + strictEqual(sdkPackage.metadata.isPreview, true); +}); + +it("isPreview is true for service with @previewVersion decorator", async () => { + const { program } = await AzureCoreTester.compile(` + @service + @versioned(Versions) + namespace TestService; + enum Versions { + v2022_10_01: "2022-10-01", + #suppress "@azure-tools/typespec-azure-core/preview-version-last-member" "for test" + @previewVersion + v2022_11_01: "2022-11-01", + v2024_10_01: "2024-10-01", + } + op test(): void; + `); + + const context = await createSdkContextForTester(program); + const sdkPackage = context.sdkPackage; + strictEqual(sdkPackage.metadata.isPreview, true); +});