diff --git a/packages/rlc-common/src/metadata/buildPackageFile.ts b/packages/rlc-common/src/metadata/buildPackageFile.ts index fe24339f22..8e9d6f7f4e 100644 --- a/packages/rlc-common/src/metadata/buildPackageFile.ts +++ b/packages/rlc-common/src/metadata/buildPackageFile.ts @@ -91,15 +91,17 @@ export function buildPackageFile( export function updatePackageFile( model: RLCModel, existingFilePathOrContent: string | Record, - { exports }: PackageFileOptions = {} + { exports, clientContextPaths }: PackageFileOptions = {} ) { const hasLro = hasPollingOperations(model); const isAzure = isAzurePackage(model); const needsLroUpdate = isAzure && hasLro; const needsExportsUpdate = exports; + const needsConstantPathsUpdate = + clientContextPaths && clientContextPaths.length > 0; // Early return if nothing needs to be updated - if (!needsLroUpdate && !needsExportsUpdate) { + if (!needsLroUpdate && !needsExportsUpdate && !needsConstantPathsUpdate) { return; } @@ -136,6 +138,21 @@ export function updatePackageFile( }; } + // Update constantPaths metadata for Azure packages + if (needsConstantPathsUpdate && isAzure && packageInfo["//metadata"]) { + const metadata = packageInfo["//metadata"]; + // Filter out existing userAgentInfo entries + const nonUserAgentPaths = (metadata.constantPaths || []).filter( + (item: any) => item.prefix !== "userAgentInfo" + ); + // Add new userAgentInfo entries from clientContextPaths + const newUserAgentPaths = clientContextPaths!.map((path) => ({ + path: path, + prefix: "userAgentInfo" + })); + metadata.constantPaths = [...nonUserAgentPaths, ...newUserAgentPaths]; + } + return { path: "package.json", content: JSON.stringify(packageInfo, null, 2) diff --git a/packages/rlc-common/src/metadata/buildReadmeFile.ts b/packages/rlc-common/src/metadata/buildReadmeFile.ts index f0dfb5c506..e0ffcf1c49 100644 --- a/packages/rlc-common/src/metadata/buildReadmeFile.ts +++ b/packages/rlc-common/src/metadata/buildReadmeFile.ts @@ -370,20 +370,38 @@ export function updateReadmeFile( try { const existingContent = readFileSync(existingReadmeFilePath, "utf8"); const metadata = createMetadata(model) ?? {}; + const newClientName = getClientName(model); + let updatedContent = existingContent; + + // Update API reference link const newApiRefLink = hbs .compile(apiReferenceTemplate, { noEscape: true })(metadata) .trim(); - if (!newApiRefLink) { - return { path: "README.md", content: existingContent }; + if (newApiRefLink) { + const apiRefRegex = + /^- \[API reference documentation\]\(https:\/\/learn\.microsoft\.com\/javascript\/api\/[^)]+\)$/m; + updatedContent = updatedContent.replace(apiRefRegex, (match) => + match ? newApiRefLink : match + ); } - const apiRefRegex = - /^- \[API reference documentation\]\(https:\/\/learn\.microsoft\.com\/javascript\/api\/[^)]+\)$/m; - const updatedContent = existingContent.replace(apiRefRegex, (match) => - match ? newApiRefLink : match + // Extract old client name from existing README import statements + const importMatch = existingContent.match( + /import\s*\{\s*([A-Za-z0-9_]+)\s*\}\s*from\s*["'][^"']*["']/ ); + const oldClientName = importMatch?.[1]; + + // Only update client name if we found an old name and it's different from the new one + if (oldClientName && newClientName && oldClientName !== newClientName) { + // Create a regex that matches the old client name as a whole word + const oldClientNameRegex = new RegExp(`\\b${oldClientName}\\b`, "g"); + updatedContent = updatedContent.replace( + oldClientNameRegex, + newClientName + ); + } return { path: "README.md", content: updatedContent }; } catch { diff --git a/packages/rlc-common/test/integration/packageJson.spec.ts b/packages/rlc-common/test/integration/packageJson.spec.ts index b18bd0baed..6369d12e26 100644 --- a/packages/rlc-common/test/integration/packageJson.spec.ts +++ b/packages/rlc-common/test/integration/packageJson.spec.ts @@ -815,6 +815,114 @@ describe("Package file generation", () => { ); expect(packageFile).to.not.have.property("tshy"); }); + + it("should update constantPaths when clientContextPaths option is provided for Azure packages", () => { + const model = createMockModel({ + moduleKind: "esm", + flavor: "azure", + isMonorepo: true, + hasLro: false + }); + + const initialPackageInfo = { + name: "@azure/test-package", + version: "1.0.0", + dependencies: { + "@azure/core-client": "^1.0.0" + }, + "//metadata": { + constantPaths: [ + { path: "src/old-path.ts", prefix: "userAgentInfo" }, + { path: "src/other-file.ts", prefix: "packageDetails" } + ] + } + }; + + const packageFileContent = updatePackageFile(model, initialPackageInfo, { + clientContextPaths: [ + "src/api/newContext.ts", + "src/api/anotherContext.ts" + ] + }); + const packageFile = JSON.parse(packageFileContent?.content ?? "{}"); + + expect(packageFile["//metadata"]).to.have.property("constantPaths"); + const constantPaths = packageFile["//metadata"]["constantPaths"]; + + // Should keep non-userAgentInfo entries + expect(constantPaths).to.deep.include({ + path: "src/other-file.ts", + prefix: "packageDetails" + }); + + // Should replace old userAgentInfo entries with new ones + expect(constantPaths).to.deep.include({ + path: "src/api/newContext.ts", + prefix: "userAgentInfo" + }); + expect(constantPaths).to.deep.include({ + path: "src/api/anotherContext.ts", + prefix: "userAgentInfo" + }); + + // Should not include old userAgentInfo entry + expect(constantPaths).to.not.deep.include({ + path: "src/old-path.ts", + prefix: "userAgentInfo" + }); + }); + + it("should not update constantPaths when clientContextPaths is empty", () => { + const model = createMockModel({ + moduleKind: "esm", + flavor: "azure", + isMonorepo: true, + hasLro: false + }); + + const initialPackageInfo = { + name: "@azure/test-package", + version: "1.0.0", + "//metadata": { + constantPaths: [{ path: "src/old-path.ts", prefix: "userAgentInfo" }] + } + }; + + const packageFileContent = updatePackageFile(model, initialPackageInfo, { + clientContextPaths: [] + }); + + // Should return undefined when nothing needs to be updated + expect(packageFileContent).to.be.undefined; + }); + + it("should not update constantPaths for non-Azure packages", () => { + const model = createMockModel({ + moduleKind: "esm", + flavor: undefined, + isMonorepo: true, + hasLro: false + }); + + const initialPackageInfo = { + name: "@test/test-package", + version: "1.0.0", + "//metadata": { + constantPaths: [{ path: "src/old-path.ts", prefix: "userAgentInfo" }] + } + }; + + const packageFileContent = updatePackageFile(model, initialPackageInfo, { + clientContextPaths: ["src/api/newContext.ts"] + }); + + // Should return package.json but without updating constantPaths for non-Azure packages + expect(packageFileContent).to.not.be.undefined; + const packageFile = JSON.parse(packageFileContent?.content ?? "{}"); + expect(packageFile["//metadata"]["constantPaths"]).to.deep.equal([ + { path: "src/old-path.ts", prefix: "userAgentInfo" } + ]); + }); }); describe("Flavorless lib", () => { diff --git a/packages/typespec-ts/src/index.ts b/packages/typespec-ts/src/index.ts index 3041e7f488..7c35608572 100644 --- a/packages/typespec-ts/src/index.ts +++ b/packages/typespec-ts/src/index.ts @@ -557,7 +557,11 @@ export async function $onEmit(context: EmitContext) { let modularPackageInfo = {}; if (option.isModularLibrary) { modularPackageInfo = { - exports: getModuleExports(context, modularEmitterOptions) + exports: getModuleExports(context, modularEmitterOptions), + clientContextPaths: getRelativeContextPaths( + context, + modularEmitterOptions + ) }; } await emitContentByBuilder( @@ -577,6 +581,19 @@ export async function $onEmit(context: EmitContext) { dpgContext.generationPathDetail?.metadataDir ); } + + // Regenerate snippets.spec.ts for documentation + if (option.azureSdkForJs) { + for (const subClient of dpgContext.sdkPackage.clients) { + await emitContentByBuilder( + program, + (model) => + buildSnippets(model, subClient.name, option.azureSdkForJs), + rlcClient, + dpgContext.generationPathDetail?.metadataDir + ); + } + } } if (isAzureFlavor) { await emitContentByBuilder(