@simplysm/sd-cli 13.0.66 → 13.0.68
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/builders/BaseBuilder.d.ts.map +1 -1
- package/dist/builders/BaseBuilder.js +2 -7
- package/dist/builders/BaseBuilder.js.map +1 -1
- package/dist/builders/DtsBuilder.d.ts.map +1 -1
- package/dist/builders/DtsBuilder.js +4 -3
- package/dist/builders/DtsBuilder.js.map +1 -1
- package/dist/builders/LibraryBuilder.d.ts.map +1 -1
- package/dist/builders/LibraryBuilder.js +2 -1
- package/dist/builders/LibraryBuilder.js.map +1 -1
- package/dist/capacitor/capacitor.js +2 -2
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/add-client.js +2 -2
- package/dist/commands/add-server.js +2 -2
- package/dist/commands/build.d.ts +2 -10
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +1 -5
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +26 -37
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/dev.d.ts +2 -9
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +1 -5
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.js +5 -5
- package/dist/commands/publish.js +16 -16
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/typecheck.d.ts +0 -1
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +5 -5
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/commands/watch.d.ts +2 -8
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +1 -5
- package/dist/commands/watch.js.map +1 -1
- package/dist/electron/electron.js +2 -2
- package/dist/electron/electron.js.map +1 -1
- package/dist/infra/ResultCollector.d.ts +0 -21
- package/dist/infra/ResultCollector.d.ts.map +1 -1
- package/dist/infra/ResultCollector.js +0 -31
- package/dist/infra/ResultCollector.js.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.d.ts +0 -1
- package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.js +10 -19
- package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
- package/dist/orchestrators/DevOrchestrator.d.ts +12 -0
- package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevOrchestrator.js +178 -167
- package/dist/orchestrators/DevOrchestrator.js.map +1 -1
- package/dist/sd-cli-entry.js +14 -14
- package/dist/sd-cli-entry.js.map +1 -1
- package/dist/sd-cli.js +8 -13
- package/dist/sd-cli.js.map +1 -1
- package/dist/utils/output-utils.d.ts +4 -7
- package/dist/utils/output-utils.d.ts.map +1 -1
- package/dist/utils/output-utils.js +13 -5
- package/dist/utils/output-utils.js.map +1 -1
- package/dist/utils/package-utils.d.ts +0 -11
- package/dist/utils/package-utils.d.ts.map +1 -1
- 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 +21 -48
- package/dist/utils/replace-deps.js.map +1 -1
- package/dist/utils/sd-config.d.ts +2 -6
- package/dist/utils/sd-config.d.ts.map +1 -1
- package/dist/utils/sd-config.js.map +1 -1
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +7 -1
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/utils/worker-events.d.ts +5 -5
- package/dist/utils/worker-events.d.ts.map +1 -1
- package/dist/utils/worker-events.js +14 -17
- package/dist/utils/worker-events.js.map +1 -1
- package/dist/utils/worker-utils.d.ts +7 -0
- package/dist/utils/worker-utils.d.ts.map +1 -1
- package/dist/utils/worker-utils.js +10 -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 +11 -9
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/dts.worker.d.ts +4 -4
- package/dist/workers/dts.worker.d.ts.map +1 -1
- package/dist/workers/dts.worker.js +10 -11
- 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 +7 -9
- 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 +4 -3
- 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 +10 -13
- package/dist/workers/server.worker.js.map +1 -1
- package/package.json +5 -4
- package/src/builders/BaseBuilder.ts +2 -7
- package/src/builders/DtsBuilder.ts +4 -3
- package/src/builders/LibraryBuilder.ts +2 -1
- package/src/capacitor/capacitor.ts +2 -2
- package/src/commands/add-client.ts +2 -2
- package/src/commands/add-server.ts +2 -2
- package/src/commands/build.ts +2 -17
- package/src/commands/check.ts +31 -44
- package/src/commands/dev.ts +2 -16
- package/src/commands/init.ts +5 -5
- package/src/commands/publish.ts +16 -16
- package/src/commands/typecheck.ts +5 -5
- package/src/commands/watch.ts +2 -15
- package/src/electron/electron.ts +2 -2
- package/src/infra/ResultCollector.ts +0 -36
- package/src/orchestrators/BuildOrchestrator.ts +12 -21
- package/src/orchestrators/DevOrchestrator.ts +221 -201
- package/src/sd-cli-entry.ts +14 -14
- package/src/sd-cli.ts +9 -14
- package/src/utils/output-utils.ts +15 -11
- package/src/utils/package-utils.ts +0 -12
- package/src/utils/replace-deps.ts +61 -88
- package/src/utils/sd-config.ts +2 -6
- package/src/utils/vite-config.ts +9 -1
- package/src/utils/worker-events.ts +22 -25
- package/src/utils/worker-utils.ts +16 -0
- package/src/workers/client.worker.ts +12 -11
- package/src/workers/dts.worker.ts +13 -15
- package/src/workers/library.worker.ts +7 -10
- package/src/workers/server-runtime.worker.ts +4 -3
- package/src/workers/server.worker.ts +10 -14
- 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/dist/utils/spawn.d.ts +0 -26
- package/dist/utils/spawn.d.ts.map +0 -1
- package/dist/utils/spawn.js +0 -50
- package/dist/utils/spawn.js.map +0 -6
- package/src/utils/spawn.ts +0 -80
package/src/sd-cli.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* .js 실행 (배포): replaceDeps 실행 후 새 프로세스로 sd-cli-entry spawn
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { execa } from "execa";
|
|
11
11
|
import os from "os";
|
|
12
12
|
import path from "path";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
@@ -40,7 +40,7 @@ if (isDev) {
|
|
|
40
40
|
|
|
41
41
|
// Phase 2: 새 프로세스로 실제 CLI 실행 (모듈 캐시 초기화)
|
|
42
42
|
const cliEntryFilePath = path.join(__dirname, "sd-cli-entry.js");
|
|
43
|
-
const
|
|
43
|
+
const subprocess = execa(
|
|
44
44
|
"node",
|
|
45
45
|
[
|
|
46
46
|
"--max-old-space-size=8192",
|
|
@@ -48,14 +48,11 @@ if (isDev) {
|
|
|
48
48
|
cliEntryFilePath,
|
|
49
49
|
...process.argv.slice(2),
|
|
50
50
|
],
|
|
51
|
-
{ stdio: "inherit" },
|
|
51
|
+
{ stdio: "inherit", reject: false },
|
|
52
52
|
);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
child.on("exit", (code) => {
|
|
57
|
-
process.exitCode = code ?? 0;
|
|
58
|
-
});
|
|
53
|
+
if (subprocess.pid != null) configureAffinityAndPriority(subprocess.pid);
|
|
54
|
+
const result = await subprocess;
|
|
55
|
+
process.exitCode = result.exitCode ?? 0;
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
/**
|
|
@@ -97,10 +94,8 @@ function configureAffinityAndPriority(pid: number): void {
|
|
|
97
94
|
command = `taskset -p ${mask} ${pid} && renice +10 -p ${pid}`;
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
console.warn("CPU affinity/priority 설정 실패:", err.message);
|
|
104
|
-
}
|
|
97
|
+
execa({ shell: true })`${command}`.catch((err: Error) => {
|
|
98
|
+
// eslint-disable-next-line no-console
|
|
99
|
+
console.warn("CPU affinity/priority 설정 실패:", err.message);
|
|
105
100
|
});
|
|
106
101
|
}
|
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
2
|
import type { BuildResult } from "../infra/ResultCollector";
|
|
3
|
-
import type { PackageResult } from "./package-utils";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
* PackageResult와 BuildResult 모두 지원
|
|
5
|
+
* 빌드 경고/에러 메시지를 포맷팅한다.
|
|
8
6
|
*/
|
|
9
|
-
|
|
7
|
+
export function formatBuildMessages(name: string, label: string, messages: string[]): string {
|
|
8
|
+
const lines: string[] = [`${name} (${label})`];
|
|
9
|
+
for (const msg of messages) {
|
|
10
|
+
for (const line of msg.split("\n")) {
|
|
11
|
+
lines.push(` → ${line}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return lines.join("\n");
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* 에러만 출력한다.
|
|
13
19
|
* @param results 패키지별 빌드 결과 상태
|
|
14
20
|
*/
|
|
15
|
-
export function printErrors(results: Map<string,
|
|
21
|
+
export function printErrors(results: Map<string, BuildResult>): void {
|
|
16
22
|
for (const result of results.values()) {
|
|
17
23
|
if (result.status === "error") {
|
|
18
24
|
const typeLabel = result.type === "dts" ? "dts" : result.target;
|
|
19
|
-
const errorLines: string[] = [`${result.name} (${typeLabel})`];
|
|
20
25
|
if (result.message != null && result.message !== "") {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
26
|
+
consola.error(formatBuildMessages(result.name, typeLabel, [result.message]));
|
|
27
|
+
} else {
|
|
28
|
+
consola.error(`${result.name} (${typeLabel})`);
|
|
24
29
|
}
|
|
25
|
-
consola.error(errorLines.join("\n"));
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -33,7 +37,7 @@ export function printErrors(results: Map<string, ErrorResult>): void {
|
|
|
33
37
|
* @param serverClientsMap 서버별 연결된 클라이언트 목록
|
|
34
38
|
*/
|
|
35
39
|
export function printServers(
|
|
36
|
-
results: Map<string,
|
|
40
|
+
results: Map<string, BuildResult>,
|
|
37
41
|
serverClientsMap?: Map<string, string[]>,
|
|
38
42
|
): void {
|
|
39
43
|
// 서버 정보 수집
|
|
@@ -83,18 +83,6 @@ export function collectDeps(
|
|
|
83
83
|
return { workspaceDeps, replaceDeps };
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/**
|
|
87
|
-
* 패키지 결과 상태
|
|
88
|
-
*/
|
|
89
|
-
export interface PackageResult {
|
|
90
|
-
name: string;
|
|
91
|
-
target: string;
|
|
92
|
-
type: "build" | "dts" | "server" | "capacitor";
|
|
93
|
-
status: "success" | "error" | "running";
|
|
94
|
-
message?: string;
|
|
95
|
-
port?: number;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
86
|
/**
|
|
99
87
|
* 패키지 설정에서 targets 필터링 (scripts 타겟 제외)
|
|
100
88
|
* @param packages 패키지 설정 맵
|
|
@@ -148,28 +148,26 @@ async function collectSearchRoots(projectRoot: string): Promise<string[]> {
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
* replaceDeps
|
|
151
|
+
* replaceDeps 설정에서 모든 교체 대상 항목을 해석한다.
|
|
152
152
|
*
|
|
153
153
|
* 1. pnpm-workspace.yaml 파싱 → workspace 패키지 경로 목록
|
|
154
154
|
* 2. [루트, ...workspace 패키지]의 node_modules에서 매칭되는 패키지 찾기
|
|
155
|
-
* 3.
|
|
155
|
+
* 3. 패턴 매칭 + 소스 경로 존재 확인 + symlink 해석
|
|
156
156
|
*
|
|
157
157
|
* @param projectRoot - 프로젝트 루트 경로
|
|
158
158
|
* @param replaceDeps - sd.config.ts의 replaceDeps 설정
|
|
159
|
+
* @param logger - consola 로거
|
|
160
|
+
* @returns 해석된 교체 대상 항목 배열
|
|
159
161
|
*/
|
|
160
|
-
|
|
162
|
+
async function resolveAllReplaceDepEntries(
|
|
161
163
|
projectRoot: string,
|
|
162
164
|
replaceDeps: Record<string, string>,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
logger.start("Setting up replace-deps");
|
|
165
|
+
logger: ReturnType<typeof consola.withTag>,
|
|
166
|
+
): Promise<ReplaceDepEntry[]> {
|
|
167
|
+
const entries: ReplaceDepEntry[] = [];
|
|
168
168
|
|
|
169
|
-
// 1. Workspace 패키지 경로 목록 수집
|
|
170
169
|
const searchRoots = await collectSearchRoots(projectRoot);
|
|
171
170
|
|
|
172
|
-
// 2. 각 searchRoot의 node_modules에서 매칭되는 패키지 찾기
|
|
173
171
|
for (const searchRoot of searchRoots) {
|
|
174
172
|
const nodeModulesDir = path.join(searchRoot, "node_modules");
|
|
175
173
|
|
|
@@ -189,10 +187,9 @@ export async function setupReplaceDeps(
|
|
|
189
187
|
if (targetNames.length === 0) continue;
|
|
190
188
|
|
|
191
189
|
// 패턴 매칭 및 경로 해석
|
|
192
|
-
const
|
|
190
|
+
const matchedEntries = resolveReplaceDepEntries(replaceDeps, targetNames);
|
|
193
191
|
|
|
194
|
-
|
|
195
|
-
for (const { targetName, sourcePath } of entries) {
|
|
192
|
+
for (const { targetName, sourcePath } of matchedEntries) {
|
|
196
193
|
const targetPath = path.join(nodeModulesDir, targetName);
|
|
197
194
|
const resolvedSourcePath = path.resolve(projectRoot, sourcePath);
|
|
198
195
|
|
|
@@ -204,25 +201,59 @@ export async function setupReplaceDeps(
|
|
|
204
201
|
continue;
|
|
205
202
|
}
|
|
206
203
|
|
|
204
|
+
// targetPath가 symlink면 realpath로 해석하여 실제 .pnpm 스토어 경로 얻기
|
|
205
|
+
let actualTargetPath = targetPath;
|
|
207
206
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const stat = await fs.promises.lstat(targetPath);
|
|
212
|
-
if (stat.isSymbolicLink()) {
|
|
213
|
-
actualTargetPath = await fs.promises.realpath(targetPath);
|
|
214
|
-
}
|
|
215
|
-
} catch {
|
|
216
|
-
// targetPath가 존재하지 않으면 그대로 사용
|
|
207
|
+
const stat = await fs.promises.lstat(targetPath);
|
|
208
|
+
if (stat.isSymbolicLink()) {
|
|
209
|
+
actualTargetPath = await fs.promises.realpath(targetPath);
|
|
217
210
|
}
|
|
211
|
+
} catch {
|
|
212
|
+
// targetPath가 존재하지 않으면 그대로 사용
|
|
213
|
+
}
|
|
218
214
|
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
entries.push({
|
|
216
|
+
targetName,
|
|
217
|
+
sourcePath,
|
|
218
|
+
targetPath,
|
|
219
|
+
resolvedSourcePath,
|
|
220
|
+
actualTargetPath,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
221
224
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
return entries;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* replaceDeps 설정에 따라 node_modules 내 패키지를 소스 디렉토리로 복사 교체한다.
|
|
230
|
+
*
|
|
231
|
+
* 1. pnpm-workspace.yaml 파싱 → workspace 패키지 경로 목록
|
|
232
|
+
* 2. [루트, ...workspace 패키지]의 node_modules에서 매칭되는 패키지 찾기
|
|
233
|
+
* 3. 기존 symlink/디렉토리 제거 → 소스 경로를 복사 (node_modules, package.json, .cache, tests 제외)
|
|
234
|
+
*
|
|
235
|
+
* @param projectRoot - 프로젝트 루트 경로
|
|
236
|
+
* @param replaceDeps - sd.config.ts의 replaceDeps 설정
|
|
237
|
+
*/
|
|
238
|
+
export async function setupReplaceDeps(
|
|
239
|
+
projectRoot: string,
|
|
240
|
+
replaceDeps: Record<string, string>,
|
|
241
|
+
): Promise<void> {
|
|
242
|
+
const logger = consola.withTag("sd:cli:replace-deps");
|
|
243
|
+
let setupCount = 0;
|
|
244
|
+
|
|
245
|
+
logger.start("Setting up replace-deps");
|
|
246
|
+
|
|
247
|
+
const entries = await resolveAllReplaceDepEntries(projectRoot, replaceDeps, logger);
|
|
248
|
+
|
|
249
|
+
for (const { targetName, resolvedSourcePath, actualTargetPath } of entries) {
|
|
250
|
+
try {
|
|
251
|
+
// 소스 파일을 actualTargetPath에 덮어쓰기 복사 (기존 디렉토리 유지, symlink 보존)
|
|
252
|
+
await fsCopy(resolvedSourcePath, actualTargetPath, replaceDepsCopyFilter);
|
|
253
|
+
|
|
254
|
+
setupCount += 1;
|
|
255
|
+
} catch (err) {
|
|
256
|
+
logger.error(`복사 교체 실패 (${targetName}): ${err instanceof Error ? err.message : err}`);
|
|
226
257
|
}
|
|
227
258
|
}
|
|
228
259
|
|
|
@@ -246,68 +277,10 @@ export async function watchReplaceDeps(
|
|
|
246
277
|
replaceDeps: Record<string, string>,
|
|
247
278
|
): Promise<WatchReplaceDepResult> {
|
|
248
279
|
const logger = consola.withTag("sd:cli:replace-deps:watch");
|
|
249
|
-
const entries: ReplaceDepEntry[] = [];
|
|
250
280
|
|
|
251
|
-
|
|
252
|
-
const searchRoots = await collectSearchRoots(projectRoot);
|
|
253
|
-
|
|
254
|
-
// 2. 각 searchRoot의 node_modules에서 매칭되는 패키지 찾기
|
|
255
|
-
for (const searchRoot of searchRoots) {
|
|
256
|
-
const nodeModulesDir = path.join(searchRoot, "node_modules");
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
await fs.promises.access(nodeModulesDir);
|
|
260
|
-
} catch {
|
|
261
|
-
continue; // node_modules 없으면 스킵
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// replaceDeps의 각 glob 패턴으로 node_modules 내 디렉토리 탐색
|
|
265
|
-
const targetNames: string[] = [];
|
|
266
|
-
for (const pattern of Object.keys(replaceDeps)) {
|
|
267
|
-
const matches = await glob(pattern, { cwd: nodeModulesDir });
|
|
268
|
-
targetNames.push(...matches);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (targetNames.length === 0) continue;
|
|
272
|
-
|
|
273
|
-
// 패턴 매칭 및 경로 해석
|
|
274
|
-
const matchedEntries = resolveReplaceDepEntries(replaceDeps, targetNames);
|
|
275
|
-
|
|
276
|
-
// 3. entry 정보 수집 (symlink 해석 포함)
|
|
277
|
-
for (const { targetName, sourcePath } of matchedEntries) {
|
|
278
|
-
const targetPath = path.join(nodeModulesDir, targetName);
|
|
279
|
-
const resolvedSourcePath = path.resolve(projectRoot, sourcePath);
|
|
280
|
-
|
|
281
|
-
// 소스 경로 존재 확인
|
|
282
|
-
try {
|
|
283
|
-
await fs.promises.access(resolvedSourcePath);
|
|
284
|
-
} catch {
|
|
285
|
-
logger.warn(`소스 경로가 존재하지 않아 스킵합니다: ${resolvedSourcePath}`);
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// targetPath가 symlink면 realpath로 해석하여 실제 .pnpm 스토어 경로 얻기
|
|
290
|
-
let actualTargetPath = targetPath;
|
|
291
|
-
try {
|
|
292
|
-
const stat = await fs.promises.lstat(targetPath);
|
|
293
|
-
if (stat.isSymbolicLink()) {
|
|
294
|
-
actualTargetPath = await fs.promises.realpath(targetPath);
|
|
295
|
-
}
|
|
296
|
-
} catch {
|
|
297
|
-
// targetPath가 존재하지 않으면 그대로 사용
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
entries.push({
|
|
301
|
-
targetName,
|
|
302
|
-
sourcePath,
|
|
303
|
-
targetPath,
|
|
304
|
-
resolvedSourcePath,
|
|
305
|
-
actualTargetPath,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
}
|
|
281
|
+
const entries = await resolveAllReplaceDepEntries(projectRoot, replaceDeps, logger);
|
|
309
282
|
|
|
310
|
-
//
|
|
283
|
+
// 소스 디렉토리 watch 설정
|
|
311
284
|
const watchers: FsWatcher[] = [];
|
|
312
285
|
const watchedSources = new Set<string>();
|
|
313
286
|
|
package/src/utils/sd-config.ts
CHANGED
|
@@ -2,18 +2,14 @@ import path from "path";
|
|
|
2
2
|
import { createJiti } from "jiti";
|
|
3
3
|
import { SdError } from "@simplysm/core-common";
|
|
4
4
|
import { fsExists } from "@simplysm/core-node";
|
|
5
|
-
import type { SdConfig } from "../sd-config.types";
|
|
5
|
+
import type { SdConfig, SdConfigParams } from "../sd-config.types";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* sd.config.ts 로드
|
|
9
9
|
* @returns SdConfig 객체
|
|
10
10
|
* @throws sd.config.ts가 없거나 형식이 잘못된 경우
|
|
11
11
|
*/
|
|
12
|
-
export async function loadSdConfig(params: {
|
|
13
|
-
cwd: string;
|
|
14
|
-
dev: boolean;
|
|
15
|
-
opt: string[];
|
|
16
|
-
}): Promise<SdConfig> {
|
|
12
|
+
export async function loadSdConfig(params: SdConfigParams): Promise<SdConfig> {
|
|
17
13
|
const sdConfigPath = path.resolve(params.cwd, "sd.config.ts");
|
|
18
14
|
|
|
19
15
|
if (!(await fsExists(sdConfigPath))) {
|
package/src/utils/vite-config.ts
CHANGED
|
@@ -111,7 +111,15 @@ function sdPublicDevPlugin(pkgDir: string): Plugin {
|
|
|
111
111
|
urlPath = urlPath.slice(1);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
// path traversal 방어: publicDevDir 범위 밖의 파일 접근 차단
|
|
115
|
+
const decodedPath = decodeURIComponent(urlPath);
|
|
116
|
+
const filePath = path.resolve(publicDevDir, decodedPath);
|
|
117
|
+
const normalizedRoot = path.resolve(publicDevDir);
|
|
118
|
+
if (!filePath.startsWith(normalizedRoot + path.sep) && filePath !== normalizedRoot) {
|
|
119
|
+
next();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
115
123
|
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
116
124
|
// sirv 대신 간단히 파일 스트림으로 응답
|
|
117
125
|
const stream = fs.createReadStream(filePath);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { consola } from "consola";
|
|
2
|
-
import type {
|
|
2
|
+
import type { BuildResult } from "../infra/ResultCollector";
|
|
3
3
|
import type { SdPackageConfig } from "../sd-config.types";
|
|
4
4
|
import type { RebuildManager } from "./rebuild-manager";
|
|
5
|
+
import { formatBuildMessages } from "./output-utils";
|
|
5
6
|
|
|
6
7
|
const workerEventsLogger = consola.withTag("sd:cli:worker-events");
|
|
7
8
|
|
|
@@ -33,12 +34,12 @@ export interface ServerBuildEventData {
|
|
|
33
34
|
/**
|
|
34
35
|
* 기본 Worker 정보 타입
|
|
35
36
|
*/
|
|
36
|
-
export interface BaseWorkerInfo<TEvents extends Record<string,
|
|
37
|
+
export interface BaseWorkerInfo<TEvents extends Record<string, unknown> = Record<string, unknown>> {
|
|
37
38
|
name: string;
|
|
38
39
|
config: SdPackageConfig;
|
|
39
40
|
worker: {
|
|
40
|
-
on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]
|
|
41
|
-
send<K extends keyof TEvents>(event: K, data: TEvents[K]
|
|
41
|
+
on<K extends keyof TEvents>(event: K, handler: (data: TEvents[K]) => void): void;
|
|
42
|
+
send<K extends keyof TEvents>(event: K, data: TEvents[K]): void;
|
|
42
43
|
};
|
|
43
44
|
isInitialBuild: boolean;
|
|
44
45
|
buildResolver: (() => void) | undefined;
|
|
@@ -63,15 +64,15 @@ export interface WorkerEventHandlerOptions {
|
|
|
63
64
|
* @returns completeTask 함수 (결과를 저장하고 빌드 완료를 알림)
|
|
64
65
|
*/
|
|
65
66
|
export function registerWorkerEventHandlers<
|
|
66
|
-
TEvents extends Record<string,
|
|
67
|
+
TEvents extends Record<string, unknown>,
|
|
67
68
|
T extends BaseWorkerInfo<TEvents>,
|
|
68
69
|
>(
|
|
69
70
|
workerInfo: T,
|
|
70
71
|
opts: WorkerEventHandlerOptions,
|
|
71
|
-
results: Map<string,
|
|
72
|
+
results: Map<string, BuildResult>,
|
|
72
73
|
rebuildManager: RebuildManager,
|
|
73
|
-
): (result:
|
|
74
|
-
const completeTask = (result:
|
|
74
|
+
): (result: BuildResult) => void {
|
|
75
|
+
const completeTask = (result: BuildResult): void => {
|
|
75
76
|
results.set(opts.resultKey, result);
|
|
76
77
|
workerInfo.buildResolver?.();
|
|
77
78
|
workerInfo.buildResolver = undefined;
|
|
@@ -86,40 +87,36 @@ export function registerWorkerEventHandlers<
|
|
|
86
87
|
});
|
|
87
88
|
|
|
88
89
|
// 빌드 완료
|
|
89
|
-
workerInfo.worker.on("build", (
|
|
90
|
-
const
|
|
91
|
-
workerEventsLogger.debug(`[${workerInfo.name}] build: success=${String(
|
|
90
|
+
workerInfo.worker.on("build", (_data) => {
|
|
91
|
+
const data = _data as BuildEventData;
|
|
92
|
+
workerEventsLogger.debug(`[${workerInfo.name}] build: success=${String(data.success)}`);
|
|
92
93
|
|
|
93
94
|
// warnings 출력
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
warnLines.push(` → ${line}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
workerEventsLogger.warn(warnLines.join("\n"));
|
|
95
|
+
if (data.warnings != null && data.warnings.length > 0) {
|
|
96
|
+
workerEventsLogger.warn(
|
|
97
|
+
formatBuildMessages(workerInfo.name, workerInfo.config.target, data.warnings),
|
|
98
|
+
);
|
|
102
99
|
}
|
|
103
100
|
|
|
104
101
|
completeTask({
|
|
105
102
|
name: workerInfo.name,
|
|
106
103
|
target: workerInfo.config.target,
|
|
107
104
|
type: opts.resultType,
|
|
108
|
-
status:
|
|
109
|
-
message:
|
|
105
|
+
status: data.success ? "success" : "error",
|
|
106
|
+
message: data.errors?.join("\n"),
|
|
110
107
|
});
|
|
111
108
|
});
|
|
112
109
|
|
|
113
110
|
// 에러
|
|
114
|
-
workerInfo.worker.on("error", (
|
|
115
|
-
const
|
|
116
|
-
workerEventsLogger.debug(`[${workerInfo.name}] error: ${
|
|
111
|
+
workerInfo.worker.on("error", (_data) => {
|
|
112
|
+
const data = _data as ErrorEventData;
|
|
113
|
+
workerEventsLogger.debug(`[${workerInfo.name}] error: ${data.message}`);
|
|
117
114
|
completeTask({
|
|
118
115
|
name: workerInfo.name,
|
|
119
116
|
target: workerInfo.config.target,
|
|
120
117
|
type: opts.resultType,
|
|
121
118
|
status: "error",
|
|
122
|
-
message:
|
|
119
|
+
message: data.message,
|
|
123
120
|
});
|
|
124
121
|
});
|
|
125
122
|
|
|
@@ -27,3 +27,19 @@ export function registerCleanupHandlers(
|
|
|
27
27
|
process.on("SIGTERM", handleSignal);
|
|
28
28
|
process.on("SIGINT", handleSignal);
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Worker 함수의 중복 호출을 방지하는 가드를 생성한다.
|
|
33
|
+
*
|
|
34
|
+
* @param label - 에러 메시지에 사용할 함수명
|
|
35
|
+
* @returns 호출 시 중복이면 에러를 throw하는 가드 함수
|
|
36
|
+
*/
|
|
37
|
+
export function createOnceGuard(label: string): () => void {
|
|
38
|
+
let called = false;
|
|
39
|
+
return () => {
|
|
40
|
+
if (called) {
|
|
41
|
+
throw new Error(`${label}는 Worker당 한 번만 호출할 수 있습니다.`);
|
|
42
|
+
}
|
|
43
|
+
called = true;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -2,12 +2,13 @@ import path from "path";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import { build as viteBuild, createServer, type ViteDevServer } from "vite";
|
|
4
4
|
import { createWorker } from "@simplysm/core-node";
|
|
5
|
+
import { errorMessage } from "@simplysm/core-common";
|
|
5
6
|
import { consola } from "consola";
|
|
6
7
|
import type { SdClientPackageConfig } from "../sd-config.types";
|
|
7
8
|
import { parseRootTsconfig, getCompilerOptionsForPackage } from "../utils/tsconfig";
|
|
8
9
|
import { createViteConfig } from "../utils/vite-config";
|
|
9
10
|
import { collectDeps } from "../utils/package-utils";
|
|
10
|
-
import { registerCleanupHandlers } from "../utils/worker-utils";
|
|
11
|
+
import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
|
|
11
12
|
|
|
12
13
|
//#region Types
|
|
13
14
|
|
|
@@ -142,13 +143,12 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
|
|
|
142
143
|
} catch (err) {
|
|
143
144
|
return {
|
|
144
145
|
success: false,
|
|
145
|
-
errors: [
|
|
146
|
+
errors: [errorMessage(err)],
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
let isWatchStarted = false;
|
|
151
|
+
const guardStartWatch = createOnceGuard("startWatch");
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
154
|
* watch 시작 (Vite dev server)
|
|
@@ -156,10 +156,7 @@ let isWatchStarted = false;
|
|
|
156
156
|
* @throws 이미 watch가 시작된 경우
|
|
157
157
|
*/
|
|
158
158
|
async function startWatch(info: ClientWatchInfo): Promise<void> {
|
|
159
|
-
|
|
160
|
-
throw new Error("startWatch는 Worker당 한 번만 호출할 수 있습니다.");
|
|
161
|
-
}
|
|
162
|
-
isWatchStarted = true;
|
|
159
|
+
guardStartWatch();
|
|
163
160
|
|
|
164
161
|
try {
|
|
165
162
|
// tsconfig 파싱
|
|
@@ -204,13 +201,17 @@ async function startWatch(info: ClientWatchInfo): Promise<void> {
|
|
|
204
201
|
|
|
205
202
|
// 실제 할당된 포트 반환 (config.server.port는 설정값이므로 httpServer에서 실제 포트를 가져옴)
|
|
206
203
|
const address = viteServer.httpServer?.address();
|
|
207
|
-
const actualPort =
|
|
208
|
-
|
|
204
|
+
const actualPort = typeof address === "object" && address != null ? address.port : undefined;
|
|
205
|
+
|
|
206
|
+
if (actualPort == null) {
|
|
207
|
+
sender.send("error", { message: "Vite dev server port를 확인할 수 없습니다." });
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
209
210
|
|
|
210
211
|
sender.send("serverReady", { port: actualPort });
|
|
211
212
|
} catch (err) {
|
|
212
213
|
sender.send("error", {
|
|
213
|
-
message:
|
|
214
|
+
message: errorMessage(err),
|
|
214
215
|
});
|
|
215
216
|
}
|
|
216
217
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
import { createWorker, pathIsChildPath, pathNorm } from "@simplysm/core-node";
|
|
4
|
+
import { errorMessage } from "@simplysm/core-common";
|
|
4
5
|
import { consola } from "consola";
|
|
5
6
|
import {
|
|
6
7
|
getCompilerOptionsForPackage,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
type TypecheckEnv,
|
|
11
12
|
} from "../utils/tsconfig";
|
|
12
13
|
import { serializeDiagnostic, type SerializedDiagnostic } from "../utils/typecheck-serialization";
|
|
14
|
+
import { createOnceGuard } from "../utils/worker-utils";
|
|
13
15
|
|
|
14
16
|
//#region Types
|
|
15
17
|
|
|
@@ -181,12 +183,12 @@ function createDtsPathRewriter(
|
|
|
181
183
|
|
|
182
184
|
//#endregion
|
|
183
185
|
|
|
184
|
-
//#region
|
|
186
|
+
//#region build (일회성 빌드)
|
|
185
187
|
|
|
186
188
|
/**
|
|
187
189
|
* DTS 일회성 빌드 (타입체크 + dts 생성)
|
|
188
190
|
*/
|
|
189
|
-
async function
|
|
191
|
+
async function build(info: DtsBuildInfo): Promise<DtsBuildResult> {
|
|
190
192
|
try {
|
|
191
193
|
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
192
194
|
|
|
@@ -327,7 +329,7 @@ async function buildDts(info: DtsBuildInfo): Promise<DtsBuildResult> {
|
|
|
327
329
|
} catch (err) {
|
|
328
330
|
return {
|
|
329
331
|
success: false,
|
|
330
|
-
errors: [
|
|
332
|
+
errors: [errorMessage(err)],
|
|
331
333
|
diagnostics: [],
|
|
332
334
|
errorCount: 1,
|
|
333
335
|
warningCount: 0,
|
|
@@ -337,21 +339,17 @@ async function buildDts(info: DtsBuildInfo): Promise<DtsBuildResult> {
|
|
|
337
339
|
|
|
338
340
|
//#endregion
|
|
339
341
|
|
|
340
|
-
//#region
|
|
342
|
+
//#region startWatch (watch 모드)
|
|
341
343
|
|
|
342
|
-
|
|
343
|
-
let isWatchStarted = false;
|
|
344
|
+
const guardStartWatch = createOnceGuard("startWatch");
|
|
344
345
|
|
|
345
346
|
/**
|
|
346
347
|
* DTS watch 시작
|
|
347
348
|
* @remarks 이 함수는 Worker당 한 번만 호출되어야 합니다.
|
|
348
349
|
* @throws 이미 watch가 시작된 경우
|
|
349
350
|
*/
|
|
350
|
-
async function
|
|
351
|
-
|
|
352
|
-
throw new Error("startDtsWatch는 Worker당 한 번만 호출할 수 있습니다.");
|
|
353
|
-
}
|
|
354
|
-
isWatchStarted = true;
|
|
351
|
+
async function startWatch(info: DtsWatchInfo): Promise<void> {
|
|
352
|
+
guardStartWatch();
|
|
355
353
|
|
|
356
354
|
try {
|
|
357
355
|
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
@@ -451,17 +449,17 @@ async function startDtsWatch(info: DtsWatchInfo): Promise<void> {
|
|
|
451
449
|
tscWatchProgram = ts.createWatchProgram(host);
|
|
452
450
|
} catch (err) {
|
|
453
451
|
sender.send("error", {
|
|
454
|
-
message:
|
|
452
|
+
message: errorMessage(err),
|
|
455
453
|
});
|
|
456
454
|
}
|
|
457
455
|
}
|
|
458
456
|
|
|
459
457
|
const sender = createWorker<
|
|
460
|
-
{
|
|
458
|
+
{ startWatch: typeof startWatch; build: typeof build },
|
|
461
459
|
DtsWorkerEvents
|
|
462
460
|
>({
|
|
463
|
-
|
|
464
|
-
|
|
461
|
+
startWatch,
|
|
462
|
+
build,
|
|
465
463
|
});
|
|
466
464
|
|
|
467
465
|
export default sender;
|