@simplysm/sd-cli 13.0.93 → 13.0.95
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 +1 -1
- package/dist/capacitor/capacitor.d.ts +15 -1
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +52 -31
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/electron/electron.d.ts +6 -2
- package/dist/electron/electron.d.ts.map +1 -1
- package/dist/electron/electron.js +12 -6
- package/dist/electron/electron.js.map +1 -1
- package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevOrchestrator.js +16 -0
- package/dist/orchestrators/DevOrchestrator.js.map +1 -1
- package/dist/sd-cli-entry.d.ts.map +1 -1
- package/dist/sd-cli-entry.js +4 -1
- package/dist/sd-cli-entry.js.map +1 -1
- package/dist/utils/esbuild-config.d.ts +2 -0
- package/dist/utils/esbuild-config.d.ts.map +1 -1
- package/dist/utils/esbuild-config.js +86 -6
- package/dist/utils/esbuild-config.js.map +1 -1
- package/dist/utils/package-utils.d.ts.map +1 -1
- package/dist/utils/package-utils.js +7 -0
- package/dist/utils/package-utils.js.map +1 -1
- package/dist/utils/replace-deps.d.ts.map +1 -1
- package/dist/utils/replace-deps.js +17 -0
- package/dist/utils/replace-deps.js.map +1 -1
- package/dist/utils/worker-utils.d.ts +9 -1
- package/dist/utils/worker-utils.d.ts.map +1 -1
- package/dist/utils/worker-utils.js +7 -0
- package/dist/utils/worker-utils.js.map +1 -1
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +2 -1
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/dts.worker.d.ts.map +1 -1
- package/dist/workers/dts.worker.js +2 -1
- package/dist/workers/dts.worker.js.map +1 -1
- package/dist/workers/library.worker.d.ts.map +1 -1
- package/dist/workers/library.worker.js +2 -1
- package/dist/workers/library.worker.js.map +1 -1
- package/dist/workers/server-runtime.worker.d.ts.map +1 -1
- package/dist/workers/server-runtime.worker.js +31 -2
- package/dist/workers/server-runtime.worker.js.map +1 -1
- package/dist/workers/server.worker.d.ts.map +1 -1
- package/dist/workers/server.worker.js +144 -4
- package/dist/workers/server.worker.js.map +1 -1
- package/package.json +4 -4
- package/src/capacitor/capacitor.ts +59 -31
- package/src/electron/electron.ts +13 -6
- package/src/orchestrators/DevOrchestrator.ts +16 -0
- package/src/sd-cli-entry.ts +4 -1
- package/src/utils/esbuild-config.ts +86 -6
- package/src/utils/package-utils.ts +8 -0
- package/src/utils/replace-deps.ts +20 -0
- package/src/utils/worker-utils.ts +14 -1
- package/src/workers/client.worker.ts +3 -1
- package/src/workers/dts.worker.ts +3 -1
- package/src/workers/library.worker.ts +3 -1
- package/src/workers/server-runtime.worker.ts +34 -2
- package/src/workers/server.worker.ts +165 -3
- package/templates/init/package.json.hbs +3 -3
- package/templates/init/packages/client-admin/package.json.hbs +7 -7
- package/templates/init/packages/db-main/package.json.hbs +2 -2
- package/templates/init/packages/server/package.json.hbs +5 -5
- package/templates/init/tests-e2e/package.json.hbs +1 -1
- package/tests/capacitor.spec.ts +49 -0
|
@@ -5,6 +5,9 @@ import { createRequire } from "module";
|
|
|
5
5
|
import type esbuild from "esbuild";
|
|
6
6
|
import { solidPlugin } from "esbuild-plugin-solid";
|
|
7
7
|
import type { TypecheckEnv } from "./tsconfig";
|
|
8
|
+
import { consola } from "consola";
|
|
9
|
+
|
|
10
|
+
const logger = consola.withTag("sd:cli:esbuild-config");
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* Write only changed files from esbuild outputFiles to disk
|
|
@@ -65,6 +68,8 @@ export interface ServerEsbuildOptions {
|
|
|
65
68
|
env?: Record<string, string>;
|
|
66
69
|
/** External modules to exclude from bundle */
|
|
67
70
|
external?: string[];
|
|
71
|
+
/** Dev mode: skip minification for faster builds */
|
|
72
|
+
dev?: boolean;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -102,7 +107,7 @@ export function createLibraryEsbuildOptions(options: LibraryEsbuildOptions): esb
|
|
|
102
107
|
bundle: false,
|
|
103
108
|
write: false,
|
|
104
109
|
tsconfigRaw: {
|
|
105
|
-
compilerOptions: options.compilerOptions
|
|
110
|
+
compilerOptions: toEsbuildTsconfigRaw(options.compilerOptions),
|
|
106
111
|
},
|
|
107
112
|
logLevel: "silent",
|
|
108
113
|
plugins,
|
|
@@ -130,7 +135,7 @@ export function createServerEsbuildOptions(options: ServerEsbuildOptions): esbui
|
|
|
130
135
|
entryPoints: options.entryPoints,
|
|
131
136
|
outdir: path.join(options.pkgDir, "dist"),
|
|
132
137
|
format: "esm",
|
|
133
|
-
minify: true,
|
|
138
|
+
minify: options.dev !== true,
|
|
134
139
|
platform: "node",
|
|
135
140
|
target: "node20",
|
|
136
141
|
bundle: true,
|
|
@@ -140,12 +145,64 @@ export function createServerEsbuildOptions(options: ServerEsbuildOptions): esbui
|
|
|
140
145
|
external: options.external,
|
|
141
146
|
define,
|
|
142
147
|
tsconfigRaw: {
|
|
143
|
-
compilerOptions: options.compilerOptions
|
|
148
|
+
compilerOptions: toEsbuildTsconfigRaw(options.compilerOptions),
|
|
144
149
|
},
|
|
145
150
|
logLevel: "silent",
|
|
146
151
|
};
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
// TypeScript ScriptTarget enum → esbuild target string
|
|
155
|
+
const TARGET_MAP: Record<number, string> = {
|
|
156
|
+
0: "es3", 1: "es5", 2: "es2015", 3: "es2016", 4: "es2017",
|
|
157
|
+
5: "es2018", 6: "es2019", 7: "es2020", 8: "es2021", 9: "es2022",
|
|
158
|
+
10: "es2023", 11: "es2024", 99: "esnext",
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// TypeScript JsxEmit enum → esbuild jsx string
|
|
162
|
+
const JSX_MAP: Record<number, string> = {
|
|
163
|
+
1: "preserve", 2: "react", 3: "react-native", 4: "react-jsx", 5: "react-jsxdev",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// TypeScript ModuleKind enum → string
|
|
167
|
+
const MODULE_MAP: Record<number, string> = {
|
|
168
|
+
0: "none", 1: "commonjs", 2: "amd", 3: "umd", 4: "system",
|
|
169
|
+
5: "es2015", 6: "es2020", 7: "es2022", 99: "esnext",
|
|
170
|
+
100: "node16", 199: "nodenext",
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// TypeScript ModuleResolutionKind enum → string
|
|
174
|
+
const MODULE_RESOLUTION_MAP: Record<number, string> = {
|
|
175
|
+
1: "node10", 2: "node16", 3: "nodenext", 100: "bundler",
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Convert TypeScript's ts.CompilerOptions to esbuild-compatible tsconfigRaw compilerOptions.
|
|
180
|
+
*
|
|
181
|
+
* TypeScript uses numeric enum values (e.g., target: 99) while esbuild expects
|
|
182
|
+
* string values (e.g., "esnext"). This converts known enum fields and passes
|
|
183
|
+
* everything else through as-is.
|
|
184
|
+
*/
|
|
185
|
+
function toEsbuildTsconfigRaw(
|
|
186
|
+
compilerOptions: Record<string, unknown>,
|
|
187
|
+
): esbuild.TsconfigRaw["compilerOptions"] {
|
|
188
|
+
const result = { ...compilerOptions };
|
|
189
|
+
|
|
190
|
+
if (typeof result["target"] === "number") {
|
|
191
|
+
result["target"] = TARGET_MAP[result["target"]] ?? "esnext";
|
|
192
|
+
}
|
|
193
|
+
if (typeof result["jsx"] === "number") {
|
|
194
|
+
result["jsx"] = JSX_MAP[result["jsx"]];
|
|
195
|
+
}
|
|
196
|
+
if (typeof result["module"] === "number") {
|
|
197
|
+
result["module"] = MODULE_MAP[result["module"]];
|
|
198
|
+
}
|
|
199
|
+
if (typeof result["moduleResolution"] === "number") {
|
|
200
|
+
result["moduleResolution"] = MODULE_RESOLUTION_MAP[result["moduleResolution"]];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return result as esbuild.TsconfigRaw["compilerOptions"];
|
|
204
|
+
}
|
|
205
|
+
|
|
149
206
|
/**
|
|
150
207
|
* Extract TypecheckEnv from build target
|
|
151
208
|
*
|
|
@@ -183,6 +240,7 @@ function scanDependencyTree(
|
|
|
183
240
|
try {
|
|
184
241
|
pkgJsonPath = req.resolve(`${pkgName}/package.json`);
|
|
185
242
|
} catch {
|
|
243
|
+
logger.debug(`[scanDependencyTree] Could not resolve: ${pkgName}`);
|
|
186
244
|
return;
|
|
187
245
|
}
|
|
188
246
|
|
|
@@ -196,7 +254,13 @@ function scanDependencyTree(
|
|
|
196
254
|
}
|
|
197
255
|
|
|
198
256
|
// Recursively traverse sub-dependencies
|
|
199
|
-
|
|
257
|
+
const subDeps = Object.keys(pkgJson.dependencies ?? {});
|
|
258
|
+
if (subDeps.length > 0) {
|
|
259
|
+
logger.debug(
|
|
260
|
+
`[scanDependencyTree] ${pkgName}: traversing ${String(subDeps.length)} sub-dependencies`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
for (const dep of subDeps) {
|
|
200
264
|
scanDependencyTree(dep, depDir, external, visited, collector);
|
|
201
265
|
}
|
|
202
266
|
}
|
|
@@ -212,7 +276,12 @@ export function collectUninstalledOptionalPeerDeps(pkgDir: string): string[] {
|
|
|
212
276
|
const visited = new Set<string>();
|
|
213
277
|
|
|
214
278
|
const pkgJson = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf-8")) as PkgJson;
|
|
215
|
-
|
|
279
|
+
const deps = Object.keys(pkgJson.dependencies ?? {});
|
|
280
|
+
logger.debug(
|
|
281
|
+
`[optionalPeerDeps] Scanning ${String(deps.length)} top-level dependencies...`,
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
for (const dep of deps) {
|
|
216
285
|
scanDependencyTree(dep, pkgDir, external, visited, (_pkgName, depDir, depPkgJson) => {
|
|
217
286
|
const found: string[] = [];
|
|
218
287
|
if (depPkgJson.peerDependenciesMeta != null) {
|
|
@@ -232,6 +301,9 @@ export function collectUninstalledOptionalPeerDeps(pkgDir: string): string[] {
|
|
|
232
301
|
});
|
|
233
302
|
}
|
|
234
303
|
|
|
304
|
+
logger.debug(
|
|
305
|
+
`[optionalPeerDeps] Done: visited ${String(visited.size)} packages, found ${String(external.size)} externals`,
|
|
306
|
+
);
|
|
235
307
|
return [...external];
|
|
236
308
|
}
|
|
237
309
|
|
|
@@ -250,7 +322,12 @@ export function collectNativeModuleExternals(pkgDir: string): string[] {
|
|
|
250
322
|
const visited = new Set<string>();
|
|
251
323
|
|
|
252
324
|
const pkgJson = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf-8")) as PkgJson;
|
|
253
|
-
|
|
325
|
+
const deps = Object.keys(pkgJson.dependencies ?? {});
|
|
326
|
+
logger.debug(
|
|
327
|
+
`[nativeModules] Scanning ${String(deps.length)} top-level dependencies...`,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
for (const dep of deps) {
|
|
254
331
|
scanDependencyTree(dep, pkgDir, external, visited, (pkgName, depDir, _pkgJson) => {
|
|
255
332
|
const found: string[] = [];
|
|
256
333
|
// Detect native modules by checking for binding.gyp
|
|
@@ -261,6 +338,9 @@ export function collectNativeModuleExternals(pkgDir: string): string[] {
|
|
|
261
338
|
});
|
|
262
339
|
}
|
|
263
340
|
|
|
341
|
+
logger.debug(
|
|
342
|
+
`[nativeModules] Done: visited ${String(visited.size)} packages, found ${String(external.size)} externals`,
|
|
343
|
+
);
|
|
264
344
|
return [...external];
|
|
265
345
|
}
|
|
266
346
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import { consola } from "consola";
|
|
3
4
|
import type { SdPackageConfig } from "../sd-config.types";
|
|
4
5
|
|
|
6
|
+
const logger = consola.withTag("sd:cli:package-utils");
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
9
|
* Walk up from import.meta.dirname to find package.json and return package root
|
|
7
10
|
*/
|
|
@@ -25,6 +28,8 @@ export function collectDeps(
|
|
|
25
28
|
cwd: string,
|
|
26
29
|
replaceDepsConfig?: Record<string, string>,
|
|
27
30
|
): DepsResult {
|
|
31
|
+
const startTime = performance.now();
|
|
32
|
+
logger.debug("[collectDeps] Starting dependency collection...");
|
|
28
33
|
const rootPkgJsonPath = path.join(cwd, "package.json");
|
|
29
34
|
const rootPkgJson = JSON.parse(fs.readFileSync(rootPkgJsonPath, "utf-8")) as { name: string };
|
|
30
35
|
const scopeMatch = rootPkgJson.name.match(/^(@[^/]+)\//);
|
|
@@ -80,6 +85,9 @@ export function collectDeps(
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
traverse(pkgDir);
|
|
88
|
+
logger.debug(
|
|
89
|
+
`[collectDeps] Done: workspace=${String(workspaceDeps.length)}, replace=${String(replaceDeps.length)} (${Math.round(performance.now() - startTime)}ms)`,
|
|
90
|
+
);
|
|
83
91
|
return { workspaceDeps, replaceDeps };
|
|
84
92
|
}
|
|
85
93
|
|
|
@@ -3,6 +3,8 @@ import path from "path";
|
|
|
3
3
|
import { glob } from "glob";
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
import { fsx, pathx, FsWatcher } from "@simplysm/core-node";
|
|
6
|
+
import { exec } from "child_process";
|
|
7
|
+
import { promisify } from "util";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Match glob patterns from replaceDeps config with target package list
|
|
@@ -258,6 +260,24 @@ export async function setupReplaceDeps(
|
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
logger.success(`Replaced ${setupCount} dependencies`);
|
|
263
|
+
|
|
264
|
+
// Run postinstall scripts from replaced packages
|
|
265
|
+
for (const { targetName, resolvedSourcePath, actualTargetPath } of entries) {
|
|
266
|
+
const sourcePkgJsonPath = path.join(resolvedSourcePath, "package.json");
|
|
267
|
+
try {
|
|
268
|
+
const pkgJson = JSON.parse(await fs.promises.readFile(sourcePkgJsonPath, "utf-8"));
|
|
269
|
+
const postinstall = pkgJson.scripts?.postinstall as string | undefined;
|
|
270
|
+
if (postinstall == null) continue;
|
|
271
|
+
|
|
272
|
+
logger.start(`Running postinstall for ${targetName}`);
|
|
273
|
+
await promisify(exec)(postinstall, { cwd: actualTargetPath });
|
|
274
|
+
logger.success(`postinstall done: ${targetName}`);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
logger.error(
|
|
277
|
+
`postinstall failed (${targetName}): ${err instanceof Error ? err.message : err}`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
261
281
|
}
|
|
262
282
|
|
|
263
283
|
/**
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import consola, { type ConsolaInstance, LogLevels } from "consola";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Apply debug log level in worker threads
|
|
5
|
+
*
|
|
6
|
+
* Checks the SD_DEBUG environment variable (set by --debug flag in main process)
|
|
7
|
+
* and applies debug log level to consola in the current worker thread.
|
|
8
|
+
* Must be called at worker module top level.
|
|
9
|
+
*/
|
|
10
|
+
export function applyDebugLevel(): void {
|
|
11
|
+
if (process.env["SD_DEBUG"] === "true") {
|
|
12
|
+
consola.level = LogLevels.debug;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
2
15
|
|
|
3
16
|
/**
|
|
4
17
|
* Register cleanup handlers for worker process shutdown signals
|
|
@@ -8,7 +8,9 @@ import type { SdClientPackageConfig } from "../sd-config.types";
|
|
|
8
8
|
import { parseRootTsconfig, getCompilerOptionsForPackage } from "../utils/tsconfig";
|
|
9
9
|
import { createViteConfig } from "../utils/vite-config";
|
|
10
10
|
import { collectDeps } from "../utils/package-utils";
|
|
11
|
-
import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
|
|
11
|
+
import { registerCleanupHandlers, createOnceGuard, applyDebugLevel } from "../utils/worker-utils";
|
|
12
|
+
|
|
13
|
+
applyDebugLevel();
|
|
12
14
|
|
|
13
15
|
//#region Types
|
|
14
16
|
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
type TypecheckEnv,
|
|
12
12
|
} from "../utils/tsconfig";
|
|
13
13
|
import { serializeDiagnostic, type SerializedDiagnostic } from "../utils/typecheck-serialization";
|
|
14
|
-
import { createOnceGuard, registerCleanupHandlers } from "../utils/worker-utils";
|
|
14
|
+
import { createOnceGuard, registerCleanupHandlers, applyDebugLevel } from "../utils/worker-utils";
|
|
15
|
+
|
|
16
|
+
applyDebugLevel();
|
|
15
17
|
|
|
16
18
|
//#region Types
|
|
17
19
|
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
getTypecheckEnvFromTarget,
|
|
15
15
|
writeChangedOutputFiles,
|
|
16
16
|
} from "../utils/esbuild-config";
|
|
17
|
-
import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
|
|
17
|
+
import { registerCleanupHandlers, createOnceGuard, applyDebugLevel } from "../utils/worker-utils";
|
|
18
|
+
|
|
19
|
+
applyDebugLevel();
|
|
18
20
|
|
|
19
21
|
//#region Types
|
|
20
22
|
|
|
@@ -4,7 +4,7 @@ import { err as errNs } from "@simplysm/core-common";
|
|
|
4
4
|
import { consola } from "consola";
|
|
5
5
|
import net from "net";
|
|
6
6
|
import { pathToFileURL } from "url";
|
|
7
|
-
import { registerCleanupHandlers } from "../utils/worker-utils";
|
|
7
|
+
import { registerCleanupHandlers, applyDebugLevel } from "../utils/worker-utils";
|
|
8
8
|
|
|
9
9
|
//#region Types
|
|
10
10
|
|
|
@@ -40,6 +40,8 @@ export interface ServerRuntimeWorkerEvents extends Record<string, unknown> {
|
|
|
40
40
|
|
|
41
41
|
//#endregion
|
|
42
42
|
|
|
43
|
+
applyDebugLevel();
|
|
44
|
+
|
|
43
45
|
const logger = consola.withTag("sd:cli:server-runtime:worker");
|
|
44
46
|
|
|
45
47
|
/** Server instance (to be cleaned up) */
|
|
@@ -109,8 +111,13 @@ async function findAvailablePort(startPort: number, maxRetries = 20): Promise<nu
|
|
|
109
111
|
*/
|
|
110
112
|
async function start(info: ServerRuntimeStartInfo): Promise<void> {
|
|
111
113
|
try {
|
|
114
|
+
const startTime = performance.now();
|
|
115
|
+
|
|
112
116
|
// Import main.js (must export a server instance)
|
|
117
|
+
logger.debug("[start] Importing main.js...");
|
|
118
|
+
let stepStart = performance.now();
|
|
113
119
|
const module = await import(pathToFileURL(info.mainJsPath).href);
|
|
120
|
+
logger.debug(`[start] main.js imported (${Math.round(performance.now() - stepStart)}ms)`);
|
|
114
121
|
const server = module.server;
|
|
115
122
|
|
|
116
123
|
if (server == null) {
|
|
@@ -121,15 +128,28 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
|
|
|
121
128
|
serverInstance = server;
|
|
122
129
|
|
|
123
130
|
// Find available port (auto-increment on port conflict)
|
|
131
|
+
logger.debug("[start] Finding available port...");
|
|
132
|
+
stepStart = performance.now();
|
|
124
133
|
const originalPort = server.options.port;
|
|
125
134
|
const availablePort = await findAvailablePort(originalPort);
|
|
126
135
|
if (availablePort !== originalPort) {
|
|
127
136
|
logger.info(`Port ${originalPort} in use, changing to ${availablePort}`);
|
|
128
137
|
server.options.port = availablePort;
|
|
129
138
|
}
|
|
139
|
+
logger.debug(
|
|
140
|
+
`[start] Port ${String(availablePort)} available (${Math.round(performance.now() - stepStart)}ms)`,
|
|
141
|
+
);
|
|
130
142
|
|
|
131
143
|
// Configure Vite proxy (only if clientPorts exists)
|
|
132
|
-
|
|
144
|
+
const clientEntries = Object.entries(info.clientPorts);
|
|
145
|
+
if (clientEntries.length > 0) {
|
|
146
|
+
logger.debug(
|
|
147
|
+
`[start] Configuring ${String(clientEntries.length)} Vite proxy(s)...`,
|
|
148
|
+
);
|
|
149
|
+
stepStart = performance.now();
|
|
150
|
+
}
|
|
151
|
+
for (const [name, port] of clientEntries) {
|
|
152
|
+
logger.debug(`[start] Registering proxy: /${name} -> http://127.0.0.1:${String(port)}`);
|
|
133
153
|
await server.fastify.register(proxy, {
|
|
134
154
|
prefix: `/${name}`,
|
|
135
155
|
upstream: `http://127.0.0.1:${port}`,
|
|
@@ -137,9 +157,21 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
|
|
|
137
157
|
websocket: true,
|
|
138
158
|
});
|
|
139
159
|
}
|
|
160
|
+
if (clientEntries.length > 0) {
|
|
161
|
+
logger.debug(
|
|
162
|
+
`[start] Proxies configured (${Math.round(performance.now() - stepStart)}ms)`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
140
165
|
|
|
141
166
|
// Start server
|
|
167
|
+
logger.debug("[start] Starting server listen...");
|
|
168
|
+
stepStart = performance.now();
|
|
142
169
|
await server.listen();
|
|
170
|
+
logger.debug(`[start] Server listening (${Math.round(performance.now() - stepStart)}ms)`);
|
|
171
|
+
|
|
172
|
+
logger.debug(
|
|
173
|
+
`[start] Total runtime startup: ${Math.round(performance.now() - startTime)}ms`,
|
|
174
|
+
);
|
|
143
175
|
|
|
144
176
|
sender.send("serverReady", { port: server.options.port });
|
|
145
177
|
} catch (err) {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
collectNativeModuleExternals,
|
|
17
17
|
writeChangedOutputFiles,
|
|
18
18
|
} from "../utils/esbuild-config";
|
|
19
|
-
import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
|
|
19
|
+
import { registerCleanupHandlers, createOnceGuard, applyDebugLevel } from "../utils/worker-utils";
|
|
20
20
|
import { collectDeps } from "../utils/package-utils";
|
|
21
21
|
import { copyPublicFiles, watchPublicFiles } from "../utils/copy-public";
|
|
22
22
|
|
|
@@ -101,6 +101,8 @@ export interface ServerWorkerEvents extends Record<string, unknown> {
|
|
|
101
101
|
|
|
102
102
|
//#region Resource Management
|
|
103
103
|
|
|
104
|
+
applyDebugLevel();
|
|
105
|
+
|
|
104
106
|
const logger = consola.withTag("sd:cli:server:worker");
|
|
105
107
|
|
|
106
108
|
/** esbuild build context (to be cleaned up) */
|
|
@@ -151,8 +153,20 @@ async function cleanup(): Promise<void> {
|
|
|
151
153
|
* 3. Manually specified in sd.config.ts
|
|
152
154
|
*/
|
|
153
155
|
function collectAllExternals(pkgDir: string, manualExternals?: string[]): string[] {
|
|
156
|
+
logger.debug("[externals] Scanning optional peer deps...");
|
|
157
|
+
let stepStart = performance.now();
|
|
154
158
|
const optionalPeerDeps = collectUninstalledOptionalPeerDeps(pkgDir);
|
|
159
|
+
logger.debug(
|
|
160
|
+
`[externals] Optional peer deps done: ${String(optionalPeerDeps.length)} found (${Math.round(performance.now() - stepStart)}ms)`,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
logger.debug("[externals] Scanning native modules...");
|
|
164
|
+
stepStart = performance.now();
|
|
155
165
|
const nativeModules = collectNativeModuleExternals(pkgDir);
|
|
166
|
+
logger.debug(
|
|
167
|
+
`[externals] Native modules done: ${String(nativeModules.length)} found (${Math.round(performance.now() - stepStart)}ms)`,
|
|
168
|
+
);
|
|
169
|
+
|
|
156
170
|
const manual = manualExternals ?? [];
|
|
157
171
|
|
|
158
172
|
const merged = [...new Set([...optionalPeerDeps, ...nativeModules, ...manual])];
|
|
@@ -307,20 +321,39 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
|
307
321
|
|
|
308
322
|
try {
|
|
309
323
|
// Parse tsconfig
|
|
324
|
+
logger.debug("[build] Parsing tsconfig...");
|
|
325
|
+
let stepStart = performance.now();
|
|
310
326
|
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
327
|
+
logger.debug(`[build] tsconfig parsed (${Math.round(performance.now() - stepStart)}ms)`);
|
|
328
|
+
|
|
329
|
+
stepStart = performance.now();
|
|
311
330
|
const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
|
|
331
|
+
logger.debug(
|
|
332
|
+
`[build] Found ${String(entryPoints.length)} source files (${Math.round(performance.now() - stepStart)}ms)`,
|
|
333
|
+
);
|
|
312
334
|
|
|
313
335
|
// Server target is node environment
|
|
336
|
+
logger.debug("[build] Getting compiler options...");
|
|
337
|
+
stepStart = performance.now();
|
|
314
338
|
const compilerOptions = await getCompilerOptionsForPackage(
|
|
315
339
|
parsedConfig.options,
|
|
316
340
|
"node",
|
|
317
341
|
info.pkgDir,
|
|
318
342
|
);
|
|
343
|
+
logger.debug(
|
|
344
|
+
`[build] Compiler options ready (${Math.round(performance.now() - stepStart)}ms)`,
|
|
345
|
+
);
|
|
319
346
|
|
|
320
347
|
// Collect all externals (optional peer deps + native modules + manual)
|
|
348
|
+
logger.debug("[build] Collecting externals...");
|
|
349
|
+
stepStart = performance.now();
|
|
321
350
|
const external = collectAllExternals(info.pkgDir, info.externals);
|
|
351
|
+
logger.debug(
|
|
352
|
+
`[build] Collected ${String(external.length)} externals (${Math.round(performance.now() - stepStart)}ms)`,
|
|
353
|
+
);
|
|
322
354
|
|
|
323
355
|
// One-time esbuild
|
|
356
|
+
logger.debug("[build] Creating esbuild options...");
|
|
324
357
|
const esbuildOptions = createServerEsbuildOptions({
|
|
325
358
|
pkgDir: info.pkgDir,
|
|
326
359
|
entryPoints,
|
|
@@ -329,17 +362,30 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
|
|
|
329
362
|
external,
|
|
330
363
|
});
|
|
331
364
|
|
|
365
|
+
logger.debug("[build] Running esbuild...");
|
|
366
|
+
stepStart = performance.now();
|
|
332
367
|
const result = await esbuild.build(esbuildOptions);
|
|
368
|
+
logger.debug(`[build] esbuild done (${Math.round(performance.now() - stepStart)}ms)`);
|
|
333
369
|
|
|
334
370
|
// Generate .config.json
|
|
335
371
|
const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
|
|
336
372
|
fs.writeFileSync(confDistPath, JSON.stringify(info.configs ?? {}, undefined, 2));
|
|
337
373
|
|
|
338
374
|
// Copy public/ to dist/ (production build: no public-dev)
|
|
375
|
+
logger.debug("[build] Copying public files...");
|
|
376
|
+
stepStart = performance.now();
|
|
339
377
|
await copyPublicFiles(info.pkgDir, false);
|
|
378
|
+
logger.debug(
|
|
379
|
+
`[build] Public files copied (${Math.round(performance.now() - stepStart)}ms)`,
|
|
380
|
+
);
|
|
340
381
|
|
|
341
382
|
// Generate production files (package.json, mise.toml, openssl.cnf, pm2.config.cjs)
|
|
383
|
+
logger.debug("[build] Generating production files...");
|
|
384
|
+
stepStart = performance.now();
|
|
342
385
|
generateProductionFiles(info, external);
|
|
386
|
+
logger.debug(
|
|
387
|
+
`[build] Production files generated (${Math.round(performance.now() - stepStart)}ms)`,
|
|
388
|
+
);
|
|
343
389
|
|
|
344
390
|
const errors = result.errors.map((e) => e.text);
|
|
345
391
|
const warnings = result.warnings.map((w) => w.text);
|
|
@@ -368,26 +414,53 @@ async function createAndBuildContext(
|
|
|
368
414
|
isFirstBuild: boolean,
|
|
369
415
|
resolveFirstBuild?: () => void,
|
|
370
416
|
): Promise<esbuild.BuildContext> {
|
|
417
|
+
const contextStart = performance.now();
|
|
418
|
+
|
|
419
|
+
logger.debug("[context] Parsing tsconfig...");
|
|
420
|
+
let stepStart = performance.now();
|
|
371
421
|
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
422
|
+
logger.debug(`[context] tsconfig parsed (${Math.round(performance.now() - stepStart)}ms)`);
|
|
423
|
+
|
|
424
|
+
stepStart = performance.now();
|
|
372
425
|
const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
|
|
426
|
+
logger.debug(
|
|
427
|
+
`[context] Found ${String(entryPoints.length)} source files (${Math.round(performance.now() - stepStart)}ms)`,
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
logger.debug("[context] Getting compiler options...");
|
|
431
|
+
stepStart = performance.now();
|
|
373
432
|
const compilerOptions = await getCompilerOptionsForPackage(
|
|
374
433
|
parsedConfig.options,
|
|
375
434
|
"node",
|
|
376
435
|
info.pkgDir,
|
|
377
436
|
);
|
|
437
|
+
logger.debug(
|
|
438
|
+
`[context] Compiler options ready (${Math.round(performance.now() - stepStart)}ms)`,
|
|
439
|
+
);
|
|
378
440
|
|
|
379
441
|
const mainJsPath = path.join(info.pkgDir, "dist", "main.js");
|
|
442
|
+
|
|
443
|
+
logger.debug("[context] Collecting externals...");
|
|
444
|
+
stepStart = performance.now();
|
|
380
445
|
const external = collectAllExternals(info.pkgDir, info.externals);
|
|
446
|
+
logger.debug(
|
|
447
|
+
`[context] Collected ${String(external.length)} externals (${Math.round(performance.now() - stepStart)}ms)`,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
logger.debug("[context] Creating esbuild options (dev mode, minify disabled)...");
|
|
381
451
|
const baseOptions = createServerEsbuildOptions({
|
|
382
452
|
pkgDir: info.pkgDir,
|
|
383
453
|
entryPoints,
|
|
384
454
|
compilerOptions,
|
|
385
455
|
env: info.env,
|
|
386
456
|
external,
|
|
457
|
+
dev: true,
|
|
387
458
|
});
|
|
388
459
|
|
|
389
460
|
let isBuildFirstTime = isFirstBuild;
|
|
390
461
|
|
|
462
|
+
logger.debug("[context] Creating esbuild context...");
|
|
463
|
+
stepStart = performance.now();
|
|
391
464
|
const context = await esbuild.context({
|
|
392
465
|
...baseOptions,
|
|
393
466
|
metafile: true,
|
|
@@ -396,14 +469,43 @@ async function createAndBuildContext(
|
|
|
396
469
|
{
|
|
397
470
|
name: "watch-notify",
|
|
398
471
|
setup(pluginBuild) {
|
|
472
|
+
let consecutiveStarts = 0;
|
|
473
|
+
|
|
399
474
|
pluginBuild.onStart(() => {
|
|
400
|
-
|
|
475
|
+
consecutiveStarts++;
|
|
476
|
+
logger.debug(`[esbuild] onStart (#${String(consecutiveStarts)})`);
|
|
477
|
+
|
|
478
|
+
if (consecutiveStarts > 3) {
|
|
479
|
+
// esbuild context.rebuild() silently retries on build errors (esbuild bug).
|
|
480
|
+
// Stop the retry loop and diagnose via esbuild.build().
|
|
481
|
+
void context.dispose().catch(() => {});
|
|
482
|
+
|
|
483
|
+
void esbuild
|
|
484
|
+
.build(baseOptions)
|
|
485
|
+
.catch((err: unknown) => {
|
|
486
|
+
sender.send("build", {
|
|
487
|
+
success: false,
|
|
488
|
+
mainJsPath,
|
|
489
|
+
errors: [errNs.message(err)],
|
|
490
|
+
});
|
|
491
|
+
})
|
|
492
|
+
.finally(() => {
|
|
493
|
+
resolveFirstBuild?.();
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
sender.send("buildStart", {});
|
|
497
|
+
}
|
|
401
498
|
});
|
|
402
499
|
|
|
403
500
|
pluginBuild.onEnd(async (result) => {
|
|
501
|
+
consecutiveStarts = 0;
|
|
502
|
+
|
|
404
503
|
// Save metafile
|
|
405
504
|
if (result.metafile != null) {
|
|
406
505
|
lastMetafile = result.metafile;
|
|
506
|
+
logger.debug(
|
|
507
|
+
`[esbuild] Metafile: ${String(Object.keys(result.metafile.inputs).length)} inputs, ${String(Object.keys(result.metafile.outputs).length)} outputs`,
|
|
508
|
+
);
|
|
407
509
|
}
|
|
408
510
|
|
|
409
511
|
const errors = result.errors.map((e) => e.text);
|
|
@@ -413,7 +515,11 @@ async function createAndBuildContext(
|
|
|
413
515
|
// Write output files and check for changes
|
|
414
516
|
let hasOutputChange = false;
|
|
415
517
|
if (success && result.outputFiles != null) {
|
|
518
|
+
const writeStart = performance.now();
|
|
416
519
|
hasOutputChange = await writeChangedOutputFiles(result.outputFiles);
|
|
520
|
+
logger.debug(
|
|
521
|
+
`[esbuild] Output files written: changed=${String(hasOutputChange)}, count=${String(result.outputFiles.length)} (${Math.round(performance.now() - writeStart)}ms)`,
|
|
522
|
+
);
|
|
417
523
|
}
|
|
418
524
|
|
|
419
525
|
if (isBuildFirstTime && success) {
|
|
@@ -442,8 +548,33 @@ async function createAndBuildContext(
|
|
|
442
548
|
},
|
|
443
549
|
],
|
|
444
550
|
});
|
|
551
|
+
logger.debug(
|
|
552
|
+
`[context] esbuild context created (${Math.round(performance.now() - stepStart)}ms)`,
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
logger.debug("[context] Running initial rebuild...");
|
|
556
|
+
stepStart = performance.now();
|
|
557
|
+
const progressTimer = setInterval(() => {
|
|
558
|
+
logger.debug(
|
|
559
|
+
`[esbuild] Still building... (${Math.round((performance.now() - stepStart) / 1000)}s elapsed)`,
|
|
560
|
+
);
|
|
561
|
+
}, 5000);
|
|
562
|
+
try {
|
|
563
|
+
await context.rebuild();
|
|
564
|
+
} catch {
|
|
565
|
+
// context.rebuild() may reject with "Cannot rebuild" when disposed
|
|
566
|
+
// from onStart guard. The real error is reported via esbuild.build()
|
|
567
|
+
// fallback in onStart, so we suppress this rejection.
|
|
568
|
+
} finally {
|
|
569
|
+
clearInterval(progressTimer);
|
|
570
|
+
}
|
|
571
|
+
logger.debug(
|
|
572
|
+
`[context] Initial rebuild done (${Math.round(performance.now() - stepStart)}ms)`,
|
|
573
|
+
);
|
|
445
574
|
|
|
446
|
-
|
|
575
|
+
logger.debug(
|
|
576
|
+
`[context] Total context setup: ${Math.round(performance.now() - contextStart)}ms`,
|
|
577
|
+
);
|
|
447
578
|
|
|
448
579
|
return context;
|
|
449
580
|
}
|
|
@@ -457,6 +588,9 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
|
|
|
457
588
|
guardStartWatch();
|
|
458
589
|
|
|
459
590
|
try {
|
|
591
|
+
const watchStart = performance.now();
|
|
592
|
+
logger.debug("[startWatch] Starting watch setup...");
|
|
593
|
+
|
|
460
594
|
// Promise to wait for first build completion
|
|
461
595
|
let resolveFirstBuild!: () => void;
|
|
462
596
|
const firstBuildPromise = new Promise<void>((resolve) => {
|
|
@@ -464,16 +598,36 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
|
|
|
464
598
|
});
|
|
465
599
|
|
|
466
600
|
// Create initial esbuild context and build
|
|
601
|
+
logger.debug("[startWatch] Creating initial esbuild context...");
|
|
602
|
+
let stepStart = performance.now();
|
|
467
603
|
esbuildContext = await createAndBuildContext(info, true, resolveFirstBuild);
|
|
604
|
+
logger.debug(
|
|
605
|
+
`[startWatch] Initial context created (${Math.round(performance.now() - stepStart)}ms)`,
|
|
606
|
+
);
|
|
468
607
|
|
|
469
608
|
// Wait for first build completion
|
|
609
|
+
logger.debug("[startWatch] Waiting for first build completion...");
|
|
610
|
+
stepStart = performance.now();
|
|
470
611
|
await firstBuildPromise;
|
|
612
|
+
logger.debug(
|
|
613
|
+
`[startWatch] First build completed (${Math.round(performance.now() - stepStart)}ms)`,
|
|
614
|
+
);
|
|
471
615
|
|
|
472
616
|
// Watch public/ and public-dev/ (dev mode includes public-dev)
|
|
617
|
+
logger.debug("[startWatch] Setting up public file watcher...");
|
|
618
|
+
stepStart = performance.now();
|
|
473
619
|
publicWatcher = await watchPublicFiles(info.pkgDir, true);
|
|
620
|
+
logger.debug(
|
|
621
|
+
`[startWatch] Public watcher ready (${Math.round(performance.now() - stepStart)}ms)`,
|
|
622
|
+
);
|
|
474
623
|
|
|
475
624
|
// Collect watch paths based on dependencies
|
|
625
|
+
logger.debug("[startWatch] Collecting dependencies for watch paths...");
|
|
626
|
+
stepStart = performance.now();
|
|
476
627
|
const { workspaceDeps, replaceDeps } = collectDeps(info.pkgDir, info.cwd, info.replaceDeps);
|
|
628
|
+
logger.debug(
|
|
629
|
+
`[startWatch] Deps collected: workspace=${String(workspaceDeps.length)}, replace=${String(replaceDeps.length)} (${Math.round(performance.now() - stepStart)}ms)`,
|
|
630
|
+
);
|
|
477
631
|
|
|
478
632
|
const watchPaths: string[] = [];
|
|
479
633
|
|
|
@@ -496,7 +650,15 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
|
|
|
496
650
|
}
|
|
497
651
|
|
|
498
652
|
// Start FsWatcher
|
|
653
|
+
logger.debug(`[startWatch] Starting FsWatcher with ${String(watchPaths.length)} paths...`);
|
|
654
|
+
stepStart = performance.now();
|
|
499
655
|
srcWatcher = await FsWatcher.watch(watchPaths);
|
|
656
|
+
logger.debug(
|
|
657
|
+
`[startWatch] FsWatcher ready (${Math.round(performance.now() - stepStart)}ms)`,
|
|
658
|
+
);
|
|
659
|
+
logger.debug(
|
|
660
|
+
`[startWatch] Total watch setup: ${Math.round(performance.now() - watchStart)}ms`,
|
|
661
|
+
);
|
|
500
662
|
|
|
501
663
|
// Handle file changes
|
|
502
664
|
srcWatcher.onChange({ delay: 300 }, async (changes) => {
|