@simplysm/sd-cli 13.0.8 → 13.0.10
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/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +5 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/sd-cli-entry.d.ts +9 -0
- package/dist/sd-cli-entry.d.ts.map +1 -0
- package/dist/sd-cli-entry.js +266 -0
- package/dist/sd-cli-entry.js.map +6 -0
- package/dist/sd-cli.d.ts +5 -5
- package/dist/sd-cli.d.ts.map +1 -1
- package/dist/sd-cli.js +56 -260
- package/dist/sd-cli.js.map +1 -1
- package/dist/utils/replace-deps.d.ts.map +1 -1
- package/dist/utils/replace-deps.js +1 -6
- package/dist/utils/replace-deps.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/dev.ts +5 -0
- package/src/sd-cli-entry.ts +330 -0
- package/src/sd-cli.ts +88 -319
- package/src/utils/replace-deps.ts +2 -11
- 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/src/sd-cli.ts
CHANGED
|
@@ -1,330 +1,99 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import { runPublish } from "./commands/publish";
|
|
13
|
-
import { runReplaceDeps } from "./commands/replace-deps";
|
|
14
|
-
import { runDevice } from "./commands/device";
|
|
3
|
+
/**
|
|
4
|
+
* CLI Launcher
|
|
5
|
+
*
|
|
6
|
+
* .ts 실행 (개발): CPU affinity 적용 후 sd-cli-entry 직접 import
|
|
7
|
+
* .js 실행 (배포): replaceDeps 실행 후 새 프로세스로 sd-cli-entry spawn
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { exec, spawn } from "child_process";
|
|
11
|
+
import os from "os";
|
|
15
12
|
import path from "path";
|
|
16
|
-
import fs from "fs";
|
|
17
13
|
import { fileURLToPath } from "url";
|
|
18
|
-
import { EventEmitter } from "node:events";
|
|
19
|
-
import { consola, LogLevels } from "consola";
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const cliEntryUrl = import.meta.resolve("./sd-cli-entry");
|
|
16
|
+
const cliEntryFilePath = fileURLToPath(cliEntryUrl);
|
|
17
|
+
|
|
18
|
+
if (path.extname(cliEntryFilePath) === ".ts") {
|
|
19
|
+
// 개발 모드 (.ts): affinity 적용 후 직접 실행
|
|
20
|
+
// import만으로는 메인 모듈 감지가 실패하므로 (process.argv[1] ≠ sd-cli-entry)
|
|
21
|
+
// createCliParser를 명시적으로 호출한다.
|
|
22
|
+
configureAffinityAndPriority(process.pid);
|
|
23
|
+
const { createCliParser } = await import(cliEntryUrl);
|
|
24
|
+
await createCliParser(process.argv.slice(2)).parse();
|
|
25
|
+
} else {
|
|
26
|
+
// 배포 모드 (.js): 2단계 실행
|
|
27
|
+
|
|
28
|
+
// Phase 1: replaceDeps (inline — 설치된 버전으로 복사)
|
|
29
|
+
try {
|
|
30
|
+
const { loadSdConfig } = await import("./utils/sd-config.js");
|
|
31
|
+
const { setupReplaceDeps } = await import("./utils/replace-deps.js");
|
|
32
|
+
const sdConfig = await loadSdConfig({ cwd: process.cwd(), dev: false, opt: [] });
|
|
33
|
+
if (sdConfig.replaceDeps != null) {
|
|
34
|
+
await setupReplaceDeps(process.cwd(), sdConfig.replaceDeps);
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// sd.config.ts 없거나 replaceDeps 미설정 시 스킵
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Phase 2: 새 프로세스로 실제 CLI 실행 (모듈 캐시 초기화)
|
|
41
|
+
const child = spawn(
|
|
42
|
+
"node",
|
|
43
|
+
["--max-old-space-size=8192", "--max-semi-space-size=16", cliEntryFilePath, ...process.argv.slice(2)],
|
|
44
|
+
{ stdio: "inherit" },
|
|
45
|
+
);
|
|
46
|
+
child.on("spawn", () => {
|
|
47
|
+
if (child.pid != null) configureAffinityAndPriority(child.pid);
|
|
48
|
+
});
|
|
49
|
+
child.on("exit", (code) => {
|
|
50
|
+
process.exitCode = code ?? 0;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
23
53
|
|
|
24
54
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
55
|
+
* CPU affinity mask 계산 (앞쪽 코어 제외)
|
|
56
|
+
*
|
|
57
|
+
* CPU 4개당 1개를 제외하고, 나머지 코어의 비트를 ON으로 설정한다.
|
|
58
|
+
* 예: 8코어 → 2개 제외 → 0xFC (코어 2~7)
|
|
27
59
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
default: false,
|
|
36
|
-
global: true,
|
|
37
|
-
})
|
|
38
|
-
.middleware((args) => {
|
|
39
|
-
if (args.debug) consola.level = LogLevels.debug;
|
|
40
|
-
})
|
|
41
|
-
.command(
|
|
42
|
-
"lint [targets..]",
|
|
43
|
-
"ESLint + Stylelint를 실행한다.",
|
|
44
|
-
(cmd) =>
|
|
45
|
-
cmd
|
|
46
|
-
.version(false)
|
|
47
|
-
.hide("help")
|
|
48
|
-
.positional("targets", {
|
|
49
|
-
type: "string",
|
|
50
|
-
array: true,
|
|
51
|
-
describe: "린트할 경로 (예: packages/core-common, tests/orm)",
|
|
52
|
-
default: [],
|
|
53
|
-
})
|
|
54
|
-
.options({
|
|
55
|
-
fix: {
|
|
56
|
-
type: "boolean",
|
|
57
|
-
describe: "자동 수정",
|
|
58
|
-
default: false,
|
|
59
|
-
},
|
|
60
|
-
timing: {
|
|
61
|
-
type: "boolean",
|
|
62
|
-
describe: "규칙별 실행 시간 출력",
|
|
63
|
-
default: false,
|
|
64
|
-
},
|
|
65
|
-
}),
|
|
66
|
-
async (args) => {
|
|
67
|
-
await runLint({
|
|
68
|
-
targets: args.targets,
|
|
69
|
-
fix: args.fix,
|
|
70
|
-
timing: args.timing,
|
|
71
|
-
});
|
|
72
|
-
},
|
|
73
|
-
)
|
|
74
|
-
.command(
|
|
75
|
-
"typecheck [targets..]",
|
|
76
|
-
"TypeScript 타입체크를 실행한다.",
|
|
77
|
-
(cmd) =>
|
|
78
|
-
cmd
|
|
79
|
-
.version(false)
|
|
80
|
-
.hide("help")
|
|
81
|
-
.positional("targets", {
|
|
82
|
-
type: "string",
|
|
83
|
-
array: true,
|
|
84
|
-
describe: "타입체크할 경로 (예: packages/core-common, tests/orm)",
|
|
85
|
-
default: [],
|
|
86
|
-
})
|
|
87
|
-
.options({
|
|
88
|
-
options: {
|
|
89
|
-
type: "string",
|
|
90
|
-
array: true,
|
|
91
|
-
alias: "o",
|
|
92
|
-
description: "sd.config.ts에 전달할 옵션 (예: -o key=value)",
|
|
93
|
-
default: [] as string[],
|
|
94
|
-
},
|
|
95
|
-
}),
|
|
96
|
-
async (args) => {
|
|
97
|
-
await runTypecheck({
|
|
98
|
-
targets: args.targets,
|
|
99
|
-
options: args.options,
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
)
|
|
103
|
-
.command(
|
|
104
|
-
"watch [targets..]",
|
|
105
|
-
"패키지를 watch 모드로 빌드한다.",
|
|
106
|
-
(cmd) =>
|
|
107
|
-
cmd
|
|
108
|
-
.version(false)
|
|
109
|
-
.hide("help")
|
|
110
|
-
.positional("targets", {
|
|
111
|
-
type: "string",
|
|
112
|
-
array: true,
|
|
113
|
-
describe: "watch할 패키지 (예: solid, solid-demo)",
|
|
114
|
-
default: [],
|
|
115
|
-
})
|
|
116
|
-
.options({
|
|
117
|
-
options: {
|
|
118
|
-
type: "string",
|
|
119
|
-
array: true,
|
|
120
|
-
alias: "o",
|
|
121
|
-
description: "sd.config.ts에 전달할 옵션 (예: -o key=value)",
|
|
122
|
-
default: [] as string[],
|
|
123
|
-
},
|
|
124
|
-
}),
|
|
125
|
-
async (args) => {
|
|
126
|
-
await runWatch({
|
|
127
|
-
targets: args.targets,
|
|
128
|
-
options: args.options,
|
|
129
|
-
});
|
|
130
|
-
},
|
|
131
|
-
)
|
|
132
|
-
.command(
|
|
133
|
-
"dev [targets..]",
|
|
134
|
-
"Client와 Server 패키지를 개발 모드로 실행한다.",
|
|
135
|
-
(cmd) =>
|
|
136
|
-
cmd
|
|
137
|
-
.version(false)
|
|
138
|
-
.hide("help")
|
|
139
|
-
.positional("targets", {
|
|
140
|
-
type: "string",
|
|
141
|
-
array: true,
|
|
142
|
-
describe: "실행할 패키지 (예: solid-demo)",
|
|
143
|
-
default: [],
|
|
144
|
-
})
|
|
145
|
-
.options({
|
|
146
|
-
options: {
|
|
147
|
-
type: "string",
|
|
148
|
-
array: true,
|
|
149
|
-
alias: "o",
|
|
150
|
-
description: "sd.config.ts에 전달할 옵션 (예: -o key=value)",
|
|
151
|
-
default: [] as string[],
|
|
152
|
-
},
|
|
153
|
-
}),
|
|
154
|
-
async (args) => {
|
|
155
|
-
await runDev({
|
|
156
|
-
targets: args.targets,
|
|
157
|
-
options: args.options,
|
|
158
|
-
});
|
|
159
|
-
},
|
|
160
|
-
)
|
|
161
|
-
.command(
|
|
162
|
-
"build [targets..]",
|
|
163
|
-
"프로덕션 빌드를 실행한다.",
|
|
164
|
-
(cmd) =>
|
|
165
|
-
cmd
|
|
166
|
-
.version(false)
|
|
167
|
-
.hide("help")
|
|
168
|
-
.positional("targets", {
|
|
169
|
-
type: "string",
|
|
170
|
-
array: true,
|
|
171
|
-
describe: "빌드할 패키지 (예: solid, core-common)",
|
|
172
|
-
default: [],
|
|
173
|
-
})
|
|
174
|
-
.options({
|
|
175
|
-
options: {
|
|
176
|
-
type: "string",
|
|
177
|
-
array: true,
|
|
178
|
-
alias: "o",
|
|
179
|
-
description: "sd.config.ts에 전달할 옵션 (예: -o key=value)",
|
|
180
|
-
default: [] as string[],
|
|
181
|
-
},
|
|
182
|
-
}),
|
|
183
|
-
async (args) => {
|
|
184
|
-
await runBuild({
|
|
185
|
-
targets: args.targets,
|
|
186
|
-
options: args.options,
|
|
187
|
-
});
|
|
188
|
-
},
|
|
189
|
-
)
|
|
190
|
-
.command(
|
|
191
|
-
"device",
|
|
192
|
-
"Android 디바이스에서 앱을 실행한다.",
|
|
193
|
-
(cmd) =>
|
|
194
|
-
cmd
|
|
195
|
-
.version(false)
|
|
196
|
-
.hide("help")
|
|
197
|
-
.options({
|
|
198
|
-
package: {
|
|
199
|
-
type: "string",
|
|
200
|
-
alias: "p",
|
|
201
|
-
describe: "패키지 이름",
|
|
202
|
-
demandOption: true,
|
|
203
|
-
},
|
|
204
|
-
url: {
|
|
205
|
-
type: "string",
|
|
206
|
-
alias: "u",
|
|
207
|
-
describe: "개발 서버 URL (미지정 시 sd.config.ts의 server 설정 사용)",
|
|
208
|
-
},
|
|
209
|
-
options: {
|
|
210
|
-
type: "string",
|
|
211
|
-
array: true,
|
|
212
|
-
alias: "o",
|
|
213
|
-
description: "sd.config.ts에 전달할 옵션 (예: -o key=value)",
|
|
214
|
-
default: [] as string[],
|
|
215
|
-
},
|
|
216
|
-
}),
|
|
217
|
-
async (args) => {
|
|
218
|
-
await runDevice({
|
|
219
|
-
package: args.package,
|
|
220
|
-
url: args.url,
|
|
221
|
-
options: args.options,
|
|
222
|
-
});
|
|
223
|
-
},
|
|
224
|
-
)
|
|
225
|
-
.command(
|
|
226
|
-
"init",
|
|
227
|
-
"새 프로젝트를 초기화한다.",
|
|
228
|
-
(cmd) => cmd.version(false).hide("help"),
|
|
229
|
-
async () => {
|
|
230
|
-
const { runInit } = await import("./commands/init.js");
|
|
231
|
-
await runInit({});
|
|
232
|
-
},
|
|
233
|
-
)
|
|
234
|
-
.command("add", "프로젝트에 패키지를 추가한다.", (cmd) =>
|
|
235
|
-
cmd
|
|
236
|
-
.version(false)
|
|
237
|
-
.hide("help")
|
|
238
|
-
.command(
|
|
239
|
-
"client",
|
|
240
|
-
"클라이언트 패키지를 추가한다.",
|
|
241
|
-
(subCmd) => subCmd.version(false).hide("help"),
|
|
242
|
-
async () => {
|
|
243
|
-
const { runAddClient } = await import("./commands/add-client.js");
|
|
244
|
-
await runAddClient({});
|
|
245
|
-
},
|
|
246
|
-
)
|
|
247
|
-
.command(
|
|
248
|
-
"server",
|
|
249
|
-
"서버 패키지를 추가한다.",
|
|
250
|
-
(subCmd) => subCmd.version(false).hide("help"),
|
|
251
|
-
async () => {
|
|
252
|
-
const { runAddServer } = await import("./commands/add-server.js");
|
|
253
|
-
await runAddServer({});
|
|
254
|
-
},
|
|
255
|
-
)
|
|
256
|
-
.demandCommand(1, "패키지 타입을 지정해주세요. (client, server)"),
|
|
257
|
-
)
|
|
258
|
-
.command(
|
|
259
|
-
"publish [targets..]",
|
|
260
|
-
"패키지를 배포한다.",
|
|
261
|
-
(cmd) =>
|
|
262
|
-
cmd
|
|
263
|
-
.version(false)
|
|
264
|
-
.hide("help")
|
|
265
|
-
.positional("targets", {
|
|
266
|
-
type: "string",
|
|
267
|
-
array: true,
|
|
268
|
-
describe: "배포할 패키지 (예: solid, core-common)",
|
|
269
|
-
default: [],
|
|
270
|
-
})
|
|
271
|
-
.options({
|
|
272
|
-
"build": {
|
|
273
|
-
type: "boolean",
|
|
274
|
-
describe: "빌드 실행 (--no-build로 스킵)",
|
|
275
|
-
default: true,
|
|
276
|
-
},
|
|
277
|
-
"dry-run": {
|
|
278
|
-
type: "boolean",
|
|
279
|
-
describe: "실제 배포 없이 시뮬레이션",
|
|
280
|
-
default: false,
|
|
281
|
-
},
|
|
282
|
-
"options": {
|
|
283
|
-
type: "string",
|
|
284
|
-
array: true,
|
|
285
|
-
alias: "o",
|
|
286
|
-
description: "sd.config.ts에 전달할 옵션 (예: -o key=value)",
|
|
287
|
-
default: [] as string[],
|
|
288
|
-
},
|
|
289
|
-
}),
|
|
290
|
-
async (args) => {
|
|
291
|
-
await runPublish({
|
|
292
|
-
targets: args.targets,
|
|
293
|
-
noBuild: !args.build,
|
|
294
|
-
dryRun: args.dryRun,
|
|
295
|
-
options: args.options,
|
|
296
|
-
});
|
|
297
|
-
},
|
|
298
|
-
)
|
|
299
|
-
.command(
|
|
300
|
-
"replace-deps",
|
|
301
|
-
"sd.config.ts의 replaceDeps 설정에 따라 node_modules 패키지를 로컬 소스로 symlink 교체한다.",
|
|
302
|
-
(cmd) =>
|
|
303
|
-
cmd
|
|
304
|
-
.version(false)
|
|
305
|
-
.hide("help")
|
|
306
|
-
.options({
|
|
307
|
-
options: {
|
|
308
|
-
type: "string",
|
|
309
|
-
array: true,
|
|
310
|
-
alias: "o",
|
|
311
|
-
description: "sd.config.ts에 전달할 옵션 (예: -o key=value)",
|
|
312
|
-
default: [] as string[],
|
|
313
|
-
},
|
|
314
|
-
}),
|
|
315
|
-
async (args) => {
|
|
316
|
-
await runReplaceDeps({
|
|
317
|
-
options: args.options,
|
|
318
|
-
});
|
|
319
|
-
},
|
|
320
|
-
)
|
|
321
|
-
.demandCommand(1, "명령어를 지정해주세요.")
|
|
322
|
-
.strict();
|
|
60
|
+
function calculateAffinityMask(cpuCount: number): string {
|
|
61
|
+
const exclude = cpuCount <= 1 ? 0 : Math.ceil(cpuCount / 4);
|
|
62
|
+
let mask = 0n;
|
|
63
|
+
for (let i = exclude; i < cpuCount; i++) {
|
|
64
|
+
mask |= 1n << BigInt(i);
|
|
65
|
+
}
|
|
66
|
+
return "0x" + mask.toString(16).toUpperCase();
|
|
323
67
|
}
|
|
324
68
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Cross-platform CPU affinity + priority 설정
|
|
71
|
+
*
|
|
72
|
+
* - Windows: PowerShell ProcessorAffinity + PriorityClass
|
|
73
|
+
* - Linux/WSL: taskset + renice
|
|
74
|
+
*
|
|
75
|
+
* 실패해도 경고만 출력하고 CLI 동작에는 영향 없음.
|
|
76
|
+
*/
|
|
77
|
+
function configureAffinityAndPriority(pid: number): void {
|
|
78
|
+
const cpuCount = os.cpus().length;
|
|
79
|
+
const mask = calculateAffinityMask(cpuCount);
|
|
80
|
+
|
|
81
|
+
let command: string;
|
|
82
|
+
if (process.platform === "win32") {
|
|
83
|
+
const commands = [
|
|
84
|
+
`$p = Get-Process -Id ${pid}`,
|
|
85
|
+
`$p.ProcessorAffinity = ${mask}`,
|
|
86
|
+
`$p.PriorityClass = 'BelowNormal'`,
|
|
87
|
+
].join("; ");
|
|
88
|
+
command = `powershell -Command "${commands}"`;
|
|
89
|
+
} else {
|
|
90
|
+
command = `taskset -p ${mask} ${pid} && renice +10 -p ${pid}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exec(command, (err) => {
|
|
94
|
+
if (err) {
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.warn("CPU affinity/priority 설정 실패:", err.message);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
330
99
|
}
|
|
@@ -186,27 +186,18 @@ export async function setupReplaceDeps(projectRoot: string, replaceDeps: Record<
|
|
|
186
186
|
try {
|
|
187
187
|
// targetPath가 symlink면 realpath로 해석하여 실제 .pnpm 스토어 경로 얻기
|
|
188
188
|
let actualTargetPath = targetPath;
|
|
189
|
-
let isSymlink = false;
|
|
190
189
|
try {
|
|
191
190
|
const stat = await fs.promises.lstat(targetPath);
|
|
192
191
|
if (stat.isSymbolicLink()) {
|
|
193
192
|
actualTargetPath = await fs.promises.realpath(targetPath);
|
|
194
|
-
isSymlink = true;
|
|
195
193
|
}
|
|
196
194
|
} catch {
|
|
197
195
|
// targetPath가 존재하지 않으면 그대로 사용
|
|
198
196
|
}
|
|
199
197
|
|
|
200
|
-
// 기존
|
|
198
|
+
// actualTargetPath의 기존 내용 제거 후 소스 복사 (symlink는 유지)
|
|
201
199
|
await fs.promises.rm(actualTargetPath, { recursive: true, force: true });
|
|
202
|
-
|
|
203
|
-
// symlink였다면 symlink도 제거
|
|
204
|
-
if (isSymlink) {
|
|
205
|
-
await fs.promises.rm(targetPath, { recursive: true, force: true });
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// 소스를 복사 (node_modules, package.json, .cache, tests 제외)
|
|
209
|
-
await fsCopy(resolvedSourcePath, targetPath, replaceDepsCopyFilter);
|
|
200
|
+
await fsCopy(resolvedSourcePath, actualTargetPath, replaceDepsCopyFilter);
|
|
210
201
|
|
|
211
202
|
logger.info(`${targetName} → ${sourcePath}`);
|
|
212
203
|
} catch (err) {
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"vitest": "vitest"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@simplysm/sd-cli": "~13.0.
|
|
19
|
-
"@simplysm/claude": "~13.0.
|
|
20
|
-
"@simplysm/lint": "~13.0.
|
|
18
|
+
"@simplysm/sd-cli": "~13.0.10",
|
|
19
|
+
"@simplysm/claude": "~13.0.10",
|
|
20
|
+
"@simplysm/lint": "~13.0.10",
|
|
21
21
|
"@types/node": "^20.19.33",
|
|
22
22
|
"eslint": "^9.39.2",
|
|
23
23
|
"prettier": "^3.8.1",
|