@simplysm/sd-cli 14.0.64 → 14.0.65

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.
Files changed (79) hide show
  1. package/dist/capacitor/capacitor-android.d.ts +2 -0
  2. package/dist/capacitor/capacitor-android.d.ts.map +1 -1
  3. package/dist/capacitor/capacitor-android.js +13 -0
  4. package/dist/capacitor/capacitor-android.js.map +1 -1
  5. package/dist/capacitor/capacitor-npm-config.d.ts.map +1 -1
  6. package/dist/capacitor/capacitor-npm-config.js +2 -6
  7. package/dist/capacitor/capacitor-npm-config.js.map +1 -1
  8. package/dist/electron/electron.d.ts.map +1 -1
  9. package/dist/electron/electron.js +1 -2
  10. package/dist/electron/electron.js.map +1 -1
  11. package/package.json +8 -8
  12. package/src/capacitor/capacitor-android.ts +14 -0
  13. package/src/capacitor/capacitor-npm-config.ts +2 -6
  14. package/src/electron/electron.ts +1 -2
  15. package/tests/angular/ngtsc-build-core.acc.spec.ts +36 -94
  16. package/tests/capacitor/capacitor-android.spec.ts +65 -28
  17. package/tests/capacitor/capacitor-build.spec.ts +40 -385
  18. package/tests/capacitor/capacitor-config-writer.acc.spec.ts +3 -17
  19. package/tests/capacitor/capacitor-config-writer.spec.ts +3 -17
  20. package/tests/capacitor/capacitor-init.spec.ts +40 -636
  21. package/tests/capacitor/capacitor-npm-config.acc.spec.ts +38 -168
  22. package/tests/capacitor/capacitor-npm-config.spec.ts +33 -71
  23. package/tests/commands/check.spec.ts +25 -36
  24. package/tests/commands/deployment-phase.acc.spec.ts +17 -26
  25. package/tests/commands/git-phase.acc.spec.ts +13 -112
  26. package/tests/commands/lint.spec.ts +7 -24
  27. package/tests/commands/post-publish-phase.acc.spec.ts +5 -10
  28. package/tests/commands/typecheck.spec.ts +43 -65
  29. package/tests/electron/electron.spec.ts +22 -46
  30. package/tests/engines/base-engine.spec.ts +4 -13
  31. package/tests/engines/engine-selection.spec.ts +14 -17
  32. package/tests/engines/engine-typecheck-selection.acc.spec.ts +13 -16
  33. package/tests/engines/esbuild-client-engine.acc.spec.ts +36 -40
  34. package/tests/engines/esbuild-client-engine.spec.ts +4 -23
  35. package/tests/engines/ngtsc-engine.spec.ts +3 -10
  36. package/tests/engines/server-esbuild-engine.spec.ts +3 -10
  37. package/tests/engines/tsc-engine.spec.ts +3 -10
  38. package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +3 -8
  39. package/tests/esbuild/esbuild-tsc-plugin.spec.ts +3 -8
  40. package/tests/orchestrators/build-orchestrator.spec.ts +57 -102
  41. package/tests/orchestrators/dev-orchestrator.spec.ts +68 -109
  42. package/tests/orchestrators/typecheck-orchestrator.spec.ts +25 -57
  43. package/tests/orchestrators/watch-orchestrator.spec.ts +73 -99
  44. package/tests/sd-cli-entry.spec.ts +17 -20
  45. package/tests/utils/angular-source-file-cache.spec.ts +4 -8
  46. package/tests/utils/copy-src.spec.ts +9 -20
  47. package/tests/utils/esbuild-client-config.acc.spec.ts +9 -15
  48. package/tests/utils/esbuild-client-config.spec.ts +12 -24
  49. package/tests/utils/esbuild-config.spec.ts +51 -42
  50. package/tests/utils/lint-core.spec.ts +13 -19
  51. package/tests/utils/lint-utils.spec.ts +8 -15
  52. package/tests/utils/lint-with-program.spec.ts +3 -7
  53. package/tests/utils/ngtsc-build-core.spec.ts +2 -99
  54. package/tests/utils/orchestrator-utils.spec.ts +7 -20
  55. package/tests/utils/output-utils.spec.ts +5 -11
  56. package/tests/utils/sd-config.spec.ts +4 -12
  57. package/tests/utils/typecheck-env.spec.ts +49 -77
  58. package/tests/utils/typecheck-non-package.spec.ts +23 -16
  59. package/tests/workers/build-watch-paths.acc.spec.ts +4 -10
  60. package/tests/workers/build-watch-paths.spec.ts +4 -9
  61. package/tests/workers/client-worker.acc.spec.ts +64 -137
  62. package/tests/workers/client-worker.spec.ts +63 -89
  63. package/tests/workers/library-build-lint.spec.ts +19 -30
  64. package/tests/workers/library-build-worker.spec.ts +28 -55
  65. package/tests/workers/server-esbuild-context.acc.spec.ts +6 -15
  66. package/tests/workers/server-esbuild-context.spec.ts +7 -16
  67. package/tests/workers/server-runtime-worker.spec.ts +8 -10
  68. package/tests/workers/shared-worker-lifecycle.acc.spec.ts +3 -5
  69. package/tests/workers/shared-worker-lifecycle.spec.ts +4 -5
  70. package/tests/capacitor/capacitor-icon.spec.ts +0 -285
  71. package/tests/capacitor/capacitor-run.spec.ts +0 -256
  72. package/tests/capacitor/capacitor-workspace.spec.ts +0 -203
  73. package/tests/commands/device.spec.ts +0 -237
  74. package/tests/commands/publish.spec.ts +0 -1183
  75. package/tests/utils/external-modules.spec.ts +0 -217
  76. package/tests/workers/server-build-lint.spec.ts +0 -201
  77. package/tests/workers/server-build-worker.spec.ts +0 -765
  78. package/tests/workers/server-watch-manager.acc.spec.ts +0 -162
  79. package/tests/workers/server-watch-manager.spec.ts +0 -199
@@ -1,285 +0,0 @@
1
- /* eslint-disable no-restricted-properties -- 테스트 환경변수 조작 필요 */
2
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
- import { consola } from "consola";
4
-
5
- //#region Mocks
6
-
7
- // fsx mock
8
- const mockFsxExists = vi.fn();
9
- const mockFsxRead = vi.fn();
10
- const mockFsxWrite = vi.fn().mockResolvedValue(undefined);
11
- const mockFsxReadJson = vi.fn();
12
- const mockFsxWriteJson = vi.fn().mockResolvedValue(undefined);
13
- const mockFsxMkdir = vi.fn().mockResolvedValue(undefined);
14
- const mockFsxRm = vi.fn().mockResolvedValue(undefined);
15
- const mockFsxGlob = vi.fn();
16
-
17
- vi.mock("@simplysm/core-node", async (importOriginal) => {
18
- const original = await importOriginal<typeof import("@simplysm/core-node")>();
19
- return {
20
- ...original,
21
- fsx: {
22
- exists: mockFsxExists,
23
- read: mockFsxRead,
24
- write: mockFsxWrite,
25
- readJson: mockFsxReadJson,
26
- writeJson: mockFsxWriteJson,
27
- mkdir: mockFsxMkdir,
28
- rm: mockFsxRm,
29
- glob: mockFsxGlob,
30
- },
31
- cpx: {
32
- spawn: mockCpxSpawn,
33
- spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
34
- },
35
- };
36
- });
37
-
38
- // cpx mock (was execa)
39
- const execaCalls: { command: string; args: string[] }[] = [];
40
- const mockCpxSpawn = vi.fn((...args: unknown[]) => {
41
- execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
42
- return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
43
- });
44
-
45
- const mockFsWriteFile = vi.fn().mockResolvedValue(undefined);
46
- vi.mock("node:fs", () => ({
47
- default: { promises: { writeFile: (...args: unknown[]) => mockFsWriteFile(...args) } },
48
- existsSync: (p: string) => {
49
- if (p.includes("pnpm-workspace.yaml")) return true;
50
- return false;
51
- },
52
- }));
53
-
54
- // sharp mock
55
- const mockSharpToBuffer = vi.fn().mockResolvedValue(new Uint8Array([0]));
56
- const mockSharpToFile = vi.fn().mockResolvedValue(undefined);
57
- const mockSharpInstance = {
58
- resize: vi.fn().mockReturnThis(),
59
- composite: vi.fn().mockReturnThis(),
60
- png: vi.fn().mockReturnThis(),
61
- toBuffer: mockSharpToBuffer,
62
- toFile: mockSharpToFile,
63
- };
64
- const mockSharp = vi.fn().mockReturnValue(mockSharpInstance);
65
- vi.mock("sharp", () => ({ default: mockSharp }));
66
-
67
- // consola mock (logger assertion 필요)
68
- const mockLoggerWarn = vi.fn();
69
- vi.spyOn(consola, "withTag").mockReturnValue({ debug: vi.fn(), warn: mockLoggerWarn, error: vi.fn(), info: vi.fn(), success: vi.fn(), start: vi.fn() } as any);
70
-
71
- //#endregion
72
-
73
- //#region Helpers
74
-
75
- const PKG_PATH = "/fake/pkg";
76
- const _CAP_PATH = "/fake/pkg/.capacitor";
77
-
78
- function setupDefaultMocks() {
79
- // fsx.exists: 모든 것 존재, lock만 없음
80
- mockFsxExists.mockImplementation((p: string) => {
81
- if (p.includes(".capacitor.lock")) return false;
82
- return true;
83
- });
84
-
85
- // fsx.readJson: package.json 응답
86
- mockFsxReadJson.mockImplementation((p: string) => {
87
- const normalized = p.replace(/\\/g, "/");
88
- if (normalized.includes(".capacitor/package.json")) {
89
- return {
90
- name: "com.test.app",
91
- version: "1.0.0",
92
- dependencies: {
93
- "@capacitor/core": "^7.0.0",
94
- "@capacitor/app": "^7.0.0",
95
- "@capacitor/android": "^7.0.0",
96
- },
97
- devDependencies: {
98
- "@capacitor/cli": "^7.0.0",
99
- "@capacitor/assets": "^3.0.0",
100
- },
101
- };
102
- }
103
- return { name: "test-pkg", version: "1.0.0" };
104
- });
105
-
106
- // fsx.read: Android 설정 파일
107
- mockFsxRead.mockImplementation((p: string) => {
108
- if (p.includes("AndroidManifest.xml")) {
109
- return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n</application>\n</manifest>';
110
- }
111
- if (p.includes("build.gradle")) {
112
- return `android {
113
- defaultConfig {
114
- versionCode 1
115
- versionName "1.0"
116
- minSdkVersion rootProject.ext.minSdkVersion
117
- targetSdkVersion rootProject.ext.targetSdkVersion
118
- }
119
- buildTypes {
120
- release {
121
- }
122
- }
123
- }`;
124
- }
125
- if (p.includes("gradle.properties")) {
126
- return "org.gradle.jvmargs=-Xmx2048m";
127
- }
128
- return "";
129
- });
130
-
131
- // fsx.glob: Java 21 경로
132
- mockFsxGlob.mockResolvedValue(["C:/Program Files/Amazon Corretto/jdk21.0.1"]);
133
-
134
- // env: Android SDK
135
- process.env["ANDROID_HOME"] = "C:/Android/Sdk";
136
-
137
- // execa 호출 기록 초기화
138
- execaCalls.length = 0;
139
- }
140
-
141
- //#endregion
142
-
143
- let savedEnv: Record<string, string | undefined>;
144
- beforeEach(() => {
145
- savedEnv = { ...process.env };
146
- });
147
- afterEach(() => {
148
- process.env = savedEnv;
149
- });
150
-
151
- describe("Capacitor 아이콘 처리", () => {
152
- beforeEach(() => {
153
- vi.clearAllMocks();
154
- setupDefaultMocks();
155
- });
156
-
157
- describe("인수 테스트", () => {
158
- it("icon 경로가 지정되어 있으면 initialize 시 아이콘을 생성한다", async () => {
159
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
160
-
161
- const cap = await Capacitor.create(PKG_PATH, {
162
- appId: "com.test.app",
163
- appName: "Test App",
164
- icon: "assets/icon.png",
165
- platform: { android: {} },
166
- });
167
-
168
- await cap.initialize();
169
-
170
- // capacitor-assets generate가 실행되었는지 확인
171
- const assetsCmd = execaCalls.find(
172
- (c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
173
- );
174
- if (assetsCmd == null) {
175
- throw new Error("capacitor-assets generate 명령이 실행되지 않았습니다.");
176
- }
177
- const iconBackgroundColorArgIndex = assetsCmd.args.indexOf("--iconBackgroundColor");
178
- expect(iconBackgroundColorArgIndex).not.toBe(-1);
179
- expect(assetsCmd.args[iconBackgroundColorArgIndex + 1]).toBe("#ffffff");
180
-
181
- // sharp가 소스 이미지를 처리했는지 확인
182
- expect(mockSharp).toHaveBeenCalled();
183
- });
184
-
185
- it("icon이 미지정이면 기본 아이콘을 유지한다", async () => {
186
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
187
-
188
- const cap = await Capacitor.create(PKG_PATH, {
189
- appId: "com.test.app",
190
- appName: "Test App",
191
- platform: { android: {} },
192
- });
193
-
194
- await cap.initialize();
195
-
196
- // sharp가 호출되지 않아야 한다
197
- expect(mockSharp).not.toHaveBeenCalled();
198
-
199
- // capacitor-assets generate가 실행되지 않아야 한다
200
- const assetsCmd = execaCalls.find(
201
- (c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
202
- );
203
- expect(assetsCmd).toBeUndefined();
204
- });
205
-
206
- it("icon 경로의 파일이 없으면 경고 후 기본 아이콘을 사용한다", async () => {
207
- // icon 파일만 존재하지 않도록 설정
208
- mockFsxExists.mockImplementation((p: string) => {
209
- if (p.includes(".capacitor.lock")) return false;
210
- if (p.includes("icon.png")) return false;
211
- return true;
212
- });
213
-
214
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
215
-
216
- const cap = await Capacitor.create(PKG_PATH, {
217
- appId: "com.test.app",
218
- appName: "Test App",
219
- icon: "assets/icon.png",
220
- platform: { android: {} },
221
- });
222
-
223
- await cap.initialize();
224
-
225
- // 경고가 출력되었는지 확인
226
- expect(mockLoggerWarn).toHaveBeenCalledWith(
227
- expect.stringContaining("icon"),
228
- );
229
-
230
- // sharp가 호출되지 않아야 한다
231
- expect(mockSharp).not.toHaveBeenCalled();
232
- });
233
- });
234
-
235
- describe("단위", () => {
236
- it("sharp로 소스 이미지를 리사이즈하고 1024x1024 캔버스에 합성한다", async () => {
237
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
238
-
239
- const cap = await Capacitor.create(PKG_PATH, {
240
- appId: "com.test.app",
241
- appName: "Test App",
242
- icon: "assets/icon.png",
243
- platform: { android: {} },
244
- });
245
-
246
- await cap.initialize();
247
-
248
- // sharp가 소스 이미지 경로로 호출되었는지 확인
249
- const sourceCall = mockSharp.mock.calls.find(
250
- (call) => typeof call[0] === "string" && call[0].includes("icon.png"),
251
- );
252
- expect(sourceCall).toBeDefined();
253
-
254
- // resize가 호출되었는지 확인
255
- expect(mockSharpInstance.resize).toHaveBeenCalled();
256
-
257
- // composite가 호출되었는지 확인 (캔버스 합성)
258
- expect(mockSharpInstance.composite).toHaveBeenCalled();
259
-
260
- // toFile로 logo.png에 저장되었는지 확인
261
- const toFileCalls = mockSharpToFile.mock.calls;
262
- const logoCall = toFileCalls.find(
263
- (call) => typeof call[0] === "string" && call[0].includes("logo.png"),
264
- );
265
- expect(logoCall).toBeDefined();
266
- });
267
-
268
- it("capacitor-assets generate 명령에 흰색 아이콘 배경색을 전달한다", async () => {
269
- const { setupIcon } = await import("../../src/capacitor/capacitor-icon.js");
270
-
271
- await setupIcon(PKG_PATH, _CAP_PATH, "assets/icon.png");
272
-
273
- const assetsCmd = execaCalls.find(
274
- (c) => c.command === "pnpm" && c.args.includes("capacitor-assets"),
275
- );
276
- if (assetsCmd == null) {
277
- throw new Error("capacitor-assets generate 명령이 실행되지 않았습니다.");
278
- }
279
-
280
- const iconBackgroundColorArgIndex = assetsCmd.args.indexOf("--iconBackgroundColor");
281
- expect(iconBackgroundColorArgIndex).not.toBe(-1);
282
- expect(assetsCmd.args[iconBackgroundColorArgIndex + 1]).toBe("#ffffff");
283
- });
284
- });
285
- });
@@ -1,256 +0,0 @@
1
- /* eslint-disable no-restricted-properties -- 테스트 환경변수 조작 필요 */
2
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
-
4
- //#region Mocks
5
-
6
- // fsx mock
7
- const mockFsxExists = vi.fn();
8
- const mockFsxRead = vi.fn();
9
- const mockFsxWrite = vi.fn().mockResolvedValue(undefined);
10
- const mockFsxReadJson = vi.fn();
11
- const mockFsxWriteJson = vi.fn().mockResolvedValue(undefined);
12
- const mockFsxMkdir = vi.fn().mockResolvedValue(undefined);
13
- const mockFsxRm = vi.fn().mockResolvedValue(undefined);
14
- const mockFsxGlob = vi.fn().mockResolvedValue([]);
15
- const mockFsxCopy = vi.fn().mockResolvedValue(undefined);
16
-
17
- vi.mock("@simplysm/core-node", async (importOriginal) => {
18
- const original = await importOriginal<typeof import("@simplysm/core-node")>();
19
- return {
20
- ...original,
21
- fsx: {
22
- exists: mockFsxExists,
23
- read: mockFsxRead,
24
- write: mockFsxWrite,
25
- readJson: mockFsxReadJson,
26
- writeJson: mockFsxWriteJson,
27
- mkdir: mockFsxMkdir,
28
- rm: mockFsxRm,
29
- glob: mockFsxGlob,
30
- copy: mockFsxCopy,
31
- },
32
- cpx: {
33
- spawn: mockCpxSpawn,
34
- spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
35
- },
36
- };
37
- });
38
-
39
- // cpx mock (was execa) — tracks commands and resolves immediately
40
- const execaCalls: { command: string; args: string[] }[] = [];
41
- let execaFactory: (...args: unknown[]) => Promise<{ stdout: string; stderr: string; exitCode: number }> = () =>
42
- Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
43
-
44
- const mockCpxSpawn = vi.fn((...args: unknown[]) => {
45
- execaCalls.push({ command: args[0] as string, args: (args[1] as string[] | undefined) ?? [] });
46
- return execaFactory(...args);
47
- });
48
-
49
- const mockFsWriteFile = vi.fn().mockResolvedValue(undefined);
50
- vi.mock("node:fs", () => ({
51
- default: { promises: { writeFile: (...args: unknown[]) => mockFsWriteFile(...args) } },
52
- existsSync: (p: string) => {
53
- if (p.includes("pnpm-workspace.yaml")) return true;
54
- return false;
55
- },
56
- }));
57
-
58
- // sharp mock
59
- vi.mock("sharp", () => ({
60
- default: vi.fn().mockReturnValue({
61
- resize: vi.fn().mockReturnThis(),
62
- composite: vi.fn().mockReturnThis(),
63
- png: vi.fn().mockReturnThis(),
64
- toBuffer: vi.fn().mockResolvedValue(new Uint8Array([0])),
65
- toFile: vi.fn().mockResolvedValue(undefined),
66
- }),
67
- }));
68
-
69
-
70
- //#endregion
71
-
72
- //#region Helpers
73
-
74
- const PKG_PATH = "/fake/pkg";
75
-
76
- function setupDefaultMocks() {
77
- mockFsxExists.mockResolvedValue(true);
78
-
79
- mockFsxReadJson.mockImplementation((p: string) => {
80
- const normalized = p.replace(/\\/g, "/");
81
- if (normalized.includes(".capacitor/package.json")) {
82
- return {
83
- name: "com.test.app",
84
- version: "1.2.3",
85
- dependencies: {
86
- "@capacitor/core": "^7.0.0",
87
- "@capacitor/app": "^7.0.0",
88
- "@capacitor/android": "^7.0.0",
89
- },
90
- devDependencies: {
91
- "@capacitor/cli": "^7.0.0",
92
- "@capacitor/assets": "^3.0.0",
93
- },
94
- };
95
- }
96
- return { name: "test-pkg", version: "1.2.3" };
97
- });
98
-
99
- mockFsxRead.mockImplementation((p: string) => {
100
- const normalized = p.replace(/\\/g, "/");
101
- if (normalized.includes("capacitor.config.ts")) {
102
- return `import type { CapacitorConfig } from "@capacitor/cli";
103
- const config: CapacitorConfig = {
104
- appId: "com.test.app",
105
- appName: "Test App",
106
- server: {
107
- url: "http://old-url:3000",
108
- },
109
- };
110
- export default config;`;
111
- }
112
- return "";
113
- });
114
-
115
- mockFsxGlob.mockImplementation((pattern: string) => {
116
- if (pattern.includes("Corretto") || pattern.includes("jdk")) {
117
- return ["C:/Program Files/Amazon Corretto/jdk21.0.1"];
118
- }
119
- return [];
120
- });
121
-
122
- process.env["ANDROID_HOME"] = "C:/Android/Sdk";
123
-
124
- execaCalls.length = 0;
125
- execaFactory = () => Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
126
- mockFsWriteFile.mockReset();
127
- mockFsWriteFile.mockResolvedValue(undefined);
128
- }
129
-
130
- //#endregion
131
-
132
- let savedEnv: Record<string, string | undefined>;
133
- beforeEach(() => {
134
- savedEnv = { ...process.env };
135
- });
136
- afterEach(() => {
137
- process.env = savedEnv;
138
- });
139
-
140
- describe("Capacitor.run()", () => {
141
- beforeEach(() => {
142
- vi.clearAllMocks();
143
- setupDefaultMocks();
144
- });
145
-
146
- // Acceptance: Scenario "Capacitor 디바이스 실행 (run 메서드 단위 테스트)"
147
- it("updates server URL, runs cap copy and cap run for android", async () => {
148
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
149
-
150
- const cap = await Capacitor.create(PKG_PATH, {
151
- appId: "com.test.app",
152
- appName: "Test App",
153
- platform: { android: {} },
154
- });
155
-
156
- await cap.run("http://localhost:4200");
157
-
158
- // _updateServerUrl should have written the new URL
159
- expect(mockFsxWrite).toHaveBeenCalledWith(
160
- expect.stringContaining("capacitor.config.ts"),
161
- expect.stringContaining("http://localhost:4200"),
162
- );
163
-
164
- // cap copy android + cap run android
165
- const capCmds = execaCalls.filter(
166
- (c) => c.command === "pnpm" && c.args.includes("cap"),
167
- );
168
- expect(capCmds.some((c) => c.args.includes("copy") && c.args.includes("android"))).toBe(true);
169
- expect(capCmds.some((c) => c.args.includes("run") && c.args.includes("android"))).toBe(true);
170
- });
171
-
172
- // Unit: cap run 실패 시 adb kill-server 호출 후 에러를 re-throw한다
173
- it("calls adb kill-server and re-throws on android platform cap run failure", async () => {
174
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
175
-
176
- execaFactory = (...args: unknown[]) => {
177
- const cmd = args[0] as string;
178
- const cmdArgs = (args[1] as string[] | undefined) ?? [];
179
-
180
- if (
181
- cmd === "pnpm" &&
182
- cmdArgs.includes("cap") &&
183
- cmdArgs.includes("run") &&
184
- cmdArgs.includes("android")
185
- ) {
186
- return Promise.reject(new Error("cap run failed"));
187
- }
188
- return Promise.resolve({ stdout: "", stderr: "", exitCode: 0 });
189
- };
190
-
191
- const cap = await Capacitor.create(PKG_PATH, {
192
- appId: "com.test.app",
193
- appName: "Test App",
194
- platform: { android: {} },
195
- });
196
-
197
- await expect(cap.run("http://localhost:4200")).rejects.toThrow("cap run failed");
198
-
199
- // adb kill-server should have been called
200
- expect(
201
- execaCalls.some((c) => c.command === "adb" && c.args.includes("kill-server")),
202
- ).toBe(true);
203
- });
204
-
205
- // Unit: _updateServerUrl replaces existing url
206
- it("replaces existing url in capacitor.config.ts", async () => {
207
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
208
-
209
- const cap = await Capacitor.create(PKG_PATH, {
210
- appId: "com.test.app",
211
- appName: "Test App",
212
- platform: { android: {} },
213
- });
214
-
215
- await cap.run("http://localhost:5555");
216
-
217
- const writeCall = mockFsxWrite.mock.calls.find(
218
- (c: string[]) => c[0].replace(/\\/g, "/").includes("capacitor.config.ts"),
219
- );
220
- expect(writeCall).toBeDefined();
221
- const content = writeCall![1] as string;
222
- expect(content).toContain('url: "http://localhost:5555"');
223
- expect(content).not.toContain("http://old-url:3000");
224
- });
225
-
226
- // Unit: _updateServerUrl inserts url when server block exists but no url field
227
- it("inserts url when server block has no url field", async () => {
228
- mockFsxRead.mockImplementation((p: string) => {
229
- if (p.replace(/\\/g, "/").includes("capacitor.config.ts")) {
230
- return `const config = {
231
- server: {
232
- hostname: "localhost",
233
- },
234
- };`;
235
- }
236
- return "";
237
- });
238
-
239
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
240
-
241
- const cap = await Capacitor.create(PKG_PATH, {
242
- appId: "com.test.app",
243
- appName: "Test App",
244
- platform: { android: {} },
245
- });
246
-
247
- await cap.run("http://localhost:4200");
248
-
249
- const writeCall = mockFsxWrite.mock.calls.find(
250
- (c: string[]) => c[0].replace(/\\/g, "/").includes("capacitor.config.ts"),
251
- );
252
- expect(writeCall).toBeDefined();
253
- const content = writeCall![1] as string;
254
- expect(content).toContain('url: "http://localhost:4200"');
255
- });
256
- });