@simplysm/sd-cli 14.0.11 → 14.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 +58 -253
- package/dist/angular/client-transform-stylesheet.js +1 -1
- package/dist/angular/client-transform-stylesheet.js.map +1 -1
- package/dist/angular/vite-angular-plugin.d.ts +4 -2
- package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
- package/dist/angular/vite-angular-plugin.js +73 -36
- package/dist/angular/vite-angular-plugin.js.map +1 -1
- package/dist/angular/vite-postcss-inline-plugin.d.ts +1 -1
- package/dist/angular/vite-postcss-inline-plugin.js +1 -1
- package/dist/capacitor/capacitor.d.ts +20 -2
- package/dist/capacitor/capacitor.d.ts.map +1 -1
- package/dist/capacitor/capacitor.js +155 -28
- package/dist/capacitor/capacitor.js.map +1 -1
- package/dist/commands/build.d.ts +3 -10
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +3 -10
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/check.js +3 -3
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/dev.d.ts +3 -9
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +3 -9
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/device.d.ts +3 -3
- package/dist/commands/device.js +5 -5
- package/dist/commands/device.js.map +1 -1
- package/dist/commands/publish.d.ts +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +24 -26
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/replace-deps.d.ts +3 -3
- package/dist/commands/replace-deps.d.ts.map +1 -1
- package/dist/commands/replace-deps.js +1 -1
- package/dist/commands/typecheck.d.ts +4 -3
- package/dist/commands/typecheck.d.ts.map +1 -1
- package/dist/commands/typecheck.js +5 -11
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/commands/watch.d.ts +9 -9
- package/dist/commands/watch.js +9 -9
- package/dist/electron/electron.d.ts.map +1 -1
- package/dist/electron/electron.js +42 -3
- package/dist/electron/electron.js.map +1 -1
- package/dist/engines/BaseEngine.d.ts +1 -1
- package/dist/engines/BaseEngine.d.ts.map +1 -1
- package/dist/engines/BaseEngine.js +3 -1
- package/dist/engines/BaseEngine.js.map +1 -1
- package/dist/engines/NgtscEngine.d.ts +7 -7
- package/dist/engines/NgtscEngine.d.ts.map +1 -1
- package/dist/engines/NgtscEngine.js +3 -3
- package/dist/engines/ServerEsbuildEngine.d.ts +7 -7
- package/dist/engines/ServerEsbuildEngine.d.ts.map +1 -1
- package/dist/engines/ServerEsbuildEngine.js +3 -3
- package/dist/engines/TscEngine.d.ts +7 -7
- package/dist/engines/TscEngine.d.ts.map +1 -1
- package/dist/engines/TscEngine.js +3 -3
- package/dist/engines/ViteEngine.d.ts +7 -1
- package/dist/engines/ViteEngine.d.ts.map +1 -1
- package/dist/engines/ViteEngine.js +13 -12
- package/dist/engines/ViteEngine.js.map +1 -1
- package/dist/engines/index.d.ts +9 -5
- package/dist/engines/index.d.ts.map +1 -1
- package/dist/engines/index.js +7 -5
- package/dist/engines/index.js.map +1 -1
- package/dist/engines/types.d.ts +20 -20
- package/dist/engines/types.d.ts.map +1 -1
- package/dist/infra/ResultCollector.d.ts +9 -9
- package/dist/infra/ResultCollector.js +8 -8
- package/dist/infra/SignalHandler.d.ts +7 -7
- package/dist/infra/SignalHandler.js +7 -7
- package/dist/infra/WorkerManager.d.ts +14 -14
- package/dist/infra/WorkerManager.js +14 -14
- package/dist/orchestrators/BuildOrchestrator.d.ts +25 -25
- package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.js +34 -30
- package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.d.ts +7 -7
- package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevWatchOrchestrator.js +34 -34
- package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
- package/dist/sd-cli-entry.d.ts +2 -2
- package/dist/sd-cli-entry.d.ts.map +1 -1
- package/dist/sd-cli-entry.js +15 -8
- package/dist/sd-cli-entry.js.map +1 -1
- package/dist/sd-cli.d.ts +3 -3
- package/dist/sd-cli.js +16 -16
- package/dist/sd-cli.js.map +1 -1
- package/dist/sd-config.types.d.ts +105 -105
- package/dist/sd-config.types.d.ts.map +1 -1
- package/dist/utils/angular-compiler.js +5 -5
- package/dist/utils/angular-compiler.js.map +1 -1
- package/dist/utils/build-env.d.ts +1 -1
- package/dist/utils/build-env.js +1 -1
- package/dist/utils/concurrency.d.ts +7 -7
- package/dist/utils/concurrency.js +7 -7
- package/dist/utils/copy-public.d.ts +9 -9
- package/dist/utils/copy-public.js +17 -17
- package/dist/utils/copy-public.js.map +1 -1
- package/dist/utils/copy-src.d.ts +9 -9
- package/dist/utils/copy-src.js +11 -11
- package/dist/utils/copy-src.js.map +1 -1
- package/dist/utils/engine-stop.d.ts +8 -9
- package/dist/utils/engine-stop.d.ts.map +1 -1
- package/dist/utils/engine-stop.js +9 -10
- package/dist/utils/engine-stop.js.map +1 -1
- package/dist/utils/esbuild-config.d.ts +23 -23
- package/dist/utils/esbuild-config.d.ts.map +1 -1
- package/dist/utils/esbuild-config.js +25 -25
- package/dist/utils/esbuild-config.js.map +1 -1
- package/dist/utils/lint-with-program.d.ts +15 -15
- package/dist/utils/lint-with-program.d.ts.map +1 -1
- package/dist/utils/lint-with-program.js +29 -29
- package/dist/utils/lint-with-program.js.map +1 -1
- package/dist/utils/ngtsc-build-core.d.ts +8 -8
- package/dist/utils/ngtsc-build-core.d.ts.map +1 -1
- package/dist/utils/ngtsc-build-core.js +14 -14
- package/dist/utils/ngtsc-build-core.js.map +1 -1
- package/dist/utils/output-path-rewriter.d.ts +14 -14
- package/dist/utils/output-path-rewriter.js +18 -18
- package/dist/utils/output-path-rewriter.js.map +1 -1
- package/dist/utils/output-utils.d.ts +6 -6
- package/dist/utils/output-utils.js +11 -11
- package/dist/utils/output-utils.js.map +1 -1
- package/dist/utils/package-utils.d.ts +21 -21
- package/dist/utils/package-utils.d.ts.map +1 -1
- package/dist/utils/package-utils.js +56 -45
- package/dist/utils/package-utils.js.map +1 -1
- package/dist/utils/replace-deps.d.ts +25 -25
- package/dist/utils/replace-deps.d.ts.map +1 -1
- package/dist/utils/replace-deps.js +84 -65
- package/dist/utils/replace-deps.js.map +1 -1
- package/dist/utils/sd-config.d.ts +3 -3
- package/dist/utils/sd-config.js +3 -3
- package/dist/utils/tsc-build.d.ts +13 -13
- package/dist/utils/tsc-build.d.ts.map +1 -1
- package/dist/utils/tsc-build.js +9 -9
- package/dist/utils/tsc-build.js.map +1 -1
- package/dist/utils/tsconfig.d.ts +11 -9
- package/dist/utils/tsconfig.d.ts.map +1 -1
- package/dist/utils/tsconfig.js +11 -9
- package/dist/utils/tsconfig.js.map +1 -1
- package/dist/utils/typecheck-non-package.d.ts +5 -6
- package/dist/utils/typecheck-non-package.d.ts.map +1 -1
- package/dist/utils/typecheck-non-package.js +7 -8
- package/dist/utils/typecheck-non-package.js.map +1 -1
- package/dist/utils/typecheck-serialization.d.ts +8 -8
- package/dist/utils/typecheck-serialization.d.ts.map +1 -1
- package/dist/utils/typecheck-serialization.js +12 -16
- package/dist/utils/typecheck-serialization.js.map +1 -1
- package/dist/utils/vite-config.d.ts +12 -5
- package/dist/utils/vite-config.d.ts.map +1 -1
- package/dist/utils/vite-config.js +95 -41
- package/dist/utils/vite-config.js.map +1 -1
- package/dist/utils/vite-scope-watch-plugin.d.ts.map +1 -1
- package/dist/utils/vite-scope-watch-plugin.js +1 -1
- package/dist/utils/vite-scope-watch-plugin.js.map +1 -1
- package/dist/utils/worker-events.d.ts +12 -12
- package/dist/utils/worker-events.d.ts.map +1 -1
- package/dist/utils/worker-events.js +10 -10
- package/dist/utils/worker-events.js.map +1 -1
- package/dist/utils/worker-utils.d.ts +12 -13
- package/dist/utils/worker-utils.d.ts.map +1 -1
- package/dist/utils/worker-utils.js +12 -13
- package/dist/utils/worker-utils.js.map +1 -1
- package/dist/vitest-plugin.d.ts.map +1 -1
- package/dist/vitest-plugin.js +5 -7
- package/dist/vitest-plugin.js.map +1 -1
- package/dist/workers/client.worker.d.ts +8 -2
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +215 -6
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/library-build.worker.d.ts +1 -1
- package/dist/workers/library-build.worker.d.ts.map +1 -1
- package/dist/workers/library-build.worker.js +7 -7
- package/dist/workers/library-build.worker.js.map +1 -1
- package/dist/workers/lint.worker.d.ts +2 -2
- package/dist/workers/lint.worker.js +2 -2
- package/dist/workers/ngtsc-build.worker.js +30 -30
- package/dist/workers/ngtsc-build.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts +17 -17
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +46 -46
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-runtime.worker.d.ts +7 -7
- package/dist/workers/server-runtime.worker.d.ts.map +1 -1
- package/dist/workers/server-runtime.worker.js +17 -17
- package/dist/workers/server-runtime.worker.js.map +1 -1
- package/docs/config.md +340 -0
- package/docs/publish-configuration-types.md +87 -0
- package/docs/pwa-configuration-types.md +55 -0
- package/docs/vitest-plugin.md +47 -0
- package/package.json +9 -7
- package/src/angular/client-transform-stylesheet.ts +1 -1
- package/src/angular/vite-angular-plugin.ts +89 -39
- package/src/angular/vite-postcss-inline-plugin.ts +1 -1
- package/src/capacitor/capacitor.ts +185 -38
- package/src/commands/build.ts +3 -10
- package/src/commands/check.ts +3 -3
- package/src/commands/dev.ts +3 -9
- package/src/commands/device.ts +5 -5
- package/src/commands/publish.ts +30 -26
- package/src/commands/replace-deps.ts +3 -3
- package/src/commands/typecheck.ts +7 -13
- package/src/commands/watch.ts +9 -9
- package/src/electron/electron.ts +49 -4
- package/src/engines/BaseEngine.ts +4 -1
- package/src/engines/NgtscEngine.ts +7 -7
- package/src/engines/ServerEsbuildEngine.ts +7 -7
- package/src/engines/TscEngine.ts +7 -7
- package/src/engines/ViteEngine.ts +18 -13
- package/src/engines/index.ts +11 -5
- package/src/engines/types.ts +20 -20
- package/src/infra/ResultCollector.ts +9 -9
- package/src/infra/SignalHandler.ts +7 -7
- package/src/infra/WorkerManager.ts +14 -14
- package/src/orchestrators/BuildOrchestrator.ts +42 -38
- package/src/orchestrators/DevWatchOrchestrator.ts +36 -36
- package/src/sd-cli-entry.ts +15 -8
- package/src/sd-cli.ts +16 -16
- package/src/sd-config.types.ts +107 -107
- package/src/utils/angular-compiler.ts +5 -5
- package/src/utils/build-env.ts +1 -1
- package/src/utils/concurrency.ts +7 -7
- package/src/utils/copy-public.ts +17 -17
- package/src/utils/copy-src.ts +11 -11
- package/src/utils/engine-stop.ts +9 -10
- package/src/utils/esbuild-config.ts +29 -29
- package/src/utils/lint-with-program.ts +34 -34
- package/src/utils/ngtsc-build-core.ts +17 -17
- package/src/utils/output-path-rewriter.ts +18 -18
- package/src/utils/output-utils.ts +11 -11
- package/src/utils/package-utils.ts +57 -45
- package/src/utils/replace-deps.ts +92 -67
- package/src/utils/sd-config.ts +3 -3
- package/src/utils/tsc-build.ts +18 -18
- package/src/utils/tsconfig.ts +11 -9
- package/src/utils/typecheck-non-package.ts +7 -8
- package/src/utils/typecheck-serialization.ts +13 -15
- package/src/utils/vite-config.ts +108 -46
- package/src/utils/vite-scope-watch-plugin.ts +6 -1
- package/src/utils/worker-events.ts +16 -16
- package/src/utils/worker-utils.ts +12 -13
- package/src/vitest-plugin.ts +5 -8
- package/src/workers/client.worker.ts +246 -7
- package/src/workers/library-build.worker.ts +8 -8
- package/src/workers/lint.worker.ts +2 -2
- package/src/workers/ngtsc-build.worker.ts +31 -31
- package/src/workers/server-build.worker.ts +60 -60
- package/src/workers/server-runtime.worker.ts +22 -22
- package/tests/angular/vite-angular-plugin-hmr-fallback.spec.ts +1 -0
- package/tests/angular/vite-angular-plugin-hmr.spec.ts +78 -0
- package/tests/angular/vite-angular-plugin.spec.ts +67 -0
- package/tests/capacitor/capacitor-build.spec.ts +93 -11
- package/tests/capacitor/capacitor-icon.spec.ts +7 -5
- package/tests/capacitor/capacitor-init.spec.ts +124 -10
- package/tests/capacitor/capacitor-run.spec.ts +14 -17
- package/tests/capacitor/capacitor-workspace.spec.ts +5 -3
- package/tests/commands/check.spec.ts +2 -2
- package/tests/commands/publish.spec.ts +2 -2
- package/tests/commands/typecheck.spec.ts +8 -0
- package/tests/electron/electron.spec.ts +12 -10
- package/tests/engines/base-engine.spec.ts +37 -0
- package/tests/engines/vite-engine.spec.ts +115 -3
- package/tests/utils/vite-config.spec.ts +162 -90
- package/tests/workers/client-worker.spec.ts +690 -0
- package/tests/workers/server-build-worker.spec.ts +3 -3
|
@@ -192,6 +192,73 @@ describe("sdAngularPlugin", () => {
|
|
|
192
192
|
// (in real use, Vite server close triggers this)
|
|
193
193
|
});
|
|
194
194
|
|
|
195
|
+
// Scenario: optimizeDeps에 Angular Linker esbuild 플러그인이 등록된다 (Feature 1.1 Angular Linker)
|
|
196
|
+
it("registers angular-vite-optimize-deps esbuild plugin in optimizeDeps config", () => {
|
|
197
|
+
const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
|
|
198
|
+
const config = (plugin as any).config?.();
|
|
199
|
+
|
|
200
|
+
const esbuildPlugins = config?.optimizeDeps?.esbuildOptions?.plugins as
|
|
201
|
+
| { name: string }[]
|
|
202
|
+
| undefined;
|
|
203
|
+
expect(esbuildPlugins).toBeDefined();
|
|
204
|
+
expect(esbuildPlugins!.some((p) => p.name === "angular-vite-optimize-deps")).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Scenario: .mjs 파일이 JavaScriptTransformer를 통과한다 (Feature 1.1 Angular Linker)
|
|
208
|
+
it("transforms .mjs files through JavaScriptTransformer", async () => {
|
|
209
|
+
const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
|
|
210
|
+
await (plugin as any).buildStart?.call({});
|
|
211
|
+
|
|
212
|
+
const mjsCode = "export const x = 1;";
|
|
213
|
+
const result = await (plugin as any).transform?.call(
|
|
214
|
+
{},
|
|
215
|
+
mjsCode,
|
|
216
|
+
"/some/library/module.mjs",
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// .mjs 파일은 transform 결과를 반환해야 한다 (undefined가 아님)
|
|
220
|
+
expect(result).toBeDefined();
|
|
221
|
+
expect(result.code).toBeDefined();
|
|
222
|
+
expect(typeof result.code).toBe("string");
|
|
223
|
+
|
|
224
|
+
await (plugin as any).buildEnd?.call({});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Scenario: .js 파일이 JavaScriptTransformer를 통과한다 (Feature 1.1 Angular Linker)
|
|
228
|
+
it("transforms .js files through JavaScriptTransformer", async () => {
|
|
229
|
+
const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
|
|
230
|
+
await (plugin as any).buildStart?.call({});
|
|
231
|
+
|
|
232
|
+
const jsCode = "export const y = 2;";
|
|
233
|
+
const result = await (plugin as any).transform?.call(
|
|
234
|
+
{},
|
|
235
|
+
jsCode,
|
|
236
|
+
"/some/library/module.js",
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(result).toBeDefined();
|
|
240
|
+
expect(result.code).toBeDefined();
|
|
241
|
+
expect(typeof result.code).toBe("string");
|
|
242
|
+
|
|
243
|
+
await (plugin as any).buildEnd?.call({});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Scenario: 비대상 파일(.css 등)은 transform하지 않는다 (Feature 1.1 Angular Linker)
|
|
247
|
+
it("returns undefined for non-JS files like .css", async () => {
|
|
248
|
+
const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: true });
|
|
249
|
+
await (plugin as any).buildStart?.call({});
|
|
250
|
+
|
|
251
|
+
const result = await (plugin as any).transform?.call(
|
|
252
|
+
{},
|
|
253
|
+
"body { color: red; }",
|
|
254
|
+
"/some/styles.css",
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(result).toBeUndefined();
|
|
258
|
+
|
|
259
|
+
await (plugin as any).buildEnd?.call({});
|
|
260
|
+
});
|
|
261
|
+
|
|
195
262
|
// Scenario: Angular .ts 파일 transform
|
|
196
263
|
it("transforms emitted .ts files with compiled JS", async () => {
|
|
197
264
|
const plugin = sdAngularPlugin({ tsconfig: TSCONFIG_PATH, dev: false });
|
|
@@ -27,8 +27,8 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
27
27
|
copy: mockFsxCopy,
|
|
28
28
|
},
|
|
29
29
|
cpx: {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
spawn: mockCpxSpawn,
|
|
31
|
+
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
32
32
|
},
|
|
33
33
|
pathx: {
|
|
34
34
|
posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
|
|
@@ -46,7 +46,7 @@ vi.mock("@simplysm/core-common", () => ({
|
|
|
46
46
|
|
|
47
47
|
// cpx mock (was execa)
|
|
48
48
|
const execaCalls: { command: string; args: string[] }[] = [];
|
|
49
|
-
const
|
|
49
|
+
const mockCpxSpawn = vi.fn((...args: unknown[]) => {
|
|
50
50
|
execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
|
|
51
51
|
return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
52
52
|
});
|
|
@@ -82,7 +82,9 @@ vi.mock("consola", () => ({
|
|
|
82
82
|
warn: mockLoggerWarn,
|
|
83
83
|
success: mockLoggerSuccess,
|
|
84
84
|
}),
|
|
85
|
+
level: 0,
|
|
85
86
|
},
|
|
87
|
+
LogLevels: { debug: 4 },
|
|
86
88
|
}));
|
|
87
89
|
|
|
88
90
|
//#endregion
|
|
@@ -91,6 +93,19 @@ vi.mock("consola", () => ({
|
|
|
91
93
|
|
|
92
94
|
const PKG_PATH = "/fake/pkg";
|
|
93
95
|
|
|
96
|
+
/** Gradle 실행 명령을 찾는다 (Windows: cmd /c gradlew.bat, 그 외: gradlew) */
|
|
97
|
+
function findGradleCall(calls: { command: string; args: string[] }[]) {
|
|
98
|
+
return calls.find(
|
|
99
|
+
(c) => c.command.includes("gradlew") || (c.command === "cmd" && c.args.includes("gradlew.bat")),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findGradleCallIndex(calls: { command: string; args: string[] }[]) {
|
|
104
|
+
return calls.findIndex(
|
|
105
|
+
(c) => c.command.includes("gradlew") || (c.command === "cmd" && c.args.includes("gradlew.bat")),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
function setupDefaultMocks() {
|
|
95
110
|
mockFsxExists.mockResolvedValue(true);
|
|
96
111
|
|
|
@@ -179,7 +194,7 @@ describe("Capacitor 빌드", () => {
|
|
|
179
194
|
|
|
180
195
|
await cap.build("/fake/out");
|
|
181
196
|
|
|
182
|
-
const gradleCmd = execaCalls
|
|
197
|
+
const gradleCmd = findGradleCall(execaCalls);
|
|
183
198
|
expect(gradleCmd).toBeDefined();
|
|
184
199
|
expect(gradleCmd!.args).toContain("bundleRelease");
|
|
185
200
|
});
|
|
@@ -195,7 +210,7 @@ describe("Capacitor 빌드", () => {
|
|
|
195
210
|
|
|
196
211
|
await cap.build("/fake/out");
|
|
197
212
|
|
|
198
|
-
const gradleCmd = execaCalls
|
|
213
|
+
const gradleCmd = findGradleCall(execaCalls);
|
|
199
214
|
expect(gradleCmd).toBeDefined();
|
|
200
215
|
expect(gradleCmd!.args).toContain("assembleRelease");
|
|
201
216
|
});
|
|
@@ -212,7 +227,7 @@ describe("Capacitor 빌드", () => {
|
|
|
212
227
|
|
|
213
228
|
await cap.build("/fake/out");
|
|
214
229
|
|
|
215
|
-
const gradleCmd = execaCalls
|
|
230
|
+
const gradleCmd = findGradleCall(execaCalls);
|
|
216
231
|
expect(gradleCmd).toBeDefined();
|
|
217
232
|
expect(gradleCmd!.args).toContain("assembleDebug");
|
|
218
233
|
});
|
|
@@ -270,14 +285,14 @@ describe("Capacitor 빌드", () => {
|
|
|
270
285
|
|
|
271
286
|
// cap copy가 gradlew보다 먼저 실행되는지 확인
|
|
272
287
|
const capCopyIndex = execaCalls.findIndex(
|
|
273
|
-
(c) => c.command === "
|
|
288
|
+
(c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("copy"),
|
|
274
289
|
);
|
|
275
|
-
const gradlewIndex = execaCalls
|
|
290
|
+
const gradlewIndex = findGradleCallIndex(execaCalls);
|
|
276
291
|
expect(capCopyIndex).toBeGreaterThanOrEqual(0);
|
|
277
292
|
expect(gradlewIndex).toBeGreaterThan(capCopyIndex);
|
|
278
293
|
});
|
|
279
294
|
|
|
280
|
-
it("Windows에서 gradlew.bat을
|
|
295
|
+
it("Windows에서 cmd /c gradlew.bat으로 Gradle을 실행한다", async () => {
|
|
281
296
|
const originalPlatform = process.platform;
|
|
282
297
|
Object.defineProperty(process, "platform", { value: "win32" });
|
|
283
298
|
|
|
@@ -292,8 +307,37 @@ describe("Capacitor 빌드", () => {
|
|
|
292
307
|
|
|
293
308
|
await cap.build("/fake/out");
|
|
294
309
|
|
|
295
|
-
const gradleCmd = execaCalls.find((c) => c.command
|
|
296
|
-
expect(gradleCmd
|
|
310
|
+
const gradleCmd = execaCalls.find((c) => c.command === "cmd");
|
|
311
|
+
expect(gradleCmd).toBeDefined();
|
|
312
|
+
expect(gradleCmd!.args[0]).toBe("/c");
|
|
313
|
+
expect(gradleCmd!.args[1]).toBe("gradlew.bat");
|
|
314
|
+
expect(gradleCmd!.args).toContain("assembleRelease");
|
|
315
|
+
expect(gradleCmd!.args).toContain("--no-daemon");
|
|
316
|
+
} finally {
|
|
317
|
+
Object.defineProperty(process, "platform", { value: originalPlatform });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("Linux/macOS에서 gradlew를 직접 실행한다", async () => {
|
|
322
|
+
const originalPlatform = process.platform;
|
|
323
|
+
Object.defineProperty(process, "platform", { value: "linux" });
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
327
|
+
|
|
328
|
+
const cap = await Capacitor.create(PKG_PATH, {
|
|
329
|
+
appId: "com.test.app",
|
|
330
|
+
appName: "Test App",
|
|
331
|
+
platform: { android: {} },
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await cap.build("/fake/out");
|
|
335
|
+
|
|
336
|
+
const gradleCmd = findGradleCall(execaCalls);
|
|
337
|
+
expect(gradleCmd).toBeDefined();
|
|
338
|
+
expect(gradleCmd!.command).toContain("gradlew");
|
|
339
|
+
expect(gradleCmd!.command).not.toContain("gradlew.bat");
|
|
340
|
+
expect(gradleCmd!.args).toContain("assembleRelease");
|
|
297
341
|
} finally {
|
|
298
342
|
Object.defineProperty(process, "platform", { value: originalPlatform });
|
|
299
343
|
}
|
|
@@ -375,6 +419,44 @@ describe("Capacitor 빌드", () => {
|
|
|
375
419
|
await expect(cap.build("/fake/out")).rejects.toThrow("keystore");
|
|
376
420
|
});
|
|
377
421
|
|
|
422
|
+
it("비밀번호에 $, \\, ' 등 특수문자가 있으면 Groovy 이스케이프하여 build.gradle에 기록한다", async () => {
|
|
423
|
+
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
424
|
+
mockFsxExists.mockResolvedValue(true);
|
|
425
|
+
|
|
426
|
+
const cap = await Capacitor.create(PKG_PATH, {
|
|
427
|
+
appId: "com.test.app",
|
|
428
|
+
appName: "Test App",
|
|
429
|
+
platform: {
|
|
430
|
+
android: {
|
|
431
|
+
sign: {
|
|
432
|
+
keystore: "my-release.keystore",
|
|
433
|
+
storePassword: "12tlavmf#$",
|
|
434
|
+
alias: "my-key",
|
|
435
|
+
password: "pass\\'word",
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
await cap.build("/fake/out");
|
|
442
|
+
|
|
443
|
+
const writeCalls = mockFsxWrite.mock.calls;
|
|
444
|
+
const gradleWrite = writeCalls.find(
|
|
445
|
+
(call) =>
|
|
446
|
+
typeof call[0] === "string" &&
|
|
447
|
+
call[0].includes("build.gradle") &&
|
|
448
|
+
typeof call[1] === "string" &&
|
|
449
|
+
call[1].includes("signingConfigs"),
|
|
450
|
+
);
|
|
451
|
+
expect(gradleWrite).toBeDefined();
|
|
452
|
+
|
|
453
|
+
const gradleContent = gradleWrite![1] as string;
|
|
454
|
+
// $ 는 Groovy single-quoted string에서 그대로 유지
|
|
455
|
+
expect(gradleContent).toContain("storePassword '12tlavmf#$'");
|
|
456
|
+
// \ → \\, ' → \' 이스케이프
|
|
457
|
+
expect(gradleContent).toContain("keyPassword 'pass\\\\\\'word'");
|
|
458
|
+
});
|
|
459
|
+
|
|
378
460
|
it("signed 빌드 산출물이 unsigned 접미사 없이 복사된다", async () => {
|
|
379
461
|
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
380
462
|
|
|
@@ -25,8 +25,8 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
25
25
|
glob: mockFsxGlob,
|
|
26
26
|
},
|
|
27
27
|
cpx: {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
spawn: mockCpxSpawn,
|
|
29
|
+
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
30
30
|
},
|
|
31
31
|
pathx: {
|
|
32
32
|
posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
|
|
@@ -44,7 +44,7 @@ vi.mock("@simplysm/core-common", () => ({
|
|
|
44
44
|
|
|
45
45
|
// cpx mock (was execa)
|
|
46
46
|
const execaCalls: { command: string; args: string[] }[] = [];
|
|
47
|
-
const
|
|
47
|
+
const mockCpxSpawn = vi.fn((...args: unknown[]) => {
|
|
48
48
|
execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
|
|
49
49
|
return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
50
50
|
});
|
|
@@ -82,7 +82,9 @@ vi.mock("consola", () => ({
|
|
|
82
82
|
warn: mockLoggerWarn,
|
|
83
83
|
success: mockLoggerSuccess,
|
|
84
84
|
}),
|
|
85
|
+
level: 0,
|
|
85
86
|
},
|
|
87
|
+
LogLevels: { debug: 4 },
|
|
86
88
|
}));
|
|
87
89
|
|
|
88
90
|
//#endregion
|
|
@@ -178,7 +180,7 @@ describe("Capacitor 아이콘 처리", () => {
|
|
|
178
180
|
|
|
179
181
|
// capacitor-assets generate가 실행되었는지 확인
|
|
180
182
|
const assetsCmd = execaCalls.find(
|
|
181
|
-
(c) => c.command === "
|
|
183
|
+
(c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
|
|
182
184
|
);
|
|
183
185
|
expect(assetsCmd).toBeDefined();
|
|
184
186
|
|
|
@@ -202,7 +204,7 @@ describe("Capacitor 아이콘 처리", () => {
|
|
|
202
204
|
|
|
203
205
|
// capacitor-assets generate가 실행되지 않아야 한다
|
|
204
206
|
const assetsCmd = execaCalls.find(
|
|
205
|
-
(c) => c.command === "
|
|
207
|
+
(c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
|
|
206
208
|
);
|
|
207
209
|
expect(assetsCmd).toBeUndefined();
|
|
208
210
|
});
|
|
@@ -26,8 +26,8 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
26
26
|
copy: mockFsxCopy,
|
|
27
27
|
},
|
|
28
28
|
cpx: {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
spawn: mockCpxSpawn,
|
|
30
|
+
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
31
31
|
},
|
|
32
32
|
pathx: {
|
|
33
33
|
posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
|
|
@@ -43,7 +43,7 @@ vi.mock("@simplysm/core-common", () => ({
|
|
|
43
43
|
}));
|
|
44
44
|
|
|
45
45
|
const execaCalls: { command: string; args: string[] }[] = [];
|
|
46
|
-
const
|
|
46
|
+
const mockCpxSpawn = vi.fn((...args: unknown[]) => {
|
|
47
47
|
execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
|
|
48
48
|
return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
49
49
|
});
|
|
@@ -71,8 +71,10 @@ const mockLoggerDebug = vi.fn();
|
|
|
71
71
|
const mockLoggerWarn = vi.fn();
|
|
72
72
|
vi.mock("consola", () => ({
|
|
73
73
|
consola: {
|
|
74
|
+
level: 0,
|
|
74
75
|
withTag: () => ({ debug: mockLoggerDebug, warn: mockLoggerWarn }),
|
|
75
76
|
},
|
|
77
|
+
LogLevels: { debug: 4 },
|
|
76
78
|
}));
|
|
77
79
|
|
|
78
80
|
//#endregion
|
|
@@ -131,6 +133,22 @@ function setupDefaultMocks() {
|
|
|
131
133
|
if (p.includes("gradle.properties")) {
|
|
132
134
|
return "org.gradle.jvmargs=-Xmx2048m";
|
|
133
135
|
}
|
|
136
|
+
if (p.includes("styles.xml")) {
|
|
137
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
138
|
+
<resources>
|
|
139
|
+
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
|
140
|
+
<item name="colorPrimary">@color/colorPrimary</item>
|
|
141
|
+
</style>
|
|
142
|
+
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
|
143
|
+
<item name="windowActionBar">false</item>
|
|
144
|
+
<item name="windowNoTitle">true</item>
|
|
145
|
+
<item name="android:background">@null</item>
|
|
146
|
+
</style>
|
|
147
|
+
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
|
148
|
+
<item name="android:background">@drawable/splash</item>
|
|
149
|
+
</style>
|
|
150
|
+
</resources>`;
|
|
151
|
+
}
|
|
134
152
|
return "";
|
|
135
153
|
});
|
|
136
154
|
|
|
@@ -197,7 +215,7 @@ describe("Capacitor 초기화", () => {
|
|
|
197
215
|
setupDefaultMocks();
|
|
198
216
|
});
|
|
199
217
|
|
|
200
|
-
it("최초 초기화:
|
|
218
|
+
it("최초 초기화: pnpm install, cap init, cap add android를 실행한다", async () => {
|
|
201
219
|
let androidAdded = false;
|
|
202
220
|
mockFsxExists.mockImplementation((p: string) => {
|
|
203
221
|
const n = p.replace(/\\/g, "/");
|
|
@@ -220,18 +238,18 @@ describe("Capacitor 초기화", () => {
|
|
|
220
238
|
});
|
|
221
239
|
await cap.initialize();
|
|
222
240
|
|
|
223
|
-
expect(execaCalls.some((c) => c.command === "
|
|
241
|
+
expect(execaCalls.some((c) => c.command === "pnpm" && c.args.includes("install"))).toBe(true);
|
|
224
242
|
expect(
|
|
225
|
-
execaCalls.some((c) => c.command === "
|
|
243
|
+
execaCalls.some((c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("init")),
|
|
226
244
|
).toBe(true);
|
|
227
245
|
expect(
|
|
228
246
|
execaCalls.some(
|
|
229
|
-
(c) => c.command === "
|
|
247
|
+
(c) => c.command === "pnpm" && c.args.includes("cap") && c.args.includes("add"),
|
|
230
248
|
),
|
|
231
249
|
).toBe(true);
|
|
232
250
|
});
|
|
233
251
|
|
|
234
|
-
it("재초기화: 설정 미변경 시
|
|
252
|
+
it("재초기화: 설정 미변경 시 pnpm install을 건너뛴다", async () => {
|
|
235
253
|
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
236
254
|
const cap = await Capacitor.create(PKG_PATH, {
|
|
237
255
|
appId: "com.test.app",
|
|
@@ -240,10 +258,10 @@ describe("Capacitor 초기화", () => {
|
|
|
240
258
|
});
|
|
241
259
|
await cap.initialize();
|
|
242
260
|
|
|
243
|
-
expect(execaCalls.some((c) => c.command === "
|
|
261
|
+
expect(execaCalls.some((c) => c.command === "pnpm" && c.args.includes("install"))).toBe(false);
|
|
244
262
|
});
|
|
245
263
|
|
|
246
|
-
it("플러그인 추가: package.json에 플러그인을 추가하고
|
|
264
|
+
it("플러그인 추가: package.json에 플러그인을 추가하고 pnpm install을 실행한다", async () => {
|
|
247
265
|
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
248
266
|
|
|
249
267
|
// 클라이언트 패키지의 deps에 플러그인 포함
|
|
@@ -531,6 +549,102 @@ describe("Android 네이티브 설정", () => {
|
|
|
531
549
|
expect(manifestWrite).toBeDefined();
|
|
532
550
|
});
|
|
533
551
|
|
|
552
|
+
it("styles.xml의 Theme.SplashScreen parent를 변경하고 android:background를 android:windowBackground로 변경한다", async () => {
|
|
553
|
+
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
554
|
+
const cap = await Capacitor.create(PKG_PATH, {
|
|
555
|
+
appId: "com.test.app",
|
|
556
|
+
appName: "Test App",
|
|
557
|
+
platform: { android: {} },
|
|
558
|
+
});
|
|
559
|
+
await cap.initialize();
|
|
560
|
+
|
|
561
|
+
const writeCalls = mockFsxWrite.mock.calls;
|
|
562
|
+
const stylesWrite = writeCalls.find(
|
|
563
|
+
(call) =>
|
|
564
|
+
typeof call[0] === "string" &&
|
|
565
|
+
call[0].includes("styles.xml") &&
|
|
566
|
+
typeof call[1] === "string" &&
|
|
567
|
+
call[1].includes('parent="Theme.AppCompat.DayNight.NoActionBar"'),
|
|
568
|
+
);
|
|
569
|
+
expect(stylesWrite).toBeDefined();
|
|
570
|
+
const content = stylesWrite![1] as string;
|
|
571
|
+
// @drawable/splash는 유지
|
|
572
|
+
expect(content).toContain("@drawable/splash");
|
|
573
|
+
// Theme.SplashScreen은 제거됨
|
|
574
|
+
expect(content).not.toContain('parent="Theme.SplashScreen"');
|
|
575
|
+
// android:background → android:windowBackground로 변경됨
|
|
576
|
+
expect(content).toContain('"android:windowBackground">@drawable/splash');
|
|
577
|
+
expect(content).not.toContain('"android:background">@drawable/splash');
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it("이미 변경된 styles.xml은 재변경하지 않는다", async () => {
|
|
581
|
+
mockFsxRead.mockImplementation((p: string) => {
|
|
582
|
+
if (p.includes("styles.xml")) {
|
|
583
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
584
|
+
<resources>
|
|
585
|
+
<style name="AppTheme.NoActionBarLaunch" parent="Theme.AppCompat.DayNight.NoActionBar">
|
|
586
|
+
<item name="android:windowBackground">@drawable/splash</item>
|
|
587
|
+
</style>
|
|
588
|
+
</resources>`;
|
|
589
|
+
}
|
|
590
|
+
if (p.includes("AndroidManifest.xml")) {
|
|
591
|
+
return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n<activity android:name=".MainActivity">\n</activity>\n</application>\n</manifest>';
|
|
592
|
+
}
|
|
593
|
+
if (p.includes("build.gradle")) {
|
|
594
|
+
return `android {
|
|
595
|
+
defaultConfig {
|
|
596
|
+
versionCode 1
|
|
597
|
+
versionName "1.0"
|
|
598
|
+
minSdkVersion rootProject.ext.minSdkVersion
|
|
599
|
+
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
600
|
+
}
|
|
601
|
+
buildTypes { release { } }
|
|
602
|
+
}`;
|
|
603
|
+
}
|
|
604
|
+
if (p.includes("gradle.properties")) {
|
|
605
|
+
return "org.gradle.jvmargs=-Xmx2048m";
|
|
606
|
+
}
|
|
607
|
+
return "";
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
611
|
+
const cap = await Capacitor.create(PKG_PATH, {
|
|
612
|
+
appId: "com.test.app",
|
|
613
|
+
appName: "Test App",
|
|
614
|
+
platform: { android: {} },
|
|
615
|
+
});
|
|
616
|
+
await cap.initialize();
|
|
617
|
+
|
|
618
|
+
const writeCalls = mockFsxWrite.mock.calls;
|
|
619
|
+
const stylesWrite = writeCalls.find(
|
|
620
|
+
(call) =>
|
|
621
|
+
typeof call[0] === "string" &&
|
|
622
|
+
call[0].includes("styles.xml"),
|
|
623
|
+
);
|
|
624
|
+
expect(stylesWrite).toBeUndefined();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("styles.xml이 없으면 warn을 출력한다", async () => {
|
|
628
|
+
mockFsxExists.mockImplementation((p: string) => {
|
|
629
|
+
const n = p.replace(/\\/g, "/");
|
|
630
|
+
if (n.includes(".capacitor.lock")) return false;
|
|
631
|
+
if (n.includes("styles.xml")) return false;
|
|
632
|
+
return true;
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
636
|
+
const cap = await Capacitor.create(PKG_PATH, {
|
|
637
|
+
appId: "com.test.app",
|
|
638
|
+
appName: "Test App",
|
|
639
|
+
platform: { android: {} },
|
|
640
|
+
});
|
|
641
|
+
await cap.initialize();
|
|
642
|
+
|
|
643
|
+
expect(mockLoggerWarn).toHaveBeenCalledWith(
|
|
644
|
+
expect.stringContaining("styles.xml"),
|
|
645
|
+
);
|
|
646
|
+
});
|
|
647
|
+
|
|
534
648
|
it("application 태그에 커스텀 속성을 추가한다", async () => {
|
|
535
649
|
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
536
650
|
const cap = await Capacitor.create(PKG_PATH, {
|
|
@@ -27,8 +27,8 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
27
27
|
copy: mockFsxCopy,
|
|
28
28
|
},
|
|
29
29
|
cpx: {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
spawn: mockCpxSpawn,
|
|
31
|
+
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
32
32
|
},
|
|
33
33
|
pathx: {
|
|
34
34
|
posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
|
|
@@ -49,7 +49,7 @@ const execaCalls: { command: string; args: string[] }[] = [];
|
|
|
49
49
|
let execaFactory: (...args: unknown[]) => Promise<{ stdout: string; stderr: string; exitCode: number }> = () =>
|
|
50
50
|
Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
51
51
|
|
|
52
|
-
const
|
|
52
|
+
const mockCpxSpawn = vi.fn((...args: unknown[]) => {
|
|
53
53
|
execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
|
|
54
54
|
return execaFactory(...args);
|
|
55
55
|
});
|
|
@@ -84,7 +84,9 @@ vi.mock("consola", () => ({
|
|
|
84
84
|
warn: mockLoggerWarn,
|
|
85
85
|
info: vi.fn(),
|
|
86
86
|
}),
|
|
87
|
+
level: 0,
|
|
87
88
|
},
|
|
89
|
+
LogLevels: { debug: 4 },
|
|
88
90
|
}));
|
|
89
91
|
|
|
90
92
|
//#endregion
|
|
@@ -175,14 +177,14 @@ describe("Capacitor.run()", () => {
|
|
|
175
177
|
|
|
176
178
|
// cap copy android + cap run android
|
|
177
179
|
const capCmds = execaCalls.filter(
|
|
178
|
-
(c) => c.command === "
|
|
180
|
+
(c) => c.command === "pnpm" && c.args.includes("cap"),
|
|
179
181
|
);
|
|
180
182
|
expect(capCmds.some((c) => c.args.includes("copy") && c.args.includes("android"))).toBe(true);
|
|
181
183
|
expect(capCmds.some((c) => c.args.includes("run") && c.args.includes("android"))).toBe(true);
|
|
182
184
|
});
|
|
183
185
|
|
|
184
|
-
// Unit: adb kill-server
|
|
185
|
-
it("
|
|
186
|
+
// Unit: cap run 실패 시 adb kill-server 호출 후 에러를 re-throw한다
|
|
187
|
+
it("calls adb kill-server and re-throws on android platform cap run failure", async () => {
|
|
186
188
|
const { Capacitor } = await import("../../src/capacitor/capacitor.js");
|
|
187
189
|
|
|
188
190
|
let capRunCallCount = 0;
|
|
@@ -191,16 +193,13 @@ describe("Capacitor.run()", () => {
|
|
|
191
193
|
const cmdArgs = (args[1] as string[] | undefined) ?? [];
|
|
192
194
|
|
|
193
195
|
if (
|
|
194
|
-
cmd === "
|
|
196
|
+
cmd === "pnpm" &&
|
|
195
197
|
cmdArgs.includes("cap") &&
|
|
196
198
|
cmdArgs.includes("run") &&
|
|
197
199
|
cmdArgs.includes("android")
|
|
198
200
|
) {
|
|
199
201
|
capRunCallCount++;
|
|
200
|
-
|
|
201
|
-
// First cap run fails
|
|
202
|
-
return Promise.reject(new Error("cap run failed"));
|
|
203
|
-
}
|
|
202
|
+
return Promise.reject(new Error("cap run failed"));
|
|
204
203
|
}
|
|
205
204
|
return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
206
205
|
};
|
|
@@ -211,16 +210,14 @@ describe("Capacitor.run()", () => {
|
|
|
211
210
|
platform: { android: {} },
|
|
212
211
|
});
|
|
213
212
|
|
|
214
|
-
await cap.run("http://localhost:4200");
|
|
213
|
+
await expect(cap.run("http://localhost:4200")).rejects.toThrow("cap run failed");
|
|
215
214
|
|
|
216
|
-
// adb kill-server should have been called
|
|
215
|
+
// adb kill-server should have been called
|
|
217
216
|
expect(
|
|
218
217
|
execaCalls.some((c) => c.command === "adb" && c.args.includes("kill-server")),
|
|
219
218
|
).toBe(true);
|
|
220
|
-
|
|
221
|
-
expect(
|
|
222
|
-
expect.stringContaining("adb kill-server"),
|
|
223
|
-
);
|
|
219
|
+
// cap run은 한 번만 호출 (재시도 없음)
|
|
220
|
+
expect(capRunCallCount).toBe(1);
|
|
224
221
|
});
|
|
225
222
|
|
|
226
223
|
// Unit: _updateServerUrl replaces existing url
|
|
@@ -26,8 +26,8 @@ vi.mock("@simplysm/core-node", () => ({
|
|
|
26
26
|
copy: mockFsxCopy,
|
|
27
27
|
},
|
|
28
28
|
cpx: {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
spawn: mockCpxSpawn,
|
|
30
|
+
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
31
31
|
},
|
|
32
32
|
pathx: {
|
|
33
33
|
posixResolve: (...args: string[]) => path.resolve(...args).replace(/\\/g, "/"),
|
|
@@ -43,7 +43,7 @@ vi.mock("@simplysm/core-common", () => ({
|
|
|
43
43
|
}));
|
|
44
44
|
|
|
45
45
|
const execaCalls: { command: string; args: string[] }[] = [];
|
|
46
|
-
const
|
|
46
|
+
const mockCpxSpawn = vi.fn((...args: unknown[]) => {
|
|
47
47
|
execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
|
|
48
48
|
return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
|
|
49
49
|
});
|
|
@@ -75,7 +75,9 @@ vi.mock("fs/promises", () => ({
|
|
|
75
75
|
vi.mock("consola", () => ({
|
|
76
76
|
consola: {
|
|
77
77
|
withTag: () => ({ debug: vi.fn(), warn: vi.fn() }),
|
|
78
|
+
level: 0,
|
|
78
79
|
},
|
|
80
|
+
LogLevels: { debug: 4 },
|
|
79
81
|
}));
|
|
80
82
|
|
|
81
83
|
//#endregion
|
|
@@ -37,8 +37,8 @@ vi.mock("../../src/utils/sd-config", () => ({
|
|
|
37
37
|
|
|
38
38
|
vi.mock("@simplysm/core-node", () => ({
|
|
39
39
|
cpx: {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
spawn: mocks.execa,
|
|
41
|
+
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
42
42
|
},
|
|
43
43
|
}));
|
|
44
44
|
|
|
@@ -51,8 +51,8 @@ vi.mock("../../src/utils/replace-deps", () => ({
|
|
|
51
51
|
vi.mock("@simplysm/core-node", () => ({
|
|
52
52
|
fsx: mocks.fsx,
|
|
53
53
|
cpx: {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
spawn: mocks.execa,
|
|
55
|
+
spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
|
|
56
56
|
},
|
|
57
57
|
}));
|
|
58
58
|
|
|
@@ -493,6 +493,14 @@ describe("executeTypecheck", () => {
|
|
|
493
493
|
|
|
494
494
|
//#endregion
|
|
495
495
|
|
|
496
|
+
it("throws when loadSdConfig fails (fail fast)", async () => {
|
|
497
|
+
mocks.loadSdConfig.mockRejectedValue(new Error("sd.config.ts not found"));
|
|
498
|
+
mocks.discoverWorkspacePackages.mockReturnValue(new Map());
|
|
499
|
+
mocks.mergeTestsPackagesIntoConfig.mockReturnValue({ merged: {}, pathMap: new Map() });
|
|
500
|
+
|
|
501
|
+
await expect(executeTypecheck({ targets: [], options: [] })).rejects.toThrow("sd.config.ts not found");
|
|
502
|
+
});
|
|
503
|
+
|
|
496
504
|
//#region Slice 1: executeTypecheck lint integration (Feature 3.2)
|
|
497
505
|
|
|
498
506
|
describe("lint integration", () => {
|