@simplysm/sd-cli 13.0.68 → 13.0.70
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/README.md +10 -957
- package/dist/builders/BaseBuilder.d.ts +23 -23
- package/dist/builders/BaseBuilder.d.ts.map +1 -1
- package/dist/builders/BaseBuilder.js +15 -15
- package/dist/builders/DtsBuilder.d.ts +4 -4
- package/dist/builders/DtsBuilder.js +1 -1
- package/dist/builders/LibraryBuilder.d.ts +3 -3
- package/dist/builders/types.d.ts +10 -10
- package/dist/capacitor/capacitor.d.ts +36 -36
- package/dist/capacitor/capacitor.js +63 -63
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/add-client.d.ts +8 -8
- package/dist/commands/add-client.js +15 -15
- package/dist/commands/add-client.js.map +1 -1
- package/dist/commands/add-server.d.ts +9 -9
- package/dist/commands/add-server.js +13 -13
- package/dist/commands/add-server.js.map +1 -1
- package/dist/commands/build.d.ts +9 -9
- package/dist/commands/check.js +3 -3
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/dev.d.ts +9 -9
- package/dist/commands/device.d.ts +9 -9
- package/dist/commands/device.d.ts.map +1 -1
- package/dist/commands/device.js +17 -17
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/init.d.ts +6 -6
- package/dist/commands/init.js +12 -12
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lint.d.ts +23 -23
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +25 -25
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/publish.d.ts +13 -13
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +61 -61
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/replace-deps.d.ts +3 -3
- package/dist/commands/replace-deps.d.ts.map +1 -1
- package/dist/commands/replace-deps.js +1 -1
- package/dist/commands/replace-deps.js.map +1 -1
- package/dist/commands/typecheck.d.ts +20 -20
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +20 -20
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/commands/watch.d.ts +7 -7
- package/dist/electron/electron.d.ts +27 -27
- package/dist/electron/electron.js +32 -32
- package/dist/electron/electron.js.map +1 -1
- package/dist/infra/ResultCollector.d.ts +9 -9
- package/dist/infra/ResultCollector.js +5 -5
- package/dist/infra/SignalHandler.d.ts +7 -7
- package/dist/infra/SignalHandler.js +4 -4
- package/dist/infra/WorkerManager.d.ts +14 -14
- package/dist/infra/WorkerManager.js +11 -11
- package/dist/orchestrators/BuildOrchestrator.d.ts +19 -19
- package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.js +26 -26
- package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
- package/dist/orchestrators/DevOrchestrator.d.ts +25 -25
- package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevOrchestrator.js +30 -30
- package/dist/orchestrators/DevOrchestrator.js.map +1 -1
- package/dist/orchestrators/WatchOrchestrator.d.ts +13 -13
- package/dist/orchestrators/WatchOrchestrator.js +17 -17
- package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
- package/dist/sd-cli-entry.d.ts +2 -2
- package/dist/sd-cli-entry.js +38 -38
- package/dist/sd-cli-entry.js.map +1 -1
- package/dist/sd-cli.d.ts +2 -2
- package/dist/sd-cli.js +1 -1
- package/dist/sd-cli.js.map +1 -1
- package/dist/sd-config.types.d.ts +84 -84
- package/dist/sd-config.types.d.ts.map +1 -1
- package/dist/utils/build-env.d.ts +1 -1
- package/dist/utils/config-editor.d.ts +5 -5
- package/dist/utils/config-editor.js +2 -2
- package/dist/utils/config-editor.js.map +1 -1
- package/dist/utils/copy-public.d.ts +9 -9
- package/dist/utils/copy-src.d.ts +9 -9
- package/dist/utils/esbuild-config.d.ts +30 -30
- package/dist/utils/esbuild-config.d.ts.map +1 -1
- package/dist/utils/output-utils.d.ts +6 -6
- package/dist/utils/package-utils.d.ts +6 -6
- package/dist/utils/package-utils.js +1 -1
- package/dist/utils/package-utils.js.map +1 -1
- package/dist/utils/rebuild-manager.js +3 -3
- package/dist/utils/rebuild-manager.js.map +1 -1
- package/dist/utils/replace-deps.d.ts +25 -25
- package/dist/utils/replace-deps.js +3 -3
- package/dist/utils/replace-deps.js.map +1 -1
- package/dist/utils/sd-config.d.ts +3 -3
- package/dist/utils/sd-config.js +3 -3
- package/dist/utils/sd-config.js.map +1 -1
- package/dist/utils/tailwind-config-deps.d.ts +3 -3
- package/dist/utils/template.d.ts +8 -8
- package/dist/utils/tsconfig.d.ts +16 -16
- package/dist/utils/tsconfig.js +2 -2
- package/dist/utils/tsconfig.js.map +1 -1
- package/dist/utils/typecheck-serialization.d.ts +8 -8
- package/dist/utils/vite-config.d.ts +8 -8
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +3 -3
- package/dist/utils/worker-events.d.ts +12 -12
- package/dist/utils/worker-events.d.ts.map +1 -1
- package/dist/utils/worker-utils.d.ts +3 -3
- package/dist/utils/worker-utils.js +2 -2
- package/dist/utils/worker-utils.js.map +1 -1
- package/dist/workers/client.worker.d.ts +14 -14
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +1 -1
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/dts.worker.d.ts +13 -13
- package/dist/workers/dts.worker.d.ts.map +1 -1
- package/dist/workers/dts.worker.js +3 -3
- package/dist/workers/dts.worker.js.map +1 -1
- package/dist/workers/library.worker.d.ts +12 -12
- package/dist/workers/library.worker.js +1 -1
- package/dist/workers/library.worker.js.map +1 -1
- package/dist/workers/lint.worker.d.ts +1 -1
- package/dist/workers/server-runtime.worker.d.ts +6 -6
- package/dist/workers/server-runtime.worker.js +6 -6
- package/dist/workers/server-runtime.worker.js.map +1 -1
- package/dist/workers/server.worker.d.ts +20 -20
- package/dist/workers/server.worker.d.ts.map +1 -1
- package/dist/workers/server.worker.js +6 -6
- package/dist/workers/server.worker.js.map +1 -1
- package/package.json +8 -7
- package/src/builders/BaseBuilder.ts +33 -33
- package/src/builders/DtsBuilder.ts +5 -5
- package/src/builders/LibraryBuilder.ts +9 -9
- package/src/builders/types.ts +10 -10
- package/src/capacitor/capacitor.ts +119 -119
- package/src/commands/add-client.ts +31 -31
- package/src/commands/add-server.ts +34 -34
- package/src/commands/build.ts +9 -9
- package/src/commands/check.ts +5 -5
- package/src/commands/dev.ts +9 -9
- package/src/commands/device.ts +30 -30
- package/src/commands/init.ts +25 -25
- package/src/commands/lint.ts +64 -64
- package/src/commands/publish.ts +139 -139
- package/src/commands/replace-deps.ts +4 -4
- package/src/commands/typecheck.ts +74 -74
- package/src/commands/watch.ts +7 -7
- package/src/electron/electron.ts +51 -51
- package/src/infra/ResultCollector.ts +9 -9
- package/src/infra/SignalHandler.ts +7 -7
- package/src/infra/WorkerManager.ts +14 -14
- package/src/orchestrators/BuildOrchestrator.ts +76 -76
- package/src/orchestrators/DevOrchestrator.ts +88 -88
- package/src/orchestrators/WatchOrchestrator.ts +39 -39
- package/src/sd-cli-entry.ts +43 -43
- package/src/sd-cli.ts +15 -15
- package/src/sd-config.types.ts +85 -85
- package/src/utils/build-env.ts +1 -1
- package/src/utils/config-editor.ts +19 -19
- package/src/utils/copy-public.ts +17 -17
- package/src/utils/copy-src.ts +11 -11
- package/src/utils/esbuild-config.ts +33 -33
- package/src/utils/output-utils.ts +11 -11
- package/src/utils/package-utils.ts +12 -12
- package/src/utils/rebuild-manager.ts +3 -3
- package/src/utils/replace-deps.ts +361 -361
- package/src/utils/sd-config.ts +44 -44
- package/src/utils/tailwind-config-deps.ts +98 -98
- package/src/utils/template.ts +56 -56
- package/src/utils/tsconfig.ts +127 -127
- package/src/utils/typecheck-serialization.ts +86 -86
- package/src/utils/vite-config.ts +341 -341
- package/src/utils/worker-events.ts +16 -16
- package/src/utils/worker-utils.ts +45 -45
- package/src/workers/client.worker.ts +34 -34
- package/src/workers/dts.worker.ts +467 -467
- package/src/workers/library.worker.ts +314 -314
- package/src/workers/lint.worker.ts +16 -16
- package/src/workers/server-runtime.worker.ts +157 -157
- package/src/workers/server.worker.ts +572 -572
- package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
- package/templates/add-server/__SERVER__/package.json.hbs +2 -2
- package/templates/init/package.json.hbs +3 -3
- package/tests/config-editor.spec.ts +160 -0
- package/tests/copy-src.spec.ts +50 -0
- package/tests/get-compiler-options-for-package.spec.ts +139 -0
- package/tests/get-package-source-files.spec.ts +181 -0
- package/tests/get-types-from-package-json.spec.ts +107 -0
- package/tests/infra/ResultCollector.spec.ts +39 -0
- package/tests/infra/SignalHandler.spec.ts +38 -0
- package/tests/infra/WorkerManager.spec.ts +97 -0
- package/tests/load-ignore-patterns.spec.ts +188 -0
- package/tests/load-sd-config.spec.ts +137 -0
- package/tests/package-utils.spec.ts +188 -0
- package/tests/parse-root-tsconfig.spec.ts +89 -0
- package/tests/replace-deps.spec.ts +308 -0
- package/tests/run-lint.spec.ts +415 -0
- package/tests/run-typecheck.spec.ts +653 -0
- package/tests/run-watch.spec.ts +75 -0
- package/tests/sd-cli.spec.ts +330 -0
- package/tests/tailwind-config-deps.spec.ts +30 -0
- package/tests/template.spec.ts +70 -0
- package/tests/utils/rebuild-manager.spec.ts +43 -0
- package/tests/write-changed-output-files.spec.ts +97 -0
|
@@ -1,572 +1,572 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import { execaSync } from "execa";
|
|
4
|
-
import esbuild from "esbuild";
|
|
5
|
-
import { createWorker, FsWatcher, pathNorm } from "@simplysm/core-node";
|
|
6
|
-
import { errorMessage } from "@simplysm/core-common";
|
|
7
|
-
import { consola } from "consola";
|
|
8
|
-
import {
|
|
9
|
-
parseRootTsconfig,
|
|
10
|
-
getPackageSourceFiles,
|
|
11
|
-
getCompilerOptionsForPackage,
|
|
12
|
-
} from "../utils/tsconfig";
|
|
13
|
-
import {
|
|
14
|
-
createServerEsbuildOptions,
|
|
15
|
-
collectUninstalledOptionalPeerDeps,
|
|
16
|
-
collectNativeModuleExternals,
|
|
17
|
-
writeChangedOutputFiles,
|
|
18
|
-
} from "../utils/esbuild-config";
|
|
19
|
-
import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
|
|
20
|
-
import { collectDeps } from "../utils/package-utils";
|
|
21
|
-
import { copyPublicFiles, watchPublicFiles } from "../utils/copy-public";
|
|
22
|
-
|
|
23
|
-
//#region Types
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Server
|
|
27
|
-
*/
|
|
28
|
-
export interface ServerBuildInfo {
|
|
29
|
-
name: string;
|
|
30
|
-
cwd: string;
|
|
31
|
-
pkgDir: string;
|
|
32
|
-
/**
|
|
33
|
-
env?: Record<string, string>;
|
|
34
|
-
/**
|
|
35
|
-
configs?: Record<string, unknown>;
|
|
36
|
-
/** sd.config.ts
|
|
37
|
-
externals?: string[];
|
|
38
|
-
/** PM2
|
|
39
|
-
pm2?: {
|
|
40
|
-
name?: string;
|
|
41
|
-
ignoreWatchPaths?: string[];
|
|
42
|
-
};
|
|
43
|
-
/** Package manager to use (affects mise.toml or volta settings generation) */
|
|
44
|
-
packageManager?: "volta" | "mise";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Server
|
|
49
|
-
*/
|
|
50
|
-
export interface ServerBuildResult {
|
|
51
|
-
success: boolean;
|
|
52
|
-
mainJsPath: string;
|
|
53
|
-
errors?: string[];
|
|
54
|
-
warnings?: string[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Server
|
|
59
|
-
*/
|
|
60
|
-
export interface ServerWatchInfo {
|
|
61
|
-
name: string;
|
|
62
|
-
cwd: string;
|
|
63
|
-
pkgDir: string;
|
|
64
|
-
/**
|
|
65
|
-
env?: Record<string, string>;
|
|
66
|
-
/**
|
|
67
|
-
configs?: Record<string, unknown>;
|
|
68
|
-
/** sd.config.ts
|
|
69
|
-
externals?: string[];
|
|
70
|
-
/** sd.config.ts
|
|
71
|
-
replaceDeps?: Record<string, string>;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
76
|
-
*/
|
|
77
|
-
export interface ServerBuildEvent {
|
|
78
|
-
success: boolean;
|
|
79
|
-
mainJsPath: string;
|
|
80
|
-
errors?: string[];
|
|
81
|
-
warnings?: string[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
*
|
|
86
|
-
*/
|
|
87
|
-
export interface ServerErrorEvent {
|
|
88
|
-
message: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Worker
|
|
93
|
-
*/
|
|
94
|
-
export interface ServerWorkerEvents extends Record<string, unknown> {
|
|
95
|
-
buildStart: Record<string, never>;
|
|
96
|
-
build: ServerBuildEvent;
|
|
97
|
-
error: ServerErrorEvent;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
//#endregion
|
|
101
|
-
|
|
102
|
-
//#region
|
|
103
|
-
|
|
104
|
-
const logger = consola.withTag("sd:cli:server:worker");
|
|
105
|
-
|
|
106
|
-
/** esbuild build context (
|
|
107
|
-
let esbuildContext: esbuild.BuildContext | undefined;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
let lastMetafile: esbuild.Metafile | undefined;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
let publicWatcher: FsWatcher | undefined;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
let srcWatcher: FsWatcher | undefined;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
*
|
|
120
|
-
*/
|
|
121
|
-
async function cleanup(): Promise<void> {
|
|
122
|
-
//
|
|
123
|
-
// (
|
|
124
|
-
const contextToDispose = esbuildContext;
|
|
125
|
-
esbuildContext = undefined;
|
|
126
|
-
lastMetafile = undefined;
|
|
127
|
-
|
|
128
|
-
const watcherToClose = publicWatcher;
|
|
129
|
-
publicWatcher = undefined;
|
|
130
|
-
|
|
131
|
-
const srcWatcherToClose = srcWatcher;
|
|
132
|
-
srcWatcher = undefined;
|
|
133
|
-
|
|
134
|
-
if (contextToDispose != null) {
|
|
135
|
-
await contextToDispose.dispose();
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (watcherToClose != null) {
|
|
139
|
-
await watcherToClose.close();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (srcWatcherToClose != null) {
|
|
143
|
-
await srcWatcherToClose.close();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
*
|
|
149
|
-
* 1.
|
|
150
|
-
* 2. binding.gyp
|
|
151
|
-
* 3. sd.config.ts
|
|
152
|
-
*/
|
|
153
|
-
function collectAllExternals(pkgDir: string, manualExternals?: string[]): string[] {
|
|
154
|
-
const optionalPeerDeps = collectUninstalledOptionalPeerDeps(pkgDir);
|
|
155
|
-
const nativeModules = collectNativeModuleExternals(pkgDir);
|
|
156
|
-
const manual = manualExternals ?? [];
|
|
157
|
-
|
|
158
|
-
const merged = [...new Set([...optionalPeerDeps, ...nativeModules, ...manual])];
|
|
159
|
-
|
|
160
|
-
if (optionalPeerDeps.length > 0) {
|
|
161
|
-
logger.debug("
|
|
162
|
-
}
|
|
163
|
-
if (nativeModules.length > 0) {
|
|
164
|
-
logger.debug("
|
|
165
|
-
}
|
|
166
|
-
if (manual.length > 0) {
|
|
167
|
-
logger.debug("
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return merged;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* - dist/package.json: external
|
|
177
|
-
* - dist/mise.toml: Node
|
|
178
|
-
* - dist/openssl.cnf: 레거시 OpenSSL 프로바이더 활성화
|
|
179
|
-
* - dist/pm2.config.cjs: PM2 프로세스 설정 (pm2 옵션이 있을 때만)
|
|
180
|
-
*/
|
|
181
|
-
function generateProductionFiles(info: ServerBuildInfo, externals: string[]): void {
|
|
182
|
-
const distDir = path.join(info.pkgDir, "dist");
|
|
183
|
-
const pkgJson = JSON.parse(fs.readFileSync(path.join(info.pkgDir, "package.json"), "utf-8"));
|
|
184
|
-
|
|
185
|
-
// dist/package.json
|
|
186
|
-
logger.debug("GEN package.json...");
|
|
187
|
-
const distPkgJson: Record<string, unknown> = {
|
|
188
|
-
name: pkgJson.name,
|
|
189
|
-
version: pkgJson.version,
|
|
190
|
-
type: pkgJson.type,
|
|
191
|
-
};
|
|
192
|
-
if (externals.length > 0) {
|
|
193
|
-
const deps: Record<string, string> = {};
|
|
194
|
-
for (const ext of externals) {
|
|
195
|
-
deps[ext] = "*";
|
|
196
|
-
}
|
|
197
|
-
distPkgJson["dependencies"] = deps;
|
|
198
|
-
}
|
|
199
|
-
if (info.packageManager === "volta") {
|
|
200
|
-
const nodeVersion = execaSync("node", ["-v"]).stdout.trim();
|
|
201
|
-
distPkgJson["volta"] = { node: nodeVersion };
|
|
202
|
-
}
|
|
203
|
-
fs.writeFileSync(path.join(distDir, "package.json"), JSON.stringify(distPkgJson, undefined, 2));
|
|
204
|
-
|
|
205
|
-
// dist/mise.toml (packageManager === "mise"일 때만)
|
|
206
|
-
if (info.packageManager === "mise") {
|
|
207
|
-
logger.debug("GEN mise.toml...");
|
|
208
|
-
const rootMiseTomlPath = path.join(info.cwd, "mise.toml");
|
|
209
|
-
let nodeVersion = "20";
|
|
210
|
-
if (fs.existsSync(rootMiseTomlPath)) {
|
|
211
|
-
const miseContent = fs.readFileSync(rootMiseTomlPath, "utf-8");
|
|
212
|
-
const match = /node\s*=\s*"([^"]+)"/.exec(miseContent);
|
|
213
|
-
if (match != null) {
|
|
214
|
-
nodeVersion = match[1];
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
fs.writeFileSync(path.join(distDir, "mise.toml"), `[tools]\nnode = "${nodeVersion}"\n`);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// dist/openssl.cnf
|
|
221
|
-
logger.debug("GEN openssl.cnf...");
|
|
222
|
-
fs.writeFileSync(
|
|
223
|
-
path.join(distDir, "openssl.cnf"),
|
|
224
|
-
[
|
|
225
|
-
"nodejs_conf = openssl_init",
|
|
226
|
-
"",
|
|
227
|
-
"[openssl_init]",
|
|
228
|
-
"providers = provider_sect",
|
|
229
|
-
"ssl_conf = ssl_sect",
|
|
230
|
-
"",
|
|
231
|
-
"[provider_sect]",
|
|
232
|
-
"default = default_sect",
|
|
233
|
-
"legacy = legacy_sect",
|
|
234
|
-
"",
|
|
235
|
-
"[default_sect]",
|
|
236
|
-
"activate = 1",
|
|
237
|
-
"",
|
|
238
|
-
"[legacy_sect]",
|
|
239
|
-
"activate = 1",
|
|
240
|
-
"",
|
|
241
|
-
"[ssl_sect]",
|
|
242
|
-
"system_default = system_default_sect",
|
|
243
|
-
"",
|
|
244
|
-
"[system_default_sect]",
|
|
245
|
-
"Options = UnsafeLegacyRenegotiation",
|
|
246
|
-
].join("\n"),
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// dist/pm2.config.cjs (pm2
|
|
250
|
-
if (info.pm2 != null) {
|
|
251
|
-
logger.debug("GEN pm2.config.cjs...");
|
|
252
|
-
|
|
253
|
-
const pm2Name = info.pm2.name ?? pkgJson.name.replace(/@/g, "").replace(/[/\\]/g, "-");
|
|
254
|
-
const ignoreWatch = JSON.stringify([
|
|
255
|
-
"node_modules",
|
|
256
|
-
"www",
|
|
257
|
-
...(info.pm2.ignoreWatchPaths ?? []),
|
|
258
|
-
]);
|
|
259
|
-
const envObj: Record<string, string> = {
|
|
260
|
-
NODE_ENV: "production",
|
|
261
|
-
TZ: "Asia/Seoul",
|
|
262
|
-
...(info.env ?? {}),
|
|
263
|
-
};
|
|
264
|
-
const envStr = JSON.stringify(envObj, undefined, 4);
|
|
265
|
-
|
|
266
|
-
const interpreterLine =
|
|
267
|
-
info.packageManager === "volta"
|
|
268
|
-
? ""
|
|
269
|
-
: ` interpreter: cp.execSync("mise which node").toString().trim(),\n`;
|
|
270
|
-
|
|
271
|
-
const pm2Config = [
|
|
272
|
-
...(info.packageManager !== "volta" ? [`const cp = require("child_process");`, ``] : []),
|
|
273
|
-
`module.exports = {`,
|
|
274
|
-
` name: ${JSON.stringify(pm2Name)},`,
|
|
275
|
-
` script: "main.js",`,
|
|
276
|
-
` watch: true,`,
|
|
277
|
-
` watch_delay: 2000,`,
|
|
278
|
-
` ignore_watch: ${ignoreWatch},`,
|
|
279
|
-
interpreterLine.trimEnd(),
|
|
280
|
-
` interpreter_args: "--openssl-config=openssl.cnf",`,
|
|
281
|
-
` env: ${envStr.replace(/\n/g, "\n ")},`,
|
|
282
|
-
` arrayProcess: "concat",`,
|
|
283
|
-
` useDelTargetNull: true,`,
|
|
284
|
-
`};`,
|
|
285
|
-
]
|
|
286
|
-
.filter((line) => line !== "")
|
|
287
|
-
.join("\n");
|
|
288
|
-
|
|
289
|
-
fs.writeFileSync(path.join(distDir, "pm2.config.cjs"), pm2Config);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
registerCleanupHandlers(cleanup, logger);
|
|
297
|
-
|
|
298
|
-
//#endregion
|
|
299
|
-
|
|
300
|
-
//#region Worker
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
*
|
|
304
|
-
*/
|
|
305
|
-
async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
306
|
-
const mainJsPath = path.join(info.pkgDir, "dist", "main.js");
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
// tsconfig
|
|
310
|
-
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
311
|
-
const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
|
|
312
|
-
|
|
313
|
-
//
|
|
314
|
-
const compilerOptions = await getCompilerOptionsForPackage(
|
|
315
|
-
parsedConfig.options,
|
|
316
|
-
"node",
|
|
317
|
-
info.pkgDir,
|
|
318
|
-
);
|
|
319
|
-
|
|
320
|
-
//
|
|
321
|
-
const external = collectAllExternals(info.pkgDir, info.externals);
|
|
322
|
-
|
|
323
|
-
// esbuild
|
|
324
|
-
const esbuildOptions = createServerEsbuildOptions({
|
|
325
|
-
pkgDir: info.pkgDir,
|
|
326
|
-
entryPoints,
|
|
327
|
-
compilerOptions,
|
|
328
|
-
env: info.env,
|
|
329
|
-
external,
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
const result = await esbuild.build(esbuildOptions);
|
|
333
|
-
|
|
334
|
-
// Generate .config.json
|
|
335
|
-
const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
|
|
336
|
-
fs.writeFileSync(confDistPath, JSON.stringify(info.configs ?? {}, undefined, 2));
|
|
337
|
-
|
|
338
|
-
// Copy public/ to dist/ (production build: no public-dev)
|
|
339
|
-
await copyPublicFiles(info.pkgDir, false);
|
|
340
|
-
|
|
341
|
-
// Generate production files (package.json, mise.toml, openssl.cnf, pm2.config.cjs)
|
|
342
|
-
generateProductionFiles(info, external);
|
|
343
|
-
|
|
344
|
-
const errors = result.errors.map((e) => e.text);
|
|
345
|
-
const warnings = result.warnings.map((w) => w.text);
|
|
346
|
-
return {
|
|
347
|
-
success: result.errors.length === 0,
|
|
348
|
-
mainJsPath,
|
|
349
|
-
errors: errors.length > 0 ? errors : undefined,
|
|
350
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
351
|
-
};
|
|
352
|
-
} catch (err) {
|
|
353
|
-
return {
|
|
354
|
-
success: false,
|
|
355
|
-
mainJsPath,
|
|
356
|
-
errors: [errorMessage(err)],
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const guardStartWatch = createOnceGuard("startWatch");
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* esbuild context
|
|
365
|
-
*/
|
|
366
|
-
async function createAndBuildContext(
|
|
367
|
-
info: ServerWatchInfo,
|
|
368
|
-
isFirstBuild: boolean,
|
|
369
|
-
resolveFirstBuild?: () => void,
|
|
370
|
-
): Promise<esbuild.BuildContext> {
|
|
371
|
-
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
372
|
-
const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
|
|
373
|
-
const compilerOptions = await getCompilerOptionsForPackage(
|
|
374
|
-
parsedConfig.options,
|
|
375
|
-
"node",
|
|
376
|
-
info.pkgDir,
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
const mainJsPath = path.join(info.pkgDir, "dist", "main.js");
|
|
380
|
-
const external = collectAllExternals(info.pkgDir, info.externals);
|
|
381
|
-
const baseOptions = createServerEsbuildOptions({
|
|
382
|
-
pkgDir: info.pkgDir,
|
|
383
|
-
entryPoints,
|
|
384
|
-
compilerOptions,
|
|
385
|
-
env: info.env,
|
|
386
|
-
external,
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
let isBuildFirstTime = isFirstBuild;
|
|
390
|
-
|
|
391
|
-
const context = await esbuild.context({
|
|
392
|
-
...baseOptions,
|
|
393
|
-
metafile: true,
|
|
394
|
-
write: false,
|
|
395
|
-
plugins: [
|
|
396
|
-
{
|
|
397
|
-
name: "watch-notify",
|
|
398
|
-
setup(pluginBuild) {
|
|
399
|
-
pluginBuild.onStart(() => {
|
|
400
|
-
sender.send("buildStart", {});
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
pluginBuild.onEnd(async (result) => {
|
|
404
|
-
// metafile
|
|
405
|
-
if (result.metafile != null) {
|
|
406
|
-
lastMetafile = result.metafile;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const errors = result.errors.map((e) => e.text);
|
|
410
|
-
const warnings = result.warnings.map((w) => w.text);
|
|
411
|
-
const success = result.errors.length === 0;
|
|
412
|
-
|
|
413
|
-
// output
|
|
414
|
-
let hasOutputChange = false;
|
|
415
|
-
if (success && result.outputFiles != null) {
|
|
416
|
-
hasOutputChange = await writeChangedOutputFiles(result.outputFiles);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (isBuildFirstTime && success) {
|
|
420
|
-
const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
|
|
421
|
-
fs.writeFileSync(confDistPath, JSON.stringify(info.configs ?? {}, undefined, 2));
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
//
|
|
425
|
-
if (isBuildFirstTime || hasOutputChange || !success) {
|
|
426
|
-
sender.send("build", {
|
|
427
|
-
success,
|
|
428
|
-
mainJsPath,
|
|
429
|
-
errors: errors.length > 0 ? errors : undefined,
|
|
430
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
431
|
-
});
|
|
432
|
-
} else {
|
|
433
|
-
logger.debug("output
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (isBuildFirstTime) {
|
|
437
|
-
isBuildFirstTime = false;
|
|
438
|
-
resolveFirstBuild?.();
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
|
-
],
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
await context.rebuild();
|
|
447
|
-
|
|
448
|
-
return context;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* watch
|
|
453
|
-
* @remarks
|
|
454
|
-
* @throws
|
|
455
|
-
*/
|
|
456
|
-
async function startWatch(info: ServerWatchInfo): Promise<void> {
|
|
457
|
-
guardStartWatch();
|
|
458
|
-
|
|
459
|
-
try {
|
|
460
|
-
//
|
|
461
|
-
let resolveFirstBuild!: () => void;
|
|
462
|
-
const firstBuildPromise = new Promise<void>((resolve) => {
|
|
463
|
-
resolveFirstBuild = resolve;
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
//
|
|
467
|
-
esbuildContext = await createAndBuildContext(info, true, resolveFirstBuild);
|
|
468
|
-
|
|
469
|
-
//
|
|
470
|
-
await firstBuildPromise;
|
|
471
|
-
|
|
472
|
-
// Watch public/ and public-dev/ (dev mode includes public-dev)
|
|
473
|
-
publicWatcher = await watchPublicFiles(info.pkgDir, true);
|
|
474
|
-
|
|
475
|
-
//
|
|
476
|
-
const { workspaceDeps, replaceDeps } = collectDeps(info.pkgDir, info.cwd, info.replaceDeps);
|
|
477
|
-
|
|
478
|
-
const watchPaths: string[] = [];
|
|
479
|
-
|
|
480
|
-
// 1)
|
|
481
|
-
const watchDirs = [
|
|
482
|
-
info.pkgDir,
|
|
483
|
-
...workspaceDeps.map((d) => path.join(info.cwd, "packages", d)),
|
|
484
|
-
];
|
|
485
|
-
for (const dir of watchDirs) {
|
|
486
|
-
watchPaths.push(path.join(dir, "src", "**", "*"));
|
|
487
|
-
watchPaths.push(path.join(dir, "*.{ts,js,css}"));
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// 2)
|
|
491
|
-
for (const pkg of replaceDeps) {
|
|
492
|
-
watchPaths.push(path.join(info.cwd, "node_modules", ...pkg.split("/"), "dist", "**", "*.js"));
|
|
493
|
-
watchPaths.push(
|
|
494
|
-
path.join(info.pkgDir, "node_modules", ...pkg.split("/"), "dist", "**", "*.js"),
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// FsWatcher
|
|
499
|
-
srcWatcher = await FsWatcher.watch(watchPaths);
|
|
500
|
-
|
|
501
|
-
//
|
|
502
|
-
srcWatcher.onChange({ delay: 300 }, async (changes) => {
|
|
503
|
-
try {
|
|
504
|
-
//
|
|
505
|
-
const hasFileAddOrRemove = changes.some((c) => c.event === "add" || c.event === "unlink");
|
|
506
|
-
|
|
507
|
-
if (hasFileAddOrRemove) {
|
|
508
|
-
logger.debug("
|
|
509
|
-
|
|
510
|
-
const oldContext = esbuildContext;
|
|
511
|
-
esbuildContext = await createAndBuildContext(info, false);
|
|
512
|
-
|
|
513
|
-
if (oldContext != null) {
|
|
514
|
-
await oldContext.dispose();
|
|
515
|
-
}
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
//
|
|
520
|
-
if (esbuildContext == null) return;
|
|
521
|
-
|
|
522
|
-
// metafile
|
|
523
|
-
if (lastMetafile == null) {
|
|
524
|
-
await esbuildContext.rebuild();
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// metafile.inputs
|
|
529
|
-
const metafileAbsPaths = new Set(
|
|
530
|
-
Object.keys(lastMetafile.inputs).map((key) => pathNorm(info.cwd, key)),
|
|
531
|
-
);
|
|
532
|
-
|
|
533
|
-
const hasRelevantChange = changes.some((c) => metafileAbsPaths.has(c.path));
|
|
534
|
-
|
|
535
|
-
if (hasRelevantChange) {
|
|
536
|
-
await esbuildContext.rebuild();
|
|
537
|
-
} else {
|
|
538
|
-
logger.debug("
|
|
539
|
-
}
|
|
540
|
-
} catch (err) {
|
|
541
|
-
sender.send("error", {
|
|
542
|
-
message: errorMessage(err),
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
});
|
|
546
|
-
} catch (err) {
|
|
547
|
-
sender.send("error", {
|
|
548
|
-
message: errorMessage(err),
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/**
|
|
554
|
-
* watch
|
|
555
|
-
* @remarks esbuild context
|
|
556
|
-
*/
|
|
557
|
-
async function stopWatch(): Promise<void> {
|
|
558
|
-
await cleanup();
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const sender = createWorker<
|
|
562
|
-
{ build: typeof build; startWatch: typeof startWatch; stopWatch: typeof stopWatch },
|
|
563
|
-
ServerWorkerEvents
|
|
564
|
-
>({
|
|
565
|
-
build,
|
|
566
|
-
startWatch,
|
|
567
|
-
stopWatch,
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
export default sender;
|
|
571
|
-
|
|
572
|
-
//#endregion
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { execaSync } from "execa";
|
|
4
|
+
import esbuild from "esbuild";
|
|
5
|
+
import { createWorker, FsWatcher, pathNorm } from "@simplysm/core-node";
|
|
6
|
+
import { errorMessage } from "@simplysm/core-common";
|
|
7
|
+
import { consola } from "consola";
|
|
8
|
+
import {
|
|
9
|
+
parseRootTsconfig,
|
|
10
|
+
getPackageSourceFiles,
|
|
11
|
+
getCompilerOptionsForPackage,
|
|
12
|
+
} from "../utils/tsconfig";
|
|
13
|
+
import {
|
|
14
|
+
createServerEsbuildOptions,
|
|
15
|
+
collectUninstalledOptionalPeerDeps,
|
|
16
|
+
collectNativeModuleExternals,
|
|
17
|
+
writeChangedOutputFiles,
|
|
18
|
+
} from "../utils/esbuild-config";
|
|
19
|
+
import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
|
|
20
|
+
import { collectDeps } from "../utils/package-utils";
|
|
21
|
+
import { copyPublicFiles, watchPublicFiles } from "../utils/copy-public";
|
|
22
|
+
|
|
23
|
+
//#region Types
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Server build information (for one-time build)
|
|
27
|
+
*/
|
|
28
|
+
export interface ServerBuildInfo {
|
|
29
|
+
name: string;
|
|
30
|
+
cwd: string;
|
|
31
|
+
pkgDir: string;
|
|
32
|
+
/** Environment variables to substitute during build */
|
|
33
|
+
env?: Record<string, string>;
|
|
34
|
+
/** Runtime configuration (recorded in dist/.config.json) */
|
|
35
|
+
configs?: Record<string, unknown>;
|
|
36
|
+
/** External modules manually specified in sd.config.ts */
|
|
37
|
+
externals?: string[];
|
|
38
|
+
/** PM2 configuration (generates dist/pm2.config.cjs when specified) */
|
|
39
|
+
pm2?: {
|
|
40
|
+
name?: string;
|
|
41
|
+
ignoreWatchPaths?: string[];
|
|
42
|
+
};
|
|
43
|
+
/** Package manager to use (affects mise.toml or volta settings generation) */
|
|
44
|
+
packageManager?: "volta" | "mise";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Server build result
|
|
49
|
+
*/
|
|
50
|
+
export interface ServerBuildResult {
|
|
51
|
+
success: boolean;
|
|
52
|
+
mainJsPath: string;
|
|
53
|
+
errors?: string[];
|
|
54
|
+
warnings?: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Server watch information
|
|
59
|
+
*/
|
|
60
|
+
export interface ServerWatchInfo {
|
|
61
|
+
name: string;
|
|
62
|
+
cwd: string;
|
|
63
|
+
pkgDir: string;
|
|
64
|
+
/** Environment variables to substitute during build */
|
|
65
|
+
env?: Record<string, string>;
|
|
66
|
+
/** Runtime configuration (recorded in dist/.config.json) */
|
|
67
|
+
configs?: Record<string, unknown>;
|
|
68
|
+
/** External modules manually specified in sd.config.ts */
|
|
69
|
+
externals?: string[];
|
|
70
|
+
/** replaceDeps configuration from sd.config.ts */
|
|
71
|
+
replaceDeps?: Record<string, string>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Build event
|
|
76
|
+
*/
|
|
77
|
+
export interface ServerBuildEvent {
|
|
78
|
+
success: boolean;
|
|
79
|
+
mainJsPath: string;
|
|
80
|
+
errors?: string[];
|
|
81
|
+
warnings?: string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Error event
|
|
86
|
+
*/
|
|
87
|
+
export interface ServerErrorEvent {
|
|
88
|
+
message: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Worker event types
|
|
93
|
+
*/
|
|
94
|
+
export interface ServerWorkerEvents extends Record<string, unknown> {
|
|
95
|
+
buildStart: Record<string, never>;
|
|
96
|
+
build: ServerBuildEvent;
|
|
97
|
+
error: ServerErrorEvent;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
|
|
102
|
+
//#region Resource Management
|
|
103
|
+
|
|
104
|
+
const logger = consola.withTag("sd:cli:server:worker");
|
|
105
|
+
|
|
106
|
+
/** esbuild build context (to be cleaned up) */
|
|
107
|
+
let esbuildContext: esbuild.BuildContext | undefined;
|
|
108
|
+
|
|
109
|
+
/** Last build metafile (for filtering changed files on rebuild) */
|
|
110
|
+
let lastMetafile: esbuild.Metafile | undefined;
|
|
111
|
+
|
|
112
|
+
/** Public files watcher (to be cleaned up) */
|
|
113
|
+
let publicWatcher: FsWatcher | undefined;
|
|
114
|
+
|
|
115
|
+
/** Source + scope packages watcher (to be cleaned up) */
|
|
116
|
+
let srcWatcher: FsWatcher | undefined;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Clean up resources
|
|
120
|
+
*/
|
|
121
|
+
async function cleanup(): Promise<void> {
|
|
122
|
+
// Capture global variables to temporary variables and initialize
|
|
123
|
+
// (other calls can modify global variables while Promise.all is waiting)
|
|
124
|
+
const contextToDispose = esbuildContext;
|
|
125
|
+
esbuildContext = undefined;
|
|
126
|
+
lastMetafile = undefined;
|
|
127
|
+
|
|
128
|
+
const watcherToClose = publicWatcher;
|
|
129
|
+
publicWatcher = undefined;
|
|
130
|
+
|
|
131
|
+
const srcWatcherToClose = srcWatcher;
|
|
132
|
+
srcWatcher = undefined;
|
|
133
|
+
|
|
134
|
+
if (contextToDispose != null) {
|
|
135
|
+
await contextToDispose.dispose();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (watcherToClose != null) {
|
|
139
|
+
await watcherToClose.close();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (srcWatcherToClose != null) {
|
|
143
|
+
await srcWatcherToClose.close();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Collect external modules from three sources and merge them.
|
|
149
|
+
* 1. Uninstalled optional peer dependencies
|
|
150
|
+
* 2. Native modules from binding.gyp
|
|
151
|
+
* 3. Manually specified in sd.config.ts
|
|
152
|
+
*/
|
|
153
|
+
function collectAllExternals(pkgDir: string, manualExternals?: string[]): string[] {
|
|
154
|
+
const optionalPeerDeps = collectUninstalledOptionalPeerDeps(pkgDir);
|
|
155
|
+
const nativeModules = collectNativeModuleExternals(pkgDir);
|
|
156
|
+
const manual = manualExternals ?? [];
|
|
157
|
+
|
|
158
|
+
const merged = [...new Set([...optionalPeerDeps, ...nativeModules, ...manual])];
|
|
159
|
+
|
|
160
|
+
if (optionalPeerDeps.length > 0) {
|
|
161
|
+
logger.debug("Uninstalled optional peer deps (external):", optionalPeerDeps);
|
|
162
|
+
}
|
|
163
|
+
if (nativeModules.length > 0) {
|
|
164
|
+
logger.debug("Native modules (external):", nativeModules);
|
|
165
|
+
}
|
|
166
|
+
if (manual.length > 0) {
|
|
167
|
+
logger.debug("Manually specified (external):", manual);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return merged;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Generate files for production deployment (called only in one-time build)
|
|
175
|
+
*
|
|
176
|
+
* - dist/package.json: include external modules as dependencies (add volta field if volta is used)
|
|
177
|
+
* - dist/mise.toml: specify Node version (only when packageManager === "mise")
|
|
178
|
+
* - dist/openssl.cnf: 레거시 OpenSSL 프로바이더 활성화
|
|
179
|
+
* - dist/pm2.config.cjs: PM2 프로세스 설정 (pm2 옵션이 있을 때만)
|
|
180
|
+
*/
|
|
181
|
+
function generateProductionFiles(info: ServerBuildInfo, externals: string[]): void {
|
|
182
|
+
const distDir = path.join(info.pkgDir, "dist");
|
|
183
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(info.pkgDir, "package.json"), "utf-8"));
|
|
184
|
+
|
|
185
|
+
// dist/package.json
|
|
186
|
+
logger.debug("GEN package.json...");
|
|
187
|
+
const distPkgJson: Record<string, unknown> = {
|
|
188
|
+
name: pkgJson.name,
|
|
189
|
+
version: pkgJson.version,
|
|
190
|
+
type: pkgJson.type,
|
|
191
|
+
};
|
|
192
|
+
if (externals.length > 0) {
|
|
193
|
+
const deps: Record<string, string> = {};
|
|
194
|
+
for (const ext of externals) {
|
|
195
|
+
deps[ext] = "*";
|
|
196
|
+
}
|
|
197
|
+
distPkgJson["dependencies"] = deps;
|
|
198
|
+
}
|
|
199
|
+
if (info.packageManager === "volta") {
|
|
200
|
+
const nodeVersion = execaSync("node", ["-v"]).stdout.trim();
|
|
201
|
+
distPkgJson["volta"] = { node: nodeVersion };
|
|
202
|
+
}
|
|
203
|
+
fs.writeFileSync(path.join(distDir, "package.json"), JSON.stringify(distPkgJson, undefined, 2));
|
|
204
|
+
|
|
205
|
+
// dist/mise.toml (packageManager === "mise"일 때만)
|
|
206
|
+
if (info.packageManager === "mise") {
|
|
207
|
+
logger.debug("GEN mise.toml...");
|
|
208
|
+
const rootMiseTomlPath = path.join(info.cwd, "mise.toml");
|
|
209
|
+
let nodeVersion = "20";
|
|
210
|
+
if (fs.existsSync(rootMiseTomlPath)) {
|
|
211
|
+
const miseContent = fs.readFileSync(rootMiseTomlPath, "utf-8");
|
|
212
|
+
const match = /node\s*=\s*"([^"]+)"/.exec(miseContent);
|
|
213
|
+
if (match != null) {
|
|
214
|
+
nodeVersion = match[1];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
fs.writeFileSync(path.join(distDir, "mise.toml"), `[tools]\nnode = "${nodeVersion}"\n`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// dist/openssl.cnf
|
|
221
|
+
logger.debug("GEN openssl.cnf...");
|
|
222
|
+
fs.writeFileSync(
|
|
223
|
+
path.join(distDir, "openssl.cnf"),
|
|
224
|
+
[
|
|
225
|
+
"nodejs_conf = openssl_init",
|
|
226
|
+
"",
|
|
227
|
+
"[openssl_init]",
|
|
228
|
+
"providers = provider_sect",
|
|
229
|
+
"ssl_conf = ssl_sect",
|
|
230
|
+
"",
|
|
231
|
+
"[provider_sect]",
|
|
232
|
+
"default = default_sect",
|
|
233
|
+
"legacy = legacy_sect",
|
|
234
|
+
"",
|
|
235
|
+
"[default_sect]",
|
|
236
|
+
"activate = 1",
|
|
237
|
+
"",
|
|
238
|
+
"[legacy_sect]",
|
|
239
|
+
"activate = 1",
|
|
240
|
+
"",
|
|
241
|
+
"[ssl_sect]",
|
|
242
|
+
"system_default = system_default_sect",
|
|
243
|
+
"",
|
|
244
|
+
"[system_default_sect]",
|
|
245
|
+
"Options = UnsafeLegacyRenegotiation",
|
|
246
|
+
].join("\n"),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// dist/pm2.config.cjs (only when pm2 option is present)
|
|
250
|
+
if (info.pm2 != null) {
|
|
251
|
+
logger.debug("GEN pm2.config.cjs...");
|
|
252
|
+
|
|
253
|
+
const pm2Name = info.pm2.name ?? pkgJson.name.replace(/@/g, "").replace(/[/\\]/g, "-");
|
|
254
|
+
const ignoreWatch = JSON.stringify([
|
|
255
|
+
"node_modules",
|
|
256
|
+
"www",
|
|
257
|
+
...(info.pm2.ignoreWatchPaths ?? []),
|
|
258
|
+
]);
|
|
259
|
+
const envObj: Record<string, string> = {
|
|
260
|
+
NODE_ENV: "production",
|
|
261
|
+
TZ: "Asia/Seoul",
|
|
262
|
+
...(info.env ?? {}),
|
|
263
|
+
};
|
|
264
|
+
const envStr = JSON.stringify(envObj, undefined, 4);
|
|
265
|
+
|
|
266
|
+
const interpreterLine =
|
|
267
|
+
info.packageManager === "volta"
|
|
268
|
+
? ""
|
|
269
|
+
: ` interpreter: cp.execSync("mise which node").toString().trim(),\n`;
|
|
270
|
+
|
|
271
|
+
const pm2Config = [
|
|
272
|
+
...(info.packageManager !== "volta" ? [`const cp = require("child_process");`, ``] : []),
|
|
273
|
+
`module.exports = {`,
|
|
274
|
+
` name: ${JSON.stringify(pm2Name)},`,
|
|
275
|
+
` script: "main.js",`,
|
|
276
|
+
` watch: true,`,
|
|
277
|
+
` watch_delay: 2000,`,
|
|
278
|
+
` ignore_watch: ${ignoreWatch},`,
|
|
279
|
+
interpreterLine.trimEnd(),
|
|
280
|
+
` interpreter_args: "--openssl-config=openssl.cnf",`,
|
|
281
|
+
` env: ${envStr.replace(/\n/g, "\n ")},`,
|
|
282
|
+
` arrayProcess: "concat",`,
|
|
283
|
+
` useDelTargetNull: true,`,
|
|
284
|
+
`};`,
|
|
285
|
+
]
|
|
286
|
+
.filter((line) => line !== "")
|
|
287
|
+
.join("\n");
|
|
288
|
+
|
|
289
|
+
fs.writeFileSync(path.join(distDir, "pm2.config.cjs"), pm2Config);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Clean up resources before process termination (SIGTERM/SIGINT)
|
|
294
|
+
// Note: worker.terminate() does not call these handlers and terminates immediately.
|
|
295
|
+
// However, normal shutdown in watch mode is handled via SIGINT/SIGTERM from the main process, so this is fine.
|
|
296
|
+
registerCleanupHandlers(cleanup, logger);
|
|
297
|
+
|
|
298
|
+
//#endregion
|
|
299
|
+
|
|
300
|
+
//#region Worker
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* One-time build
|
|
304
|
+
*/
|
|
305
|
+
async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
306
|
+
const mainJsPath = path.join(info.pkgDir, "dist", "main.js");
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
// Parse tsconfig
|
|
310
|
+
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
311
|
+
const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
|
|
312
|
+
|
|
313
|
+
// Server target is node environment
|
|
314
|
+
const compilerOptions = await getCompilerOptionsForPackage(
|
|
315
|
+
parsedConfig.options,
|
|
316
|
+
"node",
|
|
317
|
+
info.pkgDir,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// Collect all externals (optional peer deps + native modules + manual)
|
|
321
|
+
const external = collectAllExternals(info.pkgDir, info.externals);
|
|
322
|
+
|
|
323
|
+
// One-time esbuild
|
|
324
|
+
const esbuildOptions = createServerEsbuildOptions({
|
|
325
|
+
pkgDir: info.pkgDir,
|
|
326
|
+
entryPoints,
|
|
327
|
+
compilerOptions,
|
|
328
|
+
env: info.env,
|
|
329
|
+
external,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const result = await esbuild.build(esbuildOptions);
|
|
333
|
+
|
|
334
|
+
// Generate .config.json
|
|
335
|
+
const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
|
|
336
|
+
fs.writeFileSync(confDistPath, JSON.stringify(info.configs ?? {}, undefined, 2));
|
|
337
|
+
|
|
338
|
+
// Copy public/ to dist/ (production build: no public-dev)
|
|
339
|
+
await copyPublicFiles(info.pkgDir, false);
|
|
340
|
+
|
|
341
|
+
// Generate production files (package.json, mise.toml, openssl.cnf, pm2.config.cjs)
|
|
342
|
+
generateProductionFiles(info, external);
|
|
343
|
+
|
|
344
|
+
const errors = result.errors.map((e) => e.text);
|
|
345
|
+
const warnings = result.warnings.map((w) => w.text);
|
|
346
|
+
return {
|
|
347
|
+
success: result.errors.length === 0,
|
|
348
|
+
mainJsPath,
|
|
349
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
350
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
351
|
+
};
|
|
352
|
+
} catch (err) {
|
|
353
|
+
return {
|
|
354
|
+
success: false,
|
|
355
|
+
mainJsPath,
|
|
356
|
+
errors: [errorMessage(err)],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const guardStartWatch = createOnceGuard("startWatch");
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Create esbuild context and perform initial build
|
|
365
|
+
*/
|
|
366
|
+
async function createAndBuildContext(
|
|
367
|
+
info: ServerWatchInfo,
|
|
368
|
+
isFirstBuild: boolean,
|
|
369
|
+
resolveFirstBuild?: () => void,
|
|
370
|
+
): Promise<esbuild.BuildContext> {
|
|
371
|
+
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
372
|
+
const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
|
|
373
|
+
const compilerOptions = await getCompilerOptionsForPackage(
|
|
374
|
+
parsedConfig.options,
|
|
375
|
+
"node",
|
|
376
|
+
info.pkgDir,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const mainJsPath = path.join(info.pkgDir, "dist", "main.js");
|
|
380
|
+
const external = collectAllExternals(info.pkgDir, info.externals);
|
|
381
|
+
const baseOptions = createServerEsbuildOptions({
|
|
382
|
+
pkgDir: info.pkgDir,
|
|
383
|
+
entryPoints,
|
|
384
|
+
compilerOptions,
|
|
385
|
+
env: info.env,
|
|
386
|
+
external,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
let isBuildFirstTime = isFirstBuild;
|
|
390
|
+
|
|
391
|
+
const context = await esbuild.context({
|
|
392
|
+
...baseOptions,
|
|
393
|
+
metafile: true,
|
|
394
|
+
write: false,
|
|
395
|
+
plugins: [
|
|
396
|
+
{
|
|
397
|
+
name: "watch-notify",
|
|
398
|
+
setup(pluginBuild) {
|
|
399
|
+
pluginBuild.onStart(() => {
|
|
400
|
+
sender.send("buildStart", {});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
pluginBuild.onEnd(async (result) => {
|
|
404
|
+
// Save metafile
|
|
405
|
+
if (result.metafile != null) {
|
|
406
|
+
lastMetafile = result.metafile;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const errors = result.errors.map((e) => e.text);
|
|
410
|
+
const warnings = result.warnings.map((w) => w.text);
|
|
411
|
+
const success = result.errors.length === 0;
|
|
412
|
+
|
|
413
|
+
// Write output files and check for changes
|
|
414
|
+
let hasOutputChange = false;
|
|
415
|
+
if (success && result.outputFiles != null) {
|
|
416
|
+
hasOutputChange = await writeChangedOutputFiles(result.outputFiles);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (isBuildFirstTime && success) {
|
|
420
|
+
const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
|
|
421
|
+
fs.writeFileSync(confDistPath, JSON.stringify(info.configs ?? {}, undefined, 2));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Only emit build event on first build, output change, or error
|
|
425
|
+
if (isBuildFirstTime || hasOutputChange || !success) {
|
|
426
|
+
sender.send("build", {
|
|
427
|
+
success,
|
|
428
|
+
mainJsPath,
|
|
429
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
430
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
431
|
+
});
|
|
432
|
+
} else {
|
|
433
|
+
logger.debug("No output changes, skipping server restart");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (isBuildFirstTime) {
|
|
437
|
+
isBuildFirstTime = false;
|
|
438
|
+
resolveFirstBuild?.();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
await context.rebuild();
|
|
447
|
+
|
|
448
|
+
return context;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Start watch
|
|
453
|
+
* @remarks This function should be called only once per Worker.
|
|
454
|
+
* @throws If watch has already been started
|
|
455
|
+
*/
|
|
456
|
+
async function startWatch(info: ServerWatchInfo): Promise<void> {
|
|
457
|
+
guardStartWatch();
|
|
458
|
+
|
|
459
|
+
try {
|
|
460
|
+
// Promise to wait for first build completion
|
|
461
|
+
let resolveFirstBuild!: () => void;
|
|
462
|
+
const firstBuildPromise = new Promise<void>((resolve) => {
|
|
463
|
+
resolveFirstBuild = resolve;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Create initial esbuild context and build
|
|
467
|
+
esbuildContext = await createAndBuildContext(info, true, resolveFirstBuild);
|
|
468
|
+
|
|
469
|
+
// Wait for first build completion
|
|
470
|
+
await firstBuildPromise;
|
|
471
|
+
|
|
472
|
+
// Watch public/ and public-dev/ (dev mode includes public-dev)
|
|
473
|
+
publicWatcher = await watchPublicFiles(info.pkgDir, true);
|
|
474
|
+
|
|
475
|
+
// Collect watch paths based on dependencies
|
|
476
|
+
const { workspaceDeps, replaceDeps } = collectDeps(info.pkgDir, info.cwd, info.replaceDeps);
|
|
477
|
+
|
|
478
|
+
const watchPaths: string[] = [];
|
|
479
|
+
|
|
480
|
+
// 1) Server package itself + workspace dependency packages source
|
|
481
|
+
const watchDirs = [
|
|
482
|
+
info.pkgDir,
|
|
483
|
+
...workspaceDeps.map((d) => path.join(info.cwd, "packages", d)),
|
|
484
|
+
];
|
|
485
|
+
for (const dir of watchDirs) {
|
|
486
|
+
watchPaths.push(path.join(dir, "src", "**", "*"));
|
|
487
|
+
watchPaths.push(path.join(dir, "*.{ts,js,css}"));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 2) ReplaceDeps dependency packages dist (root + package node_modules)
|
|
491
|
+
for (const pkg of replaceDeps) {
|
|
492
|
+
watchPaths.push(path.join(info.cwd, "node_modules", ...pkg.split("/"), "dist", "**", "*.js"));
|
|
493
|
+
watchPaths.push(
|
|
494
|
+
path.join(info.pkgDir, "node_modules", ...pkg.split("/"), "dist", "**", "*.js"),
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Start FsWatcher
|
|
499
|
+
srcWatcher = await FsWatcher.watch(watchPaths);
|
|
500
|
+
|
|
501
|
+
// Handle file changes
|
|
502
|
+
srcWatcher.onChange({ delay: 300 }, async (changes) => {
|
|
503
|
+
try {
|
|
504
|
+
// If files are added/removed, recreate context (import graph may change)
|
|
505
|
+
const hasFileAddOrRemove = changes.some((c) => c.event === "add" || c.event === "unlink");
|
|
506
|
+
|
|
507
|
+
if (hasFileAddOrRemove) {
|
|
508
|
+
logger.debug("File add/remove detected, recreating context");
|
|
509
|
+
|
|
510
|
+
const oldContext = esbuildContext;
|
|
511
|
+
esbuildContext = await createAndBuildContext(info, false);
|
|
512
|
+
|
|
513
|
+
if (oldContext != null) {
|
|
514
|
+
await oldContext.dispose();
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Only file changes: filter by metafile
|
|
520
|
+
if (esbuildContext == null) return;
|
|
521
|
+
|
|
522
|
+
// If no metafile (before first build), always rebuild
|
|
523
|
+
if (lastMetafile == null) {
|
|
524
|
+
await esbuildContext.rebuild();
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Convert metafile.inputs keys to absolute paths (NormPath) for comparison
|
|
529
|
+
const metafileAbsPaths = new Set(
|
|
530
|
+
Object.keys(lastMetafile.inputs).map((key) => pathNorm(info.cwd, key)),
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
const hasRelevantChange = changes.some((c) => metafileAbsPaths.has(c.path));
|
|
534
|
+
|
|
535
|
+
if (hasRelevantChange) {
|
|
536
|
+
await esbuildContext.rebuild();
|
|
537
|
+
} else {
|
|
538
|
+
logger.debug("Changed files not included in build, skipping rebuild");
|
|
539
|
+
}
|
|
540
|
+
} catch (err) {
|
|
541
|
+
sender.send("error", {
|
|
542
|
+
message: errorMessage(err),
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
} catch (err) {
|
|
547
|
+
sender.send("error", {
|
|
548
|
+
message: errorMessage(err),
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Stop watch
|
|
555
|
+
* @remarks Cleans up esbuild context.
|
|
556
|
+
*/
|
|
557
|
+
async function stopWatch(): Promise<void> {
|
|
558
|
+
await cleanup();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const sender = createWorker<
|
|
562
|
+
{ build: typeof build; startWatch: typeof startWatch; stopWatch: typeof stopWatch },
|
|
563
|
+
ServerWorkerEvents
|
|
564
|
+
>({
|
|
565
|
+
build,
|
|
566
|
+
startWatch,
|
|
567
|
+
stopWatch,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
export default sender;
|
|
571
|
+
|
|
572
|
+
//#endregion
|