@azure-tools/typespec-ts 0.46.0 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.46.0",
3
+ "version": "0.46.1",
4
4
  "description": "An experimental TypeSpec emitter for TypeScript RLC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
@@ -18,15 +18,15 @@
18
18
  "license": "MIT",
19
19
  "devDependencies": {
20
20
  "@azure-rest/core-client": "^2.3.1",
21
- "@typespec/http-specs": "0.1.0-alpha.28-dev.1",
22
- "@typespec/spector": "0.1.0-alpha.20-dev.0",
23
- "@typespec/spec-api": "0.1.0-alpha.10-dev.0",
24
- "@typespec/tspd": "0.73.0",
25
- "@azure-tools/azure-http-specs": "0.1.0-alpha.32-dev.2",
26
- "@azure-tools/typespec-autorest": "^0.61.0",
27
- "@azure-tools/typespec-azure-core": "^0.61.0",
28
- "@azure-tools/typespec-azure-resource-manager": "^0.61.0",
29
- "@azure-tools/typespec-client-generator-core": "^0.61.0",
21
+ "@typespec/http-specs": "0.1.0-alpha.29-dev.2",
22
+ "@typespec/spector": "0.1.0-alpha.21-dev.1",
23
+ "@typespec/spec-api": "0.1.0-alpha.11-dev.0",
24
+ "@typespec/tspd": "0.73.1",
25
+ "@azure-tools/azure-http-specs": "0.1.0-alpha.33-dev.0",
26
+ "@azure-tools/typespec-autorest": "^0.62.0",
27
+ "@azure-tools/typespec-azure-core": "^0.62.0",
28
+ "@azure-tools/typespec-azure-resource-manager": "^0.62.0",
29
+ "@azure-tools/typespec-client-generator-core": "^0.62.0",
30
30
  "@azure/abort-controller": "^2.1.2",
31
31
  "@azure/core-auth": "^1.6.0",
32
32
  "@azure/core-lro": "^3.1.0",
@@ -41,12 +41,12 @@
41
41
  "@types/node": "^18.0.0",
42
42
  "@typescript-eslint/eslint-plugin": "^8.28.0",
43
43
  "@typescript-eslint/parser": "^8.28.0",
44
- "@typespec/compiler": "^1.5.0",
45
- "@typespec/http": "^1.5.0",
46
- "@typespec/openapi": "^1.5.0",
47
- "@typespec/rest": "^0.75.0",
44
+ "@typespec/compiler": "^1.6.0",
45
+ "@typespec/http": "^1.6.0",
46
+ "@typespec/openapi": "^1.6.0",
47
+ "@typespec/rest": "^0.76.0",
48
48
  "@typespec/ts-http-runtime": "^0.1.0",
49
- "@typespec/versioning": "^0.75.0",
49
+ "@typespec/versioning": "^0.76.0",
50
50
  "chai": "^4.3.6",
51
51
  "chalk": "^4.0.0",
52
52
  "cross-env": "^7.0.3",
@@ -68,16 +68,16 @@
68
68
  "js-yaml": "^4.1.0"
69
69
  },
70
70
  "peerDependencies": {
71
- "@azure-tools/typespec-azure-core": "^0.61.0",
72
- "@azure-tools/typespec-client-generator-core": "^0.61.0",
73
- "@typespec/compiler": "^1.5.0",
74
- "@typespec/http": "^1.5.0",
75
- "@typespec/rest": "^0.75.0",
76
- "@typespec/versioning": "^0.75.0",
77
- "@typespec/xml": "^0.75.0"
71
+ "@azure-tools/typespec-azure-core": "^0.62.0",
72
+ "@azure-tools/typespec-client-generator-core": "^0.62.0",
73
+ "@typespec/compiler": "^1.6.0",
74
+ "@typespec/http": "^1.6.0",
75
+ "@typespec/rest": "^0.76.0",
76
+ "@typespec/versioning": "^0.76.0",
77
+ "@typespec/xml": "^0.76.0"
78
78
  },
79
79
  "dependencies": {
80
- "@azure-tools/rlc-common": "^0.46.0",
80
+ "@azure-tools/rlc-common": "^0.46.1",
81
81
  "fs-extra": "^11.1.0",
82
82
  "lodash": "^4.17.21",
83
83
  "prettier": "^3.3.3",
package/src/index.ts CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  buildPollingHelper,
36
36
  buildPaginateHelper as buildRLCPaginateHelper,
37
37
  buildReadmeFile,
38
+ updateReadmeFile,
38
39
  buildRecordedClientFile,
39
40
  buildResponseTypes,
40
41
  buildRollupConfig,
@@ -162,6 +163,8 @@ export async function $onEmit(context: EmitContext) {
162
163
  // 2. Generate RLC code model
163
164
  // TODO: skip this step in modular once modular generator is sufficiently decoupled
164
165
  await buildRLCCodeModels();
166
+ // 3. Clear samples-dev folder if generateSample is true
167
+ await clearSamplesDevFolder();
165
168
 
166
169
  // 4. Generate sources
167
170
  if (emitterOptions["is-modular-library"]) {
@@ -236,6 +239,18 @@ export async function $onEmit(context: EmitContext) {
236
239
  );
237
240
  }
238
241
 
242
+ async function clearSamplesDevFolder() {
243
+ if (emitterOptions["generate-sample"] === true) {
244
+ const samplesDevPath = join(
245
+ dpgContext.generationPathDetail?.rootDir ?? "",
246
+ "samples-dev"
247
+ );
248
+ if (await fsextra.pathExists(samplesDevPath)) {
249
+ await fsextra.emptyDir(samplesDevPath);
250
+ }
251
+ }
252
+ }
253
+
239
254
  async function buildRLCCodeModels() {
240
255
  const clients = getRLCClients(dpgContext);
241
256
  for (const client of clients) {
@@ -408,6 +423,11 @@ export async function $onEmit(context: EmitContext) {
408
423
  "package.json"
409
424
  );
410
425
  const hasPackageFile = await existsSync(existingPackageFilePath);
426
+ const existingReadmeFilePath = join(
427
+ dpgContext.generationPathDetail?.metadataDir ?? "",
428
+ "README.md"
429
+ );
430
+ const hasReadmeFile = await existsSync(existingReadmeFilePath);
411
431
  const shouldGenerateMetadata =
412
432
  option.generateMetadata === true || !hasPackageFile;
413
433
  const existingTestFolderPath = join(
@@ -507,12 +527,29 @@ export async function $onEmit(context: EmitContext) {
507
527
  }
508
528
  } else if (hasPackageFile) {
509
529
  // update existing package.json file with correct dependencies
530
+ let modularPackageInfo = {};
531
+ if (option.isModularLibrary) {
532
+ modularPackageInfo = {
533
+ exports: getModuleExports(context, modularEmitterOptions)
534
+ };
535
+ }
510
536
  await emitContentByBuilder(
511
537
  program,
512
- (model) => updatePackageFile(model, existingPackageFilePath),
538
+ (model) =>
539
+ updatePackageFile(model, existingPackageFilePath, modularPackageInfo),
513
540
  rlcClient,
514
541
  dpgContext.generationPathDetail?.metadataDir
515
542
  );
543
+
544
+ // update existing README.md file if it exists
545
+ if (hasReadmeFile) {
546
+ await emitContentByBuilder(
547
+ program,
548
+ (model) => updateReadmeFile(model, existingReadmeFilePath),
549
+ rlcClient,
550
+ dpgContext.generationPathDetail?.metadataDir
551
+ );
552
+ }
516
553
  }
517
554
  if (isAzureFlavor) {
518
555
  await emitContentByBuilder(
package/src/lib.ts CHANGED
@@ -102,7 +102,8 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
102
102
  scopeName: { type: "string", nullable: true },
103
103
  nameWithoutScope: { type: "string", nullable: true },
104
104
  description: { type: "string", nullable: true },
105
- version: { type: "string", nullable: true }
105
+ version: { type: "string", nullable: true },
106
+ isVersionUserProvided: { type: "boolean", nullable: true }
106
107
  },
107
108
  required: ["name"],
108
109
  nullable: true,
@@ -112,13 +112,12 @@ export function emitTypes(
112
112
  sourceFile = outputProject.getSourceFile(filepath);
113
113
  if (!sourceFile) {
114
114
  sourceFile = outputProject.createSourceFile(filepath);
115
- sourceFile.addStatements(
116
- `/**
117
- * This file contains only generated model types and (de)serializers.
118
- * Disable this rule for deserializer functions which require 'any' for raw JSON input.
115
+ sourceFile.addStatements(`/**
116
+ * This file contains only generated model types and their (de)serializers.
117
+ * Disable the following rules for internal models with '_' prefix and deserializers which require 'any' for raw JSON input.
119
118
  */
120
- /* eslint-disable @typescript-eslint/explicit-module-boundary-types */`
121
- );
119
+ /* eslint-disable @typescript-eslint/naming-convention */
120
+ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */`);
122
121
  }
123
122
  emitType(context, type, sourceFile);
124
123
  }
@@ -637,19 +637,25 @@ function getPagingOnlyOperationFunction(
637
637
  return property.name;
638
638
  })
639
639
  .join(".");
640
+
641
+ // Check for nextLinkVerb from TCGC pagingMetadata (supports @Legacy.nextLinkVerb decorator)
642
+ const nextLinkMethod = operation.pagingMetadata.nextLinkVerb;
643
+
640
644
  if (itemName) {
641
645
  options.push(`itemName: "${itemName}"`);
642
646
  }
643
647
  if (nextLinkName) {
644
648
  options.push(`nextLinkName: "${nextLinkName}"`);
645
649
  }
650
+ if (nextLinkMethod && nextLinkMethod !== "GET") {
651
+ options.push(`nextLinkMethod: "${nextLinkMethod}"`);
652
+ }
646
653
  statements.push(
647
654
  `return ${buildPagedAsyncIteratorReference}(
648
655
  context,
649
656
  () => _${name}Send(${parameters.map((p) => p.name).join(", ")}),
650
657
  _${name}Deserialize,
651
- ${getExpectedStatuses(operation)},
652
- ${options.length > 0 ? `{${options.join(", ")}}` : ``}
658
+ ${getExpectedStatuses(operation)}${options.length > 0 ? `,\n {${options.join(", ")}}` : ""}
653
659
  );`
654
660
  );
655
661
 
@@ -5,8 +5,7 @@ import {
5
5
  PackageDetails,
6
6
  PackageFlavor,
7
7
  RLCOptions,
8
- ServiceInfo,
9
- isAzurePackage
8
+ ServiceInfo
10
9
  } from "@azure-tools/rlc-common";
11
10
  import { getHttpOperationWithCache } from "@azure-tools/typespec-client-generator-core";
12
11
  import { getDoc, NoTarget, Program } from "@typespec/compiler";
@@ -34,13 +33,7 @@ export function transformRLCOptions(
34
33
  emitterOptions,
35
34
  dpgContext.generationPathDetail?.rootDir ?? ""
36
35
  );
37
- if (
38
- !isAzurePackage({ options }) &&
39
- emitterOptions["is-modular-library"] !== false
40
- ) {
41
- options.isModularLibrary = true;
42
- }
43
- if (dpgContext.arm && emitterOptions["is-modular-library"] !== false) {
36
+ if (emitterOptions["is-modular-library"] !== false) {
44
37
  options.isModularLibrary = true;
45
38
  }
46
39
  const batch = getRLCClients(dpgContext);
@@ -302,6 +295,9 @@ function buildPackageDetails(
302
295
  nameWithoutScope: "unamedpackage",
303
296
  version: "1.0.0-beta.1"
304
297
  };
298
+ const isVersionUserProvided = Boolean(
299
+ emitterOptions["package-details"]?.version
300
+ );
305
301
  const packageDetails: PackageDetails = {
306
302
  ...emitterOptions["package-details"],
307
303
  name:
@@ -310,7 +306,8 @@ function buildPackageDetails(
310
306
  emitterOptions?.title ?? getDefaultService(program)?.title ?? "",
311
307
  NameType.Class
312
308
  ),
313
- version: emitterOptions["package-details"]?.version ?? "1.0.0-beta.1"
309
+ version: emitterOptions["package-details"]?.version ?? "1.0.0-beta.1",
310
+ isVersionUserProvided
314
311
  };
315
312
  if (emitterOptions["package-details"]?.name) {
316
313
  const nameParts = emitterOptions["package-details"]?.name.split("/");
@@ -90,6 +90,7 @@ export interface PagedResult<
90
90
  export interface BuildPagedAsyncIteratorOptions {
91
91
  itemName?: string;
92
92
  nextLinkName?: string;
93
+ nextLinkMethod?: "GET" | "POST";
93
94
  }
94
95
 
95
96
  /**
@@ -109,12 +110,15 @@ export function buildPagedAsyncIterator<
109
110
  ): PagedAsyncIterableIterator<TElement, TPage, TPageSettings> {
110
111
  const itemName = options.itemName ?? "value";
111
112
  const nextLinkName = options.nextLinkName ?? "nextLink";
113
+ const nextLinkMethod = options.nextLinkMethod ?? "GET";
112
114
  const pagedResult: PagedResult<TElement, TPage, TPageSettings> = {
113
115
  getPage: async (pageLink?: string) => {
114
116
  const result =
115
117
  pageLink === undefined
116
118
  ? await getInitialResponse()
117
- : await client.pathUnchecked(pageLink).get();
119
+ : nextLinkMethod === "POST"
120
+ ? await client.pathUnchecked(pageLink).post()
121
+ : await client.pathUnchecked(pageLink).get();
118
122
  checkPagingRequest(result, expectedStatuses);
119
123
  const results = await processResponseBody(result as TResponse);
120
124
  const nextLink = getNextLink(results, nextLinkName);
@@ -177,7 +177,7 @@ export function expandUrlTemplate(
177
177
  context: Record<string, any>,
178
178
  option?: UrlTemplateOptions
179
179
  ): string {
180
- return template.replace(/\{([^{}]+)\}|([^{}]+)/g, (_, expr, text) => {
180
+ const result = template.replace(/\{([^{}]+)\}|([^{}]+)/g, (_, expr, text) => {
181
181
  if (!expr) {
182
182
  return encodeReservedComponent(text);
183
183
  }
@@ -207,4 +207,21 @@ export function expandUrlTemplate(
207
207
  }
208
208
  return result.join("");
209
209
  });
210
+
211
+ return normalizeUnreserved(result);
212
+ }
213
+
214
+ /**
215
+ * Normalize an expanded URI by decoding percent-encoded unreserved characters.
216
+ * RFC 3986 unreserved: "-" / "." / "~"
217
+ */
218
+ function normalizeUnreserved(uri: string): string {
219
+ return uri.replace(/%([0-9A-Fa-f]{2})/g, (match, hex) => {
220
+ const char = String.fromCharCode(parseInt(hex, 16));
221
+ // Decode only if it's unreserved
222
+ if (/[\-.~]/.test(char)) {
223
+ return char;
224
+ }
225
+ return match; // leave other encodings intact
226
+ });
210
227
  }