@simplysm/sd-cli 13.0.0-beta.46 → 13.0.0-beta.47
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 +3 -3
- package/dist/builders/BaseBuilder.js.map +0 -1
- package/dist/builders/DtsBuilder.js.map +0 -1
- package/dist/builders/LibraryBuilder.js.map +0 -1
- package/dist/builders/index.js.map +0 -1
- package/dist/builders/types.js.map +0 -1
- package/dist/capacitor/capacitor.js.map +0 -1
- package/dist/commands/add-client.js.map +0 -1
- package/dist/commands/add-server.js.map +0 -1
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/device.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/lint.js.map +0 -1
- package/dist/commands/publish.js.map +0 -1
- package/dist/commands/typecheck.js.map +0 -1
- package/dist/commands/watch.js.map +0 -1
- package/dist/electron/electron.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/infra/ResultCollector.js.map +0 -1
- package/dist/infra/SignalHandler.js.map +0 -1
- package/dist/infra/WorkerManager.js.map +0 -1
- package/dist/infra/index.js.map +0 -1
- package/dist/orchestrators/WatchOrchestrator.js.map +0 -1
- package/dist/orchestrators/index.js.map +0 -1
- package/dist/sd-cli.js.map +0 -1
- package/dist/sd-config.types.js.map +0 -1
- package/dist/utils/build-env.js.map +0 -1
- package/dist/utils/config-editor.js.map +0 -1
- package/dist/utils/copy-src.js.map +0 -1
- package/dist/utils/esbuild-config.d.ts +1 -0
- package/dist/utils/esbuild-config.d.ts.map +1 -1
- package/dist/utils/esbuild-config.js +2 -1
- package/dist/utils/esbuild-config.js.map +1 -2
- package/dist/utils/listr-manager.js.map +0 -1
- package/dist/utils/output-utils.js.map +0 -1
- package/dist/utils/package-utils.js.map +0 -1
- package/dist/utils/replace-deps.js.map +0 -1
- package/dist/utils/sd-config.js.map +0 -1
- package/dist/utils/spawn.js.map +0 -1
- package/dist/utils/tailwind-config-deps.js.map +0 -1
- package/dist/utils/template.js.map +0 -1
- package/dist/utils/tsconfig.js.map +0 -1
- package/dist/utils/typecheck-serialization.js.map +0 -1
- package/dist/utils/vite-config.js.map +0 -1
- package/dist/utils/worker-events.js.map +0 -1
- package/dist/workers/client.worker.js.map +0 -1
- package/dist/workers/dts.worker.js.map +0 -1
- package/dist/workers/library.worker.js.map +0 -1
- package/dist/workers/server-runtime.worker.js.map +0 -1
- package/dist/workers/server.worker.js.map +0 -1
- package/package.json +5 -4
- package/src/builders/BaseBuilder.ts +141 -0
- package/src/builders/DtsBuilder.ts +138 -0
- package/src/builders/LibraryBuilder.ts +161 -0
- package/src/builders/index.ts +4 -0
- package/src/builders/types.ts +55 -0
- package/src/capacitor/capacitor.ts +827 -0
- package/src/commands/add-client.ts +135 -0
- package/src/commands/add-server.ts +150 -0
- package/src/commands/build.ts +475 -0
- package/src/commands/dev.ts +602 -0
- package/src/commands/device.ts +151 -0
- package/src/commands/init.ts +104 -0
- package/src/commands/lint.ts +216 -0
- package/src/commands/publish.ts +836 -0
- package/src/commands/typecheck.ts +329 -0
- package/src/commands/watch.ts +38 -0
- package/src/electron/electron.ts +329 -0
- package/src/index.ts +1 -0
- package/src/infra/ResultCollector.ts +81 -0
- package/src/infra/SignalHandler.ts +52 -0
- package/src/infra/WorkerManager.ts +65 -0
- package/src/infra/index.ts +3 -0
- package/src/orchestrators/WatchOrchestrator.ts +211 -0
- package/src/orchestrators/index.ts +1 -0
- package/src/sd-cli.ts +307 -0
- package/src/sd-config.types.ts +271 -0
- package/src/utils/build-env.ts +12 -0
- package/src/utils/config-editor.ts +131 -0
- package/src/utils/copy-src.ts +60 -0
- package/src/utils/esbuild-config.ts +263 -0
- package/src/utils/listr-manager.ts +89 -0
- package/src/utils/output-utils.ts +61 -0
- package/src/utils/package-utils.ts +63 -0
- package/src/utils/replace-deps.ts +163 -0
- package/src/utils/sd-config.ts +44 -0
- package/src/utils/spawn.ts +79 -0
- package/src/utils/tailwind-config-deps.ts +95 -0
- package/src/utils/template.ts +51 -0
- package/src/utils/tsconfig.ts +111 -0
- package/src/utils/typecheck-serialization.ts +82 -0
- package/src/utils/vite-config.ts +184 -0
- package/src/utils/worker-events.ts +102 -0
- package/src/workers/client.worker.ts +236 -0
- package/src/workers/dts.worker.ts +416 -0
- package/src/workers/library.worker.ts +245 -0
- package/src/workers/server-runtime.worker.ts +154 -0
- package/src/workers/server.worker.ts +435 -0
- 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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import type { Plugin, UserConfig as ViteUserConfig } from "vite";
|
|
5
|
+
import tsconfigPaths from "vite-tsconfig-paths";
|
|
6
|
+
import solidPlugin from "vite-plugin-solid";
|
|
7
|
+
import { VitePWA } from "vite-plugin-pwa";
|
|
8
|
+
import tailwindcss from "tailwindcss";
|
|
9
|
+
import type esbuild from "esbuild";
|
|
10
|
+
import { getTailwindConfigDeps } from "./tailwind-config-deps.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tailwind config의 scope 패키지 의존성을 watch하는 Vite 플러그인.
|
|
14
|
+
*
|
|
15
|
+
* Tailwind CSS의 내장 의존성 추적은 상대 경로 import만 처리하므로,
|
|
16
|
+
* preset 등으로 참조하는 scope 패키지의 config 변경을 감지하지 못한다.
|
|
17
|
+
* 이 플러그인이 해당 파일들을 watch하고, 변경 시 Tailwind 캐시를 무효화한다.
|
|
18
|
+
*/
|
|
19
|
+
function sdTailwindConfigDepsPlugin(pkgDir: string): Plugin {
|
|
20
|
+
return {
|
|
21
|
+
name: "sd-tailwind-config-deps",
|
|
22
|
+
configureServer(server) {
|
|
23
|
+
const configPath = path.join(pkgDir, "tailwind.config.ts");
|
|
24
|
+
if (!fs.existsSync(configPath)) return;
|
|
25
|
+
|
|
26
|
+
// 현재 패키지의 scope + @simplysm 을 항상 포함
|
|
27
|
+
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
28
|
+
const pkgName = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")).name as string;
|
|
29
|
+
const pkgScope = pkgName.match(/^(@[^/]+)\//)?.[1];
|
|
30
|
+
const scopes = new Set(["@simplysm"]);
|
|
31
|
+
if (pkgScope != null) {
|
|
32
|
+
scopes.add(pkgScope);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const allDeps = getTailwindConfigDeps(configPath, [...scopes]);
|
|
36
|
+
const configAbsolute = path.resolve(configPath);
|
|
37
|
+
const externalDeps = allDeps.filter((d) => d !== configAbsolute);
|
|
38
|
+
if (externalDeps.length === 0) return;
|
|
39
|
+
|
|
40
|
+
for (const dep of externalDeps) {
|
|
41
|
+
server.watcher.add(dep);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
server.watcher.on("change", (changed) => {
|
|
45
|
+
if (externalDeps.some((d) => path.normalize(d) === path.normalize(changed))) {
|
|
46
|
+
// jiti (Tailwind의 config 로더)가 사용하는 require 캐시를 정리하여
|
|
47
|
+
// config 재로드 시 변경된 파일이 새로 읽히도록 한다
|
|
48
|
+
const _require = createRequire(import.meta.url);
|
|
49
|
+
for (const dep of allDeps) {
|
|
50
|
+
delete _require.cache[dep];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Tailwind 캐시 무효화: config의 mtime을 갱신하여 재로드 유도
|
|
54
|
+
const now = new Date();
|
|
55
|
+
fs.utimesSync(configPath, now, now);
|
|
56
|
+
server.ws.send({ type: "full-reload" });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* scope 패키지의 dist 디렉토리 변경을 감지하는 Vite 플러그인.
|
|
65
|
+
*
|
|
66
|
+
* Vite는 node_modules를 기본적으로 watch에서 제외하므로,
|
|
67
|
+
* scope 패키지의 dist 파일이 변경되어도 HMR/리빌드가 트리거되지 않는다.
|
|
68
|
+
* 이 플러그인은 scope 패키지의 dist 디렉토리를 watcher에 명시적으로 추가하고,
|
|
69
|
+
* optimizeDeps에서 제외하여 pre-bundled 캐시로 인한 변경 무시를 방지한다.
|
|
70
|
+
*/
|
|
71
|
+
function sdScopeWatchPlugin(pkgDir: string, scopes: string[]): Plugin {
|
|
72
|
+
return {
|
|
73
|
+
name: "sd-scope-watch",
|
|
74
|
+
config() {
|
|
75
|
+
return {
|
|
76
|
+
optimizeDeps: {
|
|
77
|
+
exclude: scopes.flatMap((s) => {
|
|
78
|
+
// scope 패키지를 pre-bundling에서 제외하여 소스 코드로 취급
|
|
79
|
+
const scopeDir = path.join(pkgDir, "node_modules", s);
|
|
80
|
+
if (!fs.existsSync(scopeDir)) return [];
|
|
81
|
+
return fs.readdirSync(scopeDir).map((name) => `${s}/${name}`);
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
configureServer(server) {
|
|
87
|
+
for (const scope of scopes) {
|
|
88
|
+
const scopeDir = path.join(pkgDir, "node_modules", scope);
|
|
89
|
+
if (!fs.existsSync(scopeDir)) continue;
|
|
90
|
+
|
|
91
|
+
for (const pkgName of fs.readdirSync(scopeDir)) {
|
|
92
|
+
const distDir = path.join(scopeDir, pkgName, "dist");
|
|
93
|
+
if (fs.existsSync(distDir)) {
|
|
94
|
+
server.watcher.add(distDir);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Vite 설정 생성 옵션
|
|
104
|
+
*/
|
|
105
|
+
export interface ViteConfigOptions {
|
|
106
|
+
pkgDir: string;
|
|
107
|
+
name: string;
|
|
108
|
+
tsconfigPath: string;
|
|
109
|
+
compilerOptions: Record<string, unknown>;
|
|
110
|
+
env?: Record<string, string>;
|
|
111
|
+
mode: "build" | "dev";
|
|
112
|
+
/** dev 모드일 때 서버 포트 (0이면 자동 할당) */
|
|
113
|
+
serverPort?: number;
|
|
114
|
+
/** watch 대상 scope 목록 (예: ["@myapp", "@simplysm"]) */
|
|
115
|
+
watchScopes?: string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Vite 설정 생성
|
|
120
|
+
*
|
|
121
|
+
* SolidJS + TailwindCSS 기반의 client 패키지 빌드/개발 서버용 설정입니다.
|
|
122
|
+
* - build 모드: production 빌드 (logLevel: silent)
|
|
123
|
+
* - dev 모드: dev server (define으로 env 치환, server 설정)
|
|
124
|
+
*/
|
|
125
|
+
export function createViteConfig(options: ViteConfigOptions): ViteUserConfig {
|
|
126
|
+
const { pkgDir, name, tsconfigPath, compilerOptions, env, mode, serverPort, watchScopes } = options;
|
|
127
|
+
|
|
128
|
+
// Read package.json to extract app name for PWA manifest
|
|
129
|
+
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
130
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8")) as { name: string };
|
|
131
|
+
const appName = pkgJson.name.replace(/^@[^/]+\//, "");
|
|
132
|
+
|
|
133
|
+
// process.env 치환 (dev 모드에서만 사용, build 모드는 inline으로 처리됨)
|
|
134
|
+
const envDefine: Record<string, string> = {};
|
|
135
|
+
if (env != null) {
|
|
136
|
+
envDefine["process.env"] = JSON.stringify(env);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const config: ViteUserConfig = {
|
|
140
|
+
root: pkgDir,
|
|
141
|
+
base: `/${name}/`,
|
|
142
|
+
plugins: [
|
|
143
|
+
tsconfigPaths({ projects: [tsconfigPath] }),
|
|
144
|
+
solidPlugin(),
|
|
145
|
+
VitePWA({
|
|
146
|
+
registerType: "prompt",
|
|
147
|
+
injectRegister: "script",
|
|
148
|
+
manifest: {
|
|
149
|
+
name: appName,
|
|
150
|
+
short_name: appName,
|
|
151
|
+
display: "standalone",
|
|
152
|
+
theme_color: "#ffffff",
|
|
153
|
+
background_color: "#ffffff",
|
|
154
|
+
},
|
|
155
|
+
workbox: {
|
|
156
|
+
globPatterns: ["**/*.{js,css,html,ico,png,svg,woff2}"],
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
sdTailwindConfigDepsPlugin(pkgDir),
|
|
160
|
+
...(watchScopes != null && watchScopes.length > 0 ? [sdScopeWatchPlugin(pkgDir, watchScopes)] : []),
|
|
161
|
+
],
|
|
162
|
+
css: {
|
|
163
|
+
postcss: {
|
|
164
|
+
plugins: [tailwindcss({ config: path.join(pkgDir, "tailwind.config.ts") })],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
esbuild: {
|
|
168
|
+
tsconfigRaw: { compilerOptions: compilerOptions as esbuild.TsconfigRaw["compilerOptions"] },
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (mode === "build") {
|
|
173
|
+
config.logLevel = "silent";
|
|
174
|
+
} else {
|
|
175
|
+
// dev 모드
|
|
176
|
+
config.define = envDefine;
|
|
177
|
+
config.server = {
|
|
178
|
+
port: serverPort === 0 ? undefined : serverPort,
|
|
179
|
+
strictPort: serverPort !== 0 && serverPort !== undefined,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return config;
|
|
184
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { PackageResult } from "./package-utils";
|
|
2
|
+
import type { SdPackageConfig } from "../sd-config.types";
|
|
3
|
+
import type { RebuildListrManager } from "./listr-manager";
|
|
4
|
+
|
|
5
|
+
/** Worker 빌드 완료 이벤트 데이터 */
|
|
6
|
+
export interface BuildEventData {
|
|
7
|
+
success: boolean;
|
|
8
|
+
errors?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Worker 에러 이벤트 데이터 */
|
|
12
|
+
export interface ErrorEventData {
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Worker 서버 준비 이벤트 데이터 */
|
|
17
|
+
export interface ServerReadyEventData {
|
|
18
|
+
port: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Server Build 완료 이벤트 데이터 */
|
|
22
|
+
export interface ServerBuildEventData {
|
|
23
|
+
success: boolean;
|
|
24
|
+
mainJsPath: string;
|
|
25
|
+
errors?: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 기본 Worker 정보 타입
|
|
30
|
+
*/
|
|
31
|
+
export interface BaseWorkerInfo {
|
|
32
|
+
name: string;
|
|
33
|
+
config: SdPackageConfig;
|
|
34
|
+
worker: { on: (event: string, handler: (data: unknown) => void) => void };
|
|
35
|
+
isInitialBuild: boolean;
|
|
36
|
+
buildResolver: (() => void) | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Worker 이벤트 핸들러 옵션
|
|
41
|
+
*/
|
|
42
|
+
export interface WorkerEventHandlerOptions {
|
|
43
|
+
resultKey: string;
|
|
44
|
+
listrTitle: string;
|
|
45
|
+
resultType: "build" | "dts";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 공통 Worker 이벤트 핸들러 등록 (buildStart, build, error만 - serverReady는 포함하지 않음)
|
|
50
|
+
*
|
|
51
|
+
* @param workerInfo Worker 정보
|
|
52
|
+
* @param opts 핸들러 옵션
|
|
53
|
+
* @param results 결과 맵
|
|
54
|
+
* @param rebuildManager 리빌드 매니저
|
|
55
|
+
* @returns completeTask 함수 (결과를 저장하고 빌드 완료를 알림)
|
|
56
|
+
*/
|
|
57
|
+
export function registerWorkerEventHandlers<T extends BaseWorkerInfo>(
|
|
58
|
+
workerInfo: T,
|
|
59
|
+
opts: WorkerEventHandlerOptions,
|
|
60
|
+
results: Map<string, PackageResult>,
|
|
61
|
+
rebuildManager: RebuildListrManager,
|
|
62
|
+
): (result: PackageResult) => void {
|
|
63
|
+
const completeTask = (result: PackageResult): void => {
|
|
64
|
+
results.set(opts.resultKey, result);
|
|
65
|
+
workerInfo.buildResolver?.();
|
|
66
|
+
workerInfo.buildResolver = undefined;
|
|
67
|
+
workerInfo.isInitialBuild = false;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// 빌드 시작 (리빌드 시)
|
|
71
|
+
workerInfo.worker.on("buildStart", () => {
|
|
72
|
+
if (!workerInfo.isInitialBuild) {
|
|
73
|
+
workerInfo.buildResolver = rebuildManager.registerBuild(opts.resultKey, opts.listrTitle);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 빌드 완료
|
|
78
|
+
workerInfo.worker.on("build", (data) => {
|
|
79
|
+
const event = data as BuildEventData;
|
|
80
|
+
completeTask({
|
|
81
|
+
name: workerInfo.name,
|
|
82
|
+
target: workerInfo.config.target,
|
|
83
|
+
type: opts.resultType,
|
|
84
|
+
status: event.success ? "success" : "error",
|
|
85
|
+
message: event.errors?.join("\n"),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// 에러
|
|
90
|
+
workerInfo.worker.on("error", (data) => {
|
|
91
|
+
const event = data as ErrorEventData;
|
|
92
|
+
completeTask({
|
|
93
|
+
name: workerInfo.name,
|
|
94
|
+
target: workerInfo.config.target,
|
|
95
|
+
type: opts.resultType,
|
|
96
|
+
status: "error",
|
|
97
|
+
message: event.message,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return completeTask;
|
|
102
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { build as viteBuild, createServer, type ViteDevServer } from "vite";
|
|
4
|
+
import { createWorker } from "@simplysm/core-node";
|
|
5
|
+
import { consola } from "consola";
|
|
6
|
+
import type { SdClientPackageConfig } from "../sd-config.types";
|
|
7
|
+
import { parseRootTsconfig, getCompilerOptionsForPackage } from "../utils/tsconfig";
|
|
8
|
+
import { createViteConfig } from "../utils/vite-config";
|
|
9
|
+
|
|
10
|
+
//#region Types
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Client 빌드 정보 (일회성 빌드용)
|
|
14
|
+
*/
|
|
15
|
+
export interface ClientBuildInfo {
|
|
16
|
+
name: string;
|
|
17
|
+
config: SdClientPackageConfig;
|
|
18
|
+
cwd: string;
|
|
19
|
+
pkgDir: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Client 빌드 결과
|
|
24
|
+
*/
|
|
25
|
+
export interface ClientBuildResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
errors?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Client Watch 정보
|
|
32
|
+
*/
|
|
33
|
+
export interface ClientWatchInfo {
|
|
34
|
+
name: string;
|
|
35
|
+
config: SdClientPackageConfig;
|
|
36
|
+
cwd: string;
|
|
37
|
+
pkgDir: string;
|
|
38
|
+
/** watch 대상 scope 목록 */
|
|
39
|
+
watchScopes?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 빌드 이벤트
|
|
44
|
+
*/
|
|
45
|
+
export interface ClientBuildEvent {
|
|
46
|
+
success: boolean;
|
|
47
|
+
errors?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 서버 준비 이벤트
|
|
52
|
+
*/
|
|
53
|
+
export interface ClientServerReadyEvent {
|
|
54
|
+
port: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 에러 이벤트
|
|
59
|
+
*/
|
|
60
|
+
export interface ClientErrorEvent {
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Worker 이벤트 타입
|
|
66
|
+
*/
|
|
67
|
+
export interface ClientWorkerEvents extends Record<string, unknown> {
|
|
68
|
+
buildStart: Record<string, never>;
|
|
69
|
+
build: ClientBuildEvent;
|
|
70
|
+
serverReady: ClientServerReadyEvent;
|
|
71
|
+
error: ClientErrorEvent;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
|
|
76
|
+
//#region 리소스 관리
|
|
77
|
+
|
|
78
|
+
const logger = consola.withTag("sd:cli:client:worker");
|
|
79
|
+
|
|
80
|
+
/** Vite dev server (정리 대상) */
|
|
81
|
+
let viteServer: ViteDevServer | undefined;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 리소스 정리
|
|
85
|
+
*/
|
|
86
|
+
async function cleanup(): Promise<void> {
|
|
87
|
+
// 전역 변수를 임시 변수로 캡처 후 초기화
|
|
88
|
+
// (Promise.all 대기 중 다른 호출에서 전역 변수를 수정할 수 있으므로)
|
|
89
|
+
const serverToClose = viteServer;
|
|
90
|
+
viteServer = undefined;
|
|
91
|
+
|
|
92
|
+
if (serverToClose != null) {
|
|
93
|
+
await serverToClose.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 프로세스 종료 전 리소스 정리 (SIGTERM/SIGINT)
|
|
98
|
+
// 주의: worker.terminate()는 이 핸들러들을 호출하지 않고 즉시 종료됨.
|
|
99
|
+
// 그러나 watch 모드에서 정상 종료는 메인 프로세스의 SIGINT/SIGTERM을 통해 이루어지므로 문제없음.
|
|
100
|
+
process.on("SIGTERM", () => {
|
|
101
|
+
cleanup()
|
|
102
|
+
.catch((err) => {
|
|
103
|
+
logger.error("cleanup 실패", err);
|
|
104
|
+
})
|
|
105
|
+
.finally(() => {
|
|
106
|
+
process.exit(0);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
process.on("SIGINT", () => {
|
|
111
|
+
cleanup()
|
|
112
|
+
.catch((err) => {
|
|
113
|
+
logger.error("cleanup 실패", err);
|
|
114
|
+
})
|
|
115
|
+
.finally(() => {
|
|
116
|
+
process.exit(0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
|
|
122
|
+
//#region Worker
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 일회성 빌드
|
|
126
|
+
*/
|
|
127
|
+
async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
|
|
128
|
+
try {
|
|
129
|
+
// tsconfig 파싱
|
|
130
|
+
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
131
|
+
const tsconfigPath = path.join(info.cwd, "tsconfig.json");
|
|
132
|
+
|
|
133
|
+
// browser 타겟용 compilerOptions 생성
|
|
134
|
+
const compilerOptions = await getCompilerOptionsForPackage(parsedConfig.options, "browser", info.pkgDir);
|
|
135
|
+
|
|
136
|
+
// Vite 설정 생성 및 빌드
|
|
137
|
+
const viteConfig = createViteConfig({
|
|
138
|
+
pkgDir: info.pkgDir,
|
|
139
|
+
name: info.name,
|
|
140
|
+
tsconfigPath,
|
|
141
|
+
compilerOptions,
|
|
142
|
+
env: info.config.env,
|
|
143
|
+
mode: "build",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
await viteBuild(viteConfig);
|
|
147
|
+
|
|
148
|
+
// Generate .config.json
|
|
149
|
+
const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
|
|
150
|
+
fs.writeFileSync(confDistPath, JSON.stringify(info.config.configs ?? {}, undefined, 2));
|
|
151
|
+
|
|
152
|
+
return { success: true };
|
|
153
|
+
} catch (err) {
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
errors: [err instanceof Error ? err.message : String(err)],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** startWatch 호출 여부 플래그 */
|
|
162
|
+
let isWatchStarted = false;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* watch 시작 (Vite dev server)
|
|
166
|
+
* @remarks 이 함수는 Worker당 한 번만 호출되어야 합니다.
|
|
167
|
+
* @throws 이미 watch가 시작된 경우
|
|
168
|
+
*/
|
|
169
|
+
async function startWatch(info: ClientWatchInfo): Promise<void> {
|
|
170
|
+
if (isWatchStarted) {
|
|
171
|
+
throw new Error("startWatch는 Worker당 한 번만 호출할 수 있습니다.");
|
|
172
|
+
}
|
|
173
|
+
isWatchStarted = true;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// tsconfig 파싱
|
|
177
|
+
const parsedConfig = parseRootTsconfig(info.cwd);
|
|
178
|
+
const tsconfigPath = path.join(info.cwd, "tsconfig.json");
|
|
179
|
+
|
|
180
|
+
// browser 타겟용 compilerOptions 생성
|
|
181
|
+
const compilerOptions = await getCompilerOptionsForPackage(parsedConfig.options, "browser", info.pkgDir);
|
|
182
|
+
|
|
183
|
+
// server가 0이면 자동 포트 할당 (서버 연결 클라이언트)
|
|
184
|
+
// server가 숫자면 해당 포트로 고정 (standalone 클라이언트)
|
|
185
|
+
const serverPort = typeof info.config.server === "number" ? info.config.server : 0;
|
|
186
|
+
|
|
187
|
+
// Vite 설정 생성
|
|
188
|
+
const viteConfig = createViteConfig({
|
|
189
|
+
pkgDir: info.pkgDir,
|
|
190
|
+
name: info.name,
|
|
191
|
+
tsconfigPath,
|
|
192
|
+
compilerOptions,
|
|
193
|
+
env: info.config.env,
|
|
194
|
+
mode: "dev",
|
|
195
|
+
serverPort,
|
|
196
|
+
watchScopes: info.watchScopes,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Vite dev server 시작
|
|
200
|
+
viteServer = await createServer(viteConfig);
|
|
201
|
+
await viteServer.listen();
|
|
202
|
+
|
|
203
|
+
// Generate .config.json
|
|
204
|
+
const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
|
|
205
|
+
fs.mkdirSync(path.dirname(confDistPath), { recursive: true });
|
|
206
|
+
fs.writeFileSync(confDistPath, JSON.stringify(info.config.configs ?? {}, undefined, 2));
|
|
207
|
+
|
|
208
|
+
// 실제 할당된 포트 반환
|
|
209
|
+
sender.send("serverReady", { port: viteServer.config.server.port });
|
|
210
|
+
} catch (err) {
|
|
211
|
+
sender.send("error", {
|
|
212
|
+
message: err instanceof Error ? err.message : String(err),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* watch 중지
|
|
219
|
+
* @remarks Vite dev server를 정리합니다.
|
|
220
|
+
*/
|
|
221
|
+
async function stopWatch(): Promise<void> {
|
|
222
|
+
await cleanup();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const sender = createWorker<
|
|
226
|
+
{ build: typeof build; startWatch: typeof startWatch; stopWatch: typeof stopWatch },
|
|
227
|
+
ClientWorkerEvents
|
|
228
|
+
>({
|
|
229
|
+
build,
|
|
230
|
+
startWatch,
|
|
231
|
+
stopWatch,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
export default sender;
|
|
235
|
+
|
|
236
|
+
//#endregion
|