@simplysm/sd-cli 14.0.19 → 14.0.22
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/dist/angular/vite-postcss-inline-plugin.d.ts.map +1 -1
- package/dist/angular/vite-postcss-inline-plugin.js +4 -1
- package/dist/angular/vite-postcss-inline-plugin.js.map +1 -1
- package/dist/capacitor/capacitor-android.d.ts +16 -0
- package/dist/capacitor/capacitor-android.d.ts.map +1 -0
- package/dist/capacitor/capacitor-android.js +289 -0
- package/dist/capacitor/capacitor-android.js.map +1 -0
- package/dist/capacitor/capacitor.d.ts +0 -49
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +4 -244
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/check.js +2 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/lint.d.ts +1 -42
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +1 -151
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +2 -1
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/typecheck.d.ts +3 -40
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +3 -232
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/electron/electron.js +11 -4
- package/dist/electron/electron.js.map +1 -1
- package/dist/engines/ViteEngine.js +1 -1
- package/dist/engines/ViteEngine.js.map +1 -1
- package/dist/engines/types.d.ts +2 -0
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/infra/ResultCollector.d.ts +2 -2
- package/dist/infra/ResultCollector.d.ts.map +1 -1
- package/dist/infra/ResultCollector.js +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts +2 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.js +28 -16
- package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
- package/dist/orchestrators/TypecheckOrchestrator.d.ts +74 -0
- package/dist/orchestrators/TypecheckOrchestrator.d.ts.map +1 -0
- package/dist/orchestrators/TypecheckOrchestrator.js +285 -0
- package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -0
- package/dist/sd-cli.js +6 -1
- package/dist/sd-cli.js.map +1 -1
- package/dist/utils/lint-core.d.ts +43 -0
- package/dist/utils/lint-core.d.ts.map +1 -0
- package/dist/utils/lint-core.js +154 -0
- package/dist/utils/lint-core.js.map +1 -0
- package/dist/utils/lint-utils.d.ts +1 -1
- package/dist/utils/lint-utils.d.ts.map +1 -1
- package/dist/utils/output-utils.d.ts +2 -2
- package/dist/utils/output-utils.d.ts.map +1 -1
- package/dist/utils/output-utils.js.map +1 -1
- package/dist/utils/server-production-files.d.ts +22 -0
- package/dist/utils/server-production-files.d.ts.map +1 -0
- package/dist/utils/server-production-files.js +162 -0
- package/dist/utils/server-production-files.js.map +1 -0
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +7 -1
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/lint.worker.d.ts +1 -1
- package/dist/workers/lint.worker.d.ts.map +1 -1
- package/dist/workers/lint.worker.js +1 -1
- package/dist/workers/lint.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +12 -161
- package/dist/workers/server-build.worker.js.map +1 -1
- package/package.json +4 -4
- package/src/angular/vite-postcss-inline-plugin.ts +5 -1
- package/src/capacitor/capacitor-android.ts +368 -0
- package/src/capacitor/capacitor.ts +4 -317
- package/src/commands/check.ts +2 -2
- package/src/commands/lint.ts +1 -201
- package/src/commands/publish.ts +2 -1
- package/src/commands/typecheck.ts +7 -292
- package/src/electron/electron.ts +4 -4
- package/src/engines/ViteEngine.ts +1 -1
- package/src/engines/types.ts +3 -0
- package/src/infra/ResultCollector.ts +2 -2
- package/src/orchestrators/DevWatchOrchestrator.ts +35 -20
- package/src/orchestrators/TypecheckOrchestrator.ts +364 -0
- package/src/sd-cli.ts +6 -1
- package/src/utils/lint-core.ts +205 -0
- package/src/utils/lint-utils.ts +1 -1
- package/src/utils/output-utils.ts +3 -3
- package/src/utils/server-production-files.ts +186 -0
- package/src/utils/vite-config.ts +1 -1
- package/src/workers/client.worker.ts +7 -1
- package/src/workers/lint.worker.ts +1 -1
- package/src/workers/server-build.worker.ts +11 -185
- package/tests/angular/vite-postcss-inline-plugin.spec.ts +10 -0
- package/tests/capacitor/capacitor-android-exports.verify.md +11 -0
- package/tests/capacitor/capacitor-android.spec.ts +219 -0
- package/tests/capacitor/capacitor-build.spec.ts +17 -21
- package/tests/capacitor/capacitor-icon.spec.ts +17 -19
- package/tests/capacitor/capacitor-init.spec.ts +18 -14
- package/tests/capacitor/capacitor-run.spec.ts +10 -24
- package/tests/capacitor/capacitor-workspace.spec.ts +10 -15
- package/tests/commands/check.spec.ts +2 -2
- package/tests/commands/lint.spec.ts +33 -194
- package/tests/commands/publish-set.verify.md +7 -0
- package/tests/electron/electron-symlink-cleanup.verify.md +8 -0
- package/tests/engines/vite-engine.spec.ts +29 -0
- package/tests/infra/result-collector.spec.ts +11 -0
- package/tests/orchestrators/dev-watch-orchestrator.spec.ts +70 -0
- package/tests/orchestrators/dist-delete-watcher.verify.md +10 -0
- package/tests/orchestrators/typecheck-orchestrator.spec.ts +180 -0
- package/tests/sd-cli-catch-all.verify.md +7 -0
- package/tests/utils/lint-core-import-paths.verify.md +10 -0
- package/tests/utils/lint-core.spec.ts +188 -0
- package/tests/utils/server-production-files-import-paths.verify.md +14 -0
- package/tests/workers/client-worker.spec.ts +92 -0
- package/tests/workers/server-build-context-dispose.verify.md +8 -0
- package/tests/workers/server-build-worker.spec.ts +39 -0
|
@@ -1,294 +1,9 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
import { err as errNs } from "@simplysm/core-common";
|
|
3
|
-
import { pathx } from "@simplysm/core-node";
|
|
4
|
-
import { consola } from "consola";
|
|
5
|
-
import { loadSdConfig } from "../utils/sd-config";
|
|
6
|
-
import { deserializeDiagnostic } from "../utils/typecheck-serialization";
|
|
7
|
-
import { createBuildEngine } from "../engines/index";
|
|
8
|
-
import { typecheckNonPackageFiles } from "../utils/typecheck-non-package";
|
|
9
|
-
import { runWithConcurrency, getMaxConcurrency } from "../utils/concurrency";
|
|
10
|
-
import { discoverWorkspacePackages, mergeTestsPackagesIntoConfig } from "../utils/package-utils";
|
|
11
|
-
import type { EngineResult } from "../engines/types";
|
|
12
|
-
import type { TypecheckEnv } from "../utils/tsconfig";
|
|
13
|
-
import { toTypecheckEnvs } from "../utils/tsconfig";
|
|
14
|
-
|
|
15
|
-
//#region Types
|
|
16
|
-
|
|
17
1
|
/**
|
|
18
|
-
*
|
|
2
|
+
* TypecheckOrchestrator에 위임하는 CLI 래퍼.
|
|
3
|
+
* 타입과 executeTypecheck를 re-export하여 기존 호출자와의 호환성을 유지한다.
|
|
19
4
|
*/
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/** true이면 엔진에 lint: true를 전달하고 lint 결과를 수집한다 */
|
|
26
|
-
lint?: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* TypeScript 타입체크 실행 결과
|
|
31
|
-
*/
|
|
32
|
-
export interface TypecheckResult {
|
|
33
|
-
success: boolean;
|
|
34
|
-
errorCount: number;
|
|
35
|
-
warningCount: number;
|
|
36
|
-
formattedOutput: string;
|
|
37
|
-
/** lint 결과 (TypecheckOptions.lint가 true일 때 존재) */
|
|
38
|
-
lint?: {
|
|
39
|
-
success: boolean;
|
|
40
|
-
errorCount: number;
|
|
41
|
-
warningCount: number;
|
|
42
|
-
formattedOutput: string;
|
|
43
|
-
};
|
|
44
|
-
/** 건너뛴 scripts 패키지 경로 (별도 lint용) */
|
|
45
|
-
scriptsPackagePaths?: string[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
|
|
50
|
-
//#region Utilities
|
|
51
|
-
|
|
52
|
-
const TARGET_PATH_PATTERN = /^(?:packages|tests)\/([^/]+)/;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 대상 경로에서 패키지명을 추출한다.
|
|
56
|
-
* "packages/core-common" → "core-common"
|
|
57
|
-
* "tests/orm" → "orm"
|
|
58
|
-
*/
|
|
59
|
-
function extractTargetPackageNames(targets: string[]): Set<string> {
|
|
60
|
-
const names = new Set<string>();
|
|
61
|
-
for (const target of targets) {
|
|
62
|
-
const match = target.match(TARGET_PATH_PATTERN);
|
|
63
|
-
if (match) names.add(match[1]);
|
|
64
|
-
}
|
|
65
|
-
return names;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
//#endregion
|
|
69
|
-
|
|
70
|
-
//#region Main
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* BuildEngine을 사용하여 TypeScript 타입체크를 실행한다.
|
|
74
|
-
*
|
|
75
|
-
* sd.config.ts의 각 패키지에 대해:
|
|
76
|
-
* - 라이브러리/서버 패키지 → BuildEngine.run({js:false, dts:false})
|
|
77
|
-
* - client 패키지 → browser target으로 변환하여 포함
|
|
78
|
-
* - scripts 패키지 → 타입체크 제외 (별도 lint만 수행)
|
|
79
|
-
* 비패키지 파일 → typecheckNonPackageFiles 유틸리티
|
|
80
|
-
*
|
|
81
|
-
* @param options - 타입체크 실행 옵션
|
|
82
|
-
* @returns 타입체크 결과 (성공 여부, 에러/경고 수, 포매팅된 출력 문자열)
|
|
83
|
-
*/
|
|
84
|
-
export async function executeTypecheck(options: TypecheckOptions): Promise<TypecheckResult> {
|
|
85
|
-
const { targets } = options;
|
|
86
|
-
const cwd = process.cwd();
|
|
87
|
-
const logger = consola.withTag("sd:cli:typecheck");
|
|
88
|
-
|
|
89
|
-
const phaseLabel = options.lint === true ? "타입체크/린트" : "타입체크";
|
|
90
|
-
|
|
91
|
-
logger.debug(`${phaseLabel} 시작`, { targets, lint: options.lint });
|
|
92
|
-
|
|
93
|
-
const formatHost: ts.FormatDiagnosticsHost = {
|
|
94
|
-
getCanonicalFileName: (f) => f,
|
|
95
|
-
getCurrentDirectory: () => cwd,
|
|
96
|
-
getNewLine: () => ts.sys.newLine,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// sd.config.ts 로드
|
|
100
|
-
const sdConfig = await loadSdConfig({ cwd, dev: false, opt: options.options });
|
|
101
|
-
logger.debug("sd.config.ts 로드 완료");
|
|
102
|
-
|
|
103
|
-
// 워크스페이스 패키지 탐색 및 tests/를 설정에 병합
|
|
104
|
-
const workspacePackages = discoverWorkspacePackages(cwd);
|
|
105
|
-
const { merged, pathMap } = mergeTestsPackagesIntoConfig(sdConfig.packages, workspacePackages);
|
|
106
|
-
|
|
107
|
-
// 경로 기반 대상에서 패키지명 결정
|
|
108
|
-
const targetNames = extractTargetPackageNames(targets);
|
|
109
|
-
|
|
110
|
-
// scripts 패키지 경로 수집 (별도 lint용)
|
|
111
|
-
const scriptsPackagePaths: string[] = [];
|
|
112
|
-
|
|
113
|
-
// 타���체크할 패키지 수집 (scripts 제외), env별로 확장
|
|
114
|
-
const typecheckTasks: Array<{ name: string; dir: string; config: any; env: TypecheckEnv }> = [];
|
|
115
|
-
for (const [name, config] of Object.entries(merged)) {
|
|
116
|
-
if (config == null) continue;
|
|
117
|
-
if (config.target === "scripts") {
|
|
118
|
-
if (targets.length === 0 || targetNames.has(name)) {
|
|
119
|
-
const relPath = pathMap.get(name) ?? `packages/${name}`;
|
|
120
|
-
scriptsPackagePaths.push(relPath);
|
|
121
|
-
}
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (targets.length > 0 && !targetNames.has(name)) continue;
|
|
125
|
-
|
|
126
|
-
const relPath = pathMap.get(name) ?? `packages/${name}`;
|
|
127
|
-
// 클라이언트 패키지의 경우 browser 타겟을 사용하여 createBuildEngine이 ViteEngine 대신 NgtscEngine으로 라우팅되도록 함
|
|
128
|
-
const typecheckConfig = config.target === "client" ? { target: "browser" as const } : config;
|
|
129
|
-
const envs = toTypecheckEnvs(config.target);
|
|
130
|
-
for (const env of envs) {
|
|
131
|
-
typecheckTasks.push({
|
|
132
|
-
name,
|
|
133
|
-
dir: pathx.posixResolve(cwd, relPath),
|
|
134
|
-
config: typecheckConfig,
|
|
135
|
-
env,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 비패키지 타입체크: 대상이 지정되지 않은 경우에만 (= 전체 검사)
|
|
141
|
-
const includeNonPackage = targets.length === 0;
|
|
142
|
-
|
|
143
|
-
if (typecheckTasks.length === 0 && !includeNonPackage) {
|
|
144
|
-
logger.info(`${phaseLabel} 대상 없음`);
|
|
145
|
-
return {
|
|
146
|
-
success: true,
|
|
147
|
-
errorCount: 0,
|
|
148
|
-
warningCount: 0,
|
|
149
|
-
formattedOutput: `✔ ${phaseLabel} 대상 없음.\n`,
|
|
150
|
-
scriptsPackagePaths: scriptsPackagePaths.length > 0 ? scriptsPackagePaths : undefined,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 동시성 제한이 있는 BuildEngine 작업 생성
|
|
155
|
-
const allDiagnostics: ts.Diagnostic[] = [];
|
|
156
|
-
let totalErrorCount = 0;
|
|
157
|
-
let totalWarningCount = 0;
|
|
158
|
-
const fileCache = new Map<string, string>();
|
|
159
|
-
|
|
160
|
-
// Lint 결과 집계
|
|
161
|
-
let lintErrorCount = 0;
|
|
162
|
-
let lintWarningCount = 0;
|
|
163
|
-
let lintSuccess = true;
|
|
164
|
-
const lintOutputs: string[] = [];
|
|
165
|
-
|
|
166
|
-
if (typecheckTasks.length > 0) {
|
|
167
|
-
const tasks = typecheckTasks.map((task) => async (): Promise<EngineResult> => {
|
|
168
|
-
const label = `${task.name}:${task.env}`;
|
|
169
|
-
const engine = createBuildEngine(
|
|
170
|
-
{ name: task.name, dir: task.dir, config: task.config },
|
|
171
|
-
{ cwd },
|
|
172
|
-
);
|
|
173
|
-
try {
|
|
174
|
-
logger.debug(`[${label}] 타입체크 시작됨`);
|
|
175
|
-
const result = await engine.run({
|
|
176
|
-
js: false,
|
|
177
|
-
dts: false,
|
|
178
|
-
env: task.env,
|
|
179
|
-
includeTests: true,
|
|
180
|
-
...(options.lint === true ? { lint: true } : {}),
|
|
181
|
-
});
|
|
182
|
-
logger.debug(`[${label}] 타입체크 ${result.build.success ? "완료" : "실패"}`);
|
|
183
|
-
return result;
|
|
184
|
-
} catch (err) {
|
|
185
|
-
const message = errNs.message(err);
|
|
186
|
-
const stack = err instanceof Error ? err.stack : undefined;
|
|
187
|
-
logger.error(`[${label}] 엔진 작업 실패: ${message}`);
|
|
188
|
-
if (stack != null) {
|
|
189
|
-
logger.debug(`[${label}] 스택 트레이스:\n${stack}`);
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
build: {
|
|
193
|
-
success: false,
|
|
194
|
-
errors: [`[${label}] ${message}`],
|
|
195
|
-
warnings: [],
|
|
196
|
-
diagnostics: [],
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
} finally {
|
|
200
|
-
await engine.stop();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const concurrency = getMaxConcurrency();
|
|
205
|
-
logger.start(`${phaseLabel} 실행 중... (${tasks.length}개 작업, 동시성: ${concurrency})`);
|
|
206
|
-
const results = await runWithConcurrency(tasks, concurrency);
|
|
207
|
-
logger.success(`${phaseLabel} 실행 완료`);
|
|
208
|
-
|
|
209
|
-
// 엔진 결과 집계 (모든 task는 catch로 인해 항상 fulfilled)
|
|
210
|
-
for (const settled of results) {
|
|
211
|
-
if (settled.status !== "fulfilled") continue;
|
|
212
|
-
const engineResult = settled.value;
|
|
213
|
-
const buildDiags = engineResult.build.diagnostics.map((d) => deserializeDiagnostic(d, fileCache));
|
|
214
|
-
allDiagnostics.push(...buildDiags);
|
|
215
|
-
// 역직렬화된 진단 정보에서 에러/경고 수 집계
|
|
216
|
-
// 숫자 카테고리 값 사용 (ts.DiagnosticCategory: Error=1, Warning=0)
|
|
217
|
-
totalErrorCount += buildDiags.filter((d) => d.category === 1).length;
|
|
218
|
-
totalWarningCount += buildDiags.filter((d) => d.category === 0).length;
|
|
219
|
-
if (!engineResult.build.success && buildDiags.length === 0) {
|
|
220
|
-
for (const errMsg of engineResult.build.errors) {
|
|
221
|
-
allDiagnostics.push({
|
|
222
|
-
category: 1,
|
|
223
|
-
code: 0,
|
|
224
|
-
messageText: errMsg,
|
|
225
|
-
file: undefined,
|
|
226
|
-
start: undefined,
|
|
227
|
-
length: undefined,
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
totalErrorCount += engineResult.build.errors.length || 1;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Lint 결과 수집
|
|
234
|
-
if (engineResult.lint != null) {
|
|
235
|
-
lintErrorCount += engineResult.lint.errorCount;
|
|
236
|
-
lintWarningCount += engineResult.lint.warningCount;
|
|
237
|
-
if (!engineResult.lint.success) lintSuccess = false;
|
|
238
|
-
if (engineResult.lint.formattedOutput !== "") {
|
|
239
|
-
lintOutputs.push(engineResult.lint.formattedOutput);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 비패키지 타입체크
|
|
246
|
-
if (includeNonPackage) {
|
|
247
|
-
logger.debug("비패키지 타입체크 실행 중");
|
|
248
|
-
const nonPkgResult = typecheckNonPackageFiles(cwd);
|
|
249
|
-
totalErrorCount += nonPkgResult.errorCount;
|
|
250
|
-
totalWarningCount += nonPkgResult.warningCount;
|
|
251
|
-
const nonPkgDiags = nonPkgResult.diagnostics.map((d) => deserializeDiagnostic(d, fileCache));
|
|
252
|
-
allDiagnostics.push(...nonPkgDiags);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// 요약 로그
|
|
256
|
-
const resultMeta: Record<string, number> = { errorCount: totalErrorCount, warningCount: totalWarningCount };
|
|
257
|
-
if (options.lint === true) {
|
|
258
|
-
resultMeta["lintErrorCount"] = lintErrorCount;
|
|
259
|
-
resultMeta["lintWarningCount"] = lintWarningCount;
|
|
260
|
-
}
|
|
261
|
-
if (totalErrorCount > 0) {
|
|
262
|
-
logger.error(`${phaseLabel} 에러 발생`, resultMeta);
|
|
263
|
-
} else {
|
|
264
|
-
logger.info(`${phaseLabel} 완료`, resultMeta);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// 진단 출력 포매팅
|
|
268
|
-
let formattedOutput = "";
|
|
269
|
-
if (allDiagnostics.length > 0) {
|
|
270
|
-
const uniqueDiagnostics = ts.sortAndDeduplicateDiagnostics(allDiagnostics);
|
|
271
|
-
formattedOutput = ts.formatDiagnosticsWithColorAndContext(uniqueDiagnostics, formatHost);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// lint가 요청된 경우 lint 결과 생성
|
|
275
|
-
const lintResult = options.lint === true
|
|
276
|
-
? {
|
|
277
|
-
success: lintSuccess,
|
|
278
|
-
errorCount: lintErrorCount,
|
|
279
|
-
warningCount: lintWarningCount,
|
|
280
|
-
formattedOutput: lintOutputs.join("\n"),
|
|
281
|
-
}
|
|
282
|
-
: undefined;
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
success: totalErrorCount === 0,
|
|
286
|
-
errorCount: totalErrorCount,
|
|
287
|
-
warningCount: totalWarningCount,
|
|
288
|
-
formattedOutput,
|
|
289
|
-
lint: lintResult,
|
|
290
|
-
scriptsPackagePaths: scriptsPackagePaths.length > 0 ? scriptsPackagePaths : undefined,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
//#endregion
|
|
5
|
+
export {
|
|
6
|
+
executeTypecheck,
|
|
7
|
+
type TypecheckOptions,
|
|
8
|
+
type TypecheckResult,
|
|
9
|
+
} from "../orchestrators/TypecheckOrchestrator";
|
package/src/electron/electron.ts
CHANGED
|
@@ -348,12 +348,12 @@ export class Electron {
|
|
|
348
348
|
try {
|
|
349
349
|
fs.writeFileSync(testTarget, "test");
|
|
350
350
|
fs.symlinkSync(testTarget, testLink, "file");
|
|
351
|
-
|
|
352
|
-
fs.unlinkSync(testLink);
|
|
353
|
-
fs.unlinkSync(testTarget);
|
|
354
|
-
return isSymlink;
|
|
351
|
+
return fs.lstatSync(testLink).isSymbolicLink();
|
|
355
352
|
} catch {
|
|
356
353
|
return false;
|
|
354
|
+
} finally {
|
|
355
|
+
try { fs.unlinkSync(testLink); } catch { /* 파일 없으면 무시 */ }
|
|
356
|
+
try { fs.unlinkSync(testTarget); } catch { /* 파일 없으면 무시 */ }
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
|
|
@@ -110,7 +110,7 @@ export class ViteEngine implements BuildEngine {
|
|
|
110
110
|
|
|
111
111
|
// 리빌드 이벤트 처리 (HMR)
|
|
112
112
|
let resolver: (() => void) | undefined;
|
|
113
|
-
const workerKey =
|
|
113
|
+
const workerKey = `${this._pkg.name}:build`;
|
|
114
114
|
|
|
115
115
|
this._worker!.on("buildStart", () => {
|
|
116
116
|
if (this._rebuildManager != null) {
|
package/src/engines/types.ts
CHANGED
|
@@ -74,6 +74,7 @@ export class DevWatchOrchestrator {
|
|
|
74
74
|
|
|
75
75
|
// 워처
|
|
76
76
|
private _copySrcWatchers: FsWatcher[] = [];
|
|
77
|
+
private _distDeleteWatchers: FsWatcher[] = [];
|
|
77
78
|
private readonly _watchHookWatchers: FsWatcher[] = [];
|
|
78
79
|
private readonly _watchHookChildren = new Map<string, ChildProcess>();
|
|
79
80
|
private _replaceDepWatcher: WatchReplaceDepResult | undefined;
|
|
@@ -236,11 +237,25 @@ export class DevWatchOrchestrator {
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
async shutdown(): Promise<void> {
|
|
240
|
+
// replaceDepWatcher는 항상 정리 (initialize 부분 실패 대응 — DESIGN-002)
|
|
241
|
+
this._replaceDepWatcher?.dispose();
|
|
242
|
+
this._replaceDepWatcher = undefined;
|
|
243
|
+
|
|
239
244
|
if (!this._hasPackages) {
|
|
240
245
|
return;
|
|
241
246
|
}
|
|
242
247
|
this._logger.debug("shutdown 시작");
|
|
243
248
|
|
|
249
|
+
// pending 타이머 정리 (DESIGN-001)
|
|
250
|
+
if (this._printServersTimer != null) {
|
|
251
|
+
clearTimeout(this._printServersTimer);
|
|
252
|
+
this._printServersTimer = undefined;
|
|
253
|
+
}
|
|
254
|
+
if (this._serverRestartTimer != null) {
|
|
255
|
+
clearTimeout(this._serverRestartTimer);
|
|
256
|
+
this._serverRestartTimer = undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
244
259
|
process.stdout.write("⏳ 종료 중...\n");
|
|
245
260
|
|
|
246
261
|
const shutdownTasks: Array<Promise<void>> = [];
|
|
@@ -252,6 +267,7 @@ export class DevWatchOrchestrator {
|
|
|
252
267
|
|
|
253
268
|
// 워처 종료 (watch 모드)
|
|
254
269
|
shutdownTasks.push(...this._copySrcWatchers.map((w) => w.close()));
|
|
270
|
+
shutdownTasks.push(...this._distDeleteWatchers.map((w) => w.close()));
|
|
255
271
|
shutdownTasks.push(...this._watchHookWatchers.map((w) => w.close()));
|
|
256
272
|
|
|
257
273
|
// hook 자식 프로세스 종료
|
|
@@ -267,8 +283,8 @@ export class DevWatchOrchestrator {
|
|
|
267
283
|
|
|
268
284
|
await Promise.all(shutdownTasks);
|
|
269
285
|
this._copySrcWatchers = [];
|
|
286
|
+
this._distDeleteWatchers = [];
|
|
270
287
|
this._watchHookWatchers.length = 0;
|
|
271
|
-
this._replaceDepWatcher?.dispose();
|
|
272
288
|
|
|
273
289
|
process.stdout.write("✔ 종료 완료\n");
|
|
274
290
|
}
|
|
@@ -278,17 +294,18 @@ export class DevWatchOrchestrator {
|
|
|
278
294
|
private async _startWatchMode(): Promise<void> {
|
|
279
295
|
this._logger.debug("watch 모드 시작");
|
|
280
296
|
|
|
281
|
-
//
|
|
282
|
-
{
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
|
|
297
|
+
// 라이브러리 패키지 dist 삭제 감지 워처
|
|
298
|
+
for (const pkg of this._libraryPackages) {
|
|
299
|
+
const distDir = pathx.posixResolve(pkg.dir, "dist");
|
|
300
|
+
const watcher = await FsWatcher.watch([distDir]);
|
|
301
|
+
watcher.onChange({ delay: 100 }, (changes) => {
|
|
286
302
|
for (const c of changes) {
|
|
287
303
|
if (c.event === "unlink" || c.event === "unlinkDir") {
|
|
288
|
-
this._logger.error(`[
|
|
304
|
+
this._logger.error(`[dist-delete:${pkg.name}] ${c.event}: ${c.path}\n${new Error().stack}`);
|
|
289
305
|
}
|
|
290
306
|
}
|
|
291
307
|
});
|
|
308
|
+
this._distDeleteWatchers.push(watcher);
|
|
292
309
|
}
|
|
293
310
|
|
|
294
311
|
// Start copySrc watchers for library packages
|
|
@@ -390,12 +407,14 @@ export class DevWatchOrchestrator {
|
|
|
390
407
|
}
|
|
391
408
|
});
|
|
392
409
|
|
|
410
|
+
// 서버에 연결된 클라이언트 Set 구성 (O(1) 조회용 — PERF-001)
|
|
411
|
+
const serverConnectedClients = new Set(
|
|
412
|
+
[...this._serverClientsMap.values()].flat(),
|
|
413
|
+
);
|
|
414
|
+
|
|
393
415
|
// 독립 클라이언트 결과를 ResultCollector에 등록
|
|
394
416
|
for (const { name } of this._clientPackages) {
|
|
395
|
-
|
|
396
|
-
(clients) => clients.includes(name),
|
|
397
|
-
);
|
|
398
|
-
if (!isServerConnected) {
|
|
417
|
+
if (!serverConnectedClients.has(name)) {
|
|
399
418
|
const port = this._getClientPort(name);
|
|
400
419
|
if (port != null) {
|
|
401
420
|
this._resultCollector.add({
|
|
@@ -433,12 +452,9 @@ export class DevWatchOrchestrator {
|
|
|
433
452
|
|
|
434
453
|
// 독립 클라이언트만 존재하고 서버가 없는 경우 URL 출력 예약
|
|
435
454
|
if (this._serverPackages.length === 0) {
|
|
436
|
-
const hasIndependentClients = this._clientPackages.some(({ name }) =>
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
);
|
|
440
|
-
return !isServerConnected && this._getClientPort(name) != null;
|
|
441
|
-
});
|
|
455
|
+
const hasIndependentClients = this._clientPackages.some(({ name }) =>
|
|
456
|
+
!serverConnectedClients.has(name) && this._getClientPort(name) != null,
|
|
457
|
+
);
|
|
442
458
|
if (hasIndependentClients) {
|
|
443
459
|
this._schedulePrintServers();
|
|
444
460
|
}
|
|
@@ -500,10 +516,9 @@ export class DevWatchOrchestrator {
|
|
|
500
516
|
}, 300);
|
|
501
517
|
}
|
|
502
518
|
|
|
503
|
-
/** 클라이언트 엔진에서 포트 가져오기
|
|
519
|
+
/** 클라이언트 엔진에서 포트 가져오기 */
|
|
504
520
|
private _getClientPort(name: string): number | undefined {
|
|
505
|
-
|
|
506
|
-
return engine?.port;
|
|
521
|
+
return this._clientEngines.get(name)?.port;
|
|
507
522
|
}
|
|
508
523
|
|
|
509
524
|
/** 서버에 연결된 클라이언트들의 포트 수집 */
|