@typespec/http-server-js 0.58.0-alpha.14-dev.5 → 0.58.0-alpha.14-dev.6

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.
@@ -5,9 +5,14 @@
5
5
 
6
6
  import { hsjsDependencies } from "../../../generated-defs/package.json.js";
7
7
 
8
- import { compile, formatDiagnostic, NodeHost, OperationContainer } from "@typespec/compiler";
9
-
10
- import YAML from "yaml";
8
+ import {
9
+ compile,
10
+ formatDiagnostic,
11
+ NodeHost,
12
+ OperationContainer,
13
+ resolveCompilerOptions,
14
+ ResolveCompilerOptionsOptions,
15
+ } from "@typespec/compiler";
11
16
 
12
17
  import { getHttpService, HttpOperation, HttpService } from "@typespec/http";
13
18
  import { spawn as _spawn, SpawnOptions } from "node:child_process";
@@ -23,6 +28,7 @@ import { module as httpHelperModule } from "../../../generated-defs/helpers/http
23
28
  import { module as routerModule } from "../../../generated-defs/helpers/router.js";
24
29
  import { emitOptionsType } from "../../common/interface.js";
25
30
  import { emitTypeReference, isValueLiteralType } from "../../common/reference.js";
31
+ import { JsEmitterOptions } from "../../lib.js";
26
32
  import { getAllProperties } from "../../util/extends.js";
27
33
  import { bifilter, indent } from "../../util/iter.js";
28
34
  import { createOnceQueue } from "../../util/once-queue.js";
@@ -50,7 +56,7 @@ const COMMON_PATHS = {
50
56
  vsCodeTasksJson: "./.vscode/tasks.json",
51
57
  } as const;
52
58
 
53
- function getDefaultTsConfig(standalone: boolean) {
59
+ function getDefaultTsConfig(standalone: boolean, outputSlice: string[]) {
54
60
  return {
55
61
  compilerOptions: {
56
62
  target: "es2020",
@@ -65,9 +71,7 @@ function getDefaultTsConfig(standalone: boolean) {
65
71
  declaration: true,
66
72
  sourceMap: true,
67
73
  },
68
- include: standalone
69
- ? ["src/**/*.ts"]
70
- : ["src/**/*.ts", "tsp-output/@typespec/http-server-js/**/*.ts"],
74
+ include: standalone ? ["src/**/*.ts"] : ["src/**/*.ts", `${outputSlice.join("/")}/**/*.ts`],
71
75
  } as const;
72
76
  }
73
77
 
@@ -109,11 +113,16 @@ interface ScaffoldingOptions {
109
113
  * If true, writes will be forced even if the file or setting already exists. Use with caution.
110
114
  */
111
115
  force: boolean;
116
+ /**
117
+ * If true, stop and print a help message.
118
+ */
119
+ help: boolean;
112
120
  }
113
121
 
114
122
  const DEFAULT_SCAFFOLDING_OPTIONS: ScaffoldingOptions = {
115
123
  "no-standalone": false,
116
124
  force: false,
125
+ help: false,
117
126
  };
118
127
 
119
128
  function parseScaffoldArguments(args: string[]): ScaffoldingOptions {
@@ -127,6 +136,9 @@ function parseScaffoldArguments(args: string[]): ScaffoldingOptions {
127
136
  options["no-standalone"] = true;
128
137
  } else if (arg === "--force") {
129
138
  options.force = true;
139
+ } else if (arg === "--help") {
140
+ printHelp();
141
+ process.exit(0);
130
142
  } else {
131
143
  console.error(`[hsjs] Unrecognized scaffolding argument: '${arg}'`);
132
144
  process.exit(1);
@@ -138,6 +150,17 @@ function parseScaffoldArguments(args: string[]): ScaffoldingOptions {
138
150
  return { ...DEFAULT_SCAFFOLDING_OPTIONS, ...options };
139
151
  }
140
152
 
153
+ function printHelp() {
154
+ console.info("[hsjs] Project scaffolding for @typespec/http-server-js.");
155
+ console.info("[hsjs] This command generates a TypeScript project for your generated server.");
156
+ console.info("[hsjs] Scaffolding options:");
157
+ console.info(" --force: Force overwrite existing files and settings.");
158
+ console.info(" --help: Show this help message.");
159
+ console.info(
160
+ " --no-standalone: Generate project in current directory (WARNING: highly experimental and likely to fail).",
161
+ );
162
+ }
163
+
141
164
  async function confirmYesNo(message: string): Promise<void> {
142
165
  const rl = readline.createInterface({
143
166
  input: process.stdin,
@@ -156,8 +179,8 @@ async function confirmYesNo(message: string): Promise<void> {
156
179
  }
157
180
  }
158
181
 
159
- export async function scaffold(options: ScaffoldingOptions) {
160
- if (options.force) {
182
+ export async function scaffold(scaffoldingOptions: ScaffoldingOptions) {
183
+ if (scaffoldingOptions.force) {
161
184
  await confirmYesNo(
162
185
  "[hsjs] The `--force` flag is set and will overwrite existing files and settings that may have been modified. Continue?",
163
186
  );
@@ -173,35 +196,47 @@ export async function scaffold(options: ScaffoldingOptions) {
173
196
  `[hsjs] Using project file '${path.relative(cwd, projectYamlPath)}' and main file '${path.relative(cwd, mainTspPath)}'`,
174
197
  );
175
198
 
176
- let config: any;
199
+ const overrides: Partial<ResolveCompilerOptionsOptions["overrides"]> = {
200
+ emit: [],
201
+ };
177
202
 
178
- try {
179
- const configText = await fs.readFile(projectYamlPath);
203
+ const [compilerOptions, diagnostics] = await resolveCompilerOptions(NodeHost, {
204
+ cwd: process.cwd(),
205
+ entrypoint: mainTspPath,
206
+ overrides,
207
+ });
180
208
 
181
- config = YAML.parse(configText.toString("utf-8"));
182
- } catch {
183
- console.error(
184
- "[hsjs] Failed to read project configuration file. Is the project initialized using `tsp init`?",
185
- );
186
- process.exit(1);
209
+ let hadError = false;
210
+
211
+ for (const diagnostic of diagnostics) {
212
+ hadError ||= diagnostic.severity === "error";
213
+
214
+ console.error(formatDiagnostic(diagnostic, { pathRelativeTo: cwd, pretty: true }));
187
215
  }
188
216
 
189
- // TODO: all of this path handling is awful. We need a good API from the compiler to interpolate the paths for us and
190
- // resolve the options from the config using the schema.
217
+ if (hadError) {
218
+ console.error("[hsjs] Failed to resolve TypeSpec compiler options. Exiting.");
219
+ process.exit(1);
220
+ }
191
221
 
192
- const emitterOutputDirTemplate =
193
- config.options?.["@typespec/http-server-js"]?.["emitter-output-dir"];
194
- const defaultOutputDir = path.resolve(path.dirname(projectYamlPath), "tsp-output");
222
+ const emitterOptions = (compilerOptions.options?.["@typespec/http-server-js"] ?? {}) as Partial<
223
+ JsEmitterOptions & { "emitter-output-dir": string }
224
+ >;
195
225
 
196
226
  const emitterOutputDir =
197
- emitterOutputDirTemplate?.replace("{output-dir}", defaultOutputDir) ??
198
- path.join(defaultOutputDir, "@typespec", "http-server-js");
227
+ emitterOptions["emitter-output-dir"] ??
228
+ path.join(compilerOptions.outputDir ?? "tsp-output", "@typespec", "http-server-js");
229
+
230
+ const baseOutputDir = scaffoldingOptions["no-standalone"] ? cwd : emitterOutputDir;
199
231
 
200
- const baseOutputDir = options["no-standalone"] ? cwd : path.resolve(cwd, emitterOutputDir);
201
- const tsConfigOutputPath = path.resolve(baseOutputDir, COMMON_PATHS.tsConfigJson);
232
+ const outputSlice = path
233
+ .resolve(cwd, emitterOutputDir)
234
+ .replace(cwd, "")
235
+ .split(/[\\/]/)
236
+ .filter((segment) => !!segment);
202
237
 
203
238
  const expressOptions: PackageJsonExpressOptions = {
204
- isExpress: !!config.options?.["@typespec/http-server-js"]?.express,
239
+ isExpress: emitterOptions.express ?? false,
205
240
  openApi3: undefined,
206
241
  };
207
242
 
@@ -209,7 +244,7 @@ export async function scaffold(options: ScaffoldingOptions) {
209
244
  `[hsjs] Emitter options have 'express: ${expressOptions.isExpress}'. Generating server model: '${expressOptions.isExpress ? "Express" : "Node"}'.`,
210
245
  );
211
246
 
212
- if (options["no-standalone"]) {
247
+ if (scaffoldingOptions["no-standalone"]) {
213
248
  console.info("[hsjs] Standalone mode disabled, generating project in current directory.");
214
249
  } else {
215
250
  console.info("[hsjs] Generating standalone project in output directory.");
@@ -217,11 +252,7 @@ export async function scaffold(options: ScaffoldingOptions) {
217
252
 
218
253
  console.info("[hsjs] Compiling TypeSpec project...");
219
254
 
220
- const program = await compile(NodeHost, mainTspPath, {
221
- noEmit: true,
222
- config: projectYamlPath,
223
- emit: [],
224
- });
255
+ const program = await compile(NodeHost, mainTspPath, compilerOptions);
225
256
 
226
257
  const jsCtx = await createInitialContext(program, {
227
258
  express: expressOptions.isExpress,
@@ -238,7 +269,7 @@ export async function scaffold(options: ScaffoldingOptions) {
238
269
 
239
270
  const [httpService, httpDiagnostics] = getHttpService(program, jsCtx.service.type);
240
271
 
241
- let hadError = false;
272
+ hadError = false;
242
273
 
243
274
  for (const diagnostic of [...program.diagnostics, ...httpDiagnostics]) {
244
275
  hadError = hadError || diagnostic.severity === "error";
@@ -269,8 +300,8 @@ export async function scaffold(options: ScaffoldingOptions) {
269
300
 
270
301
  indexModule.imports.push({
271
302
  binder: ["create" + routerName],
272
- from: options["no-standalone"]
273
- ? "../tsp-output/@typespec/http-server-js/src/generated/http/router.js"
303
+ from: scaffoldingOptions["no-standalone"]
304
+ ? `../${outputSlice.join("/")}/src/generated/http/router.js`
274
305
  : "./generated/http/router.js",
275
306
  });
276
307
 
@@ -309,7 +340,9 @@ export async function scaffold(options: ScaffoldingOptions) {
309
340
  },
310
341
  {
311
342
  binder: ["openApiDocument"],
312
- from: "./generated/http/openapi3.js",
343
+ from: scaffoldingOptions["no-standalone"]
344
+ ? `../${outputSlice.join("/")}/src/generated/http/openapi3.js`
345
+ : "./generated/http/openapi3.js",
313
346
  },
314
347
  {
315
348
  binder: "type express",
@@ -381,7 +414,7 @@ export async function scaffold(options: ScaffoldingOptions) {
381
414
  for (const module of controllerModules) {
382
415
  module.imports = module.imports.map((_import) => {
383
416
  if (
384
- options["no-standalone"] &&
417
+ scaffoldingOptions["no-standalone"] &&
385
418
  typeof _import.from !== "string" &&
386
419
  !controllerModules.has(_import.from)
387
420
  ) {
@@ -395,9 +428,7 @@ export async function scaffold(options: ScaffoldingOptions) {
395
428
 
396
429
  const targetPath = [
397
430
  ...backout.slice(1),
398
- "tsp-output",
399
- "@typespec",
400
- "http-server-js",
431
+ ...outputSlice,
401
432
  ..._import.from.cursor.path.slice(0, -1),
402
433
  ...(targetIsIndex ? [modulePrincipalName, "index.js"] : [`${modulePrincipalName}.js`]),
403
434
  ].join("/");
@@ -412,11 +443,19 @@ export async function scaffold(options: ScaffoldingOptions) {
412
443
  }
413
444
 
414
445
  // Force writing of http helper module
415
- await writeModuleFile(jsCtx, baseOutputDir, httpHelperModule, queue, /* format */ true, tryWrite);
446
+ await writeModuleFile(
447
+ jsCtx,
448
+ scaffoldingOptions["no-standalone"] ? emitterOutputDir : baseOutputDir,
449
+ httpHelperModule,
450
+ queue,
451
+ /* format */ true,
452
+ tryWrite,
453
+ );
416
454
 
417
455
  await tryWrite(
418
- tsConfigOutputPath,
419
- JSON.stringify(getDefaultTsConfig(!options["no-standalone"]), null, 2) + "\n",
456
+ path.resolve(baseOutputDir, COMMON_PATHS.tsConfigJson),
457
+ JSON.stringify(getDefaultTsConfig(!scaffoldingOptions["no-standalone"], outputSlice), null, 2) +
458
+ "\n",
420
459
  );
421
460
 
422
461
  const vsCodeLaunchJsonPath = path.resolve(baseOutputDir, COMMON_PATHS.vsCodeLaunchJson);
@@ -441,10 +480,14 @@ export async function scaffold(options: ScaffoldingOptions) {
441
480
 
442
481
  let packageJsonChanged = true;
443
482
 
444
- if (options["no-standalone"]) {
483
+ if (scaffoldingOptions["no-standalone"]) {
445
484
  console.info("[hsjs] Checking package.json for changes...");
446
485
 
447
- packageJsonChanged = updatePackageJson(ownPackageJson, options.force, externalDependencies);
486
+ packageJsonChanged = updatePackageJson(
487
+ ownPackageJson,
488
+ scaffoldingOptions.force,
489
+ externalDependencies,
490
+ );
448
491
 
449
492
  if (packageJsonChanged) {
450
493
  console.info("[hsjs] Writing updated package.json...");
@@ -479,7 +522,7 @@ export async function scaffold(options: ScaffoldingOptions) {
479
522
  try {
480
523
  await spawn("npm", ["install"], {
481
524
  stdio: "inherit",
482
- cwd: options["no-standalone"] ? cwd : baseOutputDir,
525
+ cwd: scaffoldingOptions["no-standalone"] ? cwd : baseOutputDir,
483
526
  shell: process.platform === "win32",
484
527
  });
485
528
  } catch {
@@ -494,7 +537,7 @@ export async function scaffold(options: ScaffoldingOptions) {
494
537
  try {
495
538
  await spawn("npm", ["run", "build"], {
496
539
  stdio: "inherit",
497
- cwd: options["no-standalone"] ? cwd : baseOutputDir,
540
+ cwd: scaffoldingOptions["no-standalone"] ? cwd : baseOutputDir,
498
541
  shell: process.platform === "win32",
499
542
  });
500
543
  } catch {
@@ -502,7 +545,10 @@ export async function scaffold(options: ScaffoldingOptions) {
502
545
  process.exit(1);
503
546
  }
504
547
 
505
- const codeDirectory = path.relative(cwd, options["no-standalone"] ? cwd : baseOutputDir);
548
+ const codeDirectory = path.relative(
549
+ cwd,
550
+ scaffoldingOptions["no-standalone"] ? cwd : baseOutputDir,
551
+ );
506
552
 
507
553
  console.info("[hsjs] Project is ready to run. Use `npm start` to launch the server.");
508
554
  console.info("[hsjs] A debug configuration has been created for Visual Studio Code.");
@@ -523,7 +569,7 @@ export async function scaffold(options: ScaffoldingOptions) {
523
569
  .then(() => true)
524
570
  .catch(() => false);
525
571
 
526
- if (exists && !options.force) {
572
+ if (exists && !scaffoldingOptions.force) {
527
573
  console.warn(`[hsjs] File '${relative}' already exists and will not be overwritten.`);
528
574
  console.warn(`[hsjs] Manually update the file or delete it and run scaffolding again.`);
529
575