@azure-tools/typespec-ts 0.55.0-dev.2 → 0.55.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 (145) hide show
  1. package/README.md +96 -24
  2. package/dist/src/framework/hooks/binder.d.ts.map +1 -1
  3. package/dist/src/framework/hooks/binder.js +3 -4
  4. package/dist/src/framework/hooks/binder.js.map +1 -1
  5. package/dist/src/framework/load-static-helpers.d.ts +4 -1
  6. package/dist/src/framework/load-static-helpers.d.ts.map +1 -1
  7. package/dist/src/framework/load-static-helpers.js +19 -15
  8. package/dist/src/framework/load-static-helpers.js.map +1 -1
  9. package/dist/src/framework/sample.js +1 -1
  10. package/dist/src/framework/sample.js.map +1 -1
  11. package/dist/src/index.d.ts.map +1 -1
  12. package/dist/src/index.js +47 -28
  13. package/dist/src/index.js.map +1 -1
  14. package/dist/src/lib.js +30 -31
  15. package/dist/src/lib.js.map +1 -1
  16. package/dist/src/modular/build-project-files.d.ts.map +1 -1
  17. package/dist/src/modular/build-project-files.js +4 -4
  18. package/dist/src/modular/build-project-files.js.map +1 -1
  19. package/dist/src/modular/build-restore-poller.js +2 -2
  20. package/dist/src/modular/build-restore-poller.js.map +1 -1
  21. package/dist/src/modular/build-root-index.d.ts.map +1 -1
  22. package/dist/src/modular/build-root-index.js +5 -6
  23. package/dist/src/modular/build-root-index.js.map +1 -1
  24. package/dist/src/modular/build-subpath-index.d.ts.map +1 -1
  25. package/dist/src/modular/build-subpath-index.js +4 -4
  26. package/dist/src/modular/build-subpath-index.js.map +1 -1
  27. package/dist/src/modular/emit-models-options.js +2 -2
  28. package/dist/src/modular/emit-models-options.js.map +1 -1
  29. package/dist/src/modular/emit-models.d.ts.map +1 -1
  30. package/dist/src/modular/emit-models.js +2 -3
  31. package/dist/src/modular/emit-models.js.map +1 -1
  32. package/dist/src/modular/emit-samples.d.ts.map +1 -1
  33. package/dist/src/modular/emit-samples.js +3 -4
  34. package/dist/src/modular/emit-samples.js.map +1 -1
  35. package/dist/src/modular/emit-tests.d.ts +2 -1
  36. package/dist/src/modular/emit-tests.d.ts.map +1 -1
  37. package/dist/src/modular/emit-tests.js +21 -11
  38. package/dist/src/modular/emit-tests.js.map +1 -1
  39. package/dist/src/modular/helpers/example-value-helpers.js +4 -4
  40. package/dist/src/modular/helpers/example-value-helpers.js.map +1 -1
  41. package/dist/src/rlc-common/build-client-definitions.js +3 -3
  42. package/dist/src/rlc-common/build-client-definitions.js.map +1 -1
  43. package/dist/src/rlc-common/build-client.js +4 -4
  44. package/dist/src/rlc-common/build-client.js.map +1 -1
  45. package/dist/src/rlc-common/build-index-file.js +3 -3
  46. package/dist/src/rlc-common/build-index-file.js.map +1 -1
  47. package/dist/src/rlc-common/build-is-unexpected-helper.js +3 -3
  48. package/dist/src/rlc-common/build-is-unexpected-helper.js.map +1 -1
  49. package/dist/src/rlc-common/build-logger.js +3 -3
  50. package/dist/src/rlc-common/build-logger.js.map +1 -1
  51. package/dist/src/rlc-common/build-paginate-helper.js +2 -2
  52. package/dist/src/rlc-common/build-paginate-helper.js.map +1 -1
  53. package/dist/src/rlc-common/build-parameter-types.js +3 -3
  54. package/dist/src/rlc-common/build-parameter-types.js.map +1 -1
  55. package/dist/src/rlc-common/build-polling-helper.js +2 -2
  56. package/dist/src/rlc-common/build-polling-helper.js.map +1 -1
  57. package/dist/src/rlc-common/build-response-types.js +3 -3
  58. package/dist/src/rlc-common/build-response-types.js.map +1 -1
  59. package/dist/src/rlc-common/build-samples.js +2 -2
  60. package/dist/src/rlc-common/build-samples.js.map +1 -1
  61. package/dist/src/rlc-common/build-schema-type.js +4 -4
  62. package/dist/src/rlc-common/build-schema-type.js.map +1 -1
  63. package/dist/src/rlc-common/build-serialize-helper.js +2 -2
  64. package/dist/src/rlc-common/build-serialize-helper.js.map +1 -1
  65. package/dist/src/rlc-common/build-top-level-index-file.js +3 -3
  66. package/dist/src/rlc-common/build-top-level-index-file.js.map +1 -1
  67. package/dist/src/rlc-common/helpers/path-utils.d.ts.map +1 -1
  68. package/dist/src/rlc-common/helpers/path-utils.js +1 -2
  69. package/dist/src/rlc-common/helpers/path-utils.js.map +1 -1
  70. package/dist/src/rlc-common/metadata/build-api-extractor-config.js +1 -1
  71. package/dist/src/rlc-common/metadata/build-api-extractor-config.js.map +1 -1
  72. package/dist/src/rlc-common/metadata/build-es-lint-config.js +1 -1
  73. package/dist/src/rlc-common/metadata/build-es-lint-config.js.map +1 -1
  74. package/dist/src/rlc-common/metadata/build-package-file.js +1 -1
  75. package/dist/src/rlc-common/metadata/build-package-file.js.map +1 -1
  76. package/dist/src/rlc-common/metadata/build-readme-file.d.ts +2 -2
  77. package/dist/src/rlc-common/metadata/build-readme-file.d.ts.map +1 -1
  78. package/dist/src/rlc-common/metadata/build-readme-file.js +11 -14
  79. package/dist/src/rlc-common/metadata/build-readme-file.js.map +1 -1
  80. package/dist/src/rlc-common/metadata/build-rollup-config.js +1 -1
  81. package/dist/src/rlc-common/metadata/build-rollup-config.js.map +1 -1
  82. package/dist/src/rlc-common/metadata/build-ts-config.js +1 -1
  83. package/dist/src/rlc-common/metadata/build-ts-config.js.map +1 -1
  84. package/dist/src/transform/transform.d.ts.map +1 -1
  85. package/dist/src/transform/transform.js +2 -3
  86. package/dist/src/transform/transform.js.map +1 -1
  87. package/dist/src/utils/emit-util.d.ts.map +1 -1
  88. package/dist/src/utils/emit-util.js +10 -5
  89. package/dist/src/utils/emit-util.js.map +1 -1
  90. package/dist/src/utils/file-system-utils.d.ts +4 -4
  91. package/dist/src/utils/file-system-utils.d.ts.map +1 -1
  92. package/dist/src/utils/file-system-utils.js +14 -16
  93. package/dist/src/utils/file-system-utils.js.map +1 -1
  94. package/dist/src/utils/import-helper.js +3 -3
  95. package/dist/src/utils/import-helper.js.map +1 -1
  96. package/dist/src/utils/resolve-project-root.d.ts +6 -4
  97. package/dist/src/utils/resolve-project-root.d.ts.map +1 -1
  98. package/dist/src/utils/resolve-project-root.js +11 -18
  99. package/dist/src/utils/resolve-project-root.js.map +1 -1
  100. package/dist/tsconfig.tsbuildinfo +1 -1
  101. package/package.json +26 -26
  102. package/src/framework/hooks/binder.ts +3 -4
  103. package/src/framework/load-static-helpers.ts +29 -18
  104. package/src/framework/sample.ts +1 -1
  105. package/src/index.ts +61 -28
  106. package/src/lib.ts +31 -31
  107. package/src/modular/build-project-files.ts +11 -4
  108. package/src/modular/build-restore-poller.ts +2 -2
  109. package/src/modular/build-root-index.ts +5 -6
  110. package/src/modular/build-subpath-index.ts +4 -4
  111. package/src/modular/emit-models-options.ts +2 -2
  112. package/src/modular/emit-models.ts +2 -3
  113. package/src/modular/emit-samples.ts +3 -4
  114. package/src/modular/emit-tests.ts +28 -11
  115. package/src/modular/helpers/example-value-helpers.ts +4 -4
  116. package/src/rlc-common/build-client-definitions.ts +3 -3
  117. package/src/rlc-common/build-client.ts +4 -4
  118. package/src/rlc-common/build-index-file.ts +3 -3
  119. package/src/rlc-common/build-is-unexpected-helper.ts +3 -3
  120. package/src/rlc-common/build-logger.ts +3 -3
  121. package/src/rlc-common/build-paginate-helper.ts +2 -2
  122. package/src/rlc-common/build-parameter-types.ts +3 -3
  123. package/src/rlc-common/build-polling-helper.ts +2 -2
  124. package/src/rlc-common/build-response-types.ts +3 -3
  125. package/src/rlc-common/build-samples.ts +2 -2
  126. package/src/rlc-common/build-schema-type.ts +4 -4
  127. package/src/rlc-common/build-serialize-helper.ts +2 -2
  128. package/src/rlc-common/build-top-level-index-file.ts +3 -3
  129. package/src/rlc-common/helpers/path-utils.ts +1 -3
  130. package/src/rlc-common/metadata/build-api-extractor-config.ts +1 -1
  131. package/src/rlc-common/metadata/build-es-lint-config.ts +1 -1
  132. package/src/rlc-common/metadata/build-package-file.ts +1 -1
  133. package/src/rlc-common/metadata/build-readme-file.ts +12 -15
  134. package/src/rlc-common/metadata/build-rollup-config.ts +1 -1
  135. package/src/rlc-common/metadata/build-ts-config.ts +1 -1
  136. package/src/transform/transform.ts +2 -3
  137. package/src/utils/emit-util.ts +10 -8
  138. package/src/utils/file-system-utils.ts +14 -15
  139. package/src/utils/import-helper.ts +3 -3
  140. package/src/utils/resolve-project-root.ts +11 -24
  141. package/dist/src/utils/dirname.d.ts +0 -9
  142. package/dist/src/utils/dirname.d.ts.map +0 -1
  143. package/dist/src/utils/dirname.js +0 -12
  144. package/dist/src/utils/dirname.js.map +0 -1
  145. package/src/utils/dirname.ts +0 -12
package/package.json CHANGED
@@ -1,32 +1,32 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.55.0-dev.2",
3
+ "version": "0.55.0-dev.4",
4
4
  "description": "An experimental TypeSpec emitter for TypeScript RLC",
5
5
  "main": "dist/src/index.js",
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": {
9
- "default": "./dist/src/index.js",
10
- "types": "./dist/src/index.d.ts"
9
+ "types": "./dist/src/index.d.ts",
10
+ "default": "./dist/src/index.js"
11
11
  },
12
12
  "./testing": {
13
- "default": "./dist/src/testing/index.js",
14
- "types": "./dist/src/testing/index.d.ts"
13
+ "types": "./dist/src/testing/index.d.ts",
14
+ "default": "./dist/src/testing/index.js"
15
15
  }
16
16
  },
17
17
  "author": "Jose Heredia <joheredi@microsoft.com>",
18
18
  "license": "MIT",
19
19
  "devDependencies": {
20
20
  "@azure-rest/core-client": "^2.3.1",
21
- "@typespec/http-specs": "^0.1.0-alpha.37 || >=0.1.0-alpha.38-dev <0.1.0-alpha.38",
21
+ "@typespec/http-specs": "^0.1.0-alpha.38 || >=0.1.0-alpha.39-dev <0.1.0-alpha.39",
22
22
  "@typespec/spector": "^0.1.0-alpha.25 || >=0.1.0-alpha.26-dev <0.1.0-alpha.26",
23
23
  "@typespec/spec-api": "^0.1.0-alpha.14 || >=0.1.0-alpha.15-dev <0.1.0-alpha.15",
24
- "@typespec/tspd": "^0.74.2 || >=0.75.0-dev <0.75.0",
25
- "@azure-tools/azure-http-specs": "^0.1.0-alpha.40 || >=0.1.0-alpha.41-dev <0.1.0-alpha.41",
26
- "@azure-tools/typespec-autorest": "^0.68.0 || >=0.69.0-dev <0.69.0",
27
- "@azure-tools/typespec-azure-core": "^0.68.0 || >=0.69.0-dev <0.69.0",
28
- "@azure-tools/typespec-azure-resource-manager": "^0.68.0 || >=0.69.0-dev <0.69.0",
29
- "@azure-tools/typespec-client-generator-core": "^0.68.4 || >=0.69.0-dev <0.69.0",
24
+ "@typespec/tspd": "^0.75.0 || >=0.76.0-dev <0.76.0",
25
+ "@azure-tools/azure-http-specs": "^0.1.0-alpha.42 || >=0.1.0-alpha.43-dev <0.1.0-alpha.43",
26
+ "@azure-tools/typespec-autorest": "^0.69.0 || >=0.70.0-dev <0.70.0",
27
+ "@azure-tools/typespec-azure-core": "^0.69.0 || >=0.70.0-dev <0.70.0",
28
+ "@azure-tools/typespec-azure-resource-manager": "^0.69.0 || >=0.70.0-dev <0.70.0",
29
+ "@azure-tools/typespec-client-generator-core": "^0.69.0 || >=0.70.0-dev <0.70.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",
@@ -34,12 +34,12 @@
34
34
  "@azure/core-rest-pipeline": "^1.14.0",
35
35
  "@azure/core-util": "^1.4.0",
36
36
  "@azure/logger": "^1.0.4",
37
- "@typespec/compiler": "^1.12.0",
38
- "@typespec/http": "^1.12.0",
39
- "@typespec/openapi": "^1.12.0",
40
- "@typespec/rest": "^0.82.0 || >=0.83.0-dev <0.83.0",
41
- "@typespec/versioning": "^0.82.0 || >=0.83.0-dev <0.83.0",
42
- "@typespec/xml": "^0.82.0 || >=0.83.0-dev <0.83.0",
37
+ "@typespec/compiler": "^1.13.0",
38
+ "@typespec/http": "^1.13.0",
39
+ "@typespec/openapi": "^1.13.0",
40
+ "@typespec/rest": "^0.83.0 || >=0.84.0-dev <0.84.0",
41
+ "@typespec/versioning": "^0.83.0 || >=0.84.0-dev <0.84.0",
42
+ "@typespec/xml": "^0.83.0 || >=0.84.0-dev <0.84.0",
43
43
  "@typespec/ts-http-runtime": "0.3.5",
44
44
  "cross-env": "^10.1.0",
45
45
  "mkdirp": "^3.0.1",
@@ -53,13 +53,13 @@
53
53
  "yaml": "^2.8.3"
54
54
  },
55
55
  "peerDependencies": {
56
- "@azure-tools/typespec-azure-core": "^0.68.0 || >=0.69.0-dev <0.69.0",
57
- "@azure-tools/typespec-client-generator-core": "^0.68.4 || >=0.69.0-dev <0.69.0",
58
- "@typespec/compiler": "^1.12.0",
59
- "@typespec/http": "^1.12.0",
60
- "@typespec/rest": "^0.82.0 || >=0.83.0-dev <0.83.0",
61
- "@typespec/versioning": "^0.82.0 || >=0.83.0-dev <0.83.0",
62
- "@typespec/xml": "^0.82.0 || >=0.83.0-dev <0.83.0"
56
+ "@azure-tools/typespec-azure-core": "^0.69.0 || >=0.70.0-dev <0.70.0",
57
+ "@azure-tools/typespec-client-generator-core": "^0.69.0 || >=0.70.0-dev <0.70.0",
58
+ "@typespec/compiler": "^1.13.0",
59
+ "@typespec/http": "^1.13.0",
60
+ "@typespec/rest": "^0.83.0 || >=0.84.0-dev <0.84.0",
61
+ "@typespec/versioning": "^0.83.0 || >=0.84.0-dev <0.84.0",
62
+ "@typespec/xml": "^0.83.0 || >=0.84.0-dev <0.84.0"
63
63
  },
64
64
  "dependencies": {
65
65
  "fast-xml-parser": "^5.7.0",
@@ -127,6 +127,6 @@
127
127
  "unit-test": "npm-run-all --parallel unit-test:rlc unit-test:modular",
128
128
  "unit-test:rlc": "vitest run --project unit-rlc",
129
129
  "unit-test:modular": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vitest run --project unit-modular",
130
- "regen-docs": "npm run build && tspd doc . --enable-experimental --output-dir ./website/src/content/docs/docs/emitters/clients/typespec-ts/reference --skip-js"
130
+ "regen-docs": "npm run build && tspd doc . --enable-experimental --output-dir ../../website/src/content/docs/docs/emitters/clients/typespec-ts/reference --skip-js"
131
131
  }
132
132
  }
@@ -1,5 +1,4 @@
1
- import { normalizePath } from "@typespec/compiler";
2
- import path from "path/posix";
1
+ import { joinPaths, normalizePath } from "@typespec/compiler";
3
2
  import {
4
3
  ImportDeclarationStructure,
5
4
  ImportSpecifierStructure,
@@ -354,7 +353,7 @@ class BinderImp implements Binder {
354
353
 
355
354
  // Also keep files that are imported by any used helper file
356
355
  const helperFiles = this.project.getSourceFiles(
357
- normalizePath(path.join(sourceRoot, "static-helpers/**/*.*ts")),
356
+ normalizePath(joinPaths(sourceRoot, "static-helpers/**/*.*ts")),
358
357
  );
359
358
  const usedFiles = helperFiles.filter((file) => !isFileUnused(file, usedHelperNames));
360
359
  for (const usedFile of usedFiles) {
@@ -378,7 +377,7 @@ class BinderImp implements Binder {
378
377
  }
379
378
  this.project
380
379
  //normalizae the final path to adapt to different systems
381
- .getSourceFiles(normalizePath(path.join(testRoot, "test/generated/util/**/*.*ts")))
380
+ .getSourceFiles(normalizePath(joinPaths(testRoot, "test/generated/util/**/*.*ts")))
382
381
  .filter((file) => isFileUnused(file, usedHelperNames))
383
382
  .forEach((helperFile) => helperFile.delete());
384
383
  }
@@ -1,6 +1,4 @@
1
- import { NoTarget, Program } from "@typespec/compiler";
2
- import { readdir, readFile, stat } from "fs/promises";
3
- import * as path from "path";
1
+ import { type CompilerHost, joinPaths, NoTarget, Program } from "@typespec/compiler";
4
2
  import {
5
3
  ClassDeclaration,
6
4
  EnumDeclaration,
@@ -39,6 +37,9 @@ export interface LoadStaticHelpersOptions extends Partial<ModularEmitterOptions>
39
37
  sourcesDir?: string;
40
38
  rootDir?: string;
41
39
  program?: Program;
40
+ host?: CompilerHost;
41
+ /** The emitter package root directory (where static/ lives). */
42
+ packageRoot?: string;
42
43
  /** When true, also load test helpers from static/test-helpers/ into test/generated/util/ */
43
44
  loadTestHelpers?: boolean;
44
45
  }
@@ -54,13 +55,20 @@ export async function loadStaticHelpers(
54
55
  options: LoadStaticHelpersOptions = {},
55
56
  ): Promise<Map<string, StaticHelperMetadata>> {
56
57
  const helpersMap = new Map<string, StaticHelperMetadata>();
58
+ const host = options.host ?? options.program?.host;
59
+ if (!host) {
60
+ throw new Error(
61
+ "loadStaticHelpers requires either a host or program in options to access the file system.",
62
+ );
63
+ }
64
+
65
+ const packageRoot = options.packageRoot ?? resolveProjectRoot();
66
+
57
67
  // Load static helpers used in sources code
58
- const defaultStaticHelpersPath = path.join(
59
- resolveProjectRoot(),
60
- DEFAULT_SOURCES_STATIC_HELPERS_PATH,
61
- );
68
+ const defaultStaticHelpersPath = joinPaths(packageRoot, DEFAULT_SOURCES_STATIC_HELPERS_PATH);
62
69
  const filesInSources = await traverseDirectory(
63
70
  options.helpersAssetDirectory ?? defaultStaticHelpersPath,
71
+ host,
64
72
  options.program,
65
73
  );
66
74
  await loadFiles(filesInSources, options.sourcesDir ?? "");
@@ -69,12 +77,10 @@ export async function loadStaticHelpers(
69
77
  options.loadTestHelpers ??
70
78
  (options.options?.generateTest && isAzurePackage({ options: options.options }))
71
79
  ) {
72
- const defaultTestingHelpersPath = path.join(
73
- resolveProjectRoot(),
74
- DEFAULT_SOURCES_TESTING_HELPERS_PATH,
75
- );
80
+ const defaultTestingHelpersPath = joinPaths(packageRoot, DEFAULT_SOURCES_TESTING_HELPERS_PATH);
76
81
  const filesInTestings = await traverseDirectory(
77
82
  defaultTestingHelpersPath,
83
+ host,
78
84
  options.program,
79
85
  [],
80
86
  "",
@@ -86,8 +92,9 @@ export async function loadStaticHelpers(
86
92
 
87
93
  async function loadFiles(files: FileMetadata[], generateDir: string) {
88
94
  for (const file of files) {
89
- const targetPath = path.join(generateDir, file.target);
90
- const contents = await readFile(file.source, "utf-8");
95
+ const targetPath = joinPaths(generateDir, file.target);
96
+ const sourceFile = await host!.readFile(file.source);
97
+ const contents = sourceFile.text;
91
98
  const addedFile = project.createSourceFile(targetPath, contents, {
92
99
  overwrite: true,
93
100
  });
@@ -187,32 +194,36 @@ function getDeclarationByMetadata(
187
194
  }
188
195
  }
189
196
 
197
+ type FsHost = Pick<CompilerHost, "readFile" | "readDir" | "stat">;
198
+
190
199
  const _targetStaticHelpersBaseDir = "static-helpers";
191
200
  async function traverseDirectory(
192
201
  directory: string,
202
+ host: FsHost,
193
203
  program?: Program,
194
204
  result: { source: string; target: string }[] = [],
195
205
  relativePath: string = "",
196
206
  targetBaseDir: string = _targetStaticHelpersBaseDir,
197
207
  ): Promise<{ source: string; target: string }[]> {
198
208
  try {
199
- const files = await readdir(directory);
209
+ const files = await host.readDir(directory);
200
210
 
201
211
  await Promise.all(
202
212
  files.map(async (file) => {
203
- const filePath = path.join(directory, file);
204
- const fileStat = await stat(filePath);
213
+ const filePath = joinPaths(directory, file);
214
+ const fileStat = await host.stat(filePath);
205
215
 
206
216
  if (fileStat.isDirectory()) {
207
217
  await traverseDirectory(
208
218
  filePath,
219
+ host,
209
220
  program,
210
221
  result,
211
- path.join(relativePath, file),
222
+ joinPaths(relativePath, file),
212
223
  targetBaseDir,
213
224
  );
214
225
  } else if (fileStat.isFile() && !file.endsWith(".d.ts") && /.*\..?ts$/.test(file)) {
215
- const target = path.join(targetBaseDir, relativePath, file);
226
+ const target = joinPaths(targetBaseDir, relativePath, file);
216
227
  result.push({ source: filePath, target });
217
228
  }
218
229
  }),
@@ -9,7 +9,7 @@ import { useBinder } from "./hooks/binder.js";
9
9
  import { resolveReference } from "./reference.js";
10
10
 
11
11
  // Create a new ts-morph project
12
- const project = new Project();
12
+ const project = new Project({ useInMemoryFileSystem: true });
13
13
 
14
14
  // Create a source file
15
15
  const sourceFile = project.createSourceFile("test.ts", "", { overwrite: true });
package/src/index.ts CHANGED
@@ -1,7 +1,15 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
 
4
- import { EmitContext, Program } from "@typespec/compiler";
4
+ import {
5
+ EmitContext,
6
+ Program,
7
+ getBaseFileName,
8
+ getDirectoryPath,
9
+ joinPaths,
10
+ resolvePath,
11
+ type CompilerHost,
12
+ } from "@typespec/compiler";
5
13
  import { provideContext, useContext } from "./context-manager.js";
6
14
  import { buildRootIndex, buildSubClientIndexFile } from "./modular/build-root-index.js";
7
15
  import {
@@ -79,8 +87,6 @@ import {
79
87
  createSdkContext,
80
88
  listAllServiceNamespaces,
81
89
  } from "@azure-tools/typespec-client-generator-core";
82
- import { existsSync } from "fs";
83
- import { basename, join } from "path";
84
90
  import { Project } from "ts-morph";
85
91
  import { provideBinder } from "./framework/hooks/binder.js";
86
92
  import { provideSdkTypes } from "./framework/hooks/sdk-types.js";
@@ -118,8 +124,15 @@ export async function $onEmit(context: EmitContext) {
118
124
  return;
119
125
  }
120
126
  /** Shared status */
121
- const outputProject = new Project();
127
+ const outputProject = new Project({ useInMemoryFileSystem: true });
122
128
  const program: Program = context.program;
129
+ const host: CompilerHost = program.host;
130
+ // Derive the emitter package root from the compiler's resolved emitter entry point.
131
+ // This works correctly in both Node.js and the browser playground VFS.
132
+ const emitterRef = program.emitters.find((e) => e.metadata.name === "@azure-tools/typespec-ts");
133
+ const emitterPackageRoot = emitterRef
134
+ ? resolvePath(getDirectoryPath(emitterRef.main), "../..")
135
+ : undefined;
123
136
  const emitterOptions: EmitterOptions = context.options;
124
137
  const dpgContext = await createContextWithDefaultOptions(context);
125
138
 
@@ -161,6 +174,8 @@ export async function $onEmit(context: EmitContext) {
161
174
  rootDir: dpgContext.generationPathDetail?.rootDir,
162
175
  options: rlcOptions,
163
176
  program,
177
+ host,
178
+ packageRoot: emitterPackageRoot,
164
179
  },
165
180
  );
166
181
  const extraDependencies = isAzurePackage({ options: rlcOptions })
@@ -217,10 +232,11 @@ export async function $onEmit(context: EmitContext) {
217
232
  // clear output folder if needed
218
233
  if (options.clearOutputFolder) {
219
234
  // Clear output directory while preserving TempTypeSpecFiles
220
- await clearDirectory(context.emitterOutputDir, ["TempTypeSpecFiles"], program);
235
+ await clearDirectory(host, context.emitterOutputDir, ["TempTypeSpecFiles"], program);
221
236
  }
222
237
  const hasTestFolder = await pathExists(
223
- join(dpgContext.generationPathDetail?.metadataDir ?? "", "test"),
238
+ host,
239
+ joinPaths(dpgContext.generationPathDetail?.metadataDir ?? "", "test"),
224
240
  );
225
241
  options.generateTest =
226
242
  options.generateTest === true ||
@@ -232,15 +248,15 @@ export async function $onEmit(context: EmitContext) {
232
248
 
233
249
  async function calculateGenerationDir(): Promise<GenerationDirDetail> {
234
250
  const projectRoot = context.emitterOutputDir ?? "";
235
- const customizationFolder = join(projectRoot, "generated");
236
- const srcGeneratedFolder = join(projectRoot, "src", "generated");
251
+ const customizationFolder = joinPaths(projectRoot, "generated");
252
+ const srcGeneratedFolder = joinPaths(projectRoot, "src", "generated");
237
253
  // if customization folder exists, use it as sources root
238
- const finalCustomizationFolder = (await pathExists(srcGeneratedFolder))
254
+ const finalCustomizationFolder = (await pathExists(host, srcGeneratedFolder))
239
255
  ? srcGeneratedFolder
240
256
  : customizationFolder;
241
- const sourcesRoot = (await pathExists(finalCustomizationFolder))
257
+ const sourcesRoot = (await pathExists(host, finalCustomizationFolder))
242
258
  ? finalCustomizationFolder
243
- : join(projectRoot, "src");
259
+ : joinPaths(projectRoot, "src");
244
260
  return {
245
261
  rootDir: projectRoot,
246
262
  metadataDir: projectRoot,
@@ -251,6 +267,7 @@ export async function $onEmit(context: EmitContext) {
251
267
 
252
268
  async function clearSrcFolder() {
253
269
  await emptyDir(
270
+ host,
254
271
  dpgContext.generationPathDetail?.modularSourcesDir ??
255
272
  dpgContext.generationPathDetail?.rlcSourcesDir ??
256
273
  "",
@@ -259,9 +276,12 @@ export async function $onEmit(context: EmitContext) {
259
276
 
260
277
  async function clearSamplesDevFolder() {
261
278
  if (emitterOptions["generate-sample"] === true) {
262
- const samplesDevPath = join(dpgContext.generationPathDetail?.rootDir ?? "", "samples-dev");
263
- if (await pathExists(samplesDevPath)) {
264
- await emptyDir(samplesDevPath);
279
+ const samplesDevPath = joinPaths(
280
+ dpgContext.generationPathDetail?.rootDir ?? "",
281
+ "samples-dev",
282
+ );
283
+ if (await pathExists(host, samplesDevPath)) {
284
+ await emptyDir(host, samplesDevPath);
265
285
  }
266
286
  }
267
287
  }
@@ -316,7 +336,7 @@ export async function $onEmit(context: EmitContext) {
316
336
  // platform-types (and its browser/react-native variants); emit those
317
337
  // files directly under src/ (strip the static-helpers/ segment) to match
318
338
  // the RLC design where all generated output lives in src/.
319
- if (!basename(filePath).startsWith("platform-types")) {
339
+ if (!getBaseFileName(filePath).startsWith("platform-types")) {
320
340
  continue;
321
341
  }
322
342
  const outputPath = filePath.replace(/\/static-helpers\//g, "/");
@@ -450,27 +470,30 @@ export async function $onEmit(context: EmitContext) {
450
470
  // to avoid unexpected modifications to files like package.json, README.md,
451
471
  // warp.config.yml, and snippets.spec.ts. metadata.json is still updated.
452
472
  const sourcesDir = dpgContext.generationPathDetail?.modularSourcesDir ?? "";
453
- const hasManualConvenienceLayer = basename(sourcesDir) === "generated";
473
+ const hasManualConvenienceLayer = getBaseFileName(sourcesDir) === "generated";
454
474
  const isAzureFlavor = isAzurePackage({ options: option });
455
475
  // Generate metadata
456
- const existingPackageFilePath = join(
476
+ const existingPackageFilePath = joinPaths(
457
477
  dpgContext.generationPathDetail?.metadataDir ?? "",
458
478
  "package.json",
459
479
  );
460
- const hasPackageFile = await existsSync(existingPackageFilePath);
461
- const existingReadmeFilePath = join(
480
+ const hasPackageFile = await pathExists(host, existingPackageFilePath);
481
+ const existingReadmeFilePath = joinPaths(
462
482
  dpgContext.generationPathDetail?.metadataDir ?? "",
463
483
  "README.md",
464
484
  );
465
- const hasReadmeFile = await existsSync(existingReadmeFilePath);
466
- const existingChangelogFilePath = join(
485
+ const hasReadmeFile = await pathExists(host, existingReadmeFilePath);
486
+ const existingChangelogFilePath = joinPaths(
467
487
  dpgContext.generationPathDetail?.metadataDir ?? "",
468
488
  "CHANGELOG.md",
469
489
  );
470
- const hasChangelogFile = await existsSync(existingChangelogFilePath);
490
+ const hasChangelogFile = await pathExists(host, existingChangelogFilePath);
471
491
  const shouldGenerateMetadata = option.generateMetadata === true || !hasPackageFile;
472
- const existingTestFolderPath = join(dpgContext.generationPathDetail?.metadataDir ?? "", "test");
473
- const hasTestFolder = await existsSync(existingTestFolderPath);
492
+ const existingTestFolderPath = joinPaths(
493
+ dpgContext.generationPathDetail?.metadataDir ?? "",
494
+ "test",
495
+ );
496
+ const hasTestFolder = await pathExists(host, existingTestFolderPath);
474
497
  if (option.generateTest === undefined) {
475
498
  if (hasTestFolder) {
476
499
  option.generateTest = false;
@@ -511,7 +534,7 @@ export async function $onEmit(context: EmitContext) {
511
534
  option.azureSdkForJs === true &&
512
535
  emitterOptions["generate-metadata"] === true
513
536
  ) {
514
- await emitTests(dpgContext);
537
+ await emitTests(dpgContext, host);
515
538
  }
516
539
  let modularPackageInfo = {};
517
540
  if (option.isModularLibrary) {
@@ -612,8 +635,16 @@ export async function $onEmit(context: EmitContext) {
612
635
  // Always update package.json for monorepo packages (adds #platform/* imports)
613
636
  // and for modular packages (adds exports, clientContextPaths, LRO deps)
614
637
  if (option.isModularLibrary || option.azureSdkForJs) {
638
+ // Read package.json content via host and pass parsed object
639
+ const pkgSourceFile = await host.readFile(existingPackageFilePath);
640
+ let packageInfo: Record<string, any>;
641
+ try {
642
+ packageInfo = JSON.parse(pkgSourceFile.text);
643
+ } catch {
644
+ packageInfo = {};
645
+ }
615
646
  updateBuilders.push((model: RLCModel) =>
616
- updatePackageFile(model, existingPackageFilePath, modularPackageInfo),
647
+ updatePackageFile(model, packageInfo, modularPackageInfo),
617
648
  );
618
649
  }
619
650
 
@@ -625,11 +656,13 @@ export async function $onEmit(context: EmitContext) {
625
656
  // If the client name changed, regenerate the README and snippets completely;
626
657
  // otherwise update only the API reference link in-place.
627
658
  if (hasReadmeFile) {
628
- const clientNameChanged = hasClientNameChanged(rlcClient, existingReadmeFilePath);
659
+ const readmeSourceFile = await host.readFile(existingReadmeFilePath);
660
+ const existingReadmeContent = readmeSourceFile.text;
661
+ const clientNameChanged = hasClientNameChanged(rlcClient, existingReadmeContent);
629
662
  updateBuilders.push(
630
663
  clientNameChanged
631
664
  ? buildReadmeFile
632
- : (model: RLCModel) => updateReadmeFile(model, existingReadmeFilePath),
665
+ : (model: RLCModel) => updateReadmeFile(model, existingReadmeContent),
633
666
  );
634
667
 
635
668
  // Regenerate snippets.spec.ts only when the client name changed
package/src/lib.ts CHANGED
@@ -145,18 +145,18 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
145
145
  "add-credentials": {
146
146
  type: "boolean",
147
147
  nullable: true,
148
- description: `
149
- We support two types of authentication: Azure Key Credential(AzureKey) and Token credential(AADToken), any other will need to be handled manually.
150
-
151
- There are two ways to set up our credential details
152
-
153
- - To use \`@useAuth\` decorator in TypeSpec
154
- - To config in yaml file
155
-
156
- Please notice defining in TypeSpec is recommended and also has higher priority than second one.
157
-
158
- To enable credential in \`tspconfig.yaml\` and we need to provide more details to let codegen know types.
159
- `,
148
+ description: [
149
+ "We support two types of authentication: Azure Key Credential(AzureKey) and Token credential(AADToken), any other will need to be handled manually.",
150
+ "",
151
+ "There are two ways to set up our credential details",
152
+ "",
153
+ "- To use `@useAuth` decorator in TypeSpec",
154
+ "- To config in yaml file",
155
+ "",
156
+ "Please notice defining in TypeSpec is recommended and also has higher priority than second one.",
157
+ "",
158
+ "To enable credential in `tspconfig.yaml` and we need to provide more details to let codegen know types.",
159
+ ].join("\n"),
160
160
  },
161
161
  "credential-scopes": {
162
162
  type: "array",
@@ -186,20 +186,19 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
186
186
  "generate-metadata": {
187
187
  type: "boolean",
188
188
  nullable: true,
189
- description: `
190
- Whether to generate metadata files which includes package.json, README.md and tsconfig.json etc. Defaults to \`undefined\`. If there's not a package.json under package-dir, defaults to \`true\`. but if you'd like to disable this feature you could set it as \`false\`.
191
- `,
189
+ description:
190
+ "Whether to generate metadata files which includes package.json, README.md and tsconfig.json etc. Defaults to `undefined`. If there's not a package.json under package-dir, defaults to `true`. but if you'd like to disable this feature you could set it as `false`.",
192
191
  },
193
192
  "generate-test": {
194
193
  type: "boolean",
195
194
  nullable: true,
196
- description: `
197
- Whether to generate test files, for basic testing of your generated sdks. Defaults to \`undefined\`.
198
- other cases:
199
- - If azure-sdk-for-js is \`false\`. Defaults to \`false\`.
200
- - If azure-sdk-for-js is \`true\` but there's a test folder under package-dir. Defaults to \`false\`.
201
- - If azure-sdk-for-js is \`true\` but there's not a test folder under package-dir. Defaults to \`true\`.
202
- `,
195
+ description: [
196
+ "Whether to generate test files, for basic testing of your generated sdks. Defaults to `undefined`.",
197
+ "other cases:",
198
+ "- If azure-sdk-for-js is `false`. Defaults to `false`.",
199
+ "- If azure-sdk-for-js is `true` but there's a test folder under package-dir. Defaults to `false`.",
200
+ "- If azure-sdk-for-js is `true` but there's not a test folder under package-dir. Defaults to `true`.",
201
+ ].join("\n"),
203
202
  },
204
203
  "generate-sample": {
205
204
  type: "boolean",
@@ -359,15 +358,16 @@ export const RLCOptionsSchema: JSONSchemaType<EmitterOptions> = {
359
358
  },
360
359
  required: [],
361
360
  nullable: true,
362
- description: `Only for Modular generation
363
- By default, code generation uses the titles specified in the \`@client\` and \`@service\` decorators in TypeSpec to name modular clients. If you need to override these names, you can configure the \`typespec-title-map\`. The map's keys represent the original client names from TypeSpec, and the values are the desired client names. This configuration supports renaming multiple clients.
364
-
365
- \`\`\`yaml
366
- typespec-title-map:
367
- AnomalyDetectorClient: AnomalyDetectorRest
368
- AnomalyDetectorClient2: AnomalyDetectorRest2
369
- \`\`\`
370
- `,
361
+ description: [
362
+ "Only for Modular generation",
363
+ "By default, code generation uses the titles specified in the `@client` and `@service` decorators in TypeSpec to name modular clients. If you need to override these names, you can configure the `typespec-title-map`. The map's keys represent the original client names from TypeSpec, and the values are the desired client names. This configuration supports renaming multiple clients.",
364
+ "",
365
+ "```yaml",
366
+ "typespec-title-map:",
367
+ " AnomalyDetectorClient: AnomalyDetectorRest",
368
+ " AnomalyDetectorClient2: AnomalyDetectorRest2",
369
+ "```",
370
+ ].join("\n"),
371
371
  },
372
372
  "should-use-pnpm-dep": {
373
373
  type: "boolean",
@@ -1,6 +1,6 @@
1
1
  import { NameType } from "../rlc-common/index.js";
2
2
 
3
- import path from "path/posix";
3
+ import { getRelativePathFromDirectory, joinPaths } from "@typespec/compiler";
4
4
  import { useContext } from "../context-manager.js";
5
5
  import { getClientHierarchyMap, getModularClientOptions } from "../utils/client-utils.js";
6
6
  import { SdkContext } from "../utils/interfaces.js";
@@ -19,7 +19,10 @@ function getSourceRootPrefix(emitterOptions: ModularEmitterOptions, context: Sdk
19
19
  const rootDir = (context.generationPathDetail?.rootDir ?? "").replace(/\\/g, "/");
20
20
 
21
21
  if (rootDir && sourceRoot.startsWith(rootDir)) {
22
- const relativePath = path.relative(rootDir, sourceRoot).replace(/\\/g, "/");
22
+ const relativePath = getRelativePathFromDirectory(rootDir, sourceRoot, false).replace(
23
+ /\\/g,
24
+ "/",
25
+ );
23
26
  return `./${relativePath}`;
24
27
  }
25
28
 
@@ -95,7 +98,7 @@ export function getModuleExports(context: SdkContext, emitterOptions: ModularEmi
95
98
  function getModelSubpaths(emitterOptions: ModularEmitterOptions) {
96
99
  const outputProject = useContext("outputProject");
97
100
  const modelFiles = outputProject.getSourceFiles(
98
- path.join(emitterOptions.modularOptions.sourceRoot.replace(/\\/g, "/"), `models/**/*.ts`),
101
+ joinPaths(emitterOptions.modularOptions.sourceRoot.replace(/\\/g, "/"), `models/**/*.ts`),
99
102
  );
100
103
  const subpath = new Set<string>();
101
104
  for (const modelFile of modelFiles) {
@@ -104,7 +107,11 @@ function getModelSubpaths(emitterOptions: ModularEmitterOptions) {
104
107
  continue;
105
108
  }
106
109
  subpath.add(
107
- path.relative(emitterOptions.modularOptions.sourceRoot.replace(/\\/g, "/"), filepath),
110
+ getRelativePathFromDirectory(
111
+ emitterOptions.modularOptions.sourceRoot.replace(/\\/g, "/"),
112
+ filepath,
113
+ false,
114
+ ),
108
115
  );
109
116
  }
110
117
  return Array.from(subpath);
@@ -1,5 +1,5 @@
1
1
  import { SdkClientType, SdkServiceOperation } from "@azure-tools/typespec-client-generator-core";
2
- import path from "path";
2
+ import { joinPaths } from "@typespec/compiler";
3
3
  import { SourceFile } from "ts-morph";
4
4
  import { useContext } from "../context-manager.js";
5
5
  import { useDependencies } from "../framework/hooks/use-dependencies.js";
@@ -32,7 +32,7 @@ export function buildRestorePoller(
32
32
  return;
33
33
  }
34
34
  const srcPath = emitterOptions.modularOptions.sourceRoot;
35
- const filePath = path.join(
35
+ const filePath = joinPaths(
36
36
  `${srcPath}/${subfolder && subfolder !== "" ? subfolder + "/" : ""}restorePollerHelpers.ts`,
37
37
  );
38
38
  const restorePollerFile = project.createSourceFile(filePath, undefined, {
@@ -1,6 +1,5 @@
1
1
  import { SdkClientType, SdkServiceOperation } from "@azure-tools/typespec-client-generator-core";
2
- import { NoTarget } from "@typespec/compiler";
3
- import { join } from "path/posix";
2
+ import { joinPaths, NoTarget } from "@typespec/compiler";
4
3
  import { Project, SourceFile } from "ts-morph";
5
4
  import { useContext } from "../context-manager.js";
6
5
  import { resolveReference } from "../framework/reference.js";
@@ -298,7 +297,7 @@ function exportModules(
298
297
  .getDirectories()
299
298
  .filter((dir) => {
300
299
  const formattedDir = dir.getPath().replace(/\\/g, "/");
301
- const targetPath = join(srcPath, subfolder, moduleName).replace(/\\/g, "/");
300
+ const targetPath = joinPaths(srcPath, subfolder, moduleName).replace(/\\/g, "/");
302
301
  return formattedDir.startsWith(targetPath);
303
302
  })
304
303
  .map((dir) => {
@@ -309,17 +308,17 @@ function exportModules(
309
308
  .getDirectories()
310
309
  .filter((dir) => {
311
310
  const formattedDir = dir.getPath().replace(/\\/g, "/");
312
- const targetPath = join(srcPath, subfolder, moduleName).replace(/\\/g, "/");
311
+ const targetPath = joinPaths(srcPath, subfolder, moduleName).replace(/\\/g, "/");
313
312
  return formattedDir.startsWith(targetPath);
314
313
  })
315
314
  .map((dir) => {
316
315
  return dir.getPath().replace(/\\/g, "/");
317
316
  });
318
317
  } else {
319
- folders = [join(srcPath, subfolder, moduleName).replace(/\\/g, "/")];
318
+ folders = [joinPaths(srcPath, subfolder, moduleName).replace(/\\/g, "/")];
320
319
  }
321
320
  for (const folder of folders) {
322
- const apiFilePattern = join(folder, "index.ts").replace(/\\/g, "/");
321
+ const apiFilePattern = joinPaths(folder, "index.ts").replace(/\\/g, "/");
323
322
  const modelsFile = project.getSourceFile(apiFilePattern);
324
323
  if (!modelsFile) {
325
324
  continue;