@simplysm/sd-cli 14.0.46 → 14.0.48
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 +782 -0
- package/dist/angular/ngtsc-build-core.js +2 -2
- package/dist/angular/ngtsc-build-core.js.map +1 -1
- package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
- package/dist/angular/vite-angular-plugin.js +3 -2
- package/dist/angular/vite-angular-plugin.js.map +1 -1
- package/dist/capacitor/capacitor-android.js +2 -2
- package/dist/capacitor/capacitor-android.js.map +1 -1
- package/dist/capacitor/capacitor-build.d.ts.map +1 -1
- package/dist/capacitor/capacitor-build.js +2 -1
- package/dist/capacitor/capacitor-build.js.map +1 -1
- package/dist/capacitor/capacitor-icon.d.ts.map +1 -1
- package/dist/capacitor/capacitor-icon.js +2 -1
- package/dist/capacitor/capacitor-icon.js.map +1 -1
- package/dist/capacitor/capacitor-npm-config.d.ts.map +1 -1
- package/dist/capacitor/capacitor-npm-config.js +2 -1
- package/dist/capacitor/capacitor-npm-config.js.map +1 -1
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +2 -1
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/device.js +2 -2
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/publish/git-phase.js +1 -1
- package/dist/commands/publish/git-phase.js.map +1 -1
- package/dist/commands/replace-deps.js +2 -2
- package/dist/commands/replace-deps.js.map +1 -1
- package/dist/deps/replace-deps/collect-deps.js +2 -2
- package/dist/deps/replace-deps/collect-deps.js.map +1 -1
- package/dist/deps/replace-deps/replace-deps.d.ts.map +1 -1
- package/dist/deps/replace-deps/replace-deps.js +108 -81
- package/dist/deps/replace-deps/replace-deps.js.map +1 -1
- package/dist/deps/server-externals/server-production-files.js +2 -2
- package/dist/deps/server-externals/server-production-files.js.map +1 -1
- package/dist/electron/electron.d.ts.map +1 -1
- package/dist/electron/electron.js +2 -1
- package/dist/electron/electron.js.map +1 -1
- package/dist/engines/BaseEngine.d.ts.map +1 -1
- package/dist/engines/BaseEngine.js +2 -2
- package/dist/engines/BaseEngine.js.map +1 -1
- package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
- package/dist/engines/EsbuildClientEngine.js +2 -2
- package/dist/engines/EsbuildClientEngine.js.map +1 -1
- package/dist/engines/NgtscEngine.js +2 -2
- package/dist/engines/NgtscEngine.js.map +1 -1
- package/dist/engines/ServerEsbuildEngine.js +2 -2
- package/dist/engines/ServerEsbuildEngine.js.map +1 -1
- package/dist/engines/TscEngine.js +2 -2
- package/dist/engines/TscEngine.js.map +1 -1
- package/dist/engines/engine-factory.d.ts.map +1 -1
- package/dist/engines/engine-factory.js +2 -2
- package/dist/engines/engine-factory.js.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.js +52 -20
- package/dist/esbuild/esbuild-angular-compiler-plugin.js.map +1 -1
- package/dist/esbuild/esbuild-config.js +2 -2
- package/dist/esbuild/esbuild-config.js.map +1 -1
- package/dist/esbuild/esbuild-worker-plugin.d.ts +14 -2
- package/dist/esbuild/esbuild-worker-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-worker-plugin.js +13 -5
- package/dist/esbuild/esbuild-worker-plugin.js.map +1 -1
- package/dist/lint/lint-with-program.js +2 -2
- package/dist/lint/lint-with-program.js.map +1 -1
- package/dist/runtime/lazy-logger.d.ts +14 -0
- package/dist/runtime/lazy-logger.d.ts.map +1 -0
- package/dist/runtime/lazy-logger.js +23 -0
- package/dist/runtime/lazy-logger.js.map +1 -0
- package/dist/sd-cli-entry.js +2 -2
- package/dist/sd-cli-entry.js.map +1 -1
- package/dist/sd-cli.js +2 -2
- package/dist/sd-cli.js.map +1 -1
- package/dist/ts-compiler/SdTsCompiler.d.ts +11 -0
- package/dist/ts-compiler/SdTsCompiler.d.ts.map +1 -1
- package/dist/ts-compiler/SdTsCompiler.js +223 -116
- package/dist/ts-compiler/SdTsCompiler.js.map +1 -1
- package/dist/typecheck/typecheck-non-package.js +2 -2
- package/dist/typecheck/typecheck-non-package.js.map +1 -1
- package/dist/typecheck/typecheck-serialization.d.ts +31 -9
- package/dist/typecheck/typecheck-serialization.d.ts.map +1 -1
- package/dist/typecheck/typecheck-serialization.js +62 -22
- package/dist/typecheck/typecheck-serialization.js.map +1 -1
- package/dist/utils/output-utils.js +2 -2
- package/dist/utils/output-utils.js.map +1 -1
- package/dist/utils/package-classify.js +2 -2
- package/dist/utils/package-classify.js.map +1 -1
- package/dist/utils/package-utils.js +2 -2
- package/dist/utils/package-utils.js.map +1 -1
- package/dist/utils/sd-config.js +2 -2
- package/dist/utils/sd-config.js.map +1 -1
- package/dist/utils/tsconfig.d.ts.map +1 -1
- package/dist/utils/tsconfig.js +3 -5
- package/dist/utils/tsconfig.js.map +1 -1
- package/package.json +8 -8
- package/src/angular/ngtsc-build-core.ts +3 -3
- package/src/angular/vite-angular-plugin.ts +3 -2
- package/src/capacitor/capacitor-android.ts +2 -2
- package/src/capacitor/capacitor-build.ts +2 -1
- package/src/capacitor/capacitor-icon.ts +2 -1
- package/src/capacitor/capacitor-npm-config.ts +2 -1
- package/src/capacitor/capacitor.ts +2 -1
- package/src/commands/device.ts +2 -2
- package/src/commands/publish/git-phase.ts +1 -1
- package/src/commands/replace-deps.ts +2 -2
- package/src/deps/replace-deps/collect-deps.ts +2 -2
- package/src/deps/replace-deps/replace-deps.ts +119 -85
- package/src/deps/server-externals/server-production-files.ts +2 -2
- package/src/electron/electron.ts +2 -1
- package/src/engines/BaseEngine.ts +2 -2
- package/src/engines/EsbuildClientEngine.ts +2 -2
- package/src/engines/NgtscEngine.ts +2 -2
- package/src/engines/ServerEsbuildEngine.ts +2 -2
- package/src/engines/TscEngine.ts +2 -2
- package/src/engines/engine-factory.ts +2 -2
- package/src/esbuild/esbuild-angular-compiler-plugin.ts +66 -21
- package/src/esbuild/esbuild-config.ts +2 -2
- package/src/esbuild/esbuild-worker-plugin.ts +24 -4
- package/src/lint/lint-with-program.ts +2 -2
- package/src/runtime/lazy-logger.ts +23 -0
- package/src/sd-cli-entry.ts +2 -2
- package/src/sd-cli.ts +2 -2
- package/src/ts-compiler/SdTsCompiler.ts +280 -138
- package/src/typecheck/typecheck-non-package.ts +2 -2
- package/src/typecheck/typecheck-serialization.ts +100 -26
- package/src/utils/output-utils.ts +2 -2
- package/src/utils/package-classify.ts +2 -2
- package/src/utils/package-utils.ts +2 -2
- package/src/utils/sd-config.ts +2 -2
- package/src/utils/tsconfig.ts +3 -4
- package/tests/esbuild/esbuild-worker-plugin.spec.ts +245 -0
- package/tests/utils/replace-deps-watch.acc.spec.ts +85 -0
- package/tests/utils/replace-deps-watch.spec.ts +198 -1
- package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.d.ts +0 -3
- package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.d.ts.map +0 -1
- package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.js +0 -9
- package/dist/sd-cli/tests/angular/fixtures/packages/basic-app/tests/test.fixture.js.map +0 -1
|
@@ -50,14 +50,27 @@ async function copyWithUnlink(
|
|
|
50
50
|
if (stats.isDirectory()) {
|
|
51
51
|
await fsx.mkdir(targetPath);
|
|
52
52
|
const names = await fs.promises.readdir(sourcePath);
|
|
53
|
+
const allowedChildren = names
|
|
54
|
+
.map((name) => path.resolve(sourcePath, name))
|
|
55
|
+
.filter((child) => filter == null || filter(child));
|
|
56
|
+
const allowedBasenames = new Set(allowedChildren.map((c) => path.basename(c)));
|
|
57
|
+
|
|
58
|
+
// 고아 엔트리 정리: filter 범위 내이면서 소스에 없는 타겟 엔트리 삭제
|
|
59
|
+
const targetNames = await fs.promises.readdir(targetPath).catch(() => [] as string[]);
|
|
60
|
+
await Promise.all(
|
|
61
|
+
targetNames.map(async (name) => {
|
|
62
|
+
const targetChild = path.join(targetPath, name);
|
|
63
|
+
if (filter != null && !filter(targetChild)) return;
|
|
64
|
+
if (allowedBasenames.has(name)) return;
|
|
65
|
+
await fs.promises.rm(targetChild, { recursive: true, force: true });
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
53
69
|
await Promise.all(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
child,
|
|
59
|
-
path.join(targetPath, path.basename(child)),
|
|
60
|
-
)),
|
|
70
|
+
allowedChildren.map((child) => copyWithUnlink(
|
|
71
|
+
child,
|
|
72
|
+
path.join(targetPath, path.basename(child)),
|
|
73
|
+
)),
|
|
61
74
|
);
|
|
62
75
|
} else {
|
|
63
76
|
if (await isFileContentSame(sourcePath, targetPath)) return;
|
|
@@ -201,115 +214,136 @@ export async function watchReplaceDeps(
|
|
|
201
214
|
|
|
202
215
|
const entries = await resolveAllReplaceDepEntries(projectRoot, replaceDeps, logger);
|
|
203
216
|
|
|
204
|
-
// 소스 디렉토리 감시자 설정
|
|
205
|
-
const watchers: FsWatcher[] = [];
|
|
206
|
-
const watchedSources = new Set<string>();
|
|
207
|
-
|
|
208
217
|
logger.start(`replace-deps 워치 시작 중... (${entries.length}개 대상)`);
|
|
209
218
|
|
|
219
|
+
// resolvedSourcePath(posix) → entries 그룹화
|
|
220
|
+
// resolvedSourcePath는 replace-deps-resolve의 pathx.posixResolve 결과로 이미 POSIX이다.
|
|
221
|
+
const sourceMap = new Map<string, ReplaceDepEntry[]>();
|
|
210
222
|
for (const entry of entries) {
|
|
211
|
-
|
|
212
|
-
|
|
223
|
+
const key = entry.resolvedSourcePath;
|
|
224
|
+
const arr = sourceMap.get(key) ?? [];
|
|
225
|
+
arr.push(entry);
|
|
226
|
+
sourceMap.set(key, arr);
|
|
227
|
+
}
|
|
213
228
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
229
|
+
// 각 source의 watchPaths 수집 (files 필드 없는 source는 경고 후 제외). source 단위 병렬.
|
|
230
|
+
const allWatchPaths = new Set<string>();
|
|
231
|
+
await Promise.all(
|
|
232
|
+
[...sourceMap].map(async ([sourcePath, sourceEntries]) => {
|
|
233
|
+
try {
|
|
234
|
+
const files = await loadFilesField(sourcePath);
|
|
235
|
+
|
|
236
|
+
if (files == null) {
|
|
237
|
+
logger.warn(
|
|
238
|
+
`[${sourceEntries[0].targetName}] package.json에 files 필드가 없어 감시 건너뜀`,
|
|
239
|
+
);
|
|
240
|
+
sourceMap.delete(sourcePath);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
221
243
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
);
|
|
244
|
+
for (const f of files) {
|
|
245
|
+
allWatchPaths.add(pathx.posix(path.join(sourcePath, f)));
|
|
246
|
+
}
|
|
226
247
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
248
|
+
const rootEntries = await fs.promises
|
|
249
|
+
.readdir(sourcePath)
|
|
250
|
+
.catch(() => [] as string[]);
|
|
230
251
|
for (const name of rootEntries) {
|
|
231
252
|
if (NPM_DEFAULT_FILE_PATTERN.test(name)) {
|
|
232
|
-
|
|
253
|
+
allWatchPaths.add(pathx.posix(path.join(sourcePath, name)));
|
|
233
254
|
}
|
|
234
255
|
}
|
|
235
|
-
} catch {
|
|
236
|
-
|
|
256
|
+
} catch (err) {
|
|
257
|
+
logger.error(
|
|
258
|
+
`[${sourceEntries[0].targetName}] 감시 설정 실패: ${err instanceof Error ? err.message : err}`,
|
|
259
|
+
);
|
|
260
|
+
sourceMap.delete(sourcePath);
|
|
237
261
|
}
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
if (allWatchPaths.size === 0) {
|
|
266
|
+
if (entries.length > 0) {
|
|
267
|
+
logger.warn("감시 대상이 없어 워치가 시작되지 않음");
|
|
268
|
+
} else {
|
|
269
|
+
logger.success("replace-deps 워치 준비 완료");
|
|
270
|
+
}
|
|
271
|
+
return { entries, dispose: () => {} };
|
|
272
|
+
}
|
|
238
273
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
274
|
+
// longest-prefix 매칭을 위해 긴 경로 우선 정렬
|
|
275
|
+
const sortedSources = [...sourceMap.keys()].sort((a, b) => b.length - a.length);
|
|
276
|
+
|
|
277
|
+
const findSource = (changedPath: string): string | undefined => {
|
|
278
|
+
for (const src of sortedSources) {
|
|
279
|
+
if (changedPath === src || changedPath.startsWith(src + "/")) return src;
|
|
280
|
+
}
|
|
281
|
+
return undefined;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const watcher = await FsWatcher.watch([...allWatchPaths], {
|
|
285
|
+
followSymlinks: false,
|
|
286
|
+
});
|
|
243
287
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
|
|
288
|
+
watcher.onChange({ delay: 300 }, async (changeInfos) => {
|
|
289
|
+
const flags = await Promise.all(
|
|
290
|
+
changeInfos.map(async ({ path: changedPath }) => {
|
|
291
|
+
const src = findSource(changedPath);
|
|
292
|
+
if (src == null) return false;
|
|
293
|
+
const sourceEntries = sourceMap.get(src)!;
|
|
249
294
|
|
|
250
|
-
|
|
251
|
-
// 사전 필터링된 항목만 순회
|
|
252
|
-
for (const e of sourceEntries) {
|
|
253
|
-
// 소스로부터의 상대 경로 계산
|
|
254
|
-
const relativePath = pathx.posix(path.relative(e.resolvedSourcePath, changedPath));
|
|
255
|
-
const destPath = pathx.posix(path.join(e.actualTargetPath, relativePath));
|
|
295
|
+
let localActualCopy = false;
|
|
256
296
|
|
|
297
|
+
// 동일 source의 복수 target 복사는 순차로 유지한다 (destination 중복 시 race 방지).
|
|
298
|
+
for (const e of sourceEntries) {
|
|
299
|
+
const relativePath = pathx.posix(path.relative(e.resolvedSourcePath, changedPath));
|
|
300
|
+
const destPath = pathx.posix(path.join(e.actualTargetPath, relativePath));
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
let sourceExists = false;
|
|
257
304
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
} catch {
|
|
264
|
-
// 소스가 삭제됨
|
|
265
|
-
}
|
|
305
|
+
await fs.promises.access(changedPath);
|
|
306
|
+
sourceExists = true;
|
|
307
|
+
} catch {
|
|
308
|
+
// 소스가 삭제됨
|
|
309
|
+
}
|
|
266
310
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
await fsx.mkdir(destPath);
|
|
272
|
-
} else {
|
|
273
|
-
// 파일 내용이 동일하면 복사 건너뜀 (불필요한 리빌드 방지)
|
|
274
|
-
if (await isFileContentSame(changedPath, destPath)) continue;
|
|
275
|
-
await fsx.mkdir(pathx.posix(path.dirname(destPath)));
|
|
276
|
-
await fsx.copy(changedPath, destPath);
|
|
277
|
-
hasActualCopy = true;
|
|
278
|
-
}
|
|
311
|
+
if (sourceExists) {
|
|
312
|
+
const stat = await fs.promises.stat(changedPath);
|
|
313
|
+
if (stat.isDirectory()) {
|
|
314
|
+
await fsx.mkdir(destPath);
|
|
279
315
|
} else {
|
|
280
|
-
|
|
281
|
-
await fsx.
|
|
282
|
-
|
|
316
|
+
if (await isFileContentSame(changedPath, destPath)) continue;
|
|
317
|
+
await fsx.mkdir(pathx.posix(path.dirname(destPath)));
|
|
318
|
+
await fsx.copy(changedPath, destPath);
|
|
319
|
+
localActualCopy = true;
|
|
283
320
|
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
);
|
|
321
|
+
} else {
|
|
322
|
+
await fsx.rm(destPath);
|
|
323
|
+
localActualCopy = true;
|
|
288
324
|
}
|
|
325
|
+
} catch (err) {
|
|
326
|
+
logger.error(
|
|
327
|
+
`[${e.targetName}] 복사 실패 (${relativePath}): ${err instanceof Error ? err.message : err}`,
|
|
328
|
+
);
|
|
289
329
|
}
|
|
290
330
|
}
|
|
291
331
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
});
|
|
332
|
+
return localActualCopy;
|
|
333
|
+
}),
|
|
334
|
+
);
|
|
296
335
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
logger.error(
|
|
300
|
-
`[${entry.targetName}] 감시 설정 실패: ${err instanceof Error ? err.message : err}`,
|
|
301
|
-
);
|
|
336
|
+
if (flags.some((f) => f)) {
|
|
337
|
+
options?.onChanged?.();
|
|
302
338
|
}
|
|
303
|
-
}
|
|
339
|
+
});
|
|
304
340
|
|
|
305
341
|
logger.success("replace-deps 워치 준비 완료");
|
|
306
342
|
|
|
307
343
|
return {
|
|
308
344
|
entries,
|
|
309
345
|
dispose: () => {
|
|
310
|
-
|
|
311
|
-
void watcher.close();
|
|
312
|
-
}
|
|
346
|
+
void watcher.close();
|
|
313
347
|
},
|
|
314
348
|
};
|
|
315
349
|
}
|
|
@@ -4,10 +4,10 @@ import fs from "fs";
|
|
|
4
4
|
import YAML from "yaml";
|
|
5
5
|
import TOML from "smol-toml";
|
|
6
6
|
import { cpx } from "@simplysm/core-node";
|
|
7
|
-
import {
|
|
7
|
+
import { createLazyLogger } from "../../runtime/lazy-logger";
|
|
8
8
|
import { collectAllDependencyExternals } from "../../esbuild/esbuild-config";
|
|
9
9
|
|
|
10
|
-
const logger =
|
|
10
|
+
const logger = createLazyLogger("sd:cli:server-production-files");
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* 외부 모듈을 두 용도로 분리하여 수집한다.
|
package/src/electron/electron.ts
CHANGED
|
@@ -3,11 +3,12 @@ import fs from "fs";
|
|
|
3
3
|
import module from "module";
|
|
4
4
|
import { cpx, fsx, pathx } from "@simplysm/core-node";
|
|
5
5
|
import { consola, LogLevels } from "consola";
|
|
6
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
6
7
|
import type { NpmConfig, SdElectronConfig } from "../sd-config.types.js";
|
|
7
8
|
import { createEnvBanner } from "../esbuild/esbuild-config.js";
|
|
8
9
|
|
|
9
10
|
export class Electron {
|
|
10
|
-
private static readonly _logger =
|
|
11
|
+
private static readonly _logger = createLazyLogger("sd:cli:electron");
|
|
11
12
|
|
|
12
13
|
private readonly _electronPath: string;
|
|
13
14
|
private readonly _srcPath: string;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Worker, type WorkerProxy } from "@simplysm/core-node";
|
|
2
2
|
import { err as errNs } from "@simplysm/core-common";
|
|
3
|
-
import { consola } from "consola";
|
|
4
3
|
import type { BuildResult, ResultCollector } from "../runtime/ResultCollector";
|
|
5
4
|
import { stopEngineWorker } from "../runtime/engine-stop";
|
|
6
5
|
import { setupWatchEvents } from "../runtime/engine-watch-events";
|
|
6
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
7
7
|
import type { LintWithProgramResult } from "../lint/lint-with-program";
|
|
8
8
|
import type { RebuildManager } from "../runtime/rebuild-manager";
|
|
9
9
|
import type { SerializedDiagnostic } from "../typecheck/typecheck-serialization";
|
|
10
10
|
import type { BuildEngine, BuildOutput, EngineResult, PackageInfo } from "./types";
|
|
11
11
|
|
|
12
|
-
const logger =
|
|
12
|
+
const logger = createLazyLogger("sd:cli:engine");
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 모든 빌드 워커가 공유하는 공통 빌드 워커 이벤트
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { Worker, type WorkerProxy } from "@simplysm/core-node";
|
|
4
|
-
import { consola } from "consola";
|
|
5
4
|
import type * as ClientWorkerModule from "../workers/client.worker";
|
|
6
5
|
import type { ResultCollector } from "../runtime/ResultCollector";
|
|
7
6
|
import { stopEngineWorker } from "../runtime/engine-stop";
|
|
8
7
|
import { setupWatchEvents, type NormalizedBuildInfo } from "../runtime/engine-watch-events";
|
|
8
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
9
9
|
import type { RebuildManager } from "../runtime/rebuild-manager";
|
|
10
10
|
import type { BuildEngine, BuildOutput, ClientPackageInfo, EngineResult } from "./types";
|
|
11
11
|
|
|
12
|
-
const logger =
|
|
12
|
+
const logger = createLazyLogger("sd:cli:engine:esbuild-client");
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* EsbuildClientEngine 옵션
|
|
@@ -3,9 +3,9 @@ import type { ResultCollector } from "../runtime/ResultCollector";
|
|
|
3
3
|
import type { RebuildManager } from "../runtime/rebuild-manager";
|
|
4
4
|
import type { BuildOutput, BuildPackageInfo, EngineResult } from "./types";
|
|
5
5
|
import { BaseEngine, type CommonBuildWorkerModule } from "./BaseEngine";
|
|
6
|
-
import {
|
|
6
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
7
7
|
|
|
8
|
-
const logger =
|
|
8
|
+
const logger = createLazyLogger("sd:cli:engine:ngtsc");
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* NgtscEngine 옵션
|
|
@@ -3,9 +3,9 @@ import type { ResultCollector } from "../runtime/ResultCollector";
|
|
|
3
3
|
import type { RebuildManager } from "../runtime/rebuild-manager";
|
|
4
4
|
import type { BuildOutput, EngineResult, ServerPackageInfo } from "./types";
|
|
5
5
|
import { BaseEngine, type CommonBuildWorkerModule } from "./BaseEngine";
|
|
6
|
-
import {
|
|
6
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
7
7
|
|
|
8
|
-
const logger =
|
|
8
|
+
const logger = createLazyLogger("sd:cli:engine:server");
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* ServerEsbuildEngine 옵션
|
package/src/engines/TscEngine.ts
CHANGED
|
@@ -3,9 +3,9 @@ import type { ResultCollector } from "../runtime/ResultCollector";
|
|
|
3
3
|
import type { RebuildManager } from "../runtime/rebuild-manager";
|
|
4
4
|
import type { BuildOutput, BuildPackageInfo, EngineResult } from "./types";
|
|
5
5
|
import { BaseEngine, type CommonBuildWorkerModule } from "./BaseEngine";
|
|
6
|
-
import {
|
|
6
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
7
7
|
|
|
8
|
-
const logger =
|
|
8
|
+
const logger = createLazyLogger("sd:cli:engine:tsc");
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* TscEngine 옵션
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { consola } from "consola";
|
|
2
1
|
import type { ResultCollector } from "../runtime/ResultCollector";
|
|
2
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
3
3
|
import type { RebuildManager } from "../runtime/rebuild-manager";
|
|
4
4
|
import { hasAngularCoreDependency } from "../utils/package-utils";
|
|
5
5
|
import { NgtscEngine } from "./NgtscEngine";
|
|
@@ -8,7 +8,7 @@ import { TscEngine } from "./TscEngine";
|
|
|
8
8
|
import { EsbuildClientEngine } from "./EsbuildClientEngine";
|
|
9
9
|
import type { BuildEngine, BuildPackageInfo, ClientPackageInfo, ServerPackageInfo } from "./types";
|
|
10
10
|
|
|
11
|
-
const logger =
|
|
11
|
+
const logger = createLazyLogger("sd:cli:engine");
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -3,10 +3,13 @@ import fs from "fs";
|
|
|
3
3
|
import os from "os";
|
|
4
4
|
import ts from "typescript";
|
|
5
5
|
import type esbuild from "esbuild";
|
|
6
|
-
import { consola } from "consola";
|
|
7
6
|
import { JavaScriptTransformer, Cache as AngularCache } from "@angular/build/private";
|
|
7
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
8
8
|
import type { AngularSourceFileCache } from "../angular/angular-compiler";
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
SerializedDiagnostic,
|
|
11
|
+
SerializedMessageChain,
|
|
12
|
+
} from "../typecheck/typecheck-serialization";
|
|
10
13
|
import { SdTsCompiler } from "../ts-compiler/SdTsCompiler";
|
|
11
14
|
import type { ISdTsCompilerResult } from "../ts-compiler/sd-ts-compiler-result";
|
|
12
15
|
import { FileReferenceTracker } from "./file-reference-tracker";
|
|
@@ -15,7 +18,7 @@ import { createCachedLoad, type LoadResultCache } from "./load-result-cache";
|
|
|
15
18
|
import { collectHmrCandidates, HMR_MODIFIED_FILE_LIMIT } from "../angular/hmr-candidates";
|
|
16
19
|
import { transformWorkerPatterns } from "./esbuild-worker-plugin";
|
|
17
20
|
|
|
18
|
-
const logger =
|
|
21
|
+
const logger = createLazyLogger("sd:cli:angular-plugin");
|
|
19
22
|
|
|
20
23
|
//#region Types
|
|
21
24
|
|
|
@@ -125,29 +128,67 @@ export function convertDiagnostic(
|
|
|
125
128
|
return { text, location };
|
|
126
129
|
}
|
|
127
130
|
|
|
131
|
+
function flattenSerializedMessage(
|
|
132
|
+
messageText: string | SerializedMessageChain,
|
|
133
|
+
indent = 0,
|
|
134
|
+
): string {
|
|
135
|
+
if (typeof messageText === "string") return messageText;
|
|
136
|
+
const prefix = " ".repeat(indent);
|
|
137
|
+
let text = prefix + messageText.messageText;
|
|
138
|
+
if (messageText.next != null) {
|
|
139
|
+
for (const n of messageText.next) {
|
|
140
|
+
text += "\n" + flattenSerializedMessage(n, indent + 1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return text;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function locationOf(
|
|
147
|
+
fileInfo: { fileName: string } | undefined,
|
|
148
|
+
start: number | undefined,
|
|
149
|
+
length: number | undefined,
|
|
150
|
+
program: ts.Program,
|
|
151
|
+
cwd: string,
|
|
152
|
+
): esbuild.PartialMessage["location"] {
|
|
153
|
+
if (fileInfo == null || start == null) return null;
|
|
154
|
+
const sf = program.getSourceFile(fileInfo.fileName);
|
|
155
|
+
if (sf == null) return null;
|
|
156
|
+
const pos = sf.getLineAndCharacterOfPosition(start);
|
|
157
|
+
const lineStart = sf.getLineStarts()[pos.line];
|
|
158
|
+
const lineEnd = sf.getLineStarts()[pos.line + 1] ?? sf.text.length;
|
|
159
|
+
const lineText = sf.text.slice(lineStart, lineEnd).replace(/\r?\n$/, "");
|
|
160
|
+
return {
|
|
161
|
+
file: path.relative(cwd, fileInfo.fileName),
|
|
162
|
+
line: pos.line + 1,
|
|
163
|
+
column: pos.character,
|
|
164
|
+
lineText,
|
|
165
|
+
length: length ?? 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
128
169
|
export function convertSerializedDiagnosticToEsbuild(
|
|
129
170
|
d: SerializedDiagnostic,
|
|
130
171
|
program: ts.Program,
|
|
131
172
|
cwd: string,
|
|
132
173
|
): esbuild.PartialMessage {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
line: pos.line + 1,
|
|
144
|
-
column: pos.character,
|
|
145
|
-
lineText,
|
|
146
|
-
length: d.length ?? 0,
|
|
147
|
-
};
|
|
174
|
+
const location = locationOf(d.file, d.start, d.length, program, cwd);
|
|
175
|
+
const text = flattenSerializedMessage(d.messageText);
|
|
176
|
+
|
|
177
|
+
const notes: esbuild.PartialNote[] = [];
|
|
178
|
+
if (d.relatedInformation != null) {
|
|
179
|
+
for (const ri of d.relatedInformation) {
|
|
180
|
+
notes.push({
|
|
181
|
+
text: flattenSerializedMessage(ri.messageText),
|
|
182
|
+
location: locationOf(ri.file, ri.start, ri.length, program, cwd),
|
|
183
|
+
});
|
|
148
184
|
}
|
|
149
185
|
}
|
|
150
|
-
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
text,
|
|
189
|
+
location,
|
|
190
|
+
notes: notes.length > 0 ? notes : undefined,
|
|
191
|
+
};
|
|
151
192
|
}
|
|
152
193
|
|
|
153
194
|
//#endregion
|
|
@@ -340,7 +381,11 @@ export function createAngularCompilerPlugin(
|
|
|
340
381
|
// ── emitResults → typeScriptFileCache (Worker 패턴 처리 포함) ──
|
|
341
382
|
for (const { contents, sourceFileName } of compileResult.emitResults ?? []) {
|
|
342
383
|
const normalized = path.normalize(sourceFileName);
|
|
343
|
-
|
|
384
|
+
// emitResults.contents는 ngtsc가 이미 JS로 방출한 결과이므로 TS 재변환 스킵.
|
|
385
|
+
// sourceFileName은 원본 .ts 경로이지만 content는 JS이다.
|
|
386
|
+
const workerResult = transformWorkerPatterns(contents, normalized, build, {
|
|
387
|
+
skipTsTransform: true,
|
|
388
|
+
});
|
|
344
389
|
if (workerResult != null) {
|
|
345
390
|
typeScriptFileCache.set(normalized, workerResult.contents);
|
|
346
391
|
errors.push(...workerResult.errors);
|
|
@@ -541,7 +586,7 @@ export function createAngularCompilerPlugin(
|
|
|
541
586
|
sideEffects,
|
|
542
587
|
);
|
|
543
588
|
|
|
544
|
-
// Worker 패턴 처리
|
|
589
|
+
// Worker 패턴 처리
|
|
545
590
|
const textContents = new TextDecoder().decode(contents);
|
|
546
591
|
const workerResult = transformWorkerPatterns(textContents, request, build);
|
|
547
592
|
if (workerResult != null) {
|
|
@@ -3,10 +3,10 @@ import { readFileSync, existsSync } from "fs";
|
|
|
3
3
|
import fs from "fs/promises";
|
|
4
4
|
import { createRequire } from "module";
|
|
5
5
|
import type esbuild from "esbuild";
|
|
6
|
-
import {
|
|
6
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
7
7
|
import { addJsExtensionToImports } from "../utils/output-path-rewriter";
|
|
8
8
|
|
|
9
|
-
const logger =
|
|
9
|
+
const logger = createLazyLogger("sd:cli:esbuild-config");
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* esbuild outputFiles에서 변경된 파일만 디스크에 쓴다.
|
|
@@ -41,6 +41,8 @@ function isImportMetaUrl(node: any): boolean {
|
|
|
41
41
|
*
|
|
42
42
|
* 정규식과 달리 주석, 문자열 리터럴 내부의 패턴을 오탐하지 않는다.
|
|
43
43
|
* 파싱 실패 시 빈 배열을 반환한다.
|
|
44
|
+
*
|
|
45
|
+
* @param content - JavaScript 소스 코드. TypeScript는 상위 `transformWorkerPatterns()`가 사전 변환한다.
|
|
44
46
|
*/
|
|
45
47
|
export function findWorkerPatterns(content: string): WorkerMatch[] {
|
|
46
48
|
let ast: acorn.Node;
|
|
@@ -153,6 +155,17 @@ export interface TransformWorkerResult {
|
|
|
153
155
|
workerMetafile?: esbuild.Metafile;
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
/**
|
|
159
|
+
* transformWorkerPatterns 옵션.
|
|
160
|
+
*/
|
|
161
|
+
export interface TransformWorkerOptions {
|
|
162
|
+
/**
|
|
163
|
+
* true이면 .ts/.cts/.mts 확장자여도 esbuild.transformSync(loader: "ts")를 스킵한다.
|
|
164
|
+
* 호출자가 content가 이미 JS임을 보장하는 경우에만 사용한다 (예: ngtsc emit 결과).
|
|
165
|
+
*/
|
|
166
|
+
skipTsTransform?: boolean;
|
|
167
|
+
}
|
|
168
|
+
|
|
156
169
|
/**
|
|
157
170
|
* Worker 파일을 esbuild.buildSync()로 별도 ESM 번들로 빌드한다.
|
|
158
171
|
* esbuild-angular-compiler-plugin.ts의 bundleWebWorker를 기반으로 작성.
|
|
@@ -202,7 +215,7 @@ function bundleWorker(
|
|
|
202
215
|
* 파일 내용에서 Worker/SharedWorker 패턴을 감지하여 Worker 파일을 번들링하고
|
|
203
216
|
* URL 경로를 번들된 파일 경로로 치환한다.
|
|
204
217
|
*
|
|
205
|
-
* Angular 플러그인 등 외부에서 직접
|
|
218
|
+
* Angular 컴파일러 플러그인 등 외부에서 직접 호출한다.
|
|
206
219
|
*
|
|
207
220
|
* @returns 변환 결과. 패턴이 없으면 undefined.
|
|
208
221
|
*/
|
|
@@ -210,9 +223,14 @@ export function transformWorkerPatterns(
|
|
|
210
223
|
content: string,
|
|
211
224
|
filePath: string,
|
|
212
225
|
build: esbuild.PluginBuild,
|
|
226
|
+
options?: TransformWorkerOptions,
|
|
213
227
|
): TransformWorkerResult | undefined {
|
|
214
|
-
// 빠른 사전 필터 —
|
|
215
|
-
|
|
228
|
+
// 빠른 사전 필터 — new Worker(), new SharedWorker(), import.meta.resolve()의 단어 경계
|
|
229
|
+
// 호출 형태만 통과시킨다. 식별자·타입·interface로만 등장하는 Worker 키워드는 차단하여
|
|
230
|
+
// TS transformSync 오버헤드를 줄인다. 정확성은 후속 AST 판별(findWorkerPatterns)이 담당한다.
|
|
231
|
+
// new 와 Worker/SharedWorker 사이에 주석이 낀 호출(예: `new /* c */ Worker`)은 의도된
|
|
232
|
+
// 트레이드오프로 탈락한다. 실발생 가능성이 희박하여 현행을 유지한다.
|
|
233
|
+
if (!/\b(new\s+Worker|new\s+SharedWorker|import\.meta\.resolve)\b/.test(content)) {
|
|
216
234
|
return undefined;
|
|
217
235
|
}
|
|
218
236
|
|
|
@@ -221,8 +239,10 @@ export function transformWorkerPatterns(
|
|
|
221
239
|
|
|
222
240
|
// TS(.ts/.cts/.mts)는 JS로 변환한 후 AST 파싱. acorn은 TS 구문을 처리하지 못하므로
|
|
223
241
|
// import type, 타입 어노테이션 등이 있으면 파싱 실패로 Worker 패턴이 조용히 누락된다.
|
|
242
|
+
// skipTsTransform이 true이면 호출자가 content가 이미 JS임을 보장하므로 변환을 스킵한다
|
|
243
|
+
// (예: Angular 컴파일러 플러그인이 ngtsc emit 결과를 전달하는 경로).
|
|
224
244
|
let effectiveContent = content;
|
|
225
|
-
if (/\.[cm]?ts$/.test(filePath)) {
|
|
245
|
+
if (options?.skipTsTransform !== true && /\.[cm]?ts$/.test(filePath)) {
|
|
226
246
|
try {
|
|
227
247
|
const transformed = build.esbuild.transformSync(content, {
|
|
228
248
|
loader: "ts",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type ts from "typescript";
|
|
2
2
|
import { ESLint } from "eslint";
|
|
3
3
|
import { pathx } from "@simplysm/core-node";
|
|
4
|
-
import {
|
|
4
|
+
import { createLazyLogger } from "../runtime/lazy-logger";
|
|
5
5
|
|
|
6
|
-
const logger =
|
|
6
|
+
const logger = createLazyLogger("sd:cli:lint-with-program");
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* LintWithProgramRunner.lint()가 반환하는 lint 결과
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { consola, type ConsolaInstance } from "consola";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 모듈 레벨에서 선언해도 안전한 lazy logger 프록시.
|
|
5
|
+
*
|
|
6
|
+
* consola.withTag()는 호출 시점의 consola.options(level/reporters)를
|
|
7
|
+
* 스냅샷 복사한 새 인스턴스를 반환한다. 모듈 import 시점에 호출하면
|
|
8
|
+
* setupConsola 이전의 기본 level(info) 상태가 고정되어 이후 setupConsola가
|
|
9
|
+
* level을 debug로 올려도 반영되지 않는다.
|
|
10
|
+
*
|
|
11
|
+
* createLazyLogger는 실제 ConsolaInstance 생성을 첫 접근 시점까지 미뤄
|
|
12
|
+
* setupConsola 이후의 설정이 반영된 스냅샷을 갖도록 한다.
|
|
13
|
+
*/
|
|
14
|
+
export function createLazyLogger(tag: string): ConsolaInstance {
|
|
15
|
+
let cached: ConsolaInstance | undefined;
|
|
16
|
+
return new Proxy({} as ConsolaInstance, {
|
|
17
|
+
get(_target, prop) {
|
|
18
|
+
cached ??= consola.withTag(tag);
|
|
19
|
+
const value: unknown = Reflect.get(cached, prop);
|
|
20
|
+
return typeof value === "function" ? value.bind(cached) : value;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
package/src/sd-cli-entry.ts
CHANGED
|
@@ -15,10 +15,10 @@ import path from "path";
|
|
|
15
15
|
import fs from "fs";
|
|
16
16
|
import { fileURLToPath } from "url";
|
|
17
17
|
import { EventEmitter } from "node:events";
|
|
18
|
-
import { consola } from "consola";
|
|
19
18
|
import { setupConsola } from "@simplysm/core-node";
|
|
19
|
+
import { createLazyLogger } from "./runtime/lazy-logger";
|
|
20
20
|
|
|
21
|
-
const logger =
|
|
21
|
+
const logger = createLazyLogger("sd:cli:entry");
|
|
22
22
|
|
|
23
23
|
Error.stackTraceLimit = Infinity;
|
|
24
24
|
EventEmitter.defaultMaxListeners = 100;
|
package/src/sd-cli.ts
CHANGED
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { cpx, setupConsola } from "@simplysm/core-node";
|
|
11
|
-
import { consola } from "consola";
|
|
12
11
|
import os from "os";
|
|
13
12
|
import path from "path";
|
|
14
13
|
import { fileURLToPath } from "url";
|
|
14
|
+
import { createLazyLogger } from "./runtime/lazy-logger";
|
|
15
15
|
|
|
16
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
17
|
const __dirname = path.dirname(__filename);
|
|
18
18
|
const isDev = path.extname(__filename) === ".ts";
|
|
19
|
-
const logger =
|
|
19
|
+
const logger = createLazyLogger("sd:cli");
|
|
20
20
|
|
|
21
21
|
if (isDev) {
|
|
22
22
|
// 개발 모드 (.ts): affinity 적용 후 직접 실행
|