@azure-tools/typespec-ts 0.55.0-dev.3 → 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 (80) hide show
  1. package/dist/src/framework/load-static-helpers.d.ts +4 -1
  2. package/dist/src/framework/load-static-helpers.d.ts.map +1 -1
  3. package/dist/src/framework/load-static-helpers.js +15 -10
  4. package/dist/src/framework/load-static-helpers.js.map +1 -1
  5. package/dist/src/framework/sample.js +1 -1
  6. package/dist/src/framework/sample.js.map +1 -1
  7. package/dist/src/index.d.ts.map +1 -1
  8. package/dist/src/index.js +37 -18
  9. package/dist/src/index.js.map +1 -1
  10. package/dist/src/modular/emit-tests.d.ts +2 -1
  11. package/dist/src/modular/emit-tests.d.ts.map +1 -1
  12. package/dist/src/modular/emit-tests.js +18 -8
  13. package/dist/src/modular/emit-tests.js.map +1 -1
  14. package/dist/src/rlc-common/build-client-definitions.js +1 -1
  15. package/dist/src/rlc-common/build-client-definitions.js.map +1 -1
  16. package/dist/src/rlc-common/build-client.js +1 -1
  17. package/dist/src/rlc-common/build-client.js.map +1 -1
  18. package/dist/src/rlc-common/build-index-file.js +1 -1
  19. package/dist/src/rlc-common/build-index-file.js.map +1 -1
  20. package/dist/src/rlc-common/build-is-unexpected-helper.js +1 -1
  21. package/dist/src/rlc-common/build-is-unexpected-helper.js.map +1 -1
  22. package/dist/src/rlc-common/build-logger.js +1 -1
  23. package/dist/src/rlc-common/build-logger.js.map +1 -1
  24. package/dist/src/rlc-common/build-parameter-types.js +1 -1
  25. package/dist/src/rlc-common/build-parameter-types.js.map +1 -1
  26. package/dist/src/rlc-common/build-response-types.js +1 -1
  27. package/dist/src/rlc-common/build-response-types.js.map +1 -1
  28. package/dist/src/rlc-common/build-schema-type.js +1 -1
  29. package/dist/src/rlc-common/build-schema-type.js.map +1 -1
  30. package/dist/src/rlc-common/build-top-level-index-file.js +1 -1
  31. package/dist/src/rlc-common/build-top-level-index-file.js.map +1 -1
  32. package/dist/src/rlc-common/metadata/build-api-extractor-config.js +1 -1
  33. package/dist/src/rlc-common/metadata/build-api-extractor-config.js.map +1 -1
  34. package/dist/src/rlc-common/metadata/build-es-lint-config.js +1 -1
  35. package/dist/src/rlc-common/metadata/build-es-lint-config.js.map +1 -1
  36. package/dist/src/rlc-common/metadata/build-package-file.js +1 -1
  37. package/dist/src/rlc-common/metadata/build-package-file.js.map +1 -1
  38. package/dist/src/rlc-common/metadata/build-readme-file.d.ts +2 -2
  39. package/dist/src/rlc-common/metadata/build-readme-file.d.ts.map +1 -1
  40. package/dist/src/rlc-common/metadata/build-readme-file.js +5 -8
  41. package/dist/src/rlc-common/metadata/build-readme-file.js.map +1 -1
  42. package/dist/src/rlc-common/metadata/build-rollup-config.js +1 -1
  43. package/dist/src/rlc-common/metadata/build-rollup-config.js.map +1 -1
  44. package/dist/src/rlc-common/metadata/build-ts-config.js +1 -1
  45. package/dist/src/rlc-common/metadata/build-ts-config.js.map +1 -1
  46. package/dist/src/utils/emit-util.d.ts.map +1 -1
  47. package/dist/src/utils/emit-util.js +7 -1
  48. package/dist/src/utils/emit-util.js.map +1 -1
  49. package/dist/src/utils/file-system-utils.d.ts +4 -4
  50. package/dist/src/utils/file-system-utils.d.ts.map +1 -1
  51. package/dist/src/utils/file-system-utils.js +12 -13
  52. package/dist/src/utils/file-system-utils.js.map +1 -1
  53. package/dist/src/utils/resolve-project-root.d.ts +6 -4
  54. package/dist/src/utils/resolve-project-root.d.ts.map +1 -1
  55. package/dist/src/utils/resolve-project-root.js +11 -16
  56. package/dist/src/utils/resolve-project-root.js.map +1 -1
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +1 -1
  59. package/src/framework/load-static-helpers.ts +25 -13
  60. package/src/framework/sample.ts +1 -1
  61. package/src/index.ts +44 -16
  62. package/src/modular/emit-tests.ts +22 -9
  63. package/src/rlc-common/build-client-definitions.ts +1 -1
  64. package/src/rlc-common/build-client.ts +1 -1
  65. package/src/rlc-common/build-index-file.ts +1 -1
  66. package/src/rlc-common/build-is-unexpected-helper.ts +1 -1
  67. package/src/rlc-common/build-logger.ts +1 -1
  68. package/src/rlc-common/build-parameter-types.ts +1 -1
  69. package/src/rlc-common/build-response-types.ts +1 -1
  70. package/src/rlc-common/build-schema-type.ts +1 -1
  71. package/src/rlc-common/build-top-level-index-file.ts +1 -1
  72. package/src/rlc-common/metadata/build-api-extractor-config.ts +1 -1
  73. package/src/rlc-common/metadata/build-es-lint-config.ts +1 -1
  74. package/src/rlc-common/metadata/build-package-file.ts +1 -1
  75. package/src/rlc-common/metadata/build-readme-file.ts +5 -8
  76. package/src/rlc-common/metadata/build-rollup-config.ts +1 -1
  77. package/src/rlc-common/metadata/build-ts-config.ts +1 -1
  78. package/src/utils/emit-util.ts +7 -4
  79. package/src/utils/file-system-utils.ts +13 -13
  80. package/src/utils/resolve-project-root.ts +11 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@azure-tools/typespec-ts",
3
- "version": "0.55.0-dev.3",
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",
@@ -1,5 +1,4 @@
1
- import { joinPaths, NoTarget, Program } from "@typespec/compiler";
2
- import { readdir, readFile, stat } from "fs/promises";
1
+ import { type CompilerHost, joinPaths, NoTarget, Program } from "@typespec/compiler";
3
2
  import {
4
3
  ClassDeclaration,
5
4
  EnumDeclaration,
@@ -38,6 +37,9 @@ export interface LoadStaticHelpersOptions extends Partial<ModularEmitterOptions>
38
37
  sourcesDir?: string;
39
38
  rootDir?: string;
40
39
  program?: Program;
40
+ host?: CompilerHost;
41
+ /** The emitter package root directory (where static/ lives). */
42
+ packageRoot?: string;
41
43
  /** When true, also load test helpers from static/test-helpers/ into test/generated/util/ */
42
44
  loadTestHelpers?: boolean;
43
45
  }
@@ -53,13 +55,20 @@ export async function loadStaticHelpers(
53
55
  options: LoadStaticHelpersOptions = {},
54
56
  ): Promise<Map<string, StaticHelperMetadata>> {
55
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
+
56
67
  // Load static helpers used in sources code
57
- const defaultStaticHelpersPath = joinPaths(
58
- resolveProjectRoot(),
59
- DEFAULT_SOURCES_STATIC_HELPERS_PATH,
60
- );
68
+ const defaultStaticHelpersPath = joinPaths(packageRoot, DEFAULT_SOURCES_STATIC_HELPERS_PATH);
61
69
  const filesInSources = await traverseDirectory(
62
70
  options.helpersAssetDirectory ?? defaultStaticHelpersPath,
71
+ host,
63
72
  options.program,
64
73
  );
65
74
  await loadFiles(filesInSources, options.sourcesDir ?? "");
@@ -68,12 +77,10 @@ export async function loadStaticHelpers(
68
77
  options.loadTestHelpers ??
69
78
  (options.options?.generateTest && isAzurePackage({ options: options.options }))
70
79
  ) {
71
- const defaultTestingHelpersPath = joinPaths(
72
- resolveProjectRoot(),
73
- DEFAULT_SOURCES_TESTING_HELPERS_PATH,
74
- );
80
+ const defaultTestingHelpersPath = joinPaths(packageRoot, DEFAULT_SOURCES_TESTING_HELPERS_PATH);
75
81
  const filesInTestings = await traverseDirectory(
76
82
  defaultTestingHelpersPath,
83
+ host,
77
84
  options.program,
78
85
  [],
79
86
  "",
@@ -86,7 +93,8 @@ export async function loadStaticHelpers(
86
93
  async function loadFiles(files: FileMetadata[], generateDir: string) {
87
94
  for (const file of files) {
88
95
  const targetPath = joinPaths(generateDir, file.target);
89
- const contents = await readFile(file.source, "utf-8");
96
+ const sourceFile = await host!.readFile(file.source);
97
+ const contents = sourceFile.text;
90
98
  const addedFile = project.createSourceFile(targetPath, contents, {
91
99
  overwrite: true,
92
100
  });
@@ -186,25 +194,29 @@ function getDeclarationByMetadata(
186
194
  }
187
195
  }
188
196
 
197
+ type FsHost = Pick<CompilerHost, "readFile" | "readDir" | "stat">;
198
+
189
199
  const _targetStaticHelpersBaseDir = "static-helpers";
190
200
  async function traverseDirectory(
191
201
  directory: string,
202
+ host: FsHost,
192
203
  program?: Program,
193
204
  result: { source: string; target: string }[] = [],
194
205
  relativePath: string = "",
195
206
  targetBaseDir: string = _targetStaticHelpersBaseDir,
196
207
  ): Promise<{ source: string; target: string }[]> {
197
208
  try {
198
- const files = await readdir(directory);
209
+ const files = await host.readDir(directory);
199
210
 
200
211
  await Promise.all(
201
212
  files.map(async (file) => {
202
213
  const filePath = joinPaths(directory, file);
203
- const fileStat = await stat(filePath);
214
+ const fileStat = await host.stat(filePath);
204
215
 
205
216
  if (fileStat.isDirectory()) {
206
217
  await traverseDirectory(
207
218
  filePath,
219
+ host,
208
220
  program,
209
221
  result,
210
222
  joinPaths(relativePath, file),
@@ -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,8 +1,15 @@
1
1
  // Copyright (c) Microsoft Corporation.
2
2
  // Licensed under the MIT License.
3
3
 
4
- import { EmitContext, Program, getBaseFileName, joinPaths } from "@typespec/compiler";
5
- import { existsSync } from "fs";
4
+ import {
5
+ EmitContext,
6
+ Program,
7
+ getBaseFileName,
8
+ getDirectoryPath,
9
+ joinPaths,
10
+ resolvePath,
11
+ type CompilerHost,
12
+ } from "@typespec/compiler";
6
13
  import { provideContext, useContext } from "./context-manager.js";
7
14
  import { buildRootIndex, buildSubClientIndexFile } from "./modular/build-root-index.js";
8
15
  import {
@@ -117,8 +124,15 @@ export async function $onEmit(context: EmitContext) {
117
124
  return;
118
125
  }
119
126
  /** Shared status */
120
- const outputProject = new Project();
127
+ const outputProject = new Project({ useInMemoryFileSystem: true });
121
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;
122
136
  const emitterOptions: EmitterOptions = context.options;
123
137
  const dpgContext = await createContextWithDefaultOptions(context);
124
138
 
@@ -160,6 +174,8 @@ export async function $onEmit(context: EmitContext) {
160
174
  rootDir: dpgContext.generationPathDetail?.rootDir,
161
175
  options: rlcOptions,
162
176
  program,
177
+ host,
178
+ packageRoot: emitterPackageRoot,
163
179
  },
164
180
  );
165
181
  const extraDependencies = isAzurePackage({ options: rlcOptions })
@@ -216,9 +232,10 @@ export async function $onEmit(context: EmitContext) {
216
232
  // clear output folder if needed
217
233
  if (options.clearOutputFolder) {
218
234
  // Clear output directory while preserving TempTypeSpecFiles
219
- await clearDirectory(context.emitterOutputDir, ["TempTypeSpecFiles"], program);
235
+ await clearDirectory(host, context.emitterOutputDir, ["TempTypeSpecFiles"], program);
220
236
  }
221
237
  const hasTestFolder = await pathExists(
238
+ host,
222
239
  joinPaths(dpgContext.generationPathDetail?.metadataDir ?? "", "test"),
223
240
  );
224
241
  options.generateTest =
@@ -234,10 +251,10 @@ export async function $onEmit(context: EmitContext) {
234
251
  const customizationFolder = joinPaths(projectRoot, "generated");
235
252
  const srcGeneratedFolder = joinPaths(projectRoot, "src", "generated");
236
253
  // if customization folder exists, use it as sources root
237
- const finalCustomizationFolder = (await pathExists(srcGeneratedFolder))
254
+ const finalCustomizationFolder = (await pathExists(host, srcGeneratedFolder))
238
255
  ? srcGeneratedFolder
239
256
  : customizationFolder;
240
- const sourcesRoot = (await pathExists(finalCustomizationFolder))
257
+ const sourcesRoot = (await pathExists(host, finalCustomizationFolder))
241
258
  ? finalCustomizationFolder
242
259
  : joinPaths(projectRoot, "src");
243
260
  return {
@@ -250,6 +267,7 @@ export async function $onEmit(context: EmitContext) {
250
267
 
251
268
  async function clearSrcFolder() {
252
269
  await emptyDir(
270
+ host,
253
271
  dpgContext.generationPathDetail?.modularSourcesDir ??
254
272
  dpgContext.generationPathDetail?.rlcSourcesDir ??
255
273
  "",
@@ -262,8 +280,8 @@ export async function $onEmit(context: EmitContext) {
262
280
  dpgContext.generationPathDetail?.rootDir ?? "",
263
281
  "samples-dev",
264
282
  );
265
- if (await pathExists(samplesDevPath)) {
266
- await emptyDir(samplesDevPath);
283
+ if (await pathExists(host, samplesDevPath)) {
284
+ await emptyDir(host, samplesDevPath);
267
285
  }
268
286
  }
269
287
  }
@@ -459,23 +477,23 @@ export async function $onEmit(context: EmitContext) {
459
477
  dpgContext.generationPathDetail?.metadataDir ?? "",
460
478
  "package.json",
461
479
  );
462
- const hasPackageFile = existsSync(existingPackageFilePath);
480
+ const hasPackageFile = await pathExists(host, existingPackageFilePath);
463
481
  const existingReadmeFilePath = joinPaths(
464
482
  dpgContext.generationPathDetail?.metadataDir ?? "",
465
483
  "README.md",
466
484
  );
467
- const hasReadmeFile = existsSync(existingReadmeFilePath);
485
+ const hasReadmeFile = await pathExists(host, existingReadmeFilePath);
468
486
  const existingChangelogFilePath = joinPaths(
469
487
  dpgContext.generationPathDetail?.metadataDir ?? "",
470
488
  "CHANGELOG.md",
471
489
  );
472
- const hasChangelogFile = existsSync(existingChangelogFilePath);
490
+ const hasChangelogFile = await pathExists(host, existingChangelogFilePath);
473
491
  const shouldGenerateMetadata = option.generateMetadata === true || !hasPackageFile;
474
492
  const existingTestFolderPath = joinPaths(
475
493
  dpgContext.generationPathDetail?.metadataDir ?? "",
476
494
  "test",
477
495
  );
478
- const hasTestFolder = existsSync(existingTestFolderPath);
496
+ const hasTestFolder = await pathExists(host, existingTestFolderPath);
479
497
  if (option.generateTest === undefined) {
480
498
  if (hasTestFolder) {
481
499
  option.generateTest = false;
@@ -516,7 +534,7 @@ export async function $onEmit(context: EmitContext) {
516
534
  option.azureSdkForJs === true &&
517
535
  emitterOptions["generate-metadata"] === true
518
536
  ) {
519
- await emitTests(dpgContext);
537
+ await emitTests(dpgContext, host);
520
538
  }
521
539
  let modularPackageInfo = {};
522
540
  if (option.isModularLibrary) {
@@ -617,8 +635,16 @@ export async function $onEmit(context: EmitContext) {
617
635
  // Always update package.json for monorepo packages (adds #platform/* imports)
618
636
  // and for modular packages (adds exports, clientContextPaths, LRO deps)
619
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
+ }
620
646
  updateBuilders.push((model: RLCModel) =>
621
- updatePackageFile(model, existingPackageFilePath, modularPackageInfo),
647
+ updatePackageFile(model, packageInfo, modularPackageInfo),
622
648
  );
623
649
  }
624
650
 
@@ -630,11 +656,13 @@ export async function $onEmit(context: EmitContext) {
630
656
  // If the client name changed, regenerate the README and snippets completely;
631
657
  // otherwise update only the API reference link in-place.
632
658
  if (hasReadmeFile) {
633
- const clientNameChanged = hasClientNameChanged(rlcClient, existingReadmeFilePath);
659
+ const readmeSourceFile = await host.readFile(existingReadmeFilePath);
660
+ const existingReadmeContent = readmeSourceFile.text;
661
+ const clientNameChanged = hasClientNameChanged(rlcClient, existingReadmeContent);
634
662
  updateBuilders.push(
635
663
  clientNameChanged
636
664
  ? buildReadmeFile
637
- : (model: RLCModel) => updateReadmeFile(model, existingReadmeFilePath),
665
+ : (model: RLCModel) => updateReadmeFile(model, existingReadmeContent),
638
666
  );
639
667
 
640
668
  // Regenerate snippets.spec.ts only when the client name changed
@@ -1,5 +1,4 @@
1
- import { joinPaths } from "@typespec/compiler";
2
- import { existsSync, rmSync } from "fs";
1
+ import { type CompilerHost, joinPaths } from "@typespec/compiler";
3
2
  import { SourceFile } from "ts-morph";
4
3
  import { resolveReference } from "../framework/reference.js";
5
4
  import { NameType, normalizeName } from "../rlc-common/index.js";
@@ -22,7 +21,7 @@ import { CreateRecorderHelpers } from "./static-helpers-metadata.js";
22
21
  /**
23
22
  * Clean up the test/generated folder before generating new tests
24
23
  */
25
- async function cleanupTestFolder(dpgContext: SdkContext) {
24
+ async function cleanupTestFolder(dpgContext: SdkContext, host: CompilerHost) {
26
25
  const clients = dpgContext.sdkPackage.clients;
27
26
  const baseTestFolder = joinPaths(
28
27
  dpgContext.generationPathDetail?.rootDir ?? "",
@@ -30,19 +29,28 @@ async function cleanupTestFolder(dpgContext: SdkContext) {
30
29
  "generated",
31
30
  );
32
31
 
32
+ async function dirExists(path: string): Promise<boolean> {
33
+ try {
34
+ const s = await host.stat(path);
35
+ return s.isDirectory();
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
33
41
  // If there are multiple clients, clean up subfolders
34
42
  if (clients.length > 1) {
35
43
  for (const client of clients) {
36
44
  const subFolder = normalizeName(getClassicalClientName(client), NameType.File);
37
45
  const clientTestFolder = joinPaths(baseTestFolder, subFolder);
38
- if (existsSync(clientTestFolder)) {
39
- rmSync(clientTestFolder, { recursive: true, force: true });
46
+ if (await dirExists(clientTestFolder)) {
47
+ await host.rm(clientTestFolder, { recursive: true });
40
48
  }
41
49
  }
42
50
  } else {
43
51
  // Single client, clean up the entire test/generated folder
44
- if (existsSync(baseTestFolder)) {
45
- rmSync(baseTestFolder, { recursive: true, force: true });
52
+ if (await dirExists(baseTestFolder)) {
53
+ await host.rm(baseTestFolder, { recursive: true });
46
54
  }
47
55
  }
48
56
  }
@@ -50,9 +58,14 @@ async function cleanupTestFolder(dpgContext: SdkContext) {
50
58
  /**
51
59
  * Helpers to emit tests similar to samples
52
60
  */
53
- export async function emitTests(dpgContext: SdkContext): Promise<SourceFile[]> {
61
+ export async function emitTests(
62
+ dpgContext: SdkContext,
63
+ host?: CompilerHost,
64
+ ): Promise<SourceFile[]> {
54
65
  // Clean up the test/generated folder before generating new tests
55
- await cleanupTestFolder(dpgContext);
66
+ if (host) {
67
+ await cleanupTestFolder(dpgContext, host);
68
+ }
56
69
 
57
70
  return iterateClientsAndMethods(dpgContext, emitMethodTests);
58
71
  }
@@ -30,7 +30,7 @@ export function buildClientDefinitions(model: RLCModel) {
30
30
  importedResponses: new Set<string>(),
31
31
  clientImports: new Set<string>(),
32
32
  };
33
- const project = new Project();
33
+ const project = new Project({ useInMemoryFileSystem: true });
34
34
  const srcPath = model.srcPath;
35
35
  const filePath = joinPaths(srcPath, `clientDefinitions.ts`);
36
36
  const clientDefinitionsFile = project.createSourceFile(filePath, undefined, {
@@ -62,7 +62,7 @@ function getClientOptionsInterface(
62
62
  export function buildClient(model: RLCModel): File | undefined {
63
63
  const name = normalizeName(model.libraryName, NameType.File);
64
64
  const { srcPath } = model;
65
- const project = new Project();
65
+ const project = new Project({ useInMemoryFileSystem: true });
66
66
  const filePath = joinPaths(srcPath, `${name}.ts`);
67
67
  const clientFile = project.createSourceFile(filePath, undefined, {
68
68
  overwrite: true,
@@ -23,7 +23,7 @@ import { RLCModel } from "./interfaces.js";
23
23
  export function buildIndexFile(model: RLCModel) {
24
24
  const multiClient = Boolean(model.options?.multiClient),
25
25
  batch = model.options?.batch;
26
- const project = new Project();
26
+ const project = new Project({ useInMemoryFileSystem: true });
27
27
  const { srcPath } = model;
28
28
  const filePath = joinPaths(srcPath, `index.ts`);
29
29
  const indexFile = project.createSourceFile(filePath, undefined, {
@@ -15,7 +15,7 @@ export function buildIsUnexpectedHelper(model: RLCModel) {
15
15
  if (!hasUnexpectedHelper(model)) {
16
16
  return;
17
17
  }
18
- const project = new Project();
18
+ const project = new Project({ useInMemoryFileSystem: true });
19
19
  const srcPath = model.srcPath;
20
20
  const filePath = joinPaths(srcPath, `isUnexpected.ts`);
21
21
  const isErrorHelper = project.createSourceFile(filePath, undefined, {
@@ -13,7 +13,7 @@ export function buildLogger(model: RLCModel) {
13
13
  if (model.options.flavor !== "azure") {
14
14
  return undefined;
15
15
  }
16
- const project = new Project();
16
+ const project = new Project({ useInMemoryFileSystem: true });
17
17
  const { srcPath, rlcSourceDir } = model;
18
18
  const { packageDetails } = model.options;
19
19
  const filePath = joinPaths(
@@ -27,7 +27,7 @@ import {
27
27
  } from "./interfaces.js";
28
28
 
29
29
  export function buildParameterTypes(model: RLCModel) {
30
- const project = new Project();
30
+ const project = new Project({ useInMemoryFileSystem: true });
31
31
  const srcPath = model.srcPath;
32
32
  const filePath = joinPaths(srcPath, `parameters.ts`);
33
33
  const partialBodyTypeNames = new Set<string>();
@@ -19,7 +19,7 @@ import { ResponseHeaderSchema, ResponseMetadata, RLCModel } from "./interfaces.j
19
19
 
20
20
  let hasErrorResponse = false;
21
21
  export function buildResponseTypes(model: RLCModel) {
22
- const project = new Project();
22
+ const project = new Project({ useInMemoryFileSystem: true });
23
23
  const srcPath = model.srcPath;
24
24
  const filePath = joinPaths(srcPath, `responses.ts`);
25
25
  hasErrorResponse = false;
@@ -17,7 +17,7 @@ import { RLCModel, SchemaContext } from "./interfaces.js";
17
17
  */
18
18
  export function buildSchemaTypes(model: RLCModel) {
19
19
  const { srcPath } = model;
20
- const project = new Project();
20
+ const project = new Project({ useInMemoryFileSystem: true });
21
21
  let filePath = joinPaths(srcPath, `models.ts`);
22
22
  const inputModelFile = generateModelFiles(model, project, filePath, [SchemaContext.Input]);
23
23
  filePath = joinPaths(srcPath, `outputModels.ts`);
@@ -14,7 +14,7 @@ export function buildTopLevelIndex(model: RLCModel) {
14
14
  if (!model.options) {
15
15
  return undefined;
16
16
  }
17
- const project = new Project();
17
+ const project = new Project({ useInMemoryFileSystem: true });
18
18
  const { srcPath } = model;
19
19
  const { multiClient } = model.options;
20
20
  const batch = model.options.batch;
@@ -6,7 +6,7 @@ import { RLCModel } from "../interfaces.js";
6
6
 
7
7
  export function buildApiExtractorConfig(model: RLCModel) {
8
8
  const { packageDetails, isModularLibrary, generateTest, azureSdkForJs } = model.options || {};
9
- const project = new Project();
9
+ const project = new Project({ useInMemoryFileSystem: true });
10
10
 
11
11
  let mainEntryPointFilePath = "dist/esm/index.d.ts";
12
12
 
@@ -65,7 +65,7 @@ export function buildEsLintConfig(model: RLCModel) {
65
65
  if (model.options?.flavor !== "azure") {
66
66
  return;
67
67
  }
68
- const project = new Project();
68
+ const project = new Project({ useInMemoryFileSystem: true });
69
69
  const filePath = "eslint.config.mjs";
70
70
 
71
71
  let template: string;
@@ -66,7 +66,7 @@ export function buildPackageFile(
66
66
  packageInfo = buildAzureStandalonePackage(extendedConfig);
67
67
  }
68
68
 
69
- const project = new Project();
69
+ const project = new Project({ useInMemoryFileSystem: true });
70
70
  const filePath = "package.json";
71
71
 
72
72
  if (!packageInfo) {
@@ -3,7 +3,6 @@
3
3
 
4
4
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
5
5
  // @ts-ignore: to fix the handlebars issue
6
- import { readFileSync } from "fs";
7
6
  import hbs from "handlebars";
8
7
  import { getClientName } from "../helpers/name-constructors.js";
9
8
  import { NameType, normalizeName } from "../helpers/name-utils.js";
@@ -363,10 +362,9 @@ export function buildReadmeFile(model: RLCModel) {
363
362
  };
364
363
  }
365
364
 
366
- export function hasClientNameChanged(model: RLCModel, existingReadmeFilePath: string): boolean {
365
+ export function hasClientNameChanged(model: RLCModel, existingReadmeContent: string): boolean {
367
366
  try {
368
- const existingContent = readFileSync(existingReadmeFilePath, "utf8");
369
- const importMatch = existingContent.match(
367
+ const importMatch = existingReadmeContent.match(
370
368
  /import\s*\{\s*([A-Za-z0-9_]+)\s*\}\s*from\s*["'][^"']*["']/,
371
369
  );
372
370
  const existingClientName = importMatch?.[1];
@@ -379,21 +377,20 @@ export function hasClientNameChanged(model: RLCModel, existingReadmeFilePath: st
379
377
 
380
378
  export function updateReadmeFile(
381
379
  model: RLCModel,
382
- existingReadmeFilePath: string,
380
+ existingReadmeContent: string,
383
381
  ): { path: string; content: string } | undefined {
384
382
  try {
385
- const existingContent = readFileSync(existingReadmeFilePath, "utf8");
386
383
  const metadata = createMetadata(model) ?? {};
387
384
 
388
385
  const newApiRefLink = hbs.compile(apiReferenceTemplate, { noEscape: true })(metadata).trim();
389
386
 
390
387
  if (!newApiRefLink) {
391
- return { path: "README.md", content: existingContent };
388
+ return { path: "README.md", content: existingReadmeContent };
392
389
  }
393
390
 
394
391
  const apiRefRegex =
395
392
  /^- \[API reference documentation\]\(https:\/\/learn\.microsoft\.com\/javascript\/api\/[^)]+\)$/m;
396
- const updatedContent = existingContent.replace(apiRefRegex, (match) =>
393
+ const updatedContent = existingReadmeContent.replace(apiRefRegex, (match) =>
397
394
  match ? newApiRefLink : match,
398
395
  );
399
396
 
@@ -12,7 +12,7 @@ export function buildRollupConfig(model: RLCModel) {
12
12
  return;
13
13
  }
14
14
 
15
- const project = new Project();
15
+ const project = new Project({ useInMemoryFileSystem: true });
16
16
  const filePath = "rollup.config.js";
17
17
  const rollupFile = project.createSourceFile(filePath, undefined, {
18
18
  overwrite: true,
@@ -14,7 +14,7 @@ export function buildTsConfig(model: RLCModel) {
14
14
  const { packageDetails, azureSdkForJs } = model.options || {};
15
15
  const { generateTest, generateSample, generateReactNativeTarget } = model.options || {};
16
16
  const clientPackageName = packageDetails?.name ?? "";
17
- const project = new Project();
17
+ const project = new Project({ useInMemoryFileSystem: true });
18
18
 
19
19
  let tsConfig: Record<string, any>;
20
20
 
@@ -1,5 +1,8 @@
1
1
  import { CompilerHost, getDirectoryPath, joinPaths, NoTarget, Program } from "@typespec/compiler";
2
2
  import { format } from "prettier";
3
+ import prettierPluginBabel from "prettier/plugins/babel";
4
+ import prettierPluginEstree from "prettier/plugins/estree";
5
+ import prettierPluginTypescript from "prettier/plugins/typescript";
3
6
  import { prettierJSONOptions, prettierTypeScriptOptions, reportDiagnostic } from "../lib.js";
4
7
  import {
5
8
  buildSchemaTypes,
@@ -70,10 +73,10 @@ async function emitFile(
70
73
  // Format the contents if necessary
71
74
  if (isJson || isSourceCode) {
72
75
  try {
73
- prettierFileContent = await format(
74
- prettierFileContent,
75
- isJson ? prettierJSONOptions : prettierTypeScriptOptions,
76
- );
76
+ prettierFileContent = await format(prettierFileContent, {
77
+ ...(isJson ? prettierJSONOptions : prettierTypeScriptOptions),
78
+ plugins: [prettierPluginTypescript, prettierPluginEstree, prettierPluginBabel],
79
+ });
77
80
  } catch (e) {
78
81
  reportDiagnostic(program, {
79
82
  code: "file-formatting-error",
@@ -1,48 +1,48 @@
1
- import { NoTarget, Program, resolvePath } from "@typespec/compiler";
2
- import { mkdir, readdir, rm, stat } from "fs/promises";
1
+ import { NoTarget, Program, resolvePath, type CompilerHost } from "@typespec/compiler";
3
2
  import { reportDiagnostic } from "../lib.js";
4
3
 
5
- export async function pathExists(targetPath: string): Promise<boolean> {
4
+ export async function pathExists(host: CompilerHost, targetPath: string): Promise<boolean> {
6
5
  try {
7
- await stat(targetPath);
6
+ await host.stat(targetPath);
8
7
  return true;
9
8
  } catch {
10
9
  return false;
11
10
  }
12
11
  }
13
12
 
14
- export async function emptyDir(dirPath: string): Promise<void> {
13
+ export async function emptyDir(host: CompilerHost, dirPath: string): Promise<void> {
15
14
  let entries: string[];
16
15
  try {
17
- entries = await readdir(dirPath);
16
+ entries = await host.readDir(dirPath);
18
17
  } catch {
19
- await mkdir(dirPath, { recursive: true });
18
+ await host.mkdirp(dirPath);
20
19
  return;
21
20
  }
22
21
 
23
22
  await Promise.all(
24
- entries.map((entry) => rm(resolvePath(dirPath, entry), { recursive: true, force: true })),
23
+ entries.map((entry) => host.rm(resolvePath(dirPath, entry), { recursive: true })),
25
24
  );
26
25
  }
27
26
 
28
27
  export async function clearDirectory(
28
+ host: CompilerHost,
29
29
  dirPath: string,
30
30
  excludeNames: string[] = [],
31
31
  program?: Program,
32
32
  ): Promise<void> {
33
- if (!(await pathExists(dirPath))) {
33
+ if (!(await pathExists(host, dirPath))) {
34
34
  return;
35
35
  }
36
36
 
37
37
  // If no exclude names, just use regular emptyDir for efficiency
38
38
  if (excludeNames.length === 0) {
39
- await emptyDir(dirPath);
39
+ await emptyDir(host, dirPath);
40
40
  return;
41
41
  }
42
42
 
43
43
  try {
44
44
  // Get all subdirectories and files
45
- const entries = await readdir(dirPath);
45
+ const entries = await host.readDir(dirPath);
46
46
 
47
47
  // Filter entries to exclude those that should be preserved
48
48
  const filteredEntries = entries.filter((entry) => {
@@ -52,7 +52,7 @@ export async function clearDirectory(
52
52
  // Process each entry
53
53
  for (const entry of filteredEntries) {
54
54
  const entryPath = resolvePath(dirPath, entry);
55
- await rm(entryPath, { recursive: true, force: true });
55
+ await host.rm(entryPath, { recursive: true });
56
56
  }
57
57
  } catch (error) {
58
58
  // If there's an error, fall back to regular emptyDir
@@ -63,6 +63,6 @@ export async function clearDirectory(
63
63
  target: NoTarget,
64
64
  });
65
65
  }
66
- await emptyDir(dirPath);
66
+ await emptyDir(host, dirPath);
67
67
  }
68
68
  }