@typespec/http-server-js 0.58.0-alpha.10-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/build-helpers.ts +170 -0
- package/dist/generated-defs/helpers/header.d.ts +4 -0
- package/dist/generated-defs/helpers/header.d.ts.map +1 -0
- package/dist/generated-defs/helpers/header.js +76 -0
- package/dist/generated-defs/helpers/header.js.map +1 -0
- package/dist/generated-defs/helpers/http.d.ts +4 -0
- package/dist/generated-defs/helpers/http.d.ts.map +1 -0
- package/dist/generated-defs/helpers/http.js +134 -0
- package/dist/generated-defs/helpers/http.js.map +1 -0
- package/dist/generated-defs/helpers/index.d.ts +4 -0
- package/dist/generated-defs/helpers/index.d.ts.map +1 -0
- package/dist/generated-defs/helpers/index.js +21 -0
- package/dist/generated-defs/helpers/index.js.map +1 -0
- package/dist/generated-defs/helpers/multipart.d.ts +4 -0
- package/dist/generated-defs/helpers/multipart.d.ts.map +1 -0
- package/dist/generated-defs/helpers/multipart.js +249 -0
- package/dist/generated-defs/helpers/multipart.js.map +1 -0
- package/dist/generated-defs/helpers/router.d.ts +4 -0
- package/dist/generated-defs/helpers/router.d.ts.map +1 -0
- package/dist/generated-defs/helpers/router.js +259 -0
- package/dist/generated-defs/helpers/router.js.map +1 -0
- package/dist/src/common/declaration.d.ts +13 -0
- package/dist/src/common/declaration.d.ts.map +1 -0
- package/dist/src/common/declaration.js +45 -0
- package/dist/src/common/declaration.js.map +1 -0
- package/dist/src/common/documentation.d.ts +12 -0
- package/dist/src/common/documentation.d.ts.map +1 -0
- package/dist/src/common/documentation.js +21 -0
- package/dist/src/common/documentation.js.map +1 -0
- package/dist/src/common/enum.d.ts +10 -0
- package/dist/src/common/enum.d.ts.map +1 -0
- package/dist/src/common/enum.js +21 -0
- package/dist/src/common/enum.js.map +1 -0
- package/dist/src/common/interface.d.ts +50 -0
- package/dist/src/common/interface.d.ts.map +1 -0
- package/dist/src/common/interface.js +194 -0
- package/dist/src/common/interface.js.map +1 -0
- package/dist/src/common/model.d.ts +26 -0
- package/dist/src/common/model.d.ts.map +1 -0
- package/dist/src/common/model.js +115 -0
- package/dist/src/common/model.js.map +1 -0
- package/dist/src/common/namespace.d.ts +38 -0
- package/dist/src/common/namespace.d.ts.map +1 -0
- package/dist/src/common/namespace.js +184 -0
- package/dist/src/common/namespace.js.map +1 -0
- package/dist/src/common/reference.d.ts +46 -0
- package/dist/src/common/reference.d.ts.map +1 -0
- package/dist/src/common/reference.js +243 -0
- package/dist/src/common/reference.js.map +1 -0
- package/dist/src/common/scalar.d.ts +50 -0
- package/dist/src/common/scalar.d.ts.map +1 -0
- package/dist/src/common/scalar.js +144 -0
- package/dist/src/common/scalar.js.map +1 -0
- package/dist/src/common/serialization/index.d.ts +11 -0
- package/dist/src/common/serialization/index.d.ts.map +1 -0
- package/dist/src/common/serialization/index.js +72 -0
- package/dist/src/common/serialization/index.js.map +1 -0
- package/dist/src/common/serialization/json.d.ts +6 -0
- package/dist/src/common/serialization/json.d.ts.map +1 -0
- package/dist/src/common/serialization/json.js +341 -0
- package/dist/src/common/serialization/json.js.map +1 -0
- package/dist/src/common/union.d.ts +23 -0
- package/dist/src/common/union.d.ts.map +1 -0
- package/dist/src/common/union.js +57 -0
- package/dist/src/common/union.js.map +1 -0
- package/dist/src/ctx.d.ts +242 -0
- package/dist/src/ctx.d.ts.map +1 -0
- package/dist/src/ctx.js +211 -0
- package/dist/src/ctx.js.map +1 -0
- package/dist/src/helpers/header.d.ts +14 -0
- package/dist/src/helpers/header.d.ts.map +1 -0
- package/dist/src/helpers/header.js +38 -0
- package/dist/src/helpers/header.js.map +1 -0
- package/dist/src/helpers/http.d.ts +70 -0
- package/dist/src/helpers/http.d.ts.map +1 -0
- package/dist/src/helpers/http.js +86 -0
- package/dist/src/helpers/http.js.map +1 -0
- package/dist/src/helpers/multipart.d.ts +26 -0
- package/dist/src/helpers/multipart.d.ts.map +1 -0
- package/dist/src/helpers/multipart.js +182 -0
- package/dist/src/helpers/multipart.js.map +1 -0
- package/dist/src/helpers/router.d.ts +176 -0
- package/dist/src/helpers/router.d.ts.map +1 -0
- package/dist/src/helpers/router.js +55 -0
- package/dist/src/helpers/router.js.map +1 -0
- package/dist/src/http/index.d.ts +24 -0
- package/dist/src/http/index.d.ts.map +1 -0
- package/dist/src/http/index.js +52 -0
- package/dist/src/http/index.js.map +1 -0
- package/dist/src/http/server/index.d.ts +11 -0
- package/dist/src/http/server/index.d.ts.map +1 -0
- package/dist/src/http/server/index.js +413 -0
- package/dist/src/http/server/index.js.map +1 -0
- package/dist/src/http/server/multipart.d.ts +16 -0
- package/dist/src/http/server/multipart.d.ts.map +1 -0
- package/dist/src/http/server/multipart.js +214 -0
- package/dist/src/http/server/multipart.js.map +1 -0
- package/dist/src/http/server/router.d.ts +15 -0
- package/dist/src/http/server/router.d.ts.map +1 -0
- package/dist/src/http/server/router.js +459 -0
- package/dist/src/http/server/router.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib.d.ts +141 -0
- package/dist/src/lib.d.ts.map +1 -0
- package/dist/src/lib.js +116 -0
- package/dist/src/lib.js.map +1 -0
- package/dist/src/scripts/scaffold/bin.d.mts +14 -0
- package/dist/src/scripts/scaffold/bin.d.mts.map +1 -0
- package/dist/src/scripts/scaffold/bin.mjs +559 -0
- package/dist/src/scripts/scaffold/bin.mjs.map +1 -0
- package/dist/src/testing/index.d.ts +3 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +6 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/util/case.d.ts +81 -0
- package/dist/src/util/case.d.ts.map +1 -0
- package/dist/src/util/case.js +111 -0
- package/dist/src/util/case.js.map +1 -0
- package/dist/src/util/differentiate.d.ts +251 -0
- package/dist/src/util/differentiate.d.ts.map +1 -0
- package/dist/src/util/differentiate.js +580 -0
- package/dist/src/util/differentiate.js.map +1 -0
- package/dist/src/util/error.d.ts +13 -0
- package/dist/src/util/error.d.ts.map +1 -0
- package/dist/src/util/error.js +25 -0
- package/dist/src/util/error.js.map +1 -0
- package/dist/src/util/extends.d.ts +10 -0
- package/dist/src/util/extends.d.ts.map +1 -0
- package/dist/src/util/extends.js +31 -0
- package/dist/src/util/extends.js.map +1 -0
- package/dist/src/util/iter.d.ts +39 -0
- package/dist/src/util/iter.d.ts.map +1 -0
- package/dist/src/util/iter.js +72 -0
- package/dist/src/util/iter.js.map +1 -0
- package/dist/src/util/keywords.d.ts +10 -0
- package/dist/src/util/keywords.d.ts.map +1 -0
- package/dist/src/util/keywords.js +85 -0
- package/dist/src/util/keywords.js.map +1 -0
- package/dist/src/util/name.d.ts +12 -0
- package/dist/src/util/name.d.ts.map +1 -0
- package/dist/src/util/name.js +26 -0
- package/dist/src/util/name.js.map +1 -0
- package/dist/src/util/once-queue.d.ts +24 -0
- package/dist/src/util/once-queue.d.ts.map +1 -0
- package/dist/src/util/once-queue.js +34 -0
- package/dist/src/util/once-queue.js.map +1 -0
- package/dist/src/util/openapi3.d.ts +23 -0
- package/dist/src/util/openapi3.d.ts.map +1 -0
- package/dist/src/util/openapi3.js +40 -0
- package/dist/src/util/openapi3.js.map +1 -0
- package/dist/src/util/pluralism.d.ts +23 -0
- package/dist/src/util/pluralism.d.ts.map +1 -0
- package/dist/src/util/pluralism.js +36 -0
- package/dist/src/util/pluralism.js.map +1 -0
- package/dist/src/util/scope.d.ts +85 -0
- package/dist/src/util/scope.d.ts.map +1 -0
- package/dist/src/util/scope.js +111 -0
- package/dist/src/util/scope.js.map +1 -0
- package/dist/src/write.d.ts +23 -0
- package/dist/src/write.d.ts.map +1 -0
- package/dist/src/write.js +62 -0
- package/dist/src/write.js.map +1 -0
- package/generated-defs/helpers/header.ts +83 -0
- package/generated-defs/helpers/http.ts +141 -0
- package/generated-defs/helpers/index.ts +27 -0
- package/generated-defs/helpers/multipart.ts +256 -0
- package/generated-defs/helpers/router.ts +266 -0
- package/package.json +71 -0
- package/src/common/declaration.ts +52 -0
- package/src/common/documentation.ts +26 -0
- package/src/common/enum.ts +28 -0
- package/src/common/interface.ts +264 -0
- package/src/common/model.ts +160 -0
- package/src/common/namespace.ts +243 -0
- package/src/common/reference.ts +319 -0
- package/src/common/scalar.ts +173 -0
- package/src/common/serialization/index.ts +124 -0
- package/src/common/serialization/json.ts +444 -0
- package/src/common/union.ts +76 -0
- package/src/ctx.ts +497 -0
- package/src/helpers/header.ts +55 -0
- package/src/helpers/http.ts +113 -0
- package/src/helpers/multipart.ts +228 -0
- package/src/helpers/router.ts +238 -0
- package/src/http/index.ts +81 -0
- package/src/http/server/index.ts +548 -0
- package/src/http/server/multipart.ts +272 -0
- package/src/http/server/router.ts +686 -0
- package/src/index.ts +56 -0
- package/src/lib.ts +130 -0
- package/src/scripts/scaffold/bin.mts +781 -0
- package/src/testing/index.ts +10 -0
- package/src/util/case.ts +182 -0
- package/src/util/differentiate.ts +957 -0
- package/src/util/error.ts +28 -0
- package/src/util/extends.ts +43 -0
- package/src/util/iter.ts +85 -0
- package/src/util/keywords.ts +90 -0
- package/src/util/name.ts +33 -0
- package/src/util/once-queue.ts +55 -0
- package/src/util/openapi3.ts +53 -0
- package/src/util/pluralism.ts +37 -0
- package/src/util/scope.ts +211 -0
- package/src/write.ts +88 -0
- package/temp/tsconfig.tsbuildinfo +1 -0
- package/test/header.test.ts +26 -0
- package/test/multipart.test.ts +169 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Copyright (c) Microsoft Corporation.
|
|
4
|
+
// Licensed under the MIT License.
|
|
5
|
+
|
|
6
|
+
import { compile, formatDiagnostic, NodeHost, OperationContainer } from "@typespec/compiler";
|
|
7
|
+
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
|
|
10
|
+
import { getHttpService, HttpOperation, HttpService } from "@typespec/http";
|
|
11
|
+
import { spawn as _spawn, SpawnOptions } from "node:child_process";
|
|
12
|
+
import fs from "node:fs/promises";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import readline from "node:readline/promises";
|
|
15
|
+
import { createOrGetModuleForNamespace } from "../../common/namespace.js";
|
|
16
|
+
import { createInitialContext, createModule, isModule, JsContext, Module } from "../../ctx.js";
|
|
17
|
+
import { parseCase } from "../../util/case.js";
|
|
18
|
+
|
|
19
|
+
import { SupportedOpenAPIDocuments } from "@typespec/openapi3";
|
|
20
|
+
import { module as httpHelperModule } from "../../../generated-defs/helpers/http.js";
|
|
21
|
+
import { module as routerModule } from "../../../generated-defs/helpers/router.js";
|
|
22
|
+
import { emitOptionsType } from "../../common/interface.js";
|
|
23
|
+
import { emitTypeReference, isValueLiteralType } from "../../common/reference.js";
|
|
24
|
+
import { getAllProperties } from "../../util/extends.js";
|
|
25
|
+
import { bifilter, indent } from "../../util/iter.js";
|
|
26
|
+
import { createOnceQueue } from "../../util/once-queue.js";
|
|
27
|
+
import { tryGetOpenApi3 } from "../../util/openapi3.js";
|
|
28
|
+
import { writeModuleFile } from "../../write.js";
|
|
29
|
+
|
|
30
|
+
function spawn(command: string, args: string[], options: SpawnOptions): Promise<void> {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const proc = _spawn(command, args, options);
|
|
33
|
+
|
|
34
|
+
proc.on("error", reject);
|
|
35
|
+
proc.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`Exit code: ${code}`))));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* eslint-disable no-console */
|
|
40
|
+
|
|
41
|
+
const COMMON_PATHS = {
|
|
42
|
+
mainTsp: "./main.tsp",
|
|
43
|
+
projectYaml: "./tspconfig.yaml",
|
|
44
|
+
packageJson: "./package.json",
|
|
45
|
+
tsConfigJson: "./tsconfig.json",
|
|
46
|
+
vsCodeLaunchJson: "./.vscode/launch.json",
|
|
47
|
+
vsCodeTasksJson: "./.vscode/tasks.json",
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
function getDefaultTsConfig(standalone: boolean) {
|
|
51
|
+
return {
|
|
52
|
+
compilerOptions: {
|
|
53
|
+
target: "es2020",
|
|
54
|
+
module: "Node16",
|
|
55
|
+
moduleResolution: "node16",
|
|
56
|
+
rootDir: "./",
|
|
57
|
+
outDir: "./dist/",
|
|
58
|
+
esModuleInterop: true,
|
|
59
|
+
forceConsistentCasingInFileNames: true,
|
|
60
|
+
strict: true,
|
|
61
|
+
skipLibCheck: true,
|
|
62
|
+
declaration: true,
|
|
63
|
+
sourceMap: true,
|
|
64
|
+
},
|
|
65
|
+
include: standalone
|
|
66
|
+
? ["src/**/*.ts"]
|
|
67
|
+
: ["src/**/*.ts", "tsp-output/@typespec/http-server-js/**/*.ts"],
|
|
68
|
+
} as const;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const VSCODE_LAUNCH_JSON = {
|
|
72
|
+
configurations: [
|
|
73
|
+
{
|
|
74
|
+
type: "node",
|
|
75
|
+
request: "launch",
|
|
76
|
+
name: "Launch Program",
|
|
77
|
+
program: "${workspaceFolder}/dist/src/index.js",
|
|
78
|
+
preLaunchTask: "npm: build",
|
|
79
|
+
internalConsoleOptions: "neverOpen",
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const VSCODE_TASKS_JSON = {
|
|
85
|
+
version: "2.0.0",
|
|
86
|
+
tasks: [
|
|
87
|
+
{
|
|
88
|
+
type: "npm",
|
|
89
|
+
script: "build",
|
|
90
|
+
group: "build",
|
|
91
|
+
problemMatcher: [],
|
|
92
|
+
label: "npm: build",
|
|
93
|
+
presentation: {
|
|
94
|
+
reveal: "silent",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
interface ScaffoldingOptions {
|
|
101
|
+
/**
|
|
102
|
+
* If true, the project will be generated in the current directory instead of the output directory.
|
|
103
|
+
*/
|
|
104
|
+
"no-standalone": boolean;
|
|
105
|
+
/**
|
|
106
|
+
* If true, writes will be forced even if the file or setting already exists. Use with caution.
|
|
107
|
+
*/
|
|
108
|
+
force: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const DEFAULT_SCAFFOLDING_OPTIONS: ScaffoldingOptions = {
|
|
112
|
+
"no-standalone": false,
|
|
113
|
+
force: false,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function parseScaffoldArguments(args: string[]): ScaffoldingOptions {
|
|
117
|
+
let cursor = 2;
|
|
118
|
+
const options: Partial<ScaffoldingOptions> = {};
|
|
119
|
+
|
|
120
|
+
while (cursor < args.length) {
|
|
121
|
+
const arg = args[cursor];
|
|
122
|
+
|
|
123
|
+
if (arg === "--no-standalone") {
|
|
124
|
+
options["no-standalone"] = true;
|
|
125
|
+
} else if (arg === "--force") {
|
|
126
|
+
options.force = true;
|
|
127
|
+
} else {
|
|
128
|
+
console.error(`[hsj] Unrecognized scaffolding argument: '${arg}'`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
cursor++;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { ...DEFAULT_SCAFFOLDING_OPTIONS, ...options };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function confirmYesNo(message: string): Promise<void> {
|
|
139
|
+
const rl = readline.createInterface({
|
|
140
|
+
input: process.stdin,
|
|
141
|
+
output: process.stdout,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const response = await rl.question(`${message} [y/N] `);
|
|
146
|
+
|
|
147
|
+
if (response.trim().toLowerCase() !== "y") {
|
|
148
|
+
console.error("[hsj] Operation cancelled.");
|
|
149
|
+
process.exit(0);
|
|
150
|
+
}
|
|
151
|
+
} finally {
|
|
152
|
+
rl.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function scaffold(options: ScaffoldingOptions) {
|
|
157
|
+
if (options.force) {
|
|
158
|
+
await confirmYesNo(
|
|
159
|
+
"[hsj] The `--force` flag is set and will overwrite existing files and settings that may have been modified. Continue?",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const cwd = process.cwd();
|
|
164
|
+
|
|
165
|
+
const projectYamlPath = path.resolve(cwd, COMMON_PATHS.projectYaml);
|
|
166
|
+
const mainTspPath = path.resolve(cwd, COMMON_PATHS.mainTsp);
|
|
167
|
+
|
|
168
|
+
console.info("[hsj] Scaffolding TypeScript project...");
|
|
169
|
+
console.info(
|
|
170
|
+
`[hsj] Using project file '${path.relative(cwd, projectYamlPath)}' and main file '${path.relative(cwd, mainTspPath)}'`,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
let config: any;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const configText = await fs.readFile(projectYamlPath);
|
|
177
|
+
|
|
178
|
+
config = YAML.parse(configText.toString("utf-8"));
|
|
179
|
+
} catch {
|
|
180
|
+
console.error(
|
|
181
|
+
"[hsj] Failed to read project configuration file. Is the project initialized using `tsp init`?",
|
|
182
|
+
);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// TODO: all of this path handling is awful. We need a good API from the compiler to interpolate the paths for us and
|
|
187
|
+
// resolve the options from the config using the schema.
|
|
188
|
+
|
|
189
|
+
const emitterOutputDirTemplate =
|
|
190
|
+
config.options?.["@typespec/http-server-js"]?.["emitter-output-dir"];
|
|
191
|
+
const defaultOutputDir = path.resolve(path.dirname(projectYamlPath), "tsp-output");
|
|
192
|
+
|
|
193
|
+
const emitterOutputDir = emitterOutputDirTemplate.replace("{output-dir}", defaultOutputDir);
|
|
194
|
+
|
|
195
|
+
const baseOutputDir = options["no-standalone"] ? cwd : path.resolve(cwd, emitterOutputDir);
|
|
196
|
+
const tsConfigOutputPath = path.resolve(baseOutputDir, COMMON_PATHS.tsConfigJson);
|
|
197
|
+
|
|
198
|
+
const expressOptions: PackageJsonExpressOptions = {
|
|
199
|
+
isExpress: !!config.options?.["@typespec/http-server-js"]?.express,
|
|
200
|
+
openApi3: undefined,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
console.info(
|
|
204
|
+
`[hsj] Emitter options have 'express: ${expressOptions.isExpress}'. Generating server model: '${expressOptions.isExpress ? "Express" : "Node"}'.`,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (options["no-standalone"]) {
|
|
208
|
+
console.info("[hsj] Standalone mode disabled, generating project in current directory.");
|
|
209
|
+
} else {
|
|
210
|
+
console.info("[hsj] Generating standalone project in output directory.");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.info("[hsj] Compiling TypeSpec project...");
|
|
214
|
+
|
|
215
|
+
const program = await compile(NodeHost, mainTspPath, {
|
|
216
|
+
noEmit: true,
|
|
217
|
+
config: projectYamlPath,
|
|
218
|
+
emit: [],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const jsCtx = await createInitialContext(program, {
|
|
222
|
+
express: expressOptions.isExpress,
|
|
223
|
+
"no-format": false,
|
|
224
|
+
"omit-unreachable-types": true,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!jsCtx) {
|
|
228
|
+
console.error("[hsj] No services were found in the program. Exiting.");
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
expressOptions.openApi3 = await tryGetOpenApi3(program, jsCtx.service);
|
|
233
|
+
|
|
234
|
+
const [httpService, httpDiagnostics] = getHttpService(program, jsCtx.service.type);
|
|
235
|
+
|
|
236
|
+
let hadError = false;
|
|
237
|
+
|
|
238
|
+
for (const diagnostic of [...program.diagnostics, ...httpDiagnostics]) {
|
|
239
|
+
hadError = hadError || diagnostic.severity === "error";
|
|
240
|
+
console.error(formatDiagnostic(diagnostic, { pathRelativeTo: cwd, pretty: true }));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (program.hasError() || hadError) {
|
|
244
|
+
console.error("[hsj] TypeScript compilation failed. See above error output.");
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.info("[hsj] TypeSpec compiled successfully. Scaffolding implementation...");
|
|
249
|
+
|
|
250
|
+
const indexModule = jsCtx.srcModule;
|
|
251
|
+
|
|
252
|
+
const routeControllers = await createRouteControllers(jsCtx, httpService, indexModule);
|
|
253
|
+
|
|
254
|
+
console.info("[hsj] Generating server entry point...");
|
|
255
|
+
|
|
256
|
+
const controllerModules = new Set<Module>();
|
|
257
|
+
|
|
258
|
+
for (const { name, module } of routeControllers) {
|
|
259
|
+
controllerModules.add(module);
|
|
260
|
+
indexModule.imports.push({ binder: [name], from: module });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const routerName = parseCase(httpService.namespace.name).pascalCase + "Router";
|
|
264
|
+
|
|
265
|
+
indexModule.imports.push({
|
|
266
|
+
binder: ["create" + routerName],
|
|
267
|
+
from: options["no-standalone"]
|
|
268
|
+
? "../tsp-output/@typespec/http-server-js/src/generated/http/router.js"
|
|
269
|
+
: "./generated/http/router.js",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
indexModule.declarations.push([
|
|
273
|
+
`const router = create${routerName}(`,
|
|
274
|
+
...routeControllers.map((controller) => ` new ${controller.name}(),`),
|
|
275
|
+
`);`,
|
|
276
|
+
"",
|
|
277
|
+
"const PORT = process.env.PORT || 3000;",
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
if (expressOptions.isExpress) {
|
|
281
|
+
indexModule.imports.push(
|
|
282
|
+
{
|
|
283
|
+
binder: "express",
|
|
284
|
+
from: "express",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
binder: "morgan",
|
|
288
|
+
from: "morgan",
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
if (expressOptions.openApi3) {
|
|
293
|
+
const swaggerUiModule = createModule("swagger-ui", indexModule);
|
|
294
|
+
|
|
295
|
+
indexModule.imports.push({
|
|
296
|
+
from: swaggerUiModule,
|
|
297
|
+
binder: ["addSwaggerUi"],
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
swaggerUiModule.imports.push(
|
|
301
|
+
{
|
|
302
|
+
binder: "swaggerUi",
|
|
303
|
+
from: "swagger-ui-express",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
binder: ["openApiDocument"],
|
|
307
|
+
from: "./generated/http/openapi3.js",
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
binder: "type express",
|
|
311
|
+
from: "express",
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
swaggerUiModule.declarations.push([
|
|
316
|
+
"export function addSwaggerUi(path: string, app: express.Application) {",
|
|
317
|
+
" app.use(path, swaggerUi.serve, swaggerUi.setup(openApiDocument));",
|
|
318
|
+
"}",
|
|
319
|
+
]);
|
|
320
|
+
|
|
321
|
+
writeModuleFile(
|
|
322
|
+
jsCtx,
|
|
323
|
+
baseOutputDir,
|
|
324
|
+
swaggerUiModule,
|
|
325
|
+
createOnceQueue<Module>(),
|
|
326
|
+
true,
|
|
327
|
+
tryWrite,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
indexModule.declarations.push([
|
|
332
|
+
"const app = express();",
|
|
333
|
+
"",
|
|
334
|
+
"app.use(morgan('dev'));",
|
|
335
|
+
...(expressOptions.openApi3
|
|
336
|
+
? [
|
|
337
|
+
"",
|
|
338
|
+
'const SWAGGER_UI_PATH = process.env.SWAGGER_UI_PATH || "/.api-docs";',
|
|
339
|
+
"",
|
|
340
|
+
"addSwaggerUi(SWAGGER_UI_PATH, app);",
|
|
341
|
+
]
|
|
342
|
+
: []),
|
|
343
|
+
"",
|
|
344
|
+
"app.use(router.expressMiddleware);",
|
|
345
|
+
"",
|
|
346
|
+
"app.listen(PORT, () => {",
|
|
347
|
+
` console.log(\`Server is running at http://localhost:\${PORT}\`);`,
|
|
348
|
+
...(expressOptions.openApi3
|
|
349
|
+
? [
|
|
350
|
+
" console.log(`API documentation is available at http://localhost:${PORT}${SWAGGER_UI_PATH}`);",
|
|
351
|
+
]
|
|
352
|
+
: []),
|
|
353
|
+
"});",
|
|
354
|
+
]);
|
|
355
|
+
} else {
|
|
356
|
+
indexModule.imports.push({
|
|
357
|
+
binder: ["createServer"],
|
|
358
|
+
from: "node:http",
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
indexModule.declarations.push([
|
|
362
|
+
"const server = createServer(router.dispatch);",
|
|
363
|
+
"",
|
|
364
|
+
"server.listen(PORT, () => {",
|
|
365
|
+
` console.log(\`Server is running at http://localhost:\${PORT}\`);`,
|
|
366
|
+
"});",
|
|
367
|
+
]);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
console.info("[hsj] Writing files...");
|
|
371
|
+
|
|
372
|
+
const queue = createOnceQueue<Module>();
|
|
373
|
+
|
|
374
|
+
await writeModuleFile(jsCtx, baseOutputDir, indexModule, queue, /* format */ true, tryWrite);
|
|
375
|
+
|
|
376
|
+
for (const module of controllerModules) {
|
|
377
|
+
module.imports = module.imports.map((_import) => {
|
|
378
|
+
if (
|
|
379
|
+
options["no-standalone"] &&
|
|
380
|
+
typeof _import.from !== "string" &&
|
|
381
|
+
!controllerModules.has(_import.from)
|
|
382
|
+
) {
|
|
383
|
+
const backout = module.cursor.path.map(() => "..");
|
|
384
|
+
|
|
385
|
+
const [declaredModules] = bifilter(_import.from.declarations, isModule);
|
|
386
|
+
|
|
387
|
+
const targetIsIndex = _import.from.cursor.path.length === 0 || declaredModules.length > 0;
|
|
388
|
+
|
|
389
|
+
const modulePrincipalName = _import.from.cursor.path.slice(-1)[0];
|
|
390
|
+
|
|
391
|
+
const targetPath = [
|
|
392
|
+
...backout.slice(1),
|
|
393
|
+
"tsp-output",
|
|
394
|
+
"@typespec",
|
|
395
|
+
"http-server-js",
|
|
396
|
+
..._import.from.cursor.path.slice(0, -1),
|
|
397
|
+
...(targetIsIndex ? [modulePrincipalName, "index.js"] : [`${modulePrincipalName}.js`]),
|
|
398
|
+
].join("/");
|
|
399
|
+
|
|
400
|
+
_import.from = targetPath;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return _import;
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await writeModuleFile(jsCtx, baseOutputDir, module, queue, /* format */ true, tryWrite);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Force writing of http helper module
|
|
410
|
+
await writeModuleFile(jsCtx, baseOutputDir, httpHelperModule, queue, /* format */ true, tryWrite);
|
|
411
|
+
|
|
412
|
+
await tryWrite(
|
|
413
|
+
tsConfigOutputPath,
|
|
414
|
+
JSON.stringify(getDefaultTsConfig(!options["no-standalone"]), null, 2) + "\n",
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const vsCodeLaunchJsonPath = path.resolve(baseOutputDir, COMMON_PATHS.vsCodeLaunchJson);
|
|
418
|
+
const vsCodeTasksJsonPath = path.resolve(baseOutputDir, COMMON_PATHS.vsCodeTasksJson);
|
|
419
|
+
|
|
420
|
+
await tryWrite(vsCodeLaunchJsonPath, JSON.stringify(VSCODE_LAUNCH_JSON, null, 2) + "\n");
|
|
421
|
+
await tryWrite(vsCodeTasksJsonPath, JSON.stringify(VSCODE_TASKS_JSON, null, 2) + "\n");
|
|
422
|
+
|
|
423
|
+
const ownPackageJsonPath = path.resolve(cwd, COMMON_PATHS.packageJson);
|
|
424
|
+
|
|
425
|
+
let ownPackageJson;
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
ownPackageJson = JSON.parse((await fs.readFile(ownPackageJsonPath)).toString("utf-8"));
|
|
429
|
+
} catch {
|
|
430
|
+
console.error("[hsj] Failed to read package.json of TypeSpec project. Exiting.");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
let packageJsonChanged = true;
|
|
435
|
+
|
|
436
|
+
if (options["no-standalone"]) {
|
|
437
|
+
console.info("[hsj] Checking package.json for changes...");
|
|
438
|
+
|
|
439
|
+
packageJsonChanged = updatePackageJson(ownPackageJson, expressOptions, options.force);
|
|
440
|
+
|
|
441
|
+
if (packageJsonChanged) {
|
|
442
|
+
console.info("[hsj] Writing updated package.json...");
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
await fs.writeFile(ownPackageJsonPath, JSON.stringify(ownPackageJson, null, 2) + "\n");
|
|
446
|
+
} catch {
|
|
447
|
+
console.error("[hsj] Failed to write package.json.");
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
console.info("[hsj] No changes to package.json suggested.");
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// Standalone mode, need to generate package.json from scratch
|
|
455
|
+
const relativePathToSpec = path.relative(baseOutputDir, cwd);
|
|
456
|
+
const packageJson = getPackageJsonForStandaloneProject(
|
|
457
|
+
ownPackageJson,
|
|
458
|
+
expressOptions,
|
|
459
|
+
relativePathToSpec,
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
const packageJsonPath = path.resolve(baseOutputDir, COMMON_PATHS.packageJson);
|
|
463
|
+
|
|
464
|
+
await tryWrite(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (packageJsonChanged) {
|
|
468
|
+
// Run npm install to ensure dependencies are installed.
|
|
469
|
+
console.info("[hsj] Running npm install...");
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
await spawn("npm", ["install"], {
|
|
473
|
+
stdio: "inherit",
|
|
474
|
+
cwd: options["no-standalone"] ? cwd : baseOutputDir,
|
|
475
|
+
shell: process.platform === "win32",
|
|
476
|
+
});
|
|
477
|
+
} catch {
|
|
478
|
+
console.warn(
|
|
479
|
+
"[hsj] Failed to run npm install. Check the output above for errors and install dependencies manually.",
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
console.info("[hsj] Project scaffolding complete. Building project...");
|
|
485
|
+
|
|
486
|
+
try {
|
|
487
|
+
await spawn("npm", ["run", "build"], {
|
|
488
|
+
stdio: "inherit",
|
|
489
|
+
cwd: options["no-standalone"] ? cwd : baseOutputDir,
|
|
490
|
+
shell: process.platform === "win32",
|
|
491
|
+
});
|
|
492
|
+
} catch {
|
|
493
|
+
console.error("[hsj] Failed to build project. Check the output above for errors.");
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const codeDirectory = path.relative(cwd, options["no-standalone"] ? cwd : baseOutputDir);
|
|
498
|
+
|
|
499
|
+
console.info("[hsj] Project is ready to run. Use `npm start` to launch the server.");
|
|
500
|
+
console.info("[hsj] A debug configuration has been created for Visual Studio Code.");
|
|
501
|
+
console.info(
|
|
502
|
+
`[hsj] Try \`code ${codeDirectory}\` to open the project and press F5 to start debugging.`,
|
|
503
|
+
);
|
|
504
|
+
console.info(
|
|
505
|
+
`[hsj] The newly-generated route controllers in '${path.join(codeDirectory, "src", "controllers")}' are ready to be implemented.`,
|
|
506
|
+
);
|
|
507
|
+
console.info("[hsj] Done.");
|
|
508
|
+
|
|
509
|
+
async function tryWrite(file: string, contents: string): Promise<void> {
|
|
510
|
+
try {
|
|
511
|
+
const relative = path.relative(cwd, file);
|
|
512
|
+
|
|
513
|
+
const exists = await fs
|
|
514
|
+
.stat(file)
|
|
515
|
+
.then(() => true)
|
|
516
|
+
.catch(() => false);
|
|
517
|
+
|
|
518
|
+
if (exists && !options.force) {
|
|
519
|
+
console.warn(`[hsj] File '${relative}' already exists and will not be overwritten.`);
|
|
520
|
+
console.warn(`[hsj] Manually update the file or delete it and run scaffolding again.`);
|
|
521
|
+
|
|
522
|
+
return;
|
|
523
|
+
} else if (exists) {
|
|
524
|
+
console.warn(`[hsj] Overwriting file '${relative}'...`);
|
|
525
|
+
} else {
|
|
526
|
+
console.info(`[hsj] Writing file '${relative}'...`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
530
|
+
await fs.writeFile(file, contents);
|
|
531
|
+
} catch (e: unknown) {
|
|
532
|
+
console.error(`[hsj] Failed to write file: '${(e as Error).message}'`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
interface RouteController {
|
|
538
|
+
name: string;
|
|
539
|
+
module: Module;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function createRouteControllers(
|
|
543
|
+
ctx: JsContext,
|
|
544
|
+
httpService: HttpService,
|
|
545
|
+
srcModule: Module,
|
|
546
|
+
): Promise<RouteController[]> {
|
|
547
|
+
const controllers: RouteController[] = [];
|
|
548
|
+
|
|
549
|
+
const operationsByContainer = new Map<OperationContainer, Set<HttpOperation>>();
|
|
550
|
+
|
|
551
|
+
for (const operation of httpService.operations) {
|
|
552
|
+
let byContainer = operationsByContainer.get(operation.container);
|
|
553
|
+
|
|
554
|
+
if (!byContainer) {
|
|
555
|
+
byContainer = new Set();
|
|
556
|
+
operationsByContainer.set(operation.container, byContainer);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
byContainer.add(operation);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const controllersModule = createModule("controllers", srcModule);
|
|
563
|
+
|
|
564
|
+
for (const [container, operations] of operationsByContainer) {
|
|
565
|
+
controllers.push(await createRouteController(ctx, container, operations, controllersModule));
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return controllers;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function createRouteController(
|
|
572
|
+
ctx: JsContext,
|
|
573
|
+
container: OperationContainer,
|
|
574
|
+
operations: Set<HttpOperation>,
|
|
575
|
+
controllersModule: Module,
|
|
576
|
+
): Promise<RouteController> {
|
|
577
|
+
const nameCase = parseCase(container.name);
|
|
578
|
+
const module = createModule(nameCase.kebabCase, controllersModule);
|
|
579
|
+
|
|
580
|
+
const containerNameCase = parseCase(container.name);
|
|
581
|
+
|
|
582
|
+
module.imports.push(
|
|
583
|
+
{
|
|
584
|
+
binder: [containerNameCase.pascalCase],
|
|
585
|
+
from: createOrGetModuleForNamespace(ctx, container.namespace!),
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
binder: ["HttpContext"],
|
|
589
|
+
from: routerModule,
|
|
590
|
+
},
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
const controllerName = containerNameCase.pascalCase + "Impl";
|
|
594
|
+
|
|
595
|
+
console.info(`[hsj] Generating controller '${controllerName}'...`);
|
|
596
|
+
|
|
597
|
+
module.declarations.push([
|
|
598
|
+
`export class ${controllerName} implements ${containerNameCase.pascalCase}<HttpContext> {`,
|
|
599
|
+
...indent(emitControllerOperationHandlers(ctx, container, operations, module)),
|
|
600
|
+
`}`,
|
|
601
|
+
]);
|
|
602
|
+
|
|
603
|
+
return { name: controllerName, module };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function* emitControllerOperationHandlers(
|
|
607
|
+
ctx: JsContext,
|
|
608
|
+
container: OperationContainer,
|
|
609
|
+
httpOperations: Set<HttpOperation>,
|
|
610
|
+
module: Module,
|
|
611
|
+
): Iterable<string> {
|
|
612
|
+
module.imports.push({
|
|
613
|
+
binder: ["NotImplementedError"],
|
|
614
|
+
from: httpHelperModule,
|
|
615
|
+
});
|
|
616
|
+
for (const httpOperation of httpOperations) {
|
|
617
|
+
// TODO: unify construction of signature with emitOperation in common/interface.ts
|
|
618
|
+
const op = httpOperation.operation;
|
|
619
|
+
|
|
620
|
+
const opNameCase = parseCase(op.name);
|
|
621
|
+
|
|
622
|
+
const opName = opNameCase.camelCase;
|
|
623
|
+
|
|
624
|
+
const allParameters = getAllProperties(op.parameters);
|
|
625
|
+
|
|
626
|
+
const hasOptions = allParameters.some((p) => p.optional);
|
|
627
|
+
|
|
628
|
+
const returnTypeReference = emitTypeReference(ctx, op.returnType, op, module, {
|
|
629
|
+
altName: opNameCase.pascalCase + "Result",
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
const returnType = `Promise<${returnTypeReference}>`;
|
|
633
|
+
|
|
634
|
+
const params: string[] = [];
|
|
635
|
+
|
|
636
|
+
for (const param of allParameters) {
|
|
637
|
+
// If the type is a value literal, then we consider it a _setting_ and not a parameter.
|
|
638
|
+
// This allows us to exclude metadata parameters (such as contentType) from the generated interface.
|
|
639
|
+
if (param.optional || isValueLiteralType(param.type)) continue;
|
|
640
|
+
|
|
641
|
+
const paramNameCase = parseCase(param.name);
|
|
642
|
+
const paramName = paramNameCase.camelCase;
|
|
643
|
+
|
|
644
|
+
const outputTypeReference = emitTypeReference(ctx, param.type, param, module, {
|
|
645
|
+
altName: opNameCase.pascalCase + paramNameCase.pascalCase,
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
params.push(`${paramName}: ${outputTypeReference}`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const paramsDeclarationLine = params.join(", ");
|
|
652
|
+
|
|
653
|
+
if (hasOptions) {
|
|
654
|
+
const optionsTypeName = opNameCase.pascalCase + "Options";
|
|
655
|
+
|
|
656
|
+
emitOptionsType(ctx, op, module, optionsTypeName);
|
|
657
|
+
|
|
658
|
+
const paramsFragment = params.length > 0 ? `${paramsDeclarationLine}, ` : "";
|
|
659
|
+
|
|
660
|
+
// prettier-ignore
|
|
661
|
+
yield `async ${opName}(ctx: HttpContext, ${paramsFragment}options?: ${optionsTypeName}): ${returnType} {`;
|
|
662
|
+
} else {
|
|
663
|
+
// prettier-ignore
|
|
664
|
+
yield `async ${opName}(ctx: HttpContext, ${paramsDeclarationLine}): ${returnType} {`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
yield " throw new NotImplementedError();";
|
|
668
|
+
yield "}";
|
|
669
|
+
yield "";
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function getPackageJsonForStandaloneProject(
|
|
674
|
+
ownPackageJson: any,
|
|
675
|
+
express: PackageJsonExpressOptions,
|
|
676
|
+
relativePathToSpec: string,
|
|
677
|
+
): any {
|
|
678
|
+
const packageJson = {
|
|
679
|
+
name: (ownPackageJson.name ?? path.basename(process.cwd())) + "-server",
|
|
680
|
+
version: ownPackageJson.version ?? "0.1.0",
|
|
681
|
+
type: "module",
|
|
682
|
+
description: "Generated TypeSpec server project.",
|
|
683
|
+
} as any;
|
|
684
|
+
|
|
685
|
+
if (ownPackageJson.private) {
|
|
686
|
+
packageJson.private = true;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
updatePackageJson(packageJson, express, true, () => {});
|
|
690
|
+
|
|
691
|
+
delete packageJson.scripts["build:scaffold"];
|
|
692
|
+
packageJson.scripts["build:typespec"] = 'tsp compile --output-dir=".." ' + relativePathToSpec;
|
|
693
|
+
|
|
694
|
+
return packageJson;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const JS_IDENTIFIER_RE = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
698
|
+
|
|
699
|
+
interface PackageJsonExpressOptions {
|
|
700
|
+
isExpress: boolean;
|
|
701
|
+
openApi3: SupportedOpenAPIDocuments | undefined;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function updatePackageJson(
|
|
705
|
+
packageJson: any,
|
|
706
|
+
express: PackageJsonExpressOptions,
|
|
707
|
+
force: boolean,
|
|
708
|
+
info: (...args: any[]) => void = console.info,
|
|
709
|
+
): boolean {
|
|
710
|
+
let changed = false;
|
|
711
|
+
|
|
712
|
+
updateObjectPath(["scripts", "start"], "node dist/src/index.js");
|
|
713
|
+
updateObjectPath(["scripts", "build"], "npm run build:typespec && tsc");
|
|
714
|
+
updateObjectPath(["scripts", "build:typespec"], "tsp compile .");
|
|
715
|
+
updateObjectPath(["scripts", "build:scaffold"], "hsj-scaffold");
|
|
716
|
+
|
|
717
|
+
updateObjectPath(["devDependencies", "typescript"], "^5.7.3");
|
|
718
|
+
updateObjectPath(["devDependencies", "@types/node"], "^22.13.1");
|
|
719
|
+
|
|
720
|
+
if (express.isExpress) {
|
|
721
|
+
updateObjectPath(["dependencies", "express"], "^5.0.1");
|
|
722
|
+
updateObjectPath(["devDependencies", "@types/express"], "^5.0.0");
|
|
723
|
+
|
|
724
|
+
if (express.openApi3) {
|
|
725
|
+
updateObjectPath(["dependencies", "swagger-ui-express"], "^5.0.1");
|
|
726
|
+
updateObjectPath(["devDependencies", "@types/swagger-ui-express"], "^4.1.7");
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
updateObjectPath(["dependencies", "morgan"], "^1.10.0");
|
|
730
|
+
updateObjectPath(["devDependencies", "@types/morgan"], "^1.9.9");
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return changed;
|
|
734
|
+
|
|
735
|
+
function updateObjectPath(path: string[], value: string) {
|
|
736
|
+
let current = packageJson;
|
|
737
|
+
|
|
738
|
+
for (const fragment of path.slice(0, -1)) {
|
|
739
|
+
current = current[fragment] ??= {};
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const existingValue = current[path[path.length - 1]];
|
|
743
|
+
|
|
744
|
+
let property = "";
|
|
745
|
+
|
|
746
|
+
for (const fragment of path) {
|
|
747
|
+
if (!JS_IDENTIFIER_RE.test(fragment)) {
|
|
748
|
+
property += `["${fragment}"]`;
|
|
749
|
+
} else {
|
|
750
|
+
property += property === "" ? fragment : `.${fragment}`;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (!existingValue || force) {
|
|
755
|
+
if (!existingValue) {
|
|
756
|
+
info(`[hsj] - Setting package.json property '${property}' to "${value}".`);
|
|
757
|
+
} else if (force) {
|
|
758
|
+
info(`[hsj] - Overwriting package.json property '${property}' to "${value}".`);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
current[path[path.length - 1]] = value;
|
|
762
|
+
|
|
763
|
+
changed ||= true;
|
|
764
|
+
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (current[path[path.length - 1]] !== value) {
|
|
769
|
+
info(`[hsj] - Skipping package.json property '${property}'.`);
|
|
770
|
+
info(`[hsj] Scaffolding prefers "${value}", but it is already set to "${existingValue}".`);
|
|
771
|
+
info(
|
|
772
|
+
"[hsj] Manually update the property or remove it and run scaffolding again if needed.",
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
scaffold(parseScaffoldArguments(process.argv)).catch((error) => {
|
|
779
|
+
console.error(error);
|
|
780
|
+
process.exit(1);
|
|
781
|
+
});
|