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
1 change: 1 addition & 0 deletions packages/rlc-common/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export interface PackageDetails {
nameWithoutScope?: string;
description?: string;
version?: string;
isVersionUserProvided?: boolean;
}
export interface OperationParameter {
operationGroup: string;
Expand Down
35 changes: 31 additions & 4 deletions packages/rlc-common/src/metadata/buildPackageFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {
isAzurePackage,
isAzureStandalonePackage
} from "../helpers/packageUtil.js";
import { PackageCommonInfoConfig } from "./packageJson/packageCommon.js";
import {
PackageCommonInfoConfig,
getTshyConfig
} from "./packageJson/packageCommon.js";
import { Project, SourceFile } from "ts-morph";
import { RLCModel } from "../interfaces.js";
import { buildAzureMonorepoPackage } from "./packageJson/buildAzureMonorepoPackage.js";
Expand Down Expand Up @@ -83,15 +86,23 @@ export function buildPackageFile(

/**
* Automatically updates the package.json with correct paging and LRO dependencies for Azure SDK.
* Also updates tshy.exports if provided.
*/
export function updatePackageFile(
model: RLCModel,
existingFilePathOrContent: string | Record<string, any>
existingFilePathOrContent: string | Record<string, any>,
{ exports }: PackageFileOptions = {}
) {
const hasLro = hasPollingOperations(model);
if (!isAzurePackage(model) || !hasLro) {
const isAzure = isAzurePackage(model);
const needsLroUpdate = isAzure && hasLro;
const needsExportsUpdate = exports;

// Early return if nothing needs to be updated
if (!needsLroUpdate && !needsExportsUpdate) {
return;
}

let packageInfo;
if (typeof existingFilePathOrContent === "string") {
let packageFile: SourceFile;
Expand All @@ -107,7 +118,23 @@ export function updatePackageFile(
packageInfo = existingFilePathOrContent;
}

if (hasLro) {
// Update tshy.exports if exports are provided and tshy exists
if (needsExportsUpdate && packageInfo.tshy) {
const newTshy = getTshyConfig({
description: packageInfo.description ?? "",
moduleKind: "esm",
name: packageInfo.name ?? "",
version: packageInfo.version ?? "1.0.0-beta.1",
withSamples: false,
withTests: false,
exports,
azureSdkForJs: model.options?.azureSdkForJs
});
packageInfo.tshy.exports = newTshy.exports;
}

// Update LRO dependencies for Azure packages
if (needsLroUpdate) {
packageInfo.dependencies = {
...packageInfo.dependencies,
"@azure/core-lro": "^3.1.0",
Expand Down
50 changes: 47 additions & 3 deletions packages/rlc-common/src/metadata/buildReadmeFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import hbs from "handlebars";
import { NameType, normalizeName } from "../helpers/nameUtils.js";
import { isAzurePackage } from "../helpers/packageUtil.js";
import { getClientName } from "../helpers/nameConstructors.js";
import { readFileSync } from "fs";

const azureReadmeRLCTemplate = `# {{ clientDescriptiveName }} library for JavaScript

Expand Down Expand Up @@ -288,6 +289,11 @@ npm install {{ clientPackageName }}
\`\`\`
`;

const apiReferenceTemplate = `{{#if apiRefURL}}
- [API reference documentation]({{ apiRefURL }})
{{/if}}
`;

/**
* Meta data information about the service, the package, and the client.
*/
Expand Down Expand Up @@ -357,6 +363,34 @@ export function buildReadmeFile(model: RLCModel) {
};
}

export function updateReadmeFile(
model: RLCModel,
existingReadmeFilePath: string
): { path: string; content: string } | undefined {
try {
const existingContent = readFileSync(existingReadmeFilePath, "utf8");
const metadata = createMetadata(model) ?? {};

const newApiRefLink = hbs
.compile(apiReferenceTemplate, { noEscape: true })(metadata)
.trim();

if (!newApiRefLink) {
return { path: "README.md", content: existingContent };
}

const apiRefRegex =
/^- \[API reference documentation\]\(https:\/\/learn\.microsoft\.com\/javascript\/api\/[^)]+\)$/m;
const updatedContent = existingContent.replace(apiRefRegex, (match) =>
match ? newApiRefLink : match
);

return { path: "README.md", content: updatedContent };
} catch {
return;
}
}

/**
* Returns meta data information about the service, the package, and the client.
* @param codeModel - include the client details
Expand Down Expand Up @@ -392,9 +426,19 @@ function createMetadata(model: RLCModel): Metadata | undefined {
const clientClassName = getClientName(model);
const serviceName = getServiceName(model);
let apiRefUrlQueryParameter: string = "";
packageDetails.version = packageDetails.version ?? "1.0.0-beta.1";
if (packageDetails?.version.includes("beta")) {
apiRefUrlQueryParameter = "?view=azure-node-preview";
if (
packageDetails?.version === "1.0.0-beta.1" &&
!packageDetails?.isVersionUserProvided &&
model.apiVersionInfo?.defaultValue
) {
if (model.apiVersionInfo?.defaultValue?.toLowerCase().includes("preview")) {
apiRefUrlQueryParameter = "?view=azure-node-preview";
}
} else {
packageDetails.version = packageDetails.version ?? "1.0.0-beta.1";
if (packageDetails?.version.includes("beta")) {
apiRefUrlQueryParameter = "?view=azure-node-preview";
}
}

return {
Expand Down
98 changes: 95 additions & 3 deletions packages/rlc-common/test/integration/packageJson.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,21 +775,113 @@ describe("Package file generation", () => {
isMonorepo: true,
hasLro: true
});

const initialPackageInfo = {
name: "@azure/test-package",
version: "1.0.0",
dependencies: {
"@azure/core-client": "^1.0.0"
}
};

const packageFileContent = updatePackageFile(model, initialPackageInfo);
const packageFile = JSON.parse(packageFileContent?.content ?? "{}");

expect(packageFile.dependencies).to.have.property("@azure/core-lro", "^3.1.0");
expect(packageFile.dependencies).to.have.property("@azure/abort-controller", "^2.1.2");
});

it("should update tshy.exports when exports option is provided", () => {
const model = createMockModel({
moduleKind: "esm",
flavor: "azure",
isMonorepo: true,
hasLro: true
});

const initialPackageInfo = {
name: "@azure/test-package",
version: "1.0.0",
dependencies: {
"@azure/core-client": "^1.0.0"
},
tshy: {
exports: {
"./package.json": "./package.json",
".": "./src/index.ts"
},
dialects: ["esm", "commonjs"],
esmDialects: ["browser", "react-native"],
selfLink: false
}
};

const newExports = {
"./api": "./src/api/index.ts",
"./models": "./src/models/index.ts"
};

const packageFileContent = updatePackageFile(model, initialPackageInfo, {
exports: newExports
});
const packageFile = JSON.parse(packageFileContent?.content ?? "{}");

expect(packageFile.dependencies).to.have.property(
"@azure/core-lro",
"^3.1.0"
);
expect(packageFile.dependencies).to.have.property(
"@azure/abort-controller",
"^2.1.2"
);
expect(packageFile.tshy).to.have.property("exports");
expect(packageFile.tshy.exports).to.deep.equal({
"./package.json": "./package.json",
".": "./src/index.ts",
"./api": "./src/api/index.ts",
"./models": "./src/models/index.ts"
});
expect(packageFile.tshy).to.have.property("dialects");
expect(packageFile.tshy).to.have.property("esmDialects");
expect(packageFile.tshy).to.have.property("selfLink");
});

it("should not update tshy.exports when tshy does not exist in package.json", () => {
const model = createMockModel({
moduleKind: "esm",
flavor: "azure",
isMonorepo: true,
hasLro: true
});

const initialPackageInfo = {
name: "@azure/test-package",
version: "1.0.0",
dependencies: {
"@azure/core-client": "^1.0.0"
}
};

const newExports = {
"./api": "./src/api/index.ts",
"./models": "./src/models/index.ts"
};

const packageFileContent = updatePackageFile(model, initialPackageInfo, {
exports: newExports
});
const packageFile = JSON.parse(packageFileContent?.content ?? "{}");

expect(packageFile.dependencies).to.have.property(
"@azure/core-lro",
"^3.1.0"
);
expect(packageFile.dependencies).to.have.property(
"@azure/abort-controller",
"^2.1.2"
);
expect(packageFile).to.not.have.property("tshy");
});
});

describe("Flavorless lib", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure/arm-networkanalytics",
"version": "1.0.0-beta.1",
"version": "1.0.0-beta.2",
"description": "A generated SDK for NetworkAnalyticsApi.",
"engines": {
"node": ">=20.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function createNetworkAnalyticsApi(
getArmEndpoint(options.cloudSetting) ??
"https://management.azure.com";
const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix;
const userAgentInfo = `azsdk-js-arm-networkanalytics/1.0.0-beta.1`;
const userAgentInfo = `azsdk-js-arm-networkanalytics/1.0.0-beta.2`;
const userAgentPrefix = prefixFromOptions
? `${prefixFromOptions} azsdk-js-api ${userAgentInfo}`
: `azsdk-js-api ${userAgentInfo}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ options:
emitter-output-dir: "{project-root}/generated/typespec-ts/sdk/test/arm-test"
package-details:
name: "@azure/arm-networkanalytics"
version: 1.0.0-beta.2
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Face API REST Client
Key links:

- [Package (NPM)](https://www.npmjs.com/package/@azure-rest/ai-face-rest)
- [API reference documentation](https://learn.microsoft.com/javascript/api/@azure-rest/ai-face-rest?view=azure-node-preview)
- [API reference documentation](https://learn.microsoft.com/javascript/api/@azure-rest/ai-face-rest)

## Getting started

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Dictionary example Returns grammatical structure and context examples for the so
Key links:

- [Package (NPM)](https://www.npmjs.com/package/@azure-rest/cognitiveservices-translator)
- [API reference documentation](https://learn.microsoft.com/javascript/api/@azure-rest/cognitiveservices-translator?view=azure-node-preview)
- [API reference documentation](https://learn.microsoft.com/javascript/api/@azure-rest/cognitiveservices-translator)

## Getting started

Expand Down
25 changes: 24 additions & 1 deletion packages/typespec-ts/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
buildPollingHelper,
buildPaginateHelper as buildRLCPaginateHelper,
buildReadmeFile,
updateReadmeFile,
buildRecordedClientFile,
buildResponseTypes,
buildRollupConfig,
Expand Down Expand Up @@ -408,6 +409,11 @@ export async function $onEmit(context: EmitContext) {
"package.json"
);
const hasPackageFile = await existsSync(existingPackageFilePath);
const existingReadmeFilePath = join(
dpgContext.generationPathDetail?.metadataDir ?? "",
"README.md"
);
const hasReadmeFile = await existsSync(existingReadmeFilePath);
const shouldGenerateMetadata =
option.generateMetadata === true || !hasPackageFile;
const existingTestFolderPath = join(
Expand Down Expand Up @@ -507,12 +513,29 @@ export async function $onEmit(context: EmitContext) {
}
} else if (hasPackageFile) {
// update existing package.json file with correct dependencies
let modularPackageInfo = {};
if (option.isModularLibrary) {
modularPackageInfo = {
exports: getModuleExports(context, modularEmitterOptions)
};
}
await emitContentByBuilder(
program,
(model) => updatePackageFile(model, existingPackageFilePath),
(model) =>
updatePackageFile(model, existingPackageFilePath, modularPackageInfo),
rlcClient,
dpgContext.generationPathDetail?.metadataDir
);

// update existing README.md file if it exists
if (hasReadmeFile) {
await emitContentByBuilder(
program,
(model) => updateReadmeFile(model, existingReadmeFilePath),
rlcClient,
dpgContext.generationPathDetail?.metadataDir
);
}
}
if (isAzureFlavor) {
await emitContentByBuilder(
Expand Down
3 changes: 2 additions & 1 deletion packages/typespec-ts/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
scopeName: { type: "string", nullable: true },
nameWithoutScope: { type: "string", nullable: true },
description: { type: "string", nullable: true },
version: { type: "string", nullable: true }
version: { type: "string", nullable: true },
isVersionUserProvided: { type: "boolean", nullable: true }
},
required: ["name"],
nullable: true,
Expand Down
6 changes: 5 additions & 1 deletion packages/typespec-ts/src/transform/transfromRLCOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,9 @@ function buildPackageDetails(
nameWithoutScope: "unamedpackage",
version: "1.0.0-beta.1"
};
const isVersionUserProvided = Boolean(
emitterOptions["package-details"]?.version
);
const packageDetails: PackageDetails = {
...emitterOptions["package-details"],
name:
Expand All @@ -303,7 +306,8 @@ function buildPackageDetails(
emitterOptions?.title ?? getDefaultService(program)?.title ?? "",
NameType.Class
),
version: emitterOptions["package-details"]?.version ?? "1.0.0-beta.1"
version: emitterOptions["package-details"]?.version ?? "1.0.0-beta.1",
isVersionUserProvided
};
if (emitterOptions["package-details"]?.name) {
const nameParts = emitterOptions["package-details"]?.name.split("/");
Expand Down
Loading