@simplysm/sd-cli 14.0.16 → 14.0.18
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 +2 -1
- package/dist/angular/client-transform-stylesheet.d.ts +2 -0
- package/dist/angular/client-transform-stylesheet.d.ts.map +1 -1
- package/dist/angular/client-transform-stylesheet.js +88 -2
- package/dist/angular/client-transform-stylesheet.js.map +1 -1
- package/dist/angular/vite-angular-plugin.d.ts +7 -0
- package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
- package/dist/angular/vite-angular-plugin.js +78 -16
- package/dist/angular/vite-angular-plugin.js.map +1 -1
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +9 -13
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +8 -9
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/device.d.ts.map +1 -1
- package/dist/commands/device.js +33 -1
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/lint.d.ts +0 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +2 -3
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/publish.js +2 -2
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +0 -1
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/electron/electron.d.ts +3 -2
- package/dist/electron/electron.d.ts.map +1 -1
- package/dist/electron/electron.js +54 -31
- package/dist/electron/electron.js.map +1 -1
- package/dist/engines/BaseEngine.js +1 -1
- package/dist/engines/BaseEngine.js.map +1 -1
- package/dist/engines/NgtscEngine.d.ts.map +1 -1
- package/dist/engines/NgtscEngine.js +0 -1
- package/dist/engines/NgtscEngine.js.map +1 -1
- package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
- package/dist/engines/ServerEsbuildEngine.js +0 -1
- package/dist/engines/ServerEsbuildEngine.js.map +1 -1
- package/dist/engines/TscEngine.d.ts.map +1 -1
- package/dist/engines/TscEngine.js +0 -1
- package/dist/engines/TscEngine.js.map +1 -1
- package/dist/engines/ViteEngine.d.ts.map +1 -1
- package/dist/engines/ViteEngine.js +8 -1
- package/dist/engines/ViteEngine.js.map +1 -1
- package/dist/engines/index.d.ts +0 -10
- package/dist/engines/index.d.ts.map +1 -1
- package/dist/engines/index.js +0 -5
- package/dist/engines/index.js.map +1 -1
- package/dist/engines/types.d.ts +0 -1
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/infra/SignalHandler.d.ts +1 -6
- package/dist/infra/SignalHandler.d.ts.map +1 -1
- package/dist/infra/SignalHandler.js +4 -13
- package/dist/infra/SignalHandler.js.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.js +7 -12
- package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.js +17 -10
- package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
- package/dist/sd-cli-entry.d.ts +0 -1
- package/dist/sd-cli-entry.d.ts.map +1 -1
- package/dist/sd-cli-entry.js +7 -8
- package/dist/sd-cli-entry.js.map +1 -1
- package/dist/sd-config.types.d.ts +11 -1
- package/dist/sd-config.types.d.ts.map +1 -1
- package/dist/utils/angular-compiler.d.ts.map +1 -1
- package/dist/utils/angular-compiler.js +20 -13
- package/dist/utils/angular-compiler.js.map +1 -1
- package/dist/utils/esbuild-config.d.ts +1 -1
- package/dist/utils/esbuild-config.d.ts.map +1 -1
- package/dist/utils/esbuild-config.js +1 -4
- package/dist/utils/esbuild-config.js.map +1 -1
- package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
- package/dist/utils/ngtsc-build-core.js +3 -0
- package/dist/utils/ngtsc-build-core.js.map +1 -1
- package/dist/utils/tsc-build.d.ts +5 -0
- package/dist/utils/tsc-build.d.ts.map +1 -1
- package/dist/utils/tsc-build.js +2 -1
- package/dist/utils/tsc-build.js.map +1 -1
- package/dist/utils/vite-config.d.ts +1 -1
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +22 -53
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/utils/vite-pwa-plugin.d.ts +9 -0
- package/dist/utils/vite-pwa-plugin.d.ts.map +1 -0
- package/dist/utils/vite-pwa-plugin.js +139 -0
- package/dist/utils/vite-pwa-plugin.js.map +1 -0
- package/dist/utils/worker-utils.d.ts +2 -5
- package/dist/utils/worker-utils.d.ts.map +1 -1
- package/dist/utils/worker-utils.js +5 -11
- 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 +9 -3
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/library-build.worker.d.ts.map +1 -1
- package/dist/workers/library-build.worker.js +6 -2
- package/dist/workers/library-build.worker.js.map +1 -1
- package/dist/workers/ngtsc-build.worker.js +2 -2
- package/dist/workers/ngtsc-build.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +6 -2
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-runtime.worker.js +4 -4
- package/dist/workers/server-runtime.worker.js.map +1 -1
- package/docs/config.md +26 -0
- package/docs/pwa-configuration-types.md +1 -1
- package/package.json +8 -10
- package/src/angular/client-transform-stylesheet.ts +104 -2
- package/src/angular/vite-angular-plugin.ts +92 -31
- package/src/capacitor/capacitor.ts +10 -26
- package/src/commands/check.ts +8 -11
- package/src/commands/device.ts +38 -3
- package/src/commands/lint.ts +2 -3
- package/src/commands/publish.ts +2 -2
- package/src/commands/typecheck.ts +0 -1
- package/src/electron/electron.ts +62 -43
- package/src/engines/BaseEngine.ts +1 -1
- package/src/engines/NgtscEngine.ts +0 -1
- package/src/engines/ServerEsbuildEngine.ts +0 -1
- package/src/engines/TscEngine.ts +0 -1
- package/src/engines/ViteEngine.ts +7 -1
- package/src/engines/index.ts +0 -10
- package/src/engines/types.ts +0 -1
- package/src/infra/SignalHandler.ts +4 -14
- package/src/orchestrators/BuildOrchestrator.ts +7 -9
- package/src/orchestrators/DevWatchOrchestrator.ts +21 -9
- package/src/sd-cli-entry.ts +11 -16
- package/src/sd-config.types.ts +12 -1
- package/src/utils/angular-compiler.ts +21 -21
- package/src/utils/esbuild-config.ts +2 -5
- package/src/utils/ngtsc-build-core.ts +7 -0
- package/src/utils/tsc-build.ts +7 -0
- package/src/utils/vite-config.ts +23 -55
- package/src/utils/vite-pwa-plugin.ts +168 -0
- package/src/utils/worker-utils.ts +5 -11
- package/src/workers/client.worker.ts +11 -3
- package/src/workers/library-build.worker.ts +6 -2
- package/src/workers/ngtsc-build.worker.ts +2 -2
- package/src/workers/server-build.worker.ts +7 -2
- package/src/workers/server-runtime.worker.ts +4 -4
- package/tests/angular/client-transform-stylesheet.spec.ts +43 -0
- package/tests/angular/find-affected-by-scss.spec.ts +37 -0
- package/tests/angular/fixtures/basic-app/scss/_colors.scss +1 -0
- package/tests/angular/fixtures/basic-app/scss/_variables.scss +3 -0
- package/tests/angular/fixtures/basic-app/src/styled.component.ts +14 -0
- package/tests/angular/linker-disk-cache.spec.ts +158 -0
- package/tests/angular/scss-disk-cache.spec.ts +162 -0
- package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +15 -15
- package/tests/angular/vite-angular-plugin-hmr.spec.ts +9 -9
- package/tests/angular/vite-angular-plugin-lint.spec.ts +4 -4
- package/tests/angular/vite-angular-plugin-scss-hmr.spec.ts +87 -0
- package/tests/angular/vite-angular-plugin.spec.ts +15 -15
- package/tests/capacitor/capacitor-icon.spec.ts +2 -4
- package/tests/capacitor/capacitor-init.spec.ts +2 -4
- package/tests/capacitor/capacitor-workspace.spec.ts +2 -4
- package/tests/commands/device.spec.ts +100 -0
- package/tests/electron/electron.spec.ts +24 -17
- package/tests/engines/ngtsc-engine.spec.ts +0 -3
- package/tests/engines/server-esbuild-engine.spec.ts +0 -3
- package/tests/engines/tsc-engine.spec.ts +1 -2
- package/tests/engines/vite-engine.spec.ts +0 -2
- package/tests/infra/signal-handler.spec.ts +1 -12
- package/tests/orchestrators/build-orchestrator.spec.ts +0 -6
- package/tests/orchestrators/dev-watch-orchestrator.spec.ts +24 -66
- package/tests/utils/angular-compiler.spec.ts +1396 -32
- package/tests/utils/esbuild-config.spec.ts +4 -7
- package/tests/utils/{ngtsc-build-core-angular-compiler.spec.ts → ngtsc-build-core.spec.ts} +142 -11
- package/tests/utils/tsc-build.spec.ts +4 -1
- package/tests/utils/vite-config.spec.ts +130 -261
- package/tests/utils/vite-pwa-plugin.acc.spec.ts +143 -0
- package/tests/utils/vite-pwa-plugin.spec.ts +350 -0
- package/tests/utils/worker-utils.spec.ts +8 -7
- package/tests/workers/client-worker.spec.ts +50 -1
- package/tests/workers/dev-port-file.verify.md +6 -0
- package/tests/workers/library-build-lint.spec.ts +1 -1
- package/tests/workers/library-build-worker.spec.ts +1 -1
- package/tests/workers/ngtsc-build-lint.spec.ts +1 -1
- package/tests/workers/server-build-lint.spec.ts +1 -1
- package/tests/workers/server-build-worker.spec.ts +1 -1
- package/tests/workers/server-runtime-worker.spec.ts +8 -1
- package/dist/infra/WorkerManager.d.ts +0 -40
- package/dist/infra/WorkerManager.d.ts.map +0 -1
- package/dist/infra/WorkerManager.js +0 -59
- package/dist/infra/WorkerManager.js.map +0 -1
- package/dist/utils/SdCliReporter.d.ts +0 -18
- package/dist/utils/SdCliReporter.d.ts.map +0 -1
- package/dist/utils/SdCliReporter.js +0 -144
- package/dist/utils/SdCliReporter.js.map +0 -1
- package/src/infra/WorkerManager.ts +0 -65
- package/src/utils/SdCliReporter.ts +0 -177
- package/tests/angular/scss-compiler-async.spec.ts +0 -54
- package/tests/commands/dev.spec.ts +0 -53
- package/tests/commands/watch.spec.ts +0 -53
- package/tests/infra/worker-manager.spec.ts +0 -63
- package/tests/utils/angular-compiler-emit.spec.ts +0 -570
- package/tests/utils/angular-compiler-init.spec.ts +0 -705
- package/tests/utils/angular-compiler-update.spec.ts +0 -293
- package/tests/utils/build-env.spec.ts +0 -33
- package/tests/utils/ngtsc-build-core-transform-stylesheet.spec.ts +0 -124
- package/tests/utils/ngtsc-scss-refactor.spec.ts +0 -47
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { sdAngularPlugin } from "../../src/angular/vite-angular-plugin.js";
|
|
4
|
+
|
|
5
|
+
const FIXTURE_DIR = path.resolve(import.meta.dirname, "fixtures/basic-app");
|
|
6
|
+
const TSCONFIG_PATH = path.join(FIXTURE_DIR, "tsconfig.json");
|
|
7
|
+
|
|
8
|
+
function mockEnvironmentContext() {
|
|
9
|
+
return {
|
|
10
|
+
environment: {
|
|
11
|
+
moduleGraph: {
|
|
12
|
+
getModulesByFile: (file: string) => {
|
|
13
|
+
return new Set([{ file, id: file }]);
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("sdAngularPlugin SCSS @use HMR", () => {
|
|
21
|
+
let plugin: ReturnType<typeof sdAngularPlugin>;
|
|
22
|
+
|
|
23
|
+
beforeAll(async () => {
|
|
24
|
+
plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
|
|
25
|
+
await (plugin as any).buildStart?.call({});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterAll(async () => {
|
|
29
|
+
await (plugin as any).buildEnd?.call({});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Acceptance: inline styles의 직접 @use 의존성 변경 시 재컴파일
|
|
33
|
+
it("recompiles when inline SCSS @use dependency changes", async () => {
|
|
34
|
+
const variablesPath = path
|
|
35
|
+
.join(FIXTURE_DIR, "scss/_variables.scss")
|
|
36
|
+
.replace(/\\/g, "/");
|
|
37
|
+
|
|
38
|
+
const ctx = mockEnvironmentContext();
|
|
39
|
+
const result = await (plugin as any).hotUpdate?.call(ctx, {
|
|
40
|
+
file: variablesPath,
|
|
41
|
+
modules: [],
|
|
42
|
+
server: {},
|
|
43
|
+
timestamp: Date.now(),
|
|
44
|
+
read: () => Promise.resolve(""),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(result).toBeDefined();
|
|
48
|
+
expect(result!.length).toBeGreaterThan(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Acceptance: inline styles의 간접 @use 의존성 변경 시 재컴파일 (체이닝)
|
|
52
|
+
it("recompiles when chained @use dependency changes", async () => {
|
|
53
|
+
const colorsPath = path
|
|
54
|
+
.join(FIXTURE_DIR, "scss/_colors.scss")
|
|
55
|
+
.replace(/\\/g, "/");
|
|
56
|
+
|
|
57
|
+
const ctx = mockEnvironmentContext();
|
|
58
|
+
const result = await (plugin as any).hotUpdate?.call(ctx, {
|
|
59
|
+
file: colorsPath,
|
|
60
|
+
modules: [],
|
|
61
|
+
server: {},
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
read: () => Promise.resolve(""),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(result).toBeDefined();
|
|
67
|
+
expect(result!.length).toBeGreaterThan(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Acceptance: 무관한 SCSS 변경 시 재빌드하지 않음
|
|
71
|
+
it("does not recompile when unrelated SCSS changes", async () => {
|
|
72
|
+
const unrelatedPath = path
|
|
73
|
+
.join(FIXTURE_DIR, "scss/_unrelated.scss")
|
|
74
|
+
.replace(/\\/g, "/");
|
|
75
|
+
|
|
76
|
+
const ctx = mockEnvironmentContext();
|
|
77
|
+
const result = await (plugin as any).hotUpdate?.call(ctx, {
|
|
78
|
+
file: unrelatedPath,
|
|
79
|
+
modules: [],
|
|
80
|
+
server: {},
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
read: () => Promise.resolve(""),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(result).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -54,7 +54,7 @@ describe("sdAngularPlugin", () => {
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// Scenario: Angular 컴포넌트 .ts 파일 수정 시 컴포넌트 HMR (Acceptance — Feature 3.3)
|
|
57
|
-
it("updates emit cache and returns affected modules when
|
|
57
|
+
it("updates emit cache and returns affected modules when hotUpdate is called", async () => {
|
|
58
58
|
const onBuildStart = vi.fn();
|
|
59
59
|
const onBuild = vi.fn();
|
|
60
60
|
|
|
@@ -77,12 +77,12 @@ describe("sdAngularPlugin", () => {
|
|
|
77
77
|
expect(initialResult).toBeDefined();
|
|
78
78
|
expect(initialResult.code.length).toBeGreaterThan(0);
|
|
79
79
|
|
|
80
|
-
//
|
|
81
|
-
expect((plugin as any).
|
|
80
|
+
// hotUpdate must exist
|
|
81
|
+
expect((plugin as any).hotUpdate).toBeDefined();
|
|
82
82
|
|
|
83
83
|
// Simulate file change
|
|
84
84
|
const mockModule = { file: appComponentPath, id: appComponentPath };
|
|
85
|
-
const hmrResult = await (plugin as any).
|
|
85
|
+
const hmrResult = await (plugin as any).hotUpdate?.({
|
|
86
86
|
file: appComponentPath,
|
|
87
87
|
modules: [mockModule],
|
|
88
88
|
server: { watcher: { emit: vi.fn() } },
|
|
@@ -122,7 +122,7 @@ describe("sdAngularPlugin", () => {
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
// Scenario: 컴파일 에러 발생 및 복구 (Acceptance — Feature 3.3)
|
|
125
|
-
it("calls onBuild with success=false when
|
|
125
|
+
it("calls onBuild with success=false when hotUpdate encounters compile error", async () => {
|
|
126
126
|
const onBuild = vi.fn();
|
|
127
127
|
const plugin = sdAngularPlugin({
|
|
128
128
|
tsconfig: TSCONFIG_PATH,
|
|
@@ -132,8 +132,8 @@ describe("sdAngularPlugin", () => {
|
|
|
132
132
|
|
|
133
133
|
await (plugin as any).buildStart?.call({});
|
|
134
134
|
|
|
135
|
-
//
|
|
136
|
-
const _hmrResult = await (plugin as any).
|
|
135
|
+
// hotUpdate with a non-existent file — facade.update() should handle gracefully
|
|
136
|
+
const _hmrResult = await (plugin as any).hotUpdate?.({
|
|
137
137
|
file: path.join(FIXTURE_DIR, "src/nonexistent-file.ts").replace(/\\/g, "/"),
|
|
138
138
|
modules: [],
|
|
139
139
|
server: { watcher: { emit: vi.fn() } },
|
|
@@ -147,8 +147,8 @@ describe("sdAngularPlugin", () => {
|
|
|
147
147
|
await (plugin as any).buildEnd?.call({});
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
// Scenario: non-Angular .ts 파일 수정 —
|
|
151
|
-
it("
|
|
150
|
+
// Scenario: non-Angular .ts 파일 수정 — hotUpdate passes through
|
|
151
|
+
it("hotUpdate skips non-ts/html/scss files", async () => {
|
|
152
152
|
const onBuildStart = vi.fn();
|
|
153
153
|
const plugin = sdAngularPlugin({
|
|
154
154
|
tsconfig: TSCONFIG_PATH,
|
|
@@ -159,7 +159,7 @@ describe("sdAngularPlugin", () => {
|
|
|
159
159
|
await (plugin as any).buildStart?.call({});
|
|
160
160
|
|
|
161
161
|
// .json file should be ignored
|
|
162
|
-
const result = await (plugin as any).
|
|
162
|
+
const result = await (plugin as any).hotUpdate?.({
|
|
163
163
|
file: "/some/file.json",
|
|
164
164
|
modules: [],
|
|
165
165
|
server: {},
|
|
@@ -192,16 +192,16 @@ describe("sdAngularPlugin", () => {
|
|
|
192
192
|
// (in real use, Vite server close triggers this)
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
-
// Scenario: optimizeDeps에 Angular Linker
|
|
196
|
-
it("registers angular-vite-optimize-deps
|
|
195
|
+
// Scenario: optimizeDeps에 Angular Linker Rolldown 플러그인이 등록된다
|
|
196
|
+
it("registers angular-vite-optimize-deps rolldown plugin in optimizeDeps config", () => {
|
|
197
197
|
const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
|
|
198
198
|
const config = (plugin as any).config?.();
|
|
199
199
|
|
|
200
|
-
const
|
|
200
|
+
const rolldownPlugins = config?.optimizeDeps?.rolldownOptions?.plugins as
|
|
201
201
|
| { name: string }[]
|
|
202
202
|
| undefined;
|
|
203
|
-
expect(
|
|
204
|
-
expect(
|
|
203
|
+
expect(rolldownPlugins).toBeDefined();
|
|
204
|
+
expect(rolldownPlugins!.some((p) => p.name === "angular-vite-optimize-deps")).toBe(true);
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
// Scenario: .mjs 파일이 JavaScriptTransformer를 통과한다 (Feature 1.1 Angular Linker)
|
|
@@ -35,11 +35,9 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
37
|
// env mock
|
|
38
|
-
let mockEnv: Record<string,
|
|
38
|
+
let mockEnv: Record<string, string | undefined> = {};
|
|
39
39
|
vi.mock("@simplysm/core-common", () => ({
|
|
40
|
-
env:
|
|
41
|
-
get: (_target, prop) => mockEnv[prop as string],
|
|
42
|
-
}),
|
|
40
|
+
env: (key: string) => mockEnv[key],
|
|
43
41
|
}));
|
|
44
42
|
|
|
45
43
|
// cpx mock (was execa)
|
|
@@ -35,11 +35,9 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
35
35
|
},
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
let mockEnv: Record<string,
|
|
38
|
+
let mockEnv: Record<string, string | undefined> = {};
|
|
39
39
|
vi.mock("@simplysm/core-common", () => ({
|
|
40
|
-
env:
|
|
41
|
-
get: (_target, prop) => mockEnv[prop as string],
|
|
42
|
-
}),
|
|
40
|
+
env: (key: string) => mockEnv[key],
|
|
43
41
|
}));
|
|
44
42
|
|
|
45
43
|
const execaCalls: { command: string; args: string[] }[] = [];
|
|
@@ -35,11 +35,9 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
35
35
|
},
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
let mockEnv: Record<string,
|
|
38
|
+
let mockEnv: Record<string, string | undefined> = {};
|
|
39
39
|
vi.mock("@simplysm/core-common", () => ({
|
|
40
|
-
env:
|
|
41
|
-
get: (_target, prop) => mockEnv[prop as string],
|
|
42
|
-
}),
|
|
40
|
+
env: (key: string) => mockEnv[key],
|
|
43
41
|
}));
|
|
44
42
|
|
|
45
43
|
const execaCalls: { command: string; args: string[] }[] = [];
|
|
@@ -31,6 +31,27 @@ vi.mock("../../src/utils/sd-config", () => ({
|
|
|
31
31
|
loadSdConfig: vi.fn(),
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
+
// fs mock (포트 파일 읽기용)
|
|
35
|
+
const mockReadFileSync = vi.fn();
|
|
36
|
+
const mockExistsSync = vi.fn().mockReturnValue(false);
|
|
37
|
+
vi.mock("node:fs", () => ({
|
|
38
|
+
default: {
|
|
39
|
+
readFileSync: (...args: any[]) => mockReadFileSync(...args),
|
|
40
|
+
existsSync: (...args: any[]) => mockExistsSync(...args),
|
|
41
|
+
},
|
|
42
|
+
readFileSync: (...args: any[]) => mockReadFileSync(...args),
|
|
43
|
+
existsSync: (...args: any[]) => mockExistsSync(...args),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// http mock (헬스체크용)
|
|
47
|
+
const mockHttpGet = vi.fn();
|
|
48
|
+
vi.mock("node:http", () => ({
|
|
49
|
+
default: {
|
|
50
|
+
get: (...args: any[]) => mockHttpGet(...args),
|
|
51
|
+
},
|
|
52
|
+
get: (...args: any[]) => mockHttpGet(...args),
|
|
53
|
+
}));
|
|
54
|
+
|
|
34
55
|
const { Capacitor } = await import("../../src/capacitor/capacitor");
|
|
35
56
|
const { Electron } = await import("../../src/electron/electron");
|
|
36
57
|
const { loadSdConfig } = await import("../../src/utils/sd-config");
|
|
@@ -144,4 +165,83 @@ describe("runDevice", () => {
|
|
|
144
165
|
|
|
145
166
|
await expect(runDevice({ target: "my-server", options: [] })).rejects.toThrow();
|
|
146
167
|
});
|
|
168
|
+
|
|
169
|
+
// Acceptance: Scenario "dev 서버 실행 중 device 실행 시 URL 자동 생성"
|
|
170
|
+
it("auto-detects URL from .dev-port when server is a string", async () => {
|
|
171
|
+
vi.mocked(loadSdConfig).mockResolvedValue({
|
|
172
|
+
packages: {
|
|
173
|
+
"client-devtool": {
|
|
174
|
+
target: "client",
|
|
175
|
+
server: "server",
|
|
176
|
+
electron: { appId: "com.test.electron" },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
mockReadFileSync.mockReturnValue("5173");
|
|
182
|
+
|
|
183
|
+
// HTTP 헬스체크 성공 mock
|
|
184
|
+
mockHttpGet.mockImplementation((_url: string, cb: Function) => {
|
|
185
|
+
const res = { resume: vi.fn() };
|
|
186
|
+
cb(res);
|
|
187
|
+
return { on: vi.fn(), setTimeout: vi.fn() };
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await runDevice({ target: "client-devtool", options: [] });
|
|
191
|
+
|
|
192
|
+
expect(mockElectronInstance.run).toHaveBeenCalledWith(
|
|
193
|
+
"http://localhost:5173/client-devtool/",
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Acceptance: Scenario "dev 서버 미실행 시 에러"
|
|
198
|
+
it("throws when .dev-port file does not exist and server is a string", async () => {
|
|
199
|
+
vi.mocked(loadSdConfig).mockResolvedValue({
|
|
200
|
+
packages: {
|
|
201
|
+
"client-devtool": {
|
|
202
|
+
target: "client",
|
|
203
|
+
server: "server",
|
|
204
|
+
electron: { appId: "com.test.electron" },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
mockReadFileSync.mockImplementation(() => {
|
|
210
|
+
throw new Error("ENOENT");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
|
|
214
|
+
"dev 서버가 실행 중이 아닙니다",
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Acceptance: Scenario "stale 포트 파일 존재 시 헬스체크 실패 에러"
|
|
219
|
+
it("throws when .dev-port exists but health check fails", async () => {
|
|
220
|
+
vi.mocked(loadSdConfig).mockResolvedValue({
|
|
221
|
+
packages: {
|
|
222
|
+
"client-devtool": {
|
|
223
|
+
target: "client",
|
|
224
|
+
server: "server",
|
|
225
|
+
electron: { appId: "com.test.electron" },
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
mockReadFileSync.mockReturnValue("5173");
|
|
231
|
+
|
|
232
|
+
// HTTP 헬스체크 실패 mock
|
|
233
|
+
mockHttpGet.mockImplementation((_url: string, _cb: Function) => {
|
|
234
|
+
const req = {
|
|
235
|
+
on: vi.fn((event: string, handler: Function) => {
|
|
236
|
+
if (event === "error") handler(new Error("ECONNREFUSED"));
|
|
237
|
+
}),
|
|
238
|
+
setTimeout: vi.fn(),
|
|
239
|
+
};
|
|
240
|
+
return req;
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
|
|
244
|
+
"dev 서버가 응답하지 않습니다",
|
|
245
|
+
);
|
|
246
|
+
});
|
|
147
247
|
});
|
|
@@ -7,6 +7,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
7
7
|
const mockFsxExists = vi.fn();
|
|
8
8
|
const mockFsxReadJson = vi.fn();
|
|
9
9
|
const mockFsxWriteJson = vi.fn().mockResolvedValue(undefined);
|
|
10
|
+
const mockFsxWrite = vi.fn().mockResolvedValue(undefined);
|
|
10
11
|
const mockFsxMkdir = vi.fn().mockResolvedValue(undefined);
|
|
11
12
|
const mockFsxCopy = vi.fn().mockResolvedValue(undefined);
|
|
12
13
|
const mockFsxReaddir = vi.fn();
|
|
@@ -17,6 +18,7 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
17
18
|
exists: mockFsxExists,
|
|
18
19
|
readJson: mockFsxReadJson,
|
|
19
20
|
writeJson: mockFsxWriteJson,
|
|
21
|
+
write: mockFsxWrite,
|
|
20
22
|
mkdir: mockFsxMkdir,
|
|
21
23
|
copy: mockFsxCopy,
|
|
22
24
|
readdir: mockFsxReaddir,
|
|
@@ -96,6 +98,11 @@ function setupDefaultMocks() {
|
|
|
96
98
|
"better-sqlite3": "^11.0.0",
|
|
97
99
|
"sharp": "^0.34.0",
|
|
98
100
|
},
|
|
101
|
+
devDependencies: {
|
|
102
|
+
"electron": "^35.0.0",
|
|
103
|
+
"@electron/rebuild": "^4.0.0",
|
|
104
|
+
"electron-builder": "^26.0.0",
|
|
105
|
+
},
|
|
99
106
|
});
|
|
100
107
|
mockFsxReaddir.mockResolvedValue(["index.html", "assets", "electron"]);
|
|
101
108
|
// Default: glob returns one exe file matching the builder output
|
|
@@ -169,7 +176,7 @@ describe("Electron", () => {
|
|
|
169
176
|
//#region Rule: Electron 프로젝트를 초기화한다
|
|
170
177
|
|
|
171
178
|
describe("인수 테스트: 초기화", () => {
|
|
172
|
-
it("package.json 생성 +
|
|
179
|
+
it("package.json 생성 + pnpm install + electron-rebuild를 실행한다", async () => {
|
|
173
180
|
const { Electron } = await import("../../src/electron/electron.js");
|
|
174
181
|
|
|
175
182
|
const electron = await Electron.create(PKG_PATH, {
|
|
@@ -181,13 +188,13 @@ describe("Electron", () => {
|
|
|
181
188
|
|
|
182
189
|
expect(findElectronPackageJson()).toBeDefined();
|
|
183
190
|
|
|
184
|
-
const
|
|
191
|
+
const spawnCalls = mockCpxSpawn.mock.calls;
|
|
185
192
|
expect(
|
|
186
|
-
|
|
193
|
+
spawnCalls.find((c) => c[0] === "pnpm" && (c[1] as string[]).includes("install")),
|
|
187
194
|
).toBeDefined();
|
|
188
195
|
expect(
|
|
189
|
-
|
|
190
|
-
(c) =>
|
|
196
|
+
spawnCalls.find(
|
|
197
|
+
(c) => c[0] === "pnpm" && (c[1] as string[]).includes("electron-rebuild"),
|
|
191
198
|
),
|
|
192
199
|
).toBeDefined();
|
|
193
200
|
});
|
|
@@ -199,7 +206,7 @@ describe("Electron", () => {
|
|
|
199
206
|
await electron.initialize();
|
|
200
207
|
|
|
201
208
|
const rebuildCall = mockCpxSpawn.mock.calls.find(
|
|
202
|
-
(c) =>
|
|
209
|
+
(c) => c[0] === "pnpm" && (c[1] as string[]).includes("electron-rebuild"),
|
|
203
210
|
);
|
|
204
211
|
expect(rebuildCall).toBeUndefined();
|
|
205
212
|
});
|
|
@@ -455,9 +462,9 @@ describe("Electron", () => {
|
|
|
455
462
|
const electronKill = vi.fn();
|
|
456
463
|
let resolveElectron: () => void = () => {};
|
|
457
464
|
|
|
458
|
-
mockCpxSpawn.mockImplementation((cmd: string) => {
|
|
459
|
-
|
|
460
|
-
|
|
465
|
+
mockCpxSpawn.mockImplementation((cmd: string, args: string[]) => {
|
|
466
|
+
// pnpm exec electron . → Electron 프로세스
|
|
467
|
+
if (cmd === "pnpm" && args[0] === "exec" && args[1] === "electron" && args[2] === ".") {
|
|
461
468
|
const p = new Promise<void>((resolve) => {
|
|
462
469
|
resolveElectron = resolve;
|
|
463
470
|
}) as any;
|
|
@@ -537,8 +544,8 @@ describe("Electron", () => {
|
|
|
537
544
|
describe("단위: run() 플러그인 동작", () => {
|
|
538
545
|
it("passes custom env and ELECTRON_DEV_URL via esbuild banner", async () => {
|
|
539
546
|
let resolveElectron: () => void = () => {};
|
|
540
|
-
mockCpxSpawn.mockImplementation((cmd: string) => {
|
|
541
|
-
if (
|
|
547
|
+
mockCpxSpawn.mockImplementation((cmd: string, args: string[]) => {
|
|
548
|
+
if (cmd === "pnpm" && args[0] === "exec" && args[1] === "electron" && args[2] === ".") {
|
|
542
549
|
const p = new Promise<void>((resolve) => {
|
|
543
550
|
resolveElectron = resolve;
|
|
544
551
|
}) as any;
|
|
@@ -570,8 +577,8 @@ describe("Electron", () => {
|
|
|
570
577
|
|
|
571
578
|
it("calls initialize() before starting esbuild context", async () => {
|
|
572
579
|
let resolveElectron: () => void = () => {};
|
|
573
|
-
mockCpxSpawn.mockImplementation((cmd: string) => {
|
|
574
|
-
if (
|
|
580
|
+
mockCpxSpawn.mockImplementation((cmd: string, args: string[]) => {
|
|
581
|
+
if (cmd === "pnpm" && args[0] === "exec" && args[1] === "electron" && args[2] === ".") {
|
|
575
582
|
const p = new Promise<void>((resolve) => {
|
|
576
583
|
resolveElectron = resolve;
|
|
577
584
|
}) as any;
|
|
@@ -589,11 +596,11 @@ describe("Electron", () => {
|
|
|
589
596
|
resolveElectron();
|
|
590
597
|
await runPromise;
|
|
591
598
|
|
|
592
|
-
// initialize calls
|
|
593
|
-
const
|
|
594
|
-
(c: any[]) => c[0] === "
|
|
599
|
+
// initialize calls pnpm install
|
|
600
|
+
const pnpmInstallCall = mockCpxSpawn.mock.calls.find(
|
|
601
|
+
(c: any[]) => c[0] === "pnpm" && (c[1] as string[]).includes("install"),
|
|
595
602
|
);
|
|
596
|
-
expect(
|
|
603
|
+
expect(pnpmInstallCall).toBeDefined();
|
|
597
604
|
}, 10_000);
|
|
598
605
|
});
|
|
599
606
|
|
|
@@ -78,7 +78,6 @@ describe("NgtscEngine", () => {
|
|
|
78
78
|
output: { js: true, dts: true },
|
|
79
79
|
}),
|
|
80
80
|
);
|
|
81
|
-
expect(result.success).toBe(true);
|
|
82
81
|
expect(result.build.success).toBe(true);
|
|
83
82
|
expect(result.build.diagnostics).toEqual([]);
|
|
84
83
|
await engine.stop();
|
|
@@ -133,7 +132,6 @@ describe("NgtscEngine", () => {
|
|
|
133
132
|
const engine = new NgtscEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
134
133
|
const result = await engine.run({ js: true, dts: true });
|
|
135
134
|
|
|
136
|
-
expect(result.success).toBe(false);
|
|
137
135
|
expect(result.build.success).toBe(false);
|
|
138
136
|
expect(result.build.errors).toContain("TS2322: Type error");
|
|
139
137
|
await engine.stop();
|
|
@@ -148,7 +146,6 @@ describe("NgtscEngine", () => {
|
|
|
148
146
|
const engine = new NgtscEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
149
147
|
const result = await engine.run({ js: true, dts: true });
|
|
150
148
|
|
|
151
|
-
expect(result.success).toBe(false);
|
|
152
149
|
expect(result.build.success).toBe(false);
|
|
153
150
|
expect(result.build.errors).toEqual(["ngtsc compilation error"]);
|
|
154
151
|
await engine.stop();
|
|
@@ -89,7 +89,6 @@ describe("ServerEsbuildEngine", () => {
|
|
|
89
89
|
packageManager: "mise",
|
|
90
90
|
}),
|
|
91
91
|
);
|
|
92
|
-
expect(result.success).toBe(true);
|
|
93
92
|
expect(result.build.success).toBe(true);
|
|
94
93
|
await engine.stop();
|
|
95
94
|
});
|
|
@@ -104,7 +103,6 @@ describe("ServerEsbuildEngine", () => {
|
|
|
104
103
|
const engine = new ServerEsbuildEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
105
104
|
const result = await engine.run({ js: true, dts: false });
|
|
106
105
|
|
|
107
|
-
expect(result.success).toBe(false);
|
|
108
106
|
expect(result.build.warnings).toEqual(["warn1"]);
|
|
109
107
|
expect(result.build.success).toBe(false);
|
|
110
108
|
expect(result.build.diagnostics).toEqual([{ code: 2345, category: 1 }]);
|
|
@@ -121,7 +119,6 @@ describe("ServerEsbuildEngine", () => {
|
|
|
121
119
|
const engine = new ServerEsbuildEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
122
120
|
const result = await engine.run({ js: true, dts: false });
|
|
123
121
|
|
|
124
|
-
expect(result.success).toBe(false);
|
|
125
122
|
expect(result.build.success).toBe(false);
|
|
126
123
|
expect(result.build.errors).toEqual(["esbuild error"]);
|
|
127
124
|
await engine.stop();
|
|
@@ -75,7 +75,6 @@ describe("TscEngine", () => {
|
|
|
75
75
|
output: { js: true, dts: true },
|
|
76
76
|
}),
|
|
77
77
|
);
|
|
78
|
-
expect(result.success).toBe(true);
|
|
79
78
|
expect(result.build.success).toBe(true);
|
|
80
79
|
await engine.stop();
|
|
81
80
|
});
|
|
@@ -101,7 +100,7 @@ describe("TscEngine", () => {
|
|
|
101
100
|
const engine = new TscEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
102
101
|
const result = await engine.run({ js: true, dts: true });
|
|
103
102
|
|
|
104
|
-
expect(result.success).toBe(false);
|
|
103
|
+
expect(result.build.success).toBe(false);
|
|
105
104
|
expect(result.build.errors).toEqual(["type error"]);
|
|
106
105
|
expect(result.build.diagnostics).toHaveLength(1);
|
|
107
106
|
await engine.stop();
|
|
@@ -74,7 +74,6 @@ describe("ViteEngine", () => {
|
|
|
74
74
|
pkgDir: "/packages/my-client",
|
|
75
75
|
}),
|
|
76
76
|
);
|
|
77
|
-
expect(result.success).toBe(true);
|
|
78
77
|
expect(result.build.success).toBe(true);
|
|
79
78
|
expect(result.build.errors).toEqual([]);
|
|
80
79
|
expect(result.build.diagnostics).toEqual([]);
|
|
@@ -91,7 +90,6 @@ describe("ViteEngine", () => {
|
|
|
91
90
|
const engine = new ViteEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
92
91
|
const result = await engine.run({ js: true, dts: false });
|
|
93
92
|
|
|
94
|
-
expect(result.success).toBe(false);
|
|
95
93
|
expect(result.build.success).toBe(false);
|
|
96
94
|
expect(result.build.errors).toContain("TS2345: Argument of type...");
|
|
97
95
|
await engine.stop();
|
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
import { describe, it
|
|
1
|
+
import { describe, it } from "vitest";
|
|
2
2
|
import { SignalHandler } from "../../src/infra/SignalHandler";
|
|
3
3
|
|
|
4
4
|
describe("SignalHandler", () => {
|
|
5
5
|
it("resolves waitForTermination on requestTermination", async () => {
|
|
6
6
|
const handler = new SignalHandler();
|
|
7
7
|
|
|
8
|
-
expect(handler.isTerminated()).toBe(false);
|
|
9
|
-
|
|
10
8
|
handler.requestTermination();
|
|
11
9
|
|
|
12
10
|
await handler.waitForTermination();
|
|
13
|
-
expect(handler.isTerminated()).toBe(true);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("is not terminated initially", () => {
|
|
17
|
-
const handler = new SignalHandler();
|
|
18
|
-
expect(handler.isTerminated()).toBe(false);
|
|
19
|
-
// Clean up: prevent dangling signal listeners
|
|
20
|
-
handler.requestTermination();
|
|
21
11
|
});
|
|
22
12
|
|
|
23
13
|
it("handles double requestTermination gracefully", async () => {
|
|
@@ -27,6 +17,5 @@ describe("SignalHandler", () => {
|
|
|
27
17
|
handler.requestTermination();
|
|
28
18
|
|
|
29
19
|
await handler.waitForTermination();
|
|
30
|
-
expect(handler.isTerminated()).toBe(true);
|
|
31
20
|
});
|
|
32
21
|
});
|
|
@@ -66,7 +66,6 @@ vi.mock("../../src/engines/index", () => ({
|
|
|
66
66
|
createBuildEngine: vi.fn(() => {
|
|
67
67
|
const engine = {
|
|
68
68
|
run: vi.fn().mockResolvedValue({
|
|
69
|
-
success: true,
|
|
70
69
|
build: { success: true, errors: [], warnings: [], diagnostics: [] },
|
|
71
70
|
}),
|
|
72
71
|
startWatch: vi.fn().mockResolvedValue(undefined),
|
|
@@ -159,7 +158,6 @@ beforeEach(() => {
|
|
|
159
158
|
vi.mocked(createBuildEngine).mockImplementation(() => {
|
|
160
159
|
const engine = {
|
|
161
160
|
run: vi.fn().mockResolvedValue({
|
|
162
|
-
success: true,
|
|
163
161
|
build: { success: true, errors: [], warnings: [], diagnostics: [] },
|
|
164
162
|
|
|
165
163
|
}),
|
|
@@ -413,7 +411,6 @@ describe("BuildOrchestrator.start", () => {
|
|
|
413
411
|
});
|
|
414
412
|
vi.mocked(createBuildEngine).mockReturnValue({
|
|
415
413
|
run: vi.fn().mockResolvedValue({
|
|
416
|
-
success: false,
|
|
417
414
|
build: { success: false, errors: ["Module not found"], warnings: [], diagnostics: [] },
|
|
418
415
|
}),
|
|
419
416
|
startWatch: vi.fn(),
|
|
@@ -603,7 +600,6 @@ describe("BuildOrchestrator client build", () => {
|
|
|
603
600
|
});
|
|
604
601
|
vi.mocked(createBuildEngine).mockReturnValue({
|
|
605
602
|
run: vi.fn().mockResolvedValue({
|
|
606
|
-
success: true,
|
|
607
603
|
build: { success: true, errors: [], warnings: [], diagnostics: [] },
|
|
608
604
|
}),
|
|
609
605
|
startWatch: vi.fn(),
|
|
@@ -637,7 +633,6 @@ describe("BuildOrchestrator client build", () => {
|
|
|
637
633
|
});
|
|
638
634
|
vi.mocked(createBuildEngine).mockReturnValue({
|
|
639
635
|
run: vi.fn().mockResolvedValue({
|
|
640
|
-
success: false,
|
|
641
636
|
build: { success: false, errors: ["Template error"], warnings: [], diagnostics: [] },
|
|
642
637
|
}),
|
|
643
638
|
startWatch: vi.fn(),
|
|
@@ -957,7 +952,6 @@ describe("BuildOrchestrator native build integration (Slice 1)", () => {
|
|
|
957
952
|
});
|
|
958
953
|
vi.mocked(createBuildEngine).mockReturnValue({
|
|
959
954
|
run: vi.fn().mockResolvedValue({
|
|
960
|
-
success: false,
|
|
961
955
|
build: { success: false, errors: ["Build error"], warnings: [], diagnostics: [] },
|
|
962
956
|
}),
|
|
963
957
|
startWatch: vi.fn(),
|