@simplysm/sd-cli 13.0.11 → 13.0.13
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 +42 -2
- package/dist/builders/BaseBuilder.d.ts +15 -4
- package/dist/builders/BaseBuilder.d.ts.map +1 -1
- package/dist/builders/BaseBuilder.js +50 -0
- package/dist/builders/BaseBuilder.js.map +1 -1
- package/dist/builders/DtsBuilder.d.ts.map +1 -1
- package/dist/builders/DtsBuilder.js +2 -39
- package/dist/builders/DtsBuilder.js.map +1 -1
- package/dist/builders/LibraryBuilder.d.ts.map +1 -1
- package/dist/builders/LibraryBuilder.js +2 -39
- package/dist/builders/LibraryBuilder.js.map +1 -1
- package/dist/builders/types.d.ts +2 -2
- package/dist/builders/types.d.ts.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/add-client.d.ts.map +1 -1
- package/dist/commands/add-client.js +1 -9
- package/dist/commands/add-client.js.map +1 -1
- package/dist/commands/add-server.d.ts.map +1 -1
- package/dist/commands/add-server.js +1 -9
- package/dist/commands/add-server.js.map +1 -1
- package/dist/commands/build.d.ts +1 -2
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +12 -311
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/dev.d.ts +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +11 -432
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/device.d.ts.map +1 -1
- package/dist/commands/device.js +17 -32
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lint.d.ts +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +71 -118
- package/dist/commands/lint.js.map +2 -2
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +47 -69
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/typecheck.d.ts +1 -1
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +11 -24
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/infra/ResultCollector.d.ts +1 -1
- package/dist/infra/ResultCollector.d.ts.map +1 -1
- package/dist/infra/ResultCollector.js +1 -1
- package/dist/infra/ResultCollector.js.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.d.ts +53 -0
- package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -0
- package/dist/orchestrators/BuildOrchestrator.js +338 -0
- package/dist/orchestrators/BuildOrchestrator.js.map +6 -0
- package/dist/orchestrators/DevOrchestrator.d.ts +64 -0
- package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -0
- package/dist/orchestrators/DevOrchestrator.js +524 -0
- package/dist/orchestrators/DevOrchestrator.js.map +6 -0
- package/dist/orchestrators/WatchOrchestrator.d.ts +1 -1
- package/dist/orchestrators/WatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/WatchOrchestrator.js +30 -21
- package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
- package/dist/orchestrators/index.d.ts +2 -0
- package/dist/orchestrators/index.d.ts.map +1 -1
- package/dist/orchestrators/index.js +4 -0
- package/dist/orchestrators/index.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 +6 -4
- package/dist/sd-cli.js.map +1 -1
- package/dist/utils/esbuild-config.d.ts +8 -0
- package/dist/utils/esbuild-config.d.ts.map +1 -1
- package/dist/utils/esbuild-config.js +23 -28
- package/dist/utils/esbuild-config.js.map +1 -1
- package/dist/utils/output-utils.d.ts +0 -1
- package/dist/utils/output-utils.d.ts.map +1 -1
- package/dist/utils/output-utils.js +1 -1
- package/dist/utils/output-utils.js.map +1 -1
- package/dist/utils/package-utils.d.ts +5 -1
- package/dist/utils/package-utils.d.ts.map +1 -1
- package/dist/utils/package-utils.js +12 -0
- package/dist/utils/package-utils.js.map +1 -1
- package/dist/utils/rebuild-manager.d.ts +15 -0
- package/dist/utils/rebuild-manager.d.ts.map +1 -0
- package/dist/utils/rebuild-manager.js +50 -0
- package/dist/utils/rebuild-manager.js.map +6 -0
- package/dist/utils/replace-deps.d.ts.map +1 -1
- package/dist/utils/replace-deps.js +7 -13
- package/dist/utils/replace-deps.js.map +1 -1
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +61 -7
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/utils/worker-events.d.ts +5 -4
- package/dist/utils/worker-events.d.ts.map +1 -1
- package/dist/utils/worker-events.js +4 -0
- package/dist/utils/worker-events.js.map +1 -1
- package/dist/utils/worker-utils.d.ts +13 -0
- package/dist/utils/worker-utils.d.ts.map +1 -0
- package/dist/utils/worker-utils.js +15 -0
- package/dist/utils/worker-utils.js.map +6 -0
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +2 -14
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/library.worker.d.ts.map +1 -1
- package/dist/workers/library.worker.js +14 -16
- 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 +11 -11
- 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 +2 -14
- package/dist/workers/server.worker.js.map +1 -1
- package/package.json +4 -5
- package/src/builders/BaseBuilder.ts +71 -4
- package/src/builders/DtsBuilder.ts +2 -49
- package/src/builders/LibraryBuilder.ts +2 -51
- package/src/builders/types.ts +2 -2
- package/src/capacitor/capacitor.ts +2 -1
- package/src/commands/add-client.ts +1 -14
- package/src/commands/add-server.ts +1 -13
- package/src/commands/build.ts +13 -443
- package/src/commands/dev.ts +12 -582
- package/src/commands/device.ts +17 -34
- package/src/commands/init.ts +1 -13
- package/src/commands/lint.ts +85 -146
- package/src/commands/publish.ts +58 -76
- package/src/commands/typecheck.ts +13 -46
- package/src/index.ts +1 -0
- package/src/infra/ResultCollector.ts +2 -2
- package/src/orchestrators/BuildOrchestrator.ts +499 -0
- package/src/orchestrators/DevOrchestrator.ts +703 -0
- package/src/orchestrators/WatchOrchestrator.ts +42 -25
- package/src/orchestrators/index.ts +2 -0
- package/src/sd-cli-entry.ts +14 -14
- package/src/sd-cli.ts +6 -4
- package/src/utils/esbuild-config.ts +31 -33
- package/src/utils/output-utils.ts +1 -2
- package/src/utils/package-utils.ts +16 -1
- package/src/utils/rebuild-manager.ts +65 -0
- package/src/utils/replace-deps.ts +25 -23
- package/src/utils/vite-config.ts +115 -9
- package/src/utils/worker-events.ts +13 -5
- package/src/utils/worker-utils.ts +26 -0
- package/src/workers/client.worker.ts +3 -19
- package/src/workers/library.worker.ts +16 -22
- package/src/workers/server-runtime.worker.ts +16 -17
- package/src/workers/server.worker.ts +2 -19
- 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/listr-manager.d.ts +0 -37
- package/dist/utils/listr-manager.d.ts.map +0 -1
- package/dist/utils/listr-manager.js +0 -59
- package/dist/utils/listr-manager.js.map +0 -6
- package/src/utils/listr-manager.ts +0 -89
package/src/commands/device.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "path";
|
|
2
|
-
import { Listr } from "listr2";
|
|
3
2
|
import { fsExists } from "@simplysm/core-node";
|
|
4
3
|
import { consola } from "consola";
|
|
5
4
|
import type { SdConfig, SdClientPackageConfig } from "../sd-config.types";
|
|
@@ -47,7 +46,7 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
|
|
|
47
46
|
sdConfig = await loadSdConfig({ cwd, dev: true, opt: options.options });
|
|
48
47
|
logger.debug("sd.config.ts 로드 완료");
|
|
49
48
|
} catch (err) {
|
|
50
|
-
|
|
49
|
+
logger.error(`sd.config.ts 로드 실패: ${err instanceof Error ? err.message : err}`);
|
|
51
50
|
process.exitCode = 1;
|
|
52
51
|
return;
|
|
53
52
|
}
|
|
@@ -55,13 +54,13 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
|
|
|
55
54
|
// 패키지 설정 확인
|
|
56
55
|
const pkgConfig = sdConfig.packages[packageName];
|
|
57
56
|
if (pkgConfig == null) {
|
|
58
|
-
|
|
57
|
+
logger.error(`패키지를 찾을 수 없습니다: ${packageName}`);
|
|
59
58
|
process.exitCode = 1;
|
|
60
59
|
return;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
if (pkgConfig.target !== "client") {
|
|
64
|
-
|
|
63
|
+
logger.error(`client 타겟 패키지만 지원합니다: ${packageName} (현재: ${pkgConfig.target})`);
|
|
65
64
|
process.exitCode = 1;
|
|
66
65
|
return;
|
|
67
66
|
}
|
|
@@ -76,7 +75,7 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
|
|
|
76
75
|
if (typeof clientConfig.server === "number") {
|
|
77
76
|
serverUrl = `http://localhost:${clientConfig.server}/${packageName}/`;
|
|
78
77
|
} else {
|
|
79
|
-
|
|
78
|
+
logger.error(`--url 옵션이 필요합니다. server가 패키지명으로 설정되어 있습니다: ${clientConfig.server}`);
|
|
80
79
|
process.exitCode = 1;
|
|
81
80
|
return;
|
|
82
81
|
}
|
|
@@ -84,21 +83,13 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
|
|
|
84
83
|
|
|
85
84
|
logger.debug("개발 서버 URL", { serverUrl });
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
title: `${packageName} (electron)`,
|
|
90
|
-
task: async () => {
|
|
91
|
-
const electron = await Electron.create(pkgDir, clientConfig.electron!);
|
|
92
|
-
await electron.run(serverUrl);
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
]);
|
|
96
|
-
|
|
86
|
+
logger.start(`${packageName} (electron) 실행 중...`);
|
|
97
87
|
try {
|
|
98
|
-
await
|
|
99
|
-
|
|
88
|
+
const electron = await Electron.create(pkgDir, clientConfig.electron);
|
|
89
|
+
await electron.run(serverUrl);
|
|
90
|
+
logger.success("Electron 실행 완료");
|
|
100
91
|
} catch (err) {
|
|
101
|
-
|
|
92
|
+
logger.error(`Electron 실행 실패: ${err instanceof Error ? err.message : err}`);
|
|
102
93
|
process.exitCode = 1;
|
|
103
94
|
}
|
|
104
95
|
} else if (clientConfig.capacitor != null) {
|
|
@@ -108,7 +99,7 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
|
|
|
108
99
|
if (typeof clientConfig.server === "number") {
|
|
109
100
|
serverUrl = `http://localhost:${clientConfig.server}/${packageName}/capacitor/`;
|
|
110
101
|
} else {
|
|
111
|
-
|
|
102
|
+
logger.error(`--url 옵션이 필요합니다. server가 패키지명으로 설정되어 있습니다: ${clientConfig.server}`);
|
|
112
103
|
process.exitCode = 1;
|
|
113
104
|
return;
|
|
114
105
|
}
|
|
@@ -120,30 +111,22 @@ export async function runDevice(options: DeviceOptions): Promise<void> {
|
|
|
120
111
|
|
|
121
112
|
const capPath = path.join(pkgDir, ".capacitor");
|
|
122
113
|
if (!(await fsExists(capPath))) {
|
|
123
|
-
|
|
114
|
+
logger.error(`Capacitor 프로젝트가 초기화되지 않았습니다. 먼저 'pnpm watch ${packageName}'를 실행하세요.`);
|
|
124
115
|
process.exitCode = 1;
|
|
125
116
|
return;
|
|
126
117
|
}
|
|
127
118
|
|
|
128
|
-
|
|
129
|
-
{
|
|
130
|
-
title: `${packageName} (device)`,
|
|
131
|
-
task: async () => {
|
|
132
|
-
const cap = await Capacitor.create(pkgDir, clientConfig.capacitor!);
|
|
133
|
-
await cap.runOnDevice(serverUrl);
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
]);
|
|
137
|
-
|
|
119
|
+
logger.start(`${packageName} (device) 실행 중...`);
|
|
138
120
|
try {
|
|
139
|
-
await
|
|
140
|
-
|
|
121
|
+
const cap = await Capacitor.create(pkgDir, clientConfig.capacitor);
|
|
122
|
+
await cap.runOnDevice(serverUrl);
|
|
123
|
+
logger.success("디바이스 실행 완료");
|
|
141
124
|
} catch (err) {
|
|
142
|
-
|
|
125
|
+
logger.error(`디바이스 실행 실패: ${err instanceof Error ? err.message : err}`);
|
|
143
126
|
process.exitCode = 1;
|
|
144
127
|
}
|
|
145
128
|
} else {
|
|
146
|
-
|
|
129
|
+
logger.error(`electron 또는 capacitor 설정이 없습니다: ${packageName}`);
|
|
147
130
|
process.exitCode = 1;
|
|
148
131
|
}
|
|
149
132
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "fs";
|
|
|
3
3
|
import { consola } from "consola";
|
|
4
4
|
import { renderTemplateDir } from "../utils/template";
|
|
5
5
|
import { spawn } from "../utils/spawn";
|
|
6
|
+
import { findPackageRoot } from "../utils/package-utils";
|
|
6
7
|
|
|
7
8
|
//#region Types
|
|
8
9
|
|
|
@@ -15,19 +16,6 @@ export interface InitOptions {}
|
|
|
15
16
|
|
|
16
17
|
//#region Utilities
|
|
17
18
|
|
|
18
|
-
/**
|
|
19
|
-
* import.meta.dirname에서 상위로 올라가며 package.json을 찾아 패키지 루트를 반환한다.
|
|
20
|
-
*/
|
|
21
|
-
function findPackageRoot(startDir: string): string {
|
|
22
|
-
let dir = startDir;
|
|
23
|
-
while (!fs.existsSync(path.join(dir, "package.json"))) {
|
|
24
|
-
const parent = path.dirname(dir);
|
|
25
|
-
if (parent === dir) throw new Error("package.json을 찾을 수 없습니다.");
|
|
26
|
-
dir = parent;
|
|
27
|
-
}
|
|
28
|
-
return dir;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
19
|
/**
|
|
32
20
|
* npm 스코프 이름 유효성 검증
|
|
33
21
|
*/
|
package/src/commands/lint.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { ESLint } from "eslint";
|
|
2
2
|
import { createJiti } from "jiti";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { Listr } from "listr2";
|
|
5
4
|
import { fsExists, fsGlob, pathFilterByTargets } from "@simplysm/core-node";
|
|
6
5
|
import "@simplysm/core-common";
|
|
7
6
|
import { SdError } from "@simplysm/core-common";
|
|
8
|
-
import { consola
|
|
7
|
+
import { consola } from "consola";
|
|
9
8
|
import stylelint from "stylelint";
|
|
10
9
|
|
|
11
10
|
//#region Types
|
|
@@ -22,23 +21,6 @@ export interface LintOptions {
|
|
|
22
21
|
timing: boolean;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
/**
|
|
26
|
-
* Listr2 컨텍스트 타입
|
|
27
|
-
*/
|
|
28
|
-
interface LintContext {
|
|
29
|
-
ignorePatterns: string[];
|
|
30
|
-
/** 파일 수집 태스크 완료 후 초기화됨 */
|
|
31
|
-
files?: string[];
|
|
32
|
-
/** 린트 대상 파일이 있을 때만 초기화됨 */
|
|
33
|
-
eslint?: ESLint;
|
|
34
|
-
/** 린트 대상 파일이 있을 때만 초기화됨 */
|
|
35
|
-
results?: ESLint.LintResult[];
|
|
36
|
-
// Stylelint
|
|
37
|
-
hasStylelintConfig?: boolean;
|
|
38
|
-
cssFiles?: string[];
|
|
39
|
-
stylelintResult?: stylelint.LinterResult;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
24
|
//#endregion
|
|
43
25
|
|
|
44
26
|
//#region Utilities
|
|
@@ -124,7 +106,7 @@ async function hasStylelintConfig(cwd: string): Promise<boolean> {
|
|
|
124
106
|
* ESLint를 실행한다.
|
|
125
107
|
*
|
|
126
108
|
* - `eslint.config.ts/js`에서 globalIgnores 패턴을 추출하여 glob 필터링에 적용
|
|
127
|
-
* -
|
|
109
|
+
* - consola를 사용하여 진행 상황 표시
|
|
128
110
|
* - 캐시 활성화 (`.cache/eslint.cache`에 저장, 설정 변경 시 자동 무효화)
|
|
129
111
|
* - 에러 발생 시 `process.exitCode = 1` 설정
|
|
130
112
|
*
|
|
@@ -143,133 +125,90 @@ export async function runLint(options: LintOptions): Promise<void> {
|
|
|
143
125
|
process.env["TIMING"] = "1";
|
|
144
126
|
}
|
|
145
127
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
});
|
|
221
|
-
cssFiles = pathFilterByTargets(cssFiles, targets, cwd);
|
|
222
|
-
ctx.cssFiles = cssFiles;
|
|
223
|
-
task.title = `CSS 파일 수집 (${cssFiles.length}개)`;
|
|
224
|
-
if (cssFiles.length === 0) {
|
|
225
|
-
task.skip("린트할 CSS 파일이 없습니다.");
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
title: "Stylelint 실행",
|
|
231
|
-
enabled: (ctx) => (ctx.cssFiles?.length ?? 0) > 0,
|
|
232
|
-
task: async (ctx, task) => {
|
|
233
|
-
const cssFiles = ctx.cssFiles!;
|
|
234
|
-
task.title = `Stylelint 실행 중... (${cssFiles.length}개 파일)`;
|
|
235
|
-
|
|
236
|
-
// Stylelint 설정 파일 경로 찾기
|
|
237
|
-
let configFile: string | undefined;
|
|
238
|
-
for (const f of STYLELINT_CONFIG_FILES) {
|
|
239
|
-
const configPath = path.join(cwd, f);
|
|
240
|
-
if (await fsExists(configPath)) {
|
|
241
|
-
configFile = configPath;
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const result = await stylelint.lint({
|
|
247
|
-
files: cssFiles,
|
|
248
|
-
configFile,
|
|
249
|
-
fix,
|
|
250
|
-
cache: true,
|
|
251
|
-
cacheLocation: path.join(cwd, ".cache", "stylelint.cache"),
|
|
252
|
-
});
|
|
253
|
-
ctx.stylelintResult = result;
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
],
|
|
257
|
-
{
|
|
258
|
-
renderer: consola.level >= LogLevels.debug ? "verbose" : "default",
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
const ctx = await listr.run();
|
|
128
|
+
// ESLint 설정 로드
|
|
129
|
+
logger.start("ESLint 설정 로드");
|
|
130
|
+
const ignorePatterns = await loadIgnorePatterns(cwd);
|
|
131
|
+
logger.debug("ignore 패턴 로드 완료", { ignorePatternCount: ignorePatterns.length });
|
|
132
|
+
logger.success(`ESLint 설정 로드 (${ignorePatterns.length}개 ignore 패턴)`);
|
|
133
|
+
|
|
134
|
+
// 린트 대상 파일 수집
|
|
135
|
+
logger.start("린트 대상 파일 수집");
|
|
136
|
+
let files = await fsGlob("**/*.{ts,tsx,js,jsx}", {
|
|
137
|
+
cwd,
|
|
138
|
+
ignore: ignorePatterns,
|
|
139
|
+
nodir: true,
|
|
140
|
+
absolute: true,
|
|
141
|
+
});
|
|
142
|
+
files = pathFilterByTargets(files, targets, cwd);
|
|
143
|
+
logger.debug("파일 수집 완료", { fileCount: files.length });
|
|
144
|
+
logger.success(`린트 대상 파일 수집 (${files.length}개)`);
|
|
145
|
+
|
|
146
|
+
// 린트 실행
|
|
147
|
+
let eslint: ESLint | undefined;
|
|
148
|
+
let eslintResults: ESLint.LintResult[] | undefined;
|
|
149
|
+
if (files.length > 0) {
|
|
150
|
+
logger.start(`린트 실행 중... (${files.length}개 파일)`);
|
|
151
|
+
eslint = new ESLint({
|
|
152
|
+
cwd,
|
|
153
|
+
fix,
|
|
154
|
+
cache: true,
|
|
155
|
+
cacheLocation: path.join(cwd, ".cache", "eslint.cache"),
|
|
156
|
+
});
|
|
157
|
+
eslintResults = await eslint.lintFiles(files);
|
|
158
|
+
logger.success("린트 실행 완료");
|
|
159
|
+
|
|
160
|
+
// 자동 수정 적용
|
|
161
|
+
if (fix) {
|
|
162
|
+
logger.debug("자동 수정 적용 중...");
|
|
163
|
+
await ESLint.outputFixes(eslintResults);
|
|
164
|
+
logger.debug("자동 수정 적용 완료");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Stylelint
|
|
169
|
+
const hasStylelintCfg = await hasStylelintConfig(cwd);
|
|
170
|
+
let stylelintResult: stylelint.LinterResult | undefined;
|
|
171
|
+
if (hasStylelintCfg) {
|
|
172
|
+
logger.start("CSS 파일 수집");
|
|
173
|
+
let cssFiles = await fsGlob("**/*.css", {
|
|
174
|
+
cwd,
|
|
175
|
+
ignore: ignorePatterns,
|
|
176
|
+
nodir: true,
|
|
177
|
+
absolute: true,
|
|
178
|
+
});
|
|
179
|
+
cssFiles = pathFilterByTargets(cssFiles, targets, cwd);
|
|
180
|
+
logger.success(`CSS 파일 수집 (${cssFiles.length}개)`);
|
|
181
|
+
|
|
182
|
+
if (cssFiles.length > 0) {
|
|
183
|
+
logger.start(`Stylelint 실행 중... (${cssFiles.length}개 파일)`);
|
|
184
|
+
let configFile: string | undefined;
|
|
185
|
+
for (const f of STYLELINT_CONFIG_FILES) {
|
|
186
|
+
const configPath = path.join(cwd, f);
|
|
187
|
+
if (await fsExists(configPath)) {
|
|
188
|
+
configFile = configPath;
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
stylelintResult = await stylelint.lint({
|
|
193
|
+
files: cssFiles,
|
|
194
|
+
configFile,
|
|
195
|
+
fix,
|
|
196
|
+
cache: true,
|
|
197
|
+
cacheLocation: path.join(cwd, ".cache", "stylelint.cache"),
|
|
198
|
+
});
|
|
199
|
+
logger.success("Stylelint 실행 완료");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
263
202
|
|
|
264
203
|
// 파일이 없거나 린트가 실행되지 않았으면 조기 종료
|
|
265
|
-
if (
|
|
204
|
+
if (files.length === 0 || eslintResults == null || eslint == null) {
|
|
266
205
|
logger.info("린트할 파일 없음");
|
|
267
206
|
return;
|
|
268
207
|
}
|
|
269
208
|
|
|
270
209
|
// 결과 집계
|
|
271
|
-
const errorCount =
|
|
272
|
-
const warningCount =
|
|
210
|
+
const errorCount = eslintResults.sum((r) => r.errorCount);
|
|
211
|
+
const warningCount = eslintResults.sum((r) => r.warningCount);
|
|
273
212
|
|
|
274
213
|
if (errorCount > 0) {
|
|
275
214
|
logger.error("린트 에러 발생", { errorCount, warningCount });
|
|
@@ -280,8 +219,8 @@ export async function runLint(options: LintOptions): Promise<void> {
|
|
|
280
219
|
}
|
|
281
220
|
|
|
282
221
|
// 포맷터 출력
|
|
283
|
-
const formatter = await
|
|
284
|
-
const resultText = await formatter.format(
|
|
222
|
+
const formatter = await eslint.loadFormatter("stylish");
|
|
223
|
+
const resultText = await formatter.format(eslintResults);
|
|
285
224
|
if (resultText) {
|
|
286
225
|
process.stdout.write(resultText);
|
|
287
226
|
}
|
|
@@ -292,11 +231,11 @@ export async function runLint(options: LintOptions): Promise<void> {
|
|
|
292
231
|
}
|
|
293
232
|
|
|
294
233
|
// Stylelint 결과 출력
|
|
295
|
-
if (
|
|
296
|
-
const stylelintErrorCount =
|
|
234
|
+
if (stylelintResult != null && stylelintResult.results.length > 0) {
|
|
235
|
+
const stylelintErrorCount = stylelintResult.results.sum(
|
|
297
236
|
(r) => r.warnings.filter((w) => w.severity === "error").length,
|
|
298
237
|
);
|
|
299
|
-
const stylelintWarningCount =
|
|
238
|
+
const stylelintWarningCount = stylelintResult.results.sum(
|
|
300
239
|
(r) => r.warnings.filter((w) => w.severity === "warning").length,
|
|
301
240
|
);
|
|
302
241
|
|
|
@@ -313,7 +252,7 @@ export async function runLint(options: LintOptions): Promise<void> {
|
|
|
313
252
|
|
|
314
253
|
// Stylelint formatter 출력
|
|
315
254
|
const stylelintFormatter = await stylelint.formatters.string;
|
|
316
|
-
const stylelintOutput = stylelintFormatter(
|
|
255
|
+
const stylelintOutput = stylelintFormatter(stylelintResult.results, stylelintResult);
|
|
317
256
|
if (stylelintOutput) {
|
|
318
257
|
process.stdout.write(stylelintOutput);
|
|
319
258
|
}
|
package/src/commands/publish.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import semver from "semver";
|
|
3
|
-
import { consola
|
|
4
|
-
import { Listr, type ListrTask } from "listr2";
|
|
3
|
+
import { consola } from "consola";
|
|
5
4
|
import { StorageFactory } from "@simplysm/storage";
|
|
6
5
|
import { fsExists, fsRead, fsReadJson, fsWrite, fsGlob, fsCopy } from "@simplysm/core-node";
|
|
7
6
|
import { env, jsonStringify } from "@simplysm/core-common";
|
|
@@ -10,6 +9,7 @@ import type { SdConfig, SdPublishConfig } from "../sd-config.types";
|
|
|
10
9
|
import { loadSdConfig } from "../utils/sd-config";
|
|
11
10
|
import { spawn } from "../utils/spawn";
|
|
12
11
|
import { runBuild } from "./build";
|
|
12
|
+
import { parseWorkspaceGlobs } from "../utils/replace-deps";
|
|
13
13
|
import os from "os";
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import ssh2 from "ssh2";
|
|
@@ -214,10 +214,11 @@ function registerSshPublicKey(
|
|
|
214
214
|
const conn = new SshClient();
|
|
215
215
|
conn.on("ready", () => {
|
|
216
216
|
// authorized_keys에 공개키 추가
|
|
217
|
+
const escapedKey = publicKey.replace(/'/g, "'\\''");
|
|
217
218
|
const cmd = [
|
|
218
219
|
"mkdir -p ~/.ssh",
|
|
219
220
|
"chmod 700 ~/.ssh",
|
|
220
|
-
`echo '${
|
|
221
|
+
`echo '${escapedKey}' >> ~/.ssh/authorized_keys`,
|
|
221
222
|
"chmod 600 ~/.ssh/authorized_keys",
|
|
222
223
|
].join(" && ");
|
|
223
224
|
|
|
@@ -481,7 +482,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
481
482
|
sdConfig = await loadSdConfig({ cwd, dev: false, opt: options.options });
|
|
482
483
|
logger.debug("sd.config.ts 로드 완료");
|
|
483
484
|
} catch (err) {
|
|
484
|
-
|
|
485
|
+
logger.error(`sd.config.ts 로드 실패: ${err instanceof Error ? err.message : err}`);
|
|
485
486
|
process.exitCode = 1;
|
|
486
487
|
return;
|
|
487
488
|
}
|
|
@@ -495,26 +496,12 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
495
496
|
const workspaceGlobs: string[] = [];
|
|
496
497
|
if (await fsExists(workspaceYamlPath)) {
|
|
497
498
|
const yamlContent = await fsRead(workspaceYamlPath);
|
|
498
|
-
|
|
499
|
-
for (const line of yamlContent.split("\n")) {
|
|
500
|
-
if (/^packages:\s*$/.test(line)) {
|
|
501
|
-
inPackages = true;
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
if (inPackages) {
|
|
505
|
-
const match = /^\s+-\s+(.+)$/.exec(line);
|
|
506
|
-
if (match != null) {
|
|
507
|
-
workspaceGlobs.push(match[1].trim());
|
|
508
|
-
} else {
|
|
509
|
-
break;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
499
|
+
workspaceGlobs.push(...parseWorkspaceGlobs(yamlContent));
|
|
513
500
|
}
|
|
514
501
|
|
|
515
502
|
const allPkgPaths = (await Promise.all(workspaceGlobs.map((item) => fsGlob(path.resolve(cwd, item)))))
|
|
516
503
|
.flat()
|
|
517
|
-
.filter((item) => !item.includes("."));
|
|
504
|
+
.filter((item) => !path.basename(item).includes("."));
|
|
518
505
|
|
|
519
506
|
// publish 설정이 있는 패키지 필터링
|
|
520
507
|
const publishPackages: Array<{
|
|
@@ -571,7 +558,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
571
558
|
}
|
|
572
559
|
logger.debug(`npm 로그인 확인: ${whoami.trim()}`);
|
|
573
560
|
} catch {
|
|
574
|
-
|
|
561
|
+
logger.error(
|
|
575
562
|
"npm 토큰이 유효하지 않거나 만료되었습니다.\n" +
|
|
576
563
|
"https://www.npmjs.com/settings/~/tokens 에서 Granular Access Token 생성 후:\n" +
|
|
577
564
|
" npm config set //registry.npmjs.org/:_authToken <토큰>",
|
|
@@ -585,7 +572,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
585
572
|
try {
|
|
586
573
|
await ensureSshAuth(publishPackages, logger);
|
|
587
574
|
} catch (err) {
|
|
588
|
-
|
|
575
|
+
logger.error(`SSH 인증 설정 실패: ${err instanceof Error ? err.message : err}`);
|
|
589
576
|
process.exitCode = 1;
|
|
590
577
|
return;
|
|
591
578
|
}
|
|
@@ -617,7 +604,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
617
604
|
logger.info("자동 커밋 완료.");
|
|
618
605
|
}
|
|
619
606
|
} catch (err) {
|
|
620
|
-
|
|
607
|
+
logger.error(err instanceof Error ? err.message : err);
|
|
621
608
|
process.exitCode = 1;
|
|
622
609
|
return;
|
|
623
610
|
}
|
|
@@ -666,7 +653,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
666
653
|
if (dryRun) {
|
|
667
654
|
logger.error("[DRY-RUN] 빌드 실패");
|
|
668
655
|
} else {
|
|
669
|
-
|
|
656
|
+
logger.error(
|
|
670
657
|
"빌드 실패. 수동 복구가 필요할 수 있습니다:\n" +
|
|
671
658
|
" 버전 변경을 되돌리려면:\n" +
|
|
672
659
|
" git checkout -- package.json packages/*/package.json packages/sd-cli/templates/",
|
|
@@ -699,7 +686,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
699
686
|
await spawn("git", ["push", "--tags"]);
|
|
700
687
|
logger.debug("Git 작업 완료");
|
|
701
688
|
} catch (err) {
|
|
702
|
-
|
|
689
|
+
logger.error(
|
|
703
690
|
`Git 작업 실패: ${err instanceof Error ? err.message : err}\n` +
|
|
704
691
|
"수동 복구가 필요할 수 있습니다:\n" +
|
|
705
692
|
` git revert HEAD # 버전 커밋 되돌리기\n` +
|
|
@@ -716,59 +703,56 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
716
703
|
|
|
717
704
|
//#endregion
|
|
718
705
|
|
|
719
|
-
//#region Phase 4: 배포 (의존성 레벨별
|
|
706
|
+
//#region Phase 4: 배포 (의존성 레벨별 순차, 레벨 내 병렬)
|
|
720
707
|
|
|
721
708
|
const levels = await computePublishLevels(publishPackages);
|
|
722
709
|
const publishedPackages: string[] = [];
|
|
723
710
|
let publishFailed = false;
|
|
724
711
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
title: `Level ${levelIdx + 1}/${levels.length}`,
|
|
729
|
-
skip: () => publishFailed,
|
|
730
|
-
task: (_, task) =>
|
|
731
|
-
task.newListr(
|
|
732
|
-
levelPkgs.map(
|
|
733
|
-
(pkg): ListrTask => ({
|
|
734
|
-
title: dryRun ? `[DRY-RUN] ${pkg.name}` : pkg.name,
|
|
735
|
-
task: async (_ctx, pkgTask) => {
|
|
736
|
-
const maxRetries = 3;
|
|
737
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
738
|
-
try {
|
|
739
|
-
await publishPackage(pkg.path, pkg.config, version, cwd, logger, dryRun);
|
|
740
|
-
break;
|
|
741
|
-
} catch (err) {
|
|
742
|
-
if (attempt < maxRetries) {
|
|
743
|
-
const delay = attempt * 5_000;
|
|
744
|
-
pkgTask.title = dryRun
|
|
745
|
-
? `[DRY-RUN] ${pkg.name} (재시도 ${attempt + 1}/${maxRetries})`
|
|
746
|
-
: `${pkg.name} (재시도 ${attempt + 1}/${maxRetries})`;
|
|
747
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
748
|
-
} else {
|
|
749
|
-
throw err;
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
publishedPackages.push(pkg.name);
|
|
754
|
-
},
|
|
755
|
-
}),
|
|
756
|
-
),
|
|
757
|
-
{ concurrent: true, exitOnError: false },
|
|
758
|
-
),
|
|
759
|
-
}),
|
|
760
|
-
),
|
|
761
|
-
{
|
|
762
|
-
concurrent: false,
|
|
763
|
-
exitOnError: false,
|
|
764
|
-
renderer: consola.level >= LogLevels.debug ? "verbose" : "default",
|
|
765
|
-
},
|
|
766
|
-
);
|
|
712
|
+
// 레벨별 순차 실행
|
|
713
|
+
for (let levelIdx = 0; levelIdx < levels.length; levelIdx++) {
|
|
714
|
+
if (publishFailed) break;
|
|
767
715
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
//
|
|
716
|
+
const levelPkgs = levels[levelIdx];
|
|
717
|
+
logger.start(`Level ${levelIdx + 1}/${levels.length}`);
|
|
718
|
+
|
|
719
|
+
// 레벨 내 패키지 병렬 실행 (Promise.allSettled)
|
|
720
|
+
const publishPromises = levelPkgs.map(async (pkg) => {
|
|
721
|
+
const maxRetries = 3;
|
|
722
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
723
|
+
try {
|
|
724
|
+
await publishPackage(pkg.path, pkg.config, version, cwd, logger, dryRun);
|
|
725
|
+
logger.debug(dryRun ? `[DRY-RUN] ${pkg.name}` : pkg.name);
|
|
726
|
+
publishedPackages.push(pkg.name);
|
|
727
|
+
return { status: "success" as const, name: pkg.name };
|
|
728
|
+
} catch (err) {
|
|
729
|
+
if (attempt < maxRetries) {
|
|
730
|
+
const delay = attempt * 5_000;
|
|
731
|
+
logger.debug(
|
|
732
|
+
dryRun
|
|
733
|
+
? `[DRY-RUN] ${pkg.name} (재시도 ${attempt + 1}/${maxRetries})`
|
|
734
|
+
: `${pkg.name} (재시도 ${attempt + 1}/${maxRetries})`,
|
|
735
|
+
);
|
|
736
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
737
|
+
} else {
|
|
738
|
+
throw err;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
// TypeScript 타입 체커를 위한 fallback (실제로는 도달하지 않음)
|
|
743
|
+
return { status: "error" as const, name: pkg.name, error: new Error("Unknown error") };
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const results = await Promise.allSettled(publishPromises);
|
|
747
|
+
|
|
748
|
+
// 레벨 내 실패 확인
|
|
749
|
+
const levelFailed = results.some((r) => r.status === "rejected");
|
|
750
|
+
if (levelFailed) {
|
|
751
|
+
publishFailed = true;
|
|
752
|
+
logger.fail(`Level ${levelIdx + 1}/${levels.length}`);
|
|
753
|
+
} else {
|
|
754
|
+
logger.success(`Level ${levelIdx + 1}/${levels.length}`);
|
|
755
|
+
}
|
|
772
756
|
}
|
|
773
757
|
|
|
774
758
|
// 실패한 패키지 확인
|
|
@@ -776,10 +760,8 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
776
760
|
const failedPkgNames = allPkgNames.filter((n) => !publishedPackages.includes(n));
|
|
777
761
|
|
|
778
762
|
if (failedPkgNames.length > 0) {
|
|
779
|
-
publishFailed = true;
|
|
780
|
-
|
|
781
763
|
if (publishedPackages.length > 0) {
|
|
782
|
-
|
|
764
|
+
logger.error(
|
|
783
765
|
"배포 중 오류가 발생했습니다.\n" +
|
|
784
766
|
"이미 배포된 패키지:\n" +
|
|
785
767
|
publishedPackages.map((n) => ` - ${n}`).join("\n") +
|
|
@@ -789,7 +771,7 @@ export async function runPublish(options: PublishOptions): Promise<void> {
|
|
|
789
771
|
}
|
|
790
772
|
|
|
791
773
|
for (const name of failedPkgNames) {
|
|
792
|
-
|
|
774
|
+
logger.error(`[${name}] 배포 실패`);
|
|
793
775
|
}
|
|
794
776
|
process.exitCode = 1;
|
|
795
777
|
return;
|