@azure-tools/typespec-ts 0.56.0-dev.3 → 0.56.0-dev.4

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 (103) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +31 -21
  3. package/dist/src/context-manager.d.ts +17 -0
  4. package/dist/src/context-manager.d.ts.map +1 -1
  5. package/dist/src/context-manager.js +21 -0
  6. package/dist/src/context-manager.js.map +1 -1
  7. package/dist/src/framework/declaration.d.ts.map +1 -1
  8. package/dist/src/framework/declaration.js +6 -20
  9. package/dist/src/framework/declaration.js.map +1 -1
  10. package/dist/src/framework/hooks/sdk-types.d.ts +9 -0
  11. package/dist/src/framework/hooks/sdk-types.d.ts.map +1 -1
  12. package/dist/src/framework/hooks/sdk-types.js +13 -0
  13. package/dist/src/framework/hooks/sdk-types.js.map +1 -1
  14. package/dist/src/framework/source-file-batch.d.ts +35 -0
  15. package/dist/src/framework/source-file-batch.d.ts.map +1 -0
  16. package/dist/src/framework/source-file-batch.js +128 -0
  17. package/dist/src/framework/source-file-batch.js.map +1 -0
  18. package/dist/src/index.js +12 -12
  19. package/dist/src/index.js.map +1 -1
  20. package/dist/src/interfaces.d.ts +0 -2
  21. package/dist/src/interfaces.d.ts.map +1 -1
  22. package/dist/src/interfaces.js.map +1 -1
  23. package/dist/src/lib.d.ts +15 -2
  24. package/dist/src/lib.d.ts.map +1 -1
  25. package/dist/src/lib.js +22 -11
  26. package/dist/src/lib.js.map +1 -1
  27. package/dist/src/metadata/build-package-file.d.ts +2 -4
  28. package/dist/src/metadata/build-package-file.d.ts.map +1 -1
  29. package/dist/src/metadata/build-package-file.js +2 -17
  30. package/dist/src/metadata/build-package-file.js.map +1 -1
  31. package/dist/src/metadata/build-readme-file.d.ts.map +1 -1
  32. package/dist/src/metadata/build-readme-file.js +17 -70
  33. package/dist/src/metadata/build-readme-file.js.map +1 -1
  34. package/dist/src/metadata/build-ts-config.d.ts.map +1 -1
  35. package/dist/src/metadata/build-ts-config.js +12 -0
  36. package/dist/src/metadata/build-ts-config.js.map +1 -1
  37. package/dist/src/metadata/package-json/build-azure-monorepo-package.d.ts +0 -1
  38. package/dist/src/metadata/package-json/build-azure-monorepo-package.d.ts.map +1 -1
  39. package/dist/src/metadata/package-json/build-azure-monorepo-package.js +0 -14
  40. package/dist/src/metadata/package-json/build-azure-monorepo-package.js.map +1 -1
  41. package/dist/src/metadata/render-template.d.ts +24 -0
  42. package/dist/src/metadata/render-template.d.ts.map +1 -0
  43. package/dist/src/metadata/render-template.js +148 -0
  44. package/dist/src/metadata/render-template.js.map +1 -0
  45. package/dist/src/metadata/test/build-recorded-client.d.ts.map +1 -1
  46. package/dist/src/metadata/test/build-recorded-client.js +1 -7
  47. package/dist/src/metadata/test/build-recorded-client.js.map +1 -1
  48. package/dist/src/metadata/test/build-sample-test.d.ts.map +1 -1
  49. package/dist/src/metadata/test/build-sample-test.js +1 -4
  50. package/dist/src/metadata/test/build-sample-test.js.map +1 -1
  51. package/dist/src/metadata/test/build-snippets.d.ts.map +1 -1
  52. package/dist/src/metadata/test/build-snippets.js +2 -4
  53. package/dist/src/metadata/test/build-snippets.js.map +1 -1
  54. package/dist/src/metadata/test/template.d.ts +0 -1
  55. package/dist/src/metadata/test/template.d.ts.map +1 -1
  56. package/dist/src/metadata/test/template.js +0 -127
  57. package/dist/src/metadata/test/template.js.map +1 -1
  58. package/dist/src/modular/build-classical-client.d.ts.map +1 -1
  59. package/dist/src/modular/build-classical-client.js +2 -2
  60. package/dist/src/modular/build-classical-client.js.map +1 -1
  61. package/dist/src/modular/build-root-index.d.ts.map +1 -1
  62. package/dist/src/modular/build-root-index.js +37 -9
  63. package/dist/src/modular/build-root-index.js.map +1 -1
  64. package/dist/src/modular/build-subpath-index.d.ts.map +1 -1
  65. package/dist/src/modular/build-subpath-index.js +15 -2
  66. package/dist/src/modular/build-subpath-index.js.map +1 -1
  67. package/dist/src/modular/emit-models.d.ts.map +1 -1
  68. package/dist/src/modular/emit-models.js +24 -17
  69. package/dist/src/modular/emit-models.js.map +1 -1
  70. package/dist/src/modular/helpers/client-helpers.d.ts +8 -3
  71. package/dist/src/modular/helpers/client-helpers.d.ts.map +1 -1
  72. package/dist/src/modular/helpers/client-helpers.js +37 -17
  73. package/dist/src/modular/helpers/client-helpers.js.map +1 -1
  74. package/dist/src/modular/helpers/operation-helpers.js +8 -9
  75. package/dist/src/modular/helpers/operation-helpers.js.map +1 -1
  76. package/dist/src/transform/transform-client-options.d.ts.map +1 -1
  77. package/dist/src/transform/transform-client-options.js +1 -5
  78. package/dist/src/transform/transform-client-options.js.map +1 -1
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +30 -33
  81. package/src/context-manager.ts +23 -0
  82. package/src/framework/declaration.ts +8 -20
  83. package/src/framework/hooks/sdk-types.ts +14 -0
  84. package/src/framework/source-file-batch.ts +140 -0
  85. package/src/index.ts +13 -13
  86. package/src/interfaces.ts +0 -2
  87. package/src/lib.ts +40 -14
  88. package/src/metadata/build-package-file.ts +2 -21
  89. package/src/metadata/build-readme-file.ts +20 -94
  90. package/src/metadata/build-ts-config.ts +12 -0
  91. package/src/metadata/package-json/build-azure-monorepo-package.ts +0 -17
  92. package/src/metadata/render-template.ts +202 -0
  93. package/src/metadata/test/build-recorded-client.ts +1 -7
  94. package/src/metadata/test/build-sample-test.ts +1 -4
  95. package/src/metadata/test/build-snippets.ts +2 -4
  96. package/src/metadata/test/template.ts +0 -128
  97. package/src/modular/build-classical-client.ts +2 -6
  98. package/src/modular/build-root-index.ts +52 -14
  99. package/src/modular/build-subpath-index.ts +24 -3
  100. package/src/modular/emit-models.ts +24 -18
  101. package/src/modular/helpers/client-helpers.ts +43 -25
  102. package/src/modular/helpers/operation-helpers.ts +10 -10
  103. package/src/transform/transform-client-options.ts +1 -5
@@ -1,131 +1,3 @@
1
- export const karmaConfig = `
2
- // https://github.com/karma-runner/karma-chrome-launcher
3
- process.env.CHROME_BIN = require("puppeteer").executablePath();
4
- require("dotenv").config();
5
- const { relativeRecordingsPath } = require("@azure-tools/test-recorder");
6
- process.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath();
7
-
8
- module.exports = function (config) {
9
- config.set({
10
- // base path that will be used to resolve all patterns (eg. files, exclude)
11
- basePath: "./",
12
-
13
- // frameworks to use
14
- // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
15
- frameworks: ["source-map-support", "mocha"],
16
-
17
- plugins: [
18
- "karma-mocha",
19
- "karma-mocha-reporter",
20
- "karma-chrome-launcher",
21
- "karma-firefox-launcher",
22
- "karma-env-preprocessor",
23
- "karma-coverage",
24
- "karma-sourcemap-loader",
25
- "karma-junit-reporter",
26
- "karma-source-map-support",
27
- ],
28
-
29
- // list of files / patterns to load in the browser
30
- files: [
31
- "dist-test/index.browser.js",
32
- { pattern: "dist-test/index.browser.js.map", type: "html", included: false, served: true },
33
- ],
34
-
35
- // list of files / patterns to exclude
36
- exclude: [],
37
-
38
- // preprocess matching files before serving them to the browser
39
- // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
40
- preprocessors: {
41
- "**/*.js": ["sourcemap", "env"],
42
- // IMPORTANT: COMMENT following line if you want to debug in your browsers!!
43
- // Preprocess source file to calculate code coverage, however this will make source file unreadable
44
- // "dist-test/index.js": ["coverage"]
45
- },
46
-
47
- envPreprocessor: [
48
- "TEST_MODE",
49
- "ENDPOINT",
50
- "AZURE_CLIENT_SECRET",
51
- "AZURE_CLIENT_ID",
52
- "AZURE_TENANT_ID",
53
- "SUBSCRIPTION_ID",
54
- "RECORDINGS_RELATIVE_PATH",
55
- ],
56
-
57
- // test results reporter to use
58
- // possible values: 'dots', 'progress'
59
- // available reporters: https://npmjs.org/browse/keyword/karma-reporter
60
- reporters: ["mocha", "coverage", "junit"],
61
-
62
- coverageReporter: {
63
- // specify a common output directory
64
- dir: "coverage-browser/",
65
- reporters: [
66
- { type: "json", subdir: ".", file: "coverage.json" },
67
- { type: "lcovonly", subdir: ".", file: "lcov.info" },
68
- { type: "html", subdir: "html" },
69
- { type: "cobertura", subdir: ".", file: "cobertura-coverage.xml" },
70
- ],
71
- },
72
-
73
- junitReporter: {
74
- outputDir: "", // results will be saved as $outputDir/$browserName.xml
75
- outputFile: "test-results.browser.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile
76
- suite: "", // suite will become the package name attribute in xml testsuite element
77
- useBrowserName: false, // add browser name to report and classes names
78
- nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element
79
- classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element
80
- properties: {}, // key value pair of properties to add to the <properties> section of the report
81
- },
82
-
83
- // web server port
84
- port: 9876,
85
-
86
- // enable / disable colors in the output (reporters and logs)
87
- colors: true,
88
-
89
- // level of logging
90
- // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
91
- logLevel: config.LOG_INFO,
92
-
93
- // enable / disable watching file and executing tests whenever any file changes
94
- autoWatch: false,
95
-
96
- // --no-sandbox allows our tests to run in Linux without having to change the system.
97
- // --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex.
98
- browsers: ["ChromeHeadlessNoSandbox"],
99
- customLaunchers: {
100
- ChromeHeadlessNoSandbox: {
101
- base: "ChromeHeadless",
102
- flags: ["--no-sandbox", "--disable-web-security"],
103
- },
104
- },
105
-
106
- // Continuous Integration mode
107
- // if true, Karma captures browsers, runs the tests and exits
108
- singleRun: false,
109
-
110
- // Concurrency level
111
- // how many browser should be started simultaneous
112
- concurrency: 1,
113
-
114
- browserNoActivityTimeout: 60000000,
115
- browserDisconnectTimeout: 10000,
116
- browserDisconnectTolerance: 3,
117
-
118
- client: {
119
- mocha: {
120
- // change Karma's debug.html to the mocha web reporter
121
- reporter: "html",
122
- timeout: "600000",
123
- },
124
- },
125
- });
126
- };
127
- `;
128
-
129
1
  export const recordedClientContent = `
130
2
 
131
3
  import {
@@ -5,7 +5,7 @@ import {
5
5
  SourceFile,
6
6
  StructureKind,
7
7
  } from "ts-morph";
8
- import { buildUserAgentOptions, getClientParametersDeclaration } from "./helpers/client-helpers.js";
8
+ import { getClientParametersDeclaration } from "./helpers/client-helpers.js";
9
9
  import { getClassicalClientName, getClientName } from "./helpers/naming-helpers.js";
10
10
  import { ModularEmitterOptions } from "./interfaces.js";
11
11
 
@@ -127,11 +127,7 @@ export function buildClassicalClient(
127
127
  .map((p) => p.name)
128
128
  .map((x) => {
129
129
  if (x === "options") {
130
- return `{...options, userAgentOptions: ${buildUserAgentOptions(
131
- constructor,
132
- emitterOptions,
133
- "azsdk-js-client",
134
- )}}`;
130
+ return `options`;
135
131
  } else if (x.toLowerCase() === "subscriptionid" && shouldSubscriptionIdOptional) {
136
132
  return `subscriptionId ?? ""`;
137
133
  } else {
@@ -1,8 +1,15 @@
1
1
  import { SdkClientType, SdkServiceOperation } from "@azure-tools/typespec-client-generator-core";
2
2
  import { joinPaths, NoTarget } from "@typespec/compiler";
3
- import { Project, SourceFile } from "ts-morph";
3
+ import { Project, SourceFile, StructureKind } from "ts-morph";
4
4
  import { useContext } from "../context-manager.js";
5
5
  import { resolveReference } from "../framework/reference.js";
6
+ import {
7
+ beginSourceFileBatch,
8
+ enqueueStatement,
9
+ flushSourceFileBatch,
10
+ getEffectiveExportedNames,
11
+ getQueuedExportNames,
12
+ } from "../framework/source-file-batch.js";
6
13
  import { reportDiagnostic } from "../lib.js";
7
14
  import { getClientModuleInfo } from "../utils/client-utils.js";
8
15
  import { SdkContext } from "../utils/interfaces.js";
@@ -24,6 +31,20 @@ export function buildRootIndex(
24
31
  emitterOptions: ModularEmitterOptions,
25
32
  rootIndexFile: SourceFile,
26
33
  clientMap?: [string[], SdkClientType<SdkServiceOperation>],
34
+ ) {
35
+ beginSourceFileBatch();
36
+ try {
37
+ buildRootIndexImpl(context, emitterOptions, rootIndexFile, clientMap);
38
+ } finally {
39
+ flushSourceFileBatch();
40
+ }
41
+ }
42
+
43
+ function buildRootIndexImpl(
44
+ context: SdkContext,
45
+ emitterOptions: ModularEmitterOptions,
46
+ rootIndexFile: SourceFile,
47
+ clientMap?: [string[], SdkClientType<SdkServiceOperation>],
27
48
  ) {
28
49
  if (!clientMap) {
29
50
  // we still need to export the models if no client is provided
@@ -109,7 +130,8 @@ function exportRestErrorTypes(rootIndexFile: SourceFile) {
109
130
  const existingExports = getExistingExports(rootIndexFile);
110
131
  const namedExports = ["RestError", "isRestError"].filter((name) => !existingExports.has(name));
111
132
  if (namedExports.length > 0) {
112
- rootIndexFile.addExportDeclaration({
133
+ enqueueStatement(rootIndexFile, {
134
+ kind: StructureKind.ExportDeclaration,
113
135
  moduleSpecifier: "@azure/core-rest-pipeline",
114
136
  namedExports,
115
137
  });
@@ -165,13 +187,7 @@ function exportFileContentsType(context: SdkContext, rootIndexFile: SourceFile)
165
187
  }
166
188
 
167
189
  function getExistingExports(rootIndexFile: SourceFile): Set<string> {
168
- return new Set(
169
- rootIndexFile
170
- .getExportDeclarations()
171
- .flatMap((exportDecl) =>
172
- exportDecl.getNamedExports().map((namedExport) => namedExport.getName()),
173
- ),
174
- );
190
+ return getEffectiveExportedNames(rootIndexFile);
175
191
  }
176
192
 
177
193
  function getNewNamedExports(namedExports: string[], existingExports: Set<string>): string[] {
@@ -186,7 +202,8 @@ function addExportsToRootIndexFile(
186
202
  const existingExports = getExistingExports(rootIndexFile);
187
203
  const newNamedExports = getNewNamedExports(namedExports, existingExports);
188
204
  if (newNamedExports.length > 0) {
189
- rootIndexFile.addExportDeclaration({
205
+ enqueueStatement(rootIndexFile, {
206
+ kind: StructureKind.ExportDeclaration,
190
207
  isTypeOnly,
191
208
  namedExports: newNamedExports,
192
209
  });
@@ -222,7 +239,8 @@ function exportSimplePollerLike(
222
239
  const moduleSpecifier = `./${
223
240
  isTopLevel && subfolder && subfolder !== "" ? subfolder + "/" : ""
224
241
  }static-helpers/simplePollerHelpers.js`;
225
- indexFile.addExportDeclaration({
242
+ enqueueStatement(indexFile, {
243
+ kind: StructureKind.ExportDeclaration,
226
244
  isTypeOnly: true,
227
245
  moduleSpecifier,
228
246
  namedExports: ["SimplePollerLike"],
@@ -243,7 +261,10 @@ function exportRestoreHelpers(
243
261
  if (!helperFile) {
244
262
  return;
245
263
  }
246
- const exported = new Set(indexFile.getExportedDeclarations().keys());
264
+ const exported = new Set([
265
+ ...indexFile.getExportedDeclarations().keys(),
266
+ ...getQueuedExportNames(indexFile),
267
+ ]);
247
268
  const allEntries = [...helperFile.getExportedDeclarations().entries()];
248
269
  const moduleSpecifier = `./${
249
270
  isTopLevel && subfolder && subfolder !== "" ? subfolder + "/" : ""
@@ -259,7 +280,8 @@ function exportClassicalClient(
259
280
  isSubClient: boolean = false,
260
281
  ) {
261
282
  const clientName = client.name;
262
- indexFile.addExportDeclaration({
283
+ enqueueStatement(indexFile, {
284
+ kind: StructureKind.ExportDeclaration,
263
285
  namedExports: [clientName],
264
286
  moduleSpecifier: `./${
265
287
  subfolder && subfolder !== "" && !isSubClient ? subfolder + "/" : ""
@@ -321,7 +343,10 @@ function exportModules(
321
343
  continue;
322
344
  }
323
345
 
324
- const exported = new Set(indexFile.getExportedDeclarations().keys());
346
+ const exported = new Set([
347
+ ...indexFile.getExportedDeclarations().keys(),
348
+ ...getQueuedExportNames(indexFile),
349
+ ]);
325
350
  const serializerOrDeserializerRegex = /.*(Serializer|Deserializer)(_\d+)?$/;
326
351
  const filteredEntries = [...modelsFile.getExportedDeclarations().entries()].filter(
327
352
  (exDeclaration) => {
@@ -362,6 +387,19 @@ export function buildSubClientIndexFile(
362
387
  context: SdkContext,
363
388
  clientMap: [string[], SdkClientType<SdkServiceOperation>],
364
389
  emitterOptions: ModularEmitterOptions,
390
+ ) {
391
+ beginSourceFileBatch();
392
+ try {
393
+ buildSubClientIndexFileImpl(context, clientMap, emitterOptions);
394
+ } finally {
395
+ flushSourceFileBatch();
396
+ }
397
+ }
398
+
399
+ function buildSubClientIndexFileImpl(
400
+ context: SdkContext,
401
+ clientMap: [string[], SdkClientType<SdkServiceOperation>],
402
+ emitterOptions: ModularEmitterOptions,
365
403
  ) {
366
404
  const project = useContext("outputProject");
367
405
  const [_, client] = clientMap;
@@ -2,8 +2,13 @@ import { SdkClientType, SdkServiceOperation } from "@azure-tools/typespec-client
2
2
  import { joinPaths } from "@typespec/compiler";
3
3
  import { ModularEmitterOptions } from "./interfaces.js";
4
4
 
5
- import { Node, SourceFile } from "ts-morph";
5
+ import { Node, SourceFile, StructureKind } from "ts-morph";
6
6
  import { useContext } from "../context-manager.js";
7
+ import {
8
+ beginSourceFileBatch,
9
+ enqueueStatement,
10
+ flushSourceFileBatch,
11
+ } from "../framework/source-file-batch.js";
7
12
  import { getClientModuleInfo } from "../utils/client-utils.js";
8
13
 
9
14
  export interface buildSubpathIndexFileOptions {
@@ -17,6 +22,20 @@ export function buildSubpathIndexFile(
17
22
  subpath: string,
18
23
  clientMap?: [string[], SdkClientType<SdkServiceOperation>],
19
24
  options: buildSubpathIndexFileOptions = {},
25
+ ) {
26
+ beginSourceFileBatch();
27
+ try {
28
+ buildSubpathIndexFileImpl(emitterOptions, subpath, clientMap, options);
29
+ } finally {
30
+ flushSourceFileBatch();
31
+ }
32
+ }
33
+
34
+ function buildSubpathIndexFileImpl(
35
+ emitterOptions: ModularEmitterOptions,
36
+ subpath: string,
37
+ clientMap?: [string[], SdkClientType<SdkServiceOperation>],
38
+ options: buildSubpathIndexFileOptions = {},
20
39
  ) {
21
40
  const project = useContext("outputProject");
22
41
  const subfolder = clientMap ? (getClientModuleInfo(clientMap).subfolder ?? "") : "";
@@ -137,14 +156,16 @@ export function partitionAndEmitExports(
137
156
  }
138
157
  }
139
158
  if (typeOnlyExports.length > 0) {
140
- indexFile.addExportDeclaration({
159
+ enqueueStatement(indexFile, {
160
+ kind: StructureKind.ExportDeclaration,
141
161
  isTypeOnly: true,
142
162
  moduleSpecifier,
143
163
  namedExports: typeOnlyExports,
144
164
  });
145
165
  }
146
166
  if (valueExports.length > 0) {
147
- indexFile.addExportDeclaration({
167
+ enqueueStatement(indexFile, {
168
+ kind: StructureKind.ExportDeclaration,
148
169
  moduleSpecifier,
149
170
  namedExports: valueExports,
150
171
  });
@@ -46,6 +46,7 @@ import {
46
46
  pagedModelsKeptPublic,
47
47
  } from "../framework/hooks/sdk-types.js";
48
48
  import { refkey } from "../framework/refkey.js";
49
+ import { beginSourceFileBatch, flushSourceFileBatch } from "../framework/source-file-batch.js";
49
50
  import { reportDiagnostic } from "../lib.js";
50
51
  import { getClientHierarchyMap } from "../utils/client-utils.js";
51
52
  import { SdkContext } from "../utils/interfaces.js";
@@ -117,32 +118,37 @@ export function emitTypes(context: SdkContext, { sourceRoot }: { sourceRoot: str
117
118
 
118
119
  let sourceFile;
119
120
 
120
- for (const type of emitQueue) {
121
- if (!isGenerableType(type)) {
122
- continue;
123
- }
121
+ beginSourceFileBatch();
122
+ try {
123
+ for (const type of emitQueue) {
124
+ if (!isGenerableType(type)) {
125
+ continue;
126
+ }
124
127
 
125
- const namespaces = getModelNamespaces(context, type);
126
- const filepath = getModelsPath(sourceRoot, namespaces);
127
- sourceFile = outputProject.getSourceFile(filepath);
128
- if (!sourceFile) {
129
- sourceFile = outputProject.createSourceFile(filepath);
130
- sourceFile.addStatements(`/**
128
+ const namespaces = getModelNamespaces(context, type);
129
+ const filepath = getModelsPath(sourceRoot, namespaces);
130
+ sourceFile = outputProject.getSourceFile(filepath);
131
+ if (!sourceFile) {
132
+ sourceFile = outputProject.createSourceFile(filepath);
133
+ sourceFile.addStatements(`/*
131
134
  * This file contains only generated model types and their (de)serializers.
132
135
  * Disable the following rules for internal models with '_' prefix and deserializers which require 'any' for raw JSON input.
133
136
  */
134
137
  /* eslint-disable @typescript-eslint/naming-convention */
135
138
  /* eslint-disable @typescript-eslint/explicit-module-boundary-types */`);
139
+ }
140
+ emitType(context, type, sourceFile);
136
141
  }
137
- emitType(context, type, sourceFile);
138
- }
139
142
 
140
- // Emit serialization/deserialization functions for flattened properties
141
- for (const [property, _] of flattenPropertyModelMap) {
142
- const namespaces = getModelNamespaces(context, property.type);
143
- const filepath = getModelsPath(sourceRoot, namespaces);
144
- sourceFile = outputProject.getSourceFile(filepath);
145
- addSerializationFunctions(context, property, sourceFile!);
143
+ // Emit serialization/deserialization functions for flattened properties
144
+ for (const [property, _] of flattenPropertyModelMap) {
145
+ const namespaces = getModelNamespaces(context, property.type);
146
+ const filepath = getModelsPath(sourceRoot, namespaces);
147
+ sourceFile = outputProject.getSourceFile(filepath);
148
+ addSerializationFunctions(context, property, sourceFile!);
149
+ }
150
+ } finally {
151
+ flushSourceFileBatch();
146
152
  }
147
153
 
148
154
  const modelFiles = outputProject.getSourceFiles(sourceRoot + "/models/**/*.ts");
@@ -6,7 +6,7 @@ import {
6
6
  SdkMethodParameter,
7
7
  SdkServiceOperation,
8
8
  } from "@azure-tools/typespec-client-generator-core";
9
- import { OptionalKind, ParameterDeclarationStructure, StatementedNode } from "ts-morph";
9
+ import { Node, OptionalKind, ParameterDeclarationStructure, StatementedNode } from "ts-morph";
10
10
  import { ModularEmitterOptions } from "../interfaces.js";
11
11
 
12
12
  import { resolveReference } from "../../framework/reference.js";
@@ -236,12 +236,12 @@ export function buildGetClientEndpointParam(
236
236
  * @returns - an expression representing the options to be passed in to getClient
237
237
  */
238
238
  export function buildGetClientOptionsParam(
239
- context: StatementedNode,
239
+ context: StatementedNode & Node,
240
240
  emitterOptions: ModularEmitterOptions,
241
241
  endpointParam: string,
242
242
  apiVersionParamName?: string,
243
243
  ): string {
244
- const userAgentOptions = buildUserAgentOptions(context, emitterOptions, "azsdk-js-api");
244
+ const userAgentOptions = buildUserAgentOptions(context, emitterOptions);
245
245
  const loggingOptions = buildLoggingOptions();
246
246
  const credentials = buildCredentials(emitterOptions, endpointParam);
247
247
 
@@ -313,37 +313,55 @@ function buildLoggingOptions(): string | undefined {
313
313
  return `{ logger: options.loggingOptions?.logger ?? logger.info }`;
314
314
  }
315
315
 
316
+ /**
317
+ * Builds the `userAgentOptions` for the generated client, producing a telemetry
318
+ * prefix of the form `[<application_id> ]azsdk-js-<package>/<version>` per the Azure
319
+ * Core telemetry policy: https://azure.github.io/azure-sdk/general_azurecore.html#telemetry-policy
320
+ */
316
321
  export function buildUserAgentOptions(
317
- context: StatementedNode,
322
+ context: StatementedNode & Node,
318
323
  emitterOptions: ModularEmitterOptions,
319
- sdkUserAgentPrefix: string,
320
324
  ): string {
321
325
  const userAgentStatements = [];
322
326
  const prefixFromOptions = "const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix;";
323
327
  userAgentStatements.push(prefixFromOptions);
324
328
 
329
+ const packageName = emitterOptions.options.packageDetails?.name;
325
330
  const clientPackageName =
326
- emitterOptions.options.packageDetails?.nameWithoutScope ??
327
- emitterOptions.options.packageDetails?.name ??
328
- "";
329
- const packageVersion = emitterOptions.options.packageDetails?.version ?? "";
330
-
331
- const userAgentInfoStatement =
332
- packageVersion && clientPackageName && sdkUserAgentPrefix.includes("api")
333
- ? "const userAgentInfo = `azsdk-js-" + clientPackageName + "/" + packageVersion + "`;"
334
- : "";
335
-
336
- if (userAgentInfoStatement) {
337
- userAgentStatements.push(userAgentInfoStatement);
331
+ emitterOptions.options.packageDetails?.nameWithoutScope ?? packageName ?? "";
332
+
333
+ // The version is read from the generated package.json at runtime rather than
334
+ // hardcoded here: once the package is generated, its version is owned by
335
+ // package.json. We import it via the package's own "./package.json" export so
336
+ // the specifier is identical in `src` and the built `dist` output.
337
+ let userAgentInfoStatement = "";
338
+ if (clientPackageName && packageName) {
339
+ const packageJsonModule = `${packageName}/package.json`;
340
+ const sourceFile = context.getSourceFile();
341
+ const hasPackageJsonImport = sourceFile
342
+ .getImportDeclarations()
343
+ .some((declaration) => declaration.getModuleSpecifierValue() === packageJsonModule);
344
+ if (!hasPackageJsonImport) {
345
+ sourceFile.addImportDeclaration({
346
+ defaultImport: "pkgJson",
347
+ moduleSpecifier: packageJsonModule,
348
+ attributes: [{ name: "type", value: "json" }],
349
+ });
350
+ }
351
+ userAgentInfoStatement =
352
+ "const userAgentInfo = `azsdk-js-" + clientPackageName + "/${pkgJson.version}`;";
353
+ }
354
+
355
+ if (!userAgentInfoStatement) {
356
+ // Without package name/version we cannot build a meaningful telemetry token,
357
+ // so we leave the user agent untouched (the caller's userAgentOptions still flow
358
+ // through `...options`) and let core-rest-pipeline defaults apply.
359
+ return "";
338
360
  }
339
- const userAgentPrefix = `const userAgentPrefix = ${
340
- "prefixFromOptions ? `${prefixFromOptions} " +
341
- sdkUserAgentPrefix +
342
- `${userAgentInfoStatement ? " ${userAgentInfo}" : ""}` +
343
- "` : `" +
344
- `${sdkUserAgentPrefix}` +
345
- `${userAgentInfoStatement ? " ${userAgentInfo}`" : "`"}`
346
- };`;
361
+
362
+ userAgentStatements.push(userAgentInfoStatement);
363
+ const userAgentPrefix =
364
+ "const userAgentPrefix = prefixFromOptions ? `${prefixFromOptions} ${userAgentInfo}` : `${userAgentInfo}`;";
347
365
  userAgentStatements.push(userAgentPrefix);
348
366
 
349
367
  context.addStatements(userAgentStatements.join("\n"));
@@ -213,11 +213,11 @@ export function getDeserializePrivateFunction(
213
213
  };
214
214
  } else if (response.type) {
215
215
  // When response.optional is true, some HTTP responses have no body (e.g. 204), so
216
- // the return type must include undefined to reflect that possibility.
216
+ // the return type must include void to reflect that possibility.
217
217
  const baseType = getTypeExpression(context, response.type);
218
218
  returnType = {
219
219
  name: (response as any).name ?? "",
220
- type: response.optional ? `${baseType} | undefined` : baseType,
220
+ type: response.optional ? `${baseType} | void` : baseType,
221
221
  };
222
222
  } else if (isHeadAsBooleanOperation(operation)) {
223
223
  returnType = { name: "", type: "boolean" };
@@ -405,15 +405,15 @@ export function getDeserializePrivateFunction(
405
405
  }
406
406
  if (deserializeFunctionName) {
407
407
  if (needsBodyGuard) {
408
- // Use ternary form: return result.body ? deserializer(result.body) : undefined
409
408
  statements.push(
410
- `return ${deserializedRoot} ? ${deserializeFunctionName}(${deserializedRoot})${multipartCastSuffix} : undefined`,
411
- );
412
- } else {
413
- statements.push(
414
- `return ${deserializeFunctionName}(${deserializedRoot})${multipartCastSuffix}`,
409
+ `if (!${deserializedRoot}) {
410
+ return;
411
+ }`,
415
412
  );
416
413
  }
414
+ statements.push(
415
+ `return ${deserializeFunctionName}(${deserializedRoot})${multipartCastSuffix}`,
416
+ );
417
417
  } else if (isAzureCoreErrorType(context.program, deserializedType.__raw)) {
418
418
  statements.push(`return ${deserializedRoot}${multipartCastSuffix}`);
419
419
  } else if (isHeadAsBooleanOperation(operation)) {
@@ -949,13 +949,13 @@ export function getOperationFunction(
949
949
  const baseCompositeType = buildCompositeResponseType(context, type, responseHeaders);
950
950
  returnType = {
951
951
  name: (type as any).name ?? "",
952
- type: response.optional ? `${baseCompositeType} | undefined` : baseCompositeType,
952
+ type: response.optional ? `${baseCompositeType} | void` : baseCompositeType,
953
953
  };
954
954
  } else {
955
955
  const baseType = getTypeExpression(context, type!);
956
956
  returnType = {
957
957
  name: (type as any).name ?? "",
958
- type: response.optional ? `${baseType} | undefined` : baseType,
958
+ type: response.optional ? `${baseType} | void` : baseType,
959
959
  };
960
960
  }
961
961
  } else if (hasHeaderOnlyResponse && isResponseHeadersEnabled) {
@@ -44,7 +44,6 @@ function extractClientOptions(
44
44
  const enableModelNamespace = getEnableModelNamespace(dpgContext, emitterOptions);
45
45
  const hierarchyClient = getHierarchyClient(emitterOptions);
46
46
  const clearOutputFolder = getClearOutputFolder(emitterOptions);
47
- const isTypeSpecTest = emitterOptions["is-typespec-test"];
48
47
  const compatibilityMode = emitterOptions["compatibility-mode"];
49
48
  const compatibilityLro = emitterOptions["compatibility-lro"];
50
49
  const experimentalExtensibleEnums = emitterOptions["experimental-extensible-enums"];
@@ -73,7 +72,6 @@ function extractClientOptions(
73
72
  hierarchyClient,
74
73
  azureArm: dpgContext.arm,
75
74
  clearOutputFolder,
76
- isTypeSpecTest,
77
75
  compatibilityMode,
78
76
  compatibilityLro,
79
77
  experimentalExtensibleEnums,
@@ -234,14 +232,12 @@ function buildPackageDetails(program: Program, emitterOptions: EmitterOptions):
234
232
  nameWithoutScope: "unamedpackage",
235
233
  version: "1.0.0-beta.1",
236
234
  };
237
- const isVersionUserProvided = Boolean(emitterOptions["package-details"]?.version);
238
235
  const packageDetails: PackageDetails = {
239
236
  ...emitterOptions["package-details"],
240
237
  name:
241
238
  emitterOptions["package-details"]?.name ??
242
239
  normalizeName(getDefaultService(program)?.title ?? "", NameType.Class),
243
- version: emitterOptions["package-details"]?.version ?? "1.0.0-beta.1",
244
- isVersionUserProvided,
240
+ version: emitterOptions["package-details"]?.version,
245
241
  };
246
242
  if (emitterOptions["package-details"]?.name) {
247
243
  const nameParts = emitterOptions["package-details"]?.name.split("/");