@azure-tools/typespec-ts 0.52.2 → 0.52.3

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/src/framework/hooks/sdkTypes.d.ts +5 -0
  3. package/dist/src/framework/hooks/sdkTypes.d.ts.map +1 -1
  4. package/dist/src/framework/hooks/sdkTypes.js +5 -0
  5. package/dist/src/framework/hooks/sdkTypes.js.map +1 -1
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +23 -11
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/modular/buildProjectFiles.d.ts +1 -1
  10. package/dist/src/modular/buildProjectFiles.d.ts.map +1 -1
  11. package/dist/src/modular/buildProjectFiles.js +27 -7
  12. package/dist/src/modular/buildProjectFiles.js.map +1 -1
  13. package/dist/src/modular/emitModels.d.ts.map +1 -1
  14. package/dist/src/modular/emitModels.js +26 -5
  15. package/dist/src/modular/emitModels.js.map +1 -1
  16. package/dist/src/modular/helpers/namingHelpers.d.ts +2 -1
  17. package/dist/src/modular/helpers/namingHelpers.d.ts.map +1 -1
  18. package/dist/src/modular/helpers/namingHelpers.js +4 -2
  19. package/dist/src/modular/helpers/namingHelpers.js.map +1 -1
  20. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  21. package/dist/src/modular/helpers/operationHelpers.js +15 -9
  22. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/package.json +10 -10
  25. package/src/framework/hooks/sdkTypes.ts +5 -0
  26. package/src/index.ts +18 -2
  27. package/src/modular/buildProjectFiles.ts +40 -8
  28. package/src/modular/emitModels.ts +28 -3
  29. package/src/modular/helpers/namingHelpers.ts +8 -2
  30. package/src/modular/helpers/operationHelpers.ts +19 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.52.2",
3
+ "version": "0.52.3",
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.36-dev.3",
22
- "@typespec/spector": "0.1.0-alpha.25-dev.5",
23
- "@typespec/spec-api": "0.1.0-alpha.14-dev.4",
21
+ "@typespec/http-specs": "0.1.0-alpha.37-dev.3",
22
+ "@typespec/spector": "0.1.0-dev.4",
23
+ "@typespec/spec-api": "0.1.0-dev.3",
24
24
  "@typespec/tspd": "0.74.1",
25
- "@azure-tools/azure-http-specs": "0.1.0-alpha.40-dev.4",
25
+ "@azure-tools/azure-http-specs": "0.1.0-alpha.40-dev.6",
26
26
  "@azure-tools/typespec-autorest": "^0.67.0",
27
- "@azure-tools/typespec-azure-core": "^0.67.0",
27
+ "@azure-tools/typespec-azure-core": "^0.67.1",
28
28
  "@azure-tools/typespec-azure-resource-manager": "^0.67.1",
29
- "@azure-tools/typespec-client-generator-core": "^0.67.2",
29
+ "@azure-tools/typespec-client-generator-core": "^0.67.3",
30
30
  "@azure/abort-controller": "^2.1.2",
31
31
  "@azure/core-auth": "^1.6.0",
32
32
  "@azure/core-lro": "^3.1.0",
@@ -63,8 +63,8 @@
63
63
  "js-yaml": "^4.1.0"
64
64
  },
65
65
  "peerDependencies": {
66
- "@azure-tools/typespec-azure-core": "^0.67.0",
67
- "@azure-tools/typespec-client-generator-core": "^0.67.2",
66
+ "@azure-tools/typespec-azure-core": "^0.67.1",
67
+ "@azure-tools/typespec-client-generator-core": "^0.67.3",
68
68
  "@typespec/compiler": "^1.11.0",
69
69
  "@typespec/http": "^1.11.0",
70
70
  "@typespec/rest": "^0.81.0",
@@ -72,7 +72,7 @@
72
72
  "@typespec/xml": "^0.81.0"
73
73
  },
74
74
  "dependencies": {
75
- "@azure-tools/rlc-common": "^0.52.2",
75
+ "@azure-tools/rlc-common": "^0.52.3",
76
76
  "fast-xml-parser": "^4.5.0",
77
77
  "fs-extra": "^11.1.0",
78
78
  "lodash": "^4.17.21",
@@ -23,6 +23,11 @@ import { NameType, normalizeName } from "@azure-tools/rlc-common";
23
23
  export const emitQueue: Set<SdkType> = new Set<SdkType>();
24
24
  export const flattenPropertyModelMap: Map<SdkModelPropertyType, SdkModelType> =
25
25
  new Map<SdkModelPropertyType, SdkModelType>();
26
+ /**
27
+ * Set of paged result models that are also used in non-paging operations.
28
+ * Precomputed during visitPackageTypes for O(1) lookups in normalizeModelName.
29
+ */
30
+ export const pagedModelsUsedInNonPagingOps = new Set<SdkType>();
26
31
 
27
32
  export interface SdkTypeContext {
28
33
  operations: Map<Type, SdkServiceMethod<SdkHttpOperation>>;
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  buildIndexFile,
35
35
  buildIsUnexpectedHelper,
36
36
  buildLicenseFile,
37
+ buildChangelogFile,
37
38
  buildLogger,
38
39
  buildPackageFile,
39
40
  buildParameterTypes,
@@ -101,7 +102,7 @@ import {
101
102
  getRLCClients,
102
103
  getModularClientOptions
103
104
  } from "./utils/clientUtils.js";
104
- import { join } from "path";
105
+ import { basename, join } from "path";
105
106
  import { loadStaticHelpers } from "./framework/load-static-helpers.js";
106
107
  import { packageUsesXmlSerialization } from "./modular/serialization/buildXmlSerializerFunction.js";
107
108
  import { provideBinder } from "./framework/hooks/binder.js";
@@ -450,6 +451,13 @@ export async function $onEmit(context: EmitContext) {
450
451
  }
451
452
  const rlcClient: RLCModel = rlcCodeModels[0];
452
453
  const option = dpgContext.rlcOptions!;
454
+ // When generateMetadata is explicitly false and the sources are generated
455
+ // into a path ending with "generated" (e.g. src/generated), this package
456
+ // has a manual convenience layer. Skip all metadata/test file generation
457
+ // to avoid unexpected modifications to files like package.json, README.md,
458
+ // warp.config.yml, and snippets.spec.ts. metadata.json is still updated.
459
+ const sourcesDir = dpgContext.generationPathDetail?.modularSourcesDir ?? "";
460
+ const hasManualConvenienceLayer = basename(sourcesDir) === "generated";
453
461
  const isAzureFlavor = isAzurePackage({ options: option });
454
462
  // Generate metadata
455
463
  const existingPackageFilePath = join(
@@ -462,6 +470,11 @@ export async function $onEmit(context: EmitContext) {
462
470
  "README.md"
463
471
  );
464
472
  const hasReadmeFile = await existsSync(existingReadmeFilePath);
473
+ const existingChangelogFilePath = join(
474
+ dpgContext.generationPathDetail?.metadataDir ?? "",
475
+ "CHANGELOG.md"
476
+ );
477
+ const hasChangelogFile = await existsSync(existingChangelogFilePath);
465
478
  const shouldGenerateMetadata =
466
479
  option.generateMetadata === true || !hasPackageFile;
467
480
  const existingTestFolderPath = join(
@@ -503,6 +516,9 @@ export async function $onEmit(context: EmitContext) {
503
516
  if (isAzureFlavor) {
504
517
  commonBuilders.push(buildEsLintConfig);
505
518
  }
519
+ if (!hasChangelogFile) {
520
+ commonBuilders.push(buildChangelogFile);
521
+ }
506
522
  if (
507
523
  emitterOptions["generate-test"] === true &&
508
524
  option.azureSdkForJs === true &&
@@ -584,7 +600,7 @@ export async function $onEmit(context: EmitContext) {
584
600
  );
585
601
  }
586
602
  }
587
- } else if (hasPackageFile) {
603
+ } else if (hasPackageFile && !hasManualConvenienceLayer) {
588
604
  const updateBuilders = [];
589
605
  let modularPackageInfo = {};
590
606
 
@@ -2,7 +2,7 @@ import { NameType } from "@azure-tools/rlc-common";
2
2
 
3
3
  import { ModularEmitterOptions } from "./interfaces.js";
4
4
  import { getClassicalLayerPrefix } from "./helpers/namingHelpers.js";
5
- import { SdkContext } from "@azure-tools/typespec-client-generator-core";
5
+ import { SdkContext } from "../utils/interfaces.js";
6
6
  import {
7
7
  getClientHierarchyMap,
8
8
  getModularClientOptions
@@ -11,11 +11,39 @@ import { getMethodHierarchiesMap } from "../utils/operationUtil.js";
11
11
  import { useContext } from "../contextManager.js";
12
12
  import path from "path/posix";
13
13
 
14
+ /**
15
+ * Computes the relative path prefix (e.g. `./src` or `./src/generated`) from
16
+ * the project root to the modular sources directory. This prefix is used when
17
+ * building export map entries so that they always point to the correct folder
18
+ * even when the generated code lives under `src/generated` instead of `src`.
19
+ */
20
+ function getSourceRootPrefix(
21
+ emitterOptions: ModularEmitterOptions,
22
+ context: SdkContext
23
+ ): string {
24
+ const sourceRoot = emitterOptions.modularOptions.sourceRoot.replace(
25
+ /\\/g,
26
+ "/"
27
+ );
28
+ const rootDir = (context.generationPathDetail?.rootDir ?? "").replace(
29
+ /\\/g,
30
+ "/"
31
+ );
32
+
33
+ if (rootDir && sourceRoot.startsWith(rootDir)) {
34
+ const relativePath = path.relative(rootDir, sourceRoot).replace(/\\/g, "/");
35
+ return `./${relativePath}`;
36
+ }
37
+
38
+ return "./src";
39
+ }
40
+
14
41
  function buildExportsForMultiClient(
15
42
  context: SdkContext,
16
43
  emitterOptions: ModularEmitterOptions,
17
44
  packageInfo: any
18
45
  ) {
46
+ const srcPrefix = getSourceRootPrefix(emitterOptions, context);
19
47
  const clientMap = getClientHierarchyMap(context);
20
48
  let hasTopLevelClient = false;
21
49
  for (const [hierarchy, client] of clientMap) {
@@ -25,10 +53,11 @@ function buildExportsForMultiClient(
25
53
  }
26
54
  const { subfolder } = getModularClientOptions([hierarchy, client]);
27
55
  if (subfolder !== "" && methodMap.size > 0) {
28
- packageInfo.exports[`./${subfolder}`] = `./src/${subfolder}/index.ts`;
56
+ packageInfo.exports[`./${subfolder}`] =
57
+ `${srcPrefix}/${subfolder}/index.ts`;
29
58
 
30
59
  packageInfo.exports[`./${subfolder}/api`] =
31
- `./src/${subfolder}/api/index.ts`;
60
+ `${srcPrefix}/${subfolder}/api/index.ts`;
32
61
  }
33
62
  }
34
63
  if (!hasTopLevelClient) {
@@ -51,9 +80,11 @@ function buildExportsForMultiClient(
51
80
  NameType.File,
52
81
  "/"
53
82
  )}`;
83
+
54
84
  packageInfo.exports[
55
85
  `./${subfolder ? subfolder + "/" : ""}${subApiPath}`
56
- ] = `src/${subfolder ? subfolder + "/" : ""}${subApiPath}/index.ts`;
86
+ ] =
87
+ `${srcPrefix}/${subfolder ? subfolder + "/" : ""}${subApiPath}/index.ts`;
57
88
  }
58
89
  }
59
90
  }
@@ -61,7 +92,7 @@ function buildExportsForMultiClient(
61
92
  const modelSubpaths = getModelSubpaths(emitterOptions);
62
93
  for (const modelSubpath of modelSubpaths) {
63
94
  packageInfo.exports[`./${modelSubpath.replace("/index.ts", "")}`] =
64
- `./src/${modelSubpath}`;
95
+ `${srcPrefix}/${modelSubpath}`;
65
96
  }
66
97
  }
67
98
 
@@ -72,13 +103,14 @@ export function getModuleExports(
72
103
  context: SdkContext,
73
104
  emitterOptions: ModularEmitterOptions
74
105
  ) {
106
+ const srcPrefix = getSourceRootPrefix(emitterOptions, context);
75
107
  const exports: Record<string, any> = {
76
108
  exports: {
77
- ".": "./src/index.ts",
78
- "./models": "./src/models/index.ts"
109
+ ".": `${srcPrefix}/index.ts`,
110
+ "./models": `${srcPrefix}/models/index.ts`
79
111
  }
80
112
  };
81
- exports["exports"]["./api"] = "./src/api/index.ts";
113
+ exports["exports"]["./api"] = `${srcPrefix}/api/index.ts`;
82
114
 
83
115
  return buildExportsForMultiClient(context, emitterOptions, exports);
84
116
  }
@@ -78,7 +78,8 @@ import {
78
78
  import {
79
79
  emitQueue,
80
80
  flattenPropertyModelMap,
81
- getAllOperationsFromClient
81
+ getAllOperationsFromClient,
82
+ pagedModelsUsedInNonPagingOps
82
83
  } from "../framework/hooks/sdkTypes.js";
83
84
  import {
84
85
  getAllAncestors,
@@ -746,12 +747,15 @@ function buildModelInterface(
746
747
  if (!hasInputUsage && p.__raw && isMetadata(context.program, p.__raw)) {
747
748
  return false;
748
749
  }
749
- // Skip required metadata properties with Read visibility for ARM as they are not intended to be in the model
750
+ // Skip required metadata properties with Read-only visibility for ARM as they are not intended to be in the model
750
751
  // These properties are not be generated no matter they are in input or output models in most cases in HLC
752
+ // Only skip properties that have exclusively Read visibility to avoid stripping @path properties
753
+ // on parameter bag models that also carry other visibility flags (e.g. Create/Update)
751
754
  if (
752
755
  context.arm &&
753
756
  p.__raw &&
754
757
  isMetadata(context.program, p.__raw) &&
758
+ p.visibility?.length === 1 &&
755
759
  p.visibility?.includes(Visibility.Read)
756
760
  ) {
757
761
  return false;
@@ -945,7 +949,11 @@ export function normalizeModelName(
945
949
  ? segments.join("")
946
950
  : "";
947
951
  const internalModelPrefix =
948
- isPagedResultModel(context, type) || type.isGeneratedName ? "_" : "";
952
+ (isPagedResultModel(context, type) &&
953
+ !pagedModelsUsedInNonPagingOps.has(type)) ||
954
+ type.isGeneratedName
955
+ ? "_"
956
+ : "";
949
957
  return `${internalModelPrefix}${normalizeName(namespacePrefix + type.name, nameType, true)}${unionSuffix}`;
950
958
  }
951
959
 
@@ -1038,6 +1046,7 @@ export function visitPackageTypes(context: SdkContext) {
1038
1046
  const { sdkPackage } = context;
1039
1047
  emitQueue.clear();
1040
1048
  flattenPropertyModelMap.clear();
1049
+ pagedModelsUsedInNonPagingOps.clear();
1041
1050
  // Add all models in the package to the emit queue
1042
1051
  for (const model of sdkPackage.models) {
1043
1052
  visitType(context, model);
@@ -1119,6 +1128,22 @@ function visitMethod(
1119
1128
  visitType(context, parameter.type);
1120
1129
  });
1121
1130
  visitType(context, method.response.type);
1131
+ trackPagedModelInNonPagingMethod(context, method);
1132
+ }
1133
+
1134
+ /**
1135
+ * If a non-paging method's direct response type is a paged result model,
1136
+ * mark it so that normalizeModelName keeps it public (no "_" prefix).
1137
+ */
1138
+ function trackPagedModelInNonPagingMethod(
1139
+ context: SdkContext,
1140
+ method: SdkServiceMethod<SdkHttpOperation>
1141
+ ): void {
1142
+ if (method.kind !== "basic" && method.kind !== "lro") return;
1143
+ const respType = method.response.type;
1144
+ if (respType && isPagedResultModel(context, respType)) {
1145
+ pagedModelsUsedInNonPagingOps.add(respType);
1146
+ }
1122
1147
  }
1123
1148
 
1124
1149
  function visitType(context: SdkContext, type: SdkType | undefined) {
@@ -7,6 +7,7 @@ import {
7
7
  SdkClientType,
8
8
  SdkServiceOperation
9
9
  } from "@azure-tools/typespec-client-generator-core";
10
+ import { SdkContext } from "../../utils/interfaces.js";
10
11
  import { ServiceOperation } from "../../utils/operationUtil.js";
11
12
 
12
13
  export function getClientName(
@@ -26,9 +27,14 @@ export interface GuardedName {
26
27
  fixme?: string[];
27
28
  }
28
29
 
29
- export function getOperationName(operation: ServiceOperation): GuardedName {
30
+ export function getOperationName(
31
+ operation: ServiceOperation,
32
+ dpgContext?: SdkContext
33
+ ): GuardedName {
30
34
  const norm = normalizeName(operation.name, NameType.Method, true);
31
- if (isReservedName(operation.name, NameType.Method)) {
35
+ const isDataplane =
36
+ dpgContext !== undefined && !dpgContext.rlcOptions?.azureArm;
37
+ if (isReservedName(operation.name, NameType.Method) && isDataplane) {
32
38
  return {
33
39
  name: norm,
34
40
  fixme: [
@@ -1100,7 +1100,7 @@ export function getOperationFunction(
1100
1100
  }
1101
1101
  }
1102
1102
 
1103
- const { name, fixme = [] } = getOperationName(operation);
1103
+ const { name, fixme = [] } = getOperationName(operation, context);
1104
1104
  const functionStatement = {
1105
1105
  kind: StructureKind.Function,
1106
1106
  docs: [
@@ -1248,7 +1248,7 @@ function getLroOnlyOperationFunction(
1248
1248
  const parameters: OptionalKind<ParameterDeclarationStructure>[] =
1249
1249
  getOperationSignatureParameters(context, method, clientType);
1250
1250
  const returnType = buildLroReturnType(context, operation);
1251
- const { name, fixme = [] } = getOperationName(operation);
1251
+ const { name, fixme = [] } = getOperationName(operation, context);
1252
1252
  const pollerLikeReference = resolveReference(
1253
1253
  AzurePollingDependencies.PollerLike
1254
1254
  );
@@ -1343,7 +1343,7 @@ function getLroAndPagingOperationFunction(
1343
1343
  method,
1344
1344
  clientType
1345
1345
  );
1346
- const { name, fixme = [] } = getOperationName(operation);
1346
+ const { name, fixme = [] } = getOperationName(operation, context);
1347
1347
 
1348
1348
  const returnType = buildLroPagingReturnType(context, operation);
1349
1349
 
@@ -1476,7 +1476,7 @@ function getPagingOnlyOperationFunction(
1476
1476
  type: getTypeExpression(context, type.valueType)
1477
1477
  };
1478
1478
  }
1479
- const { name, fixme = [] } = getOperationName(operation);
1479
+ const { name, fixme = [] } = getOperationName(operation, context);
1480
1480
  const pagedAsyncIterableIteratorReference = resolveReference(
1481
1481
  PagingHelpers.PagedAsyncIterableIterator
1482
1482
  );
@@ -2318,12 +2318,20 @@ export function getSerializationExpression(
2318
2318
  skipDiscriminatedUnionSuffix: false
2319
2319
  }
2320
2320
  );
2321
+
2322
+ // Apply clientDefaultValue for model properties that have one
2323
+ const defaultValueSuffix =
2324
+ property.clientDefaultValue !== undefined &&
2325
+ isDefaultValueTypeMatch(property, property.clientDefaultValue)
2326
+ ? ` ?? ${formatDefaultValue(property.clientDefaultValue)}`
2327
+ : "";
2328
+
2321
2329
  if (serializeFunctionName) {
2322
- return `${nullOrUndefinedPrefix}${serializeFunctionName}(${propertyFullName})`;
2330
+ return `${nullOrUndefinedPrefix}${serializeFunctionName}(${propertyFullName})${defaultValueSuffix}`;
2323
2331
  } else if (isAzureCoreErrorType(context.program, property.type.__raw)) {
2324
- return `${nullOrUndefinedPrefix}${propertyFullName}`;
2332
+ return `${nullOrUndefinedPrefix}${propertyFullName}${defaultValueSuffix}`;
2325
2333
  } else {
2326
- return serializeRequestValue(
2334
+ const baseExpr = serializeRequestValue(
2327
2335
  context,
2328
2336
  property.type,
2329
2337
  propertyFullName,
@@ -2332,6 +2340,7 @@ export function getSerializationExpression(
2332
2340
  getPropertySerializedName(property),
2333
2341
  propertyPath === "" ? true : false
2334
2342
  );
2343
+ return `${baseExpr}${defaultValueSuffix}`;
2335
2344
  }
2336
2345
  }
2337
2346
 
@@ -2880,11 +2889,11 @@ export function getAllAncestors(type: SdkType): SdkType[] {
2880
2889
  }
2881
2890
 
2882
2891
  /**
2883
- * Checks if a clientDefaultValue type matches the parameter type.
2884
- * Returns true if the default value type is compatible with the parameter type.
2892
+ * Checks if a clientDefaultValue type matches a parameter or model property type.
2893
+ * Returns true if the default value type is compatible with the target type.
2885
2894
  */
2886
2895
  function isDefaultValueTypeMatch(
2887
- param: SdkHttpParameter | SdkBodyParameter,
2896
+ param: SdkHttpParameter | SdkBodyParameter | SdkModelPropertyType,
2888
2897
  defaultValue: unknown
2889
2898
  ): boolean {
2890
2899
  const defaultType = typeof defaultValue;