@simplysm/sd-cli 14.0.63 → 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,203 +0,0 @@
1
- /* eslint-disable no-restricted-properties -- 테스트 환경변수 조작 필요 */
2
- import path from "path";
3
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
4
-
5
- //#region Mocks
6
-
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();
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
- 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
- vi.mock("sharp", () => ({
55
- default: vi.fn().mockReturnValue({
56
- resize: vi.fn().mockReturnThis(),
57
- composite: vi.fn().mockReturnThis(),
58
- png: vi.fn().mockReturnThis(),
59
- toBuffer: vi.fn().mockResolvedValue(new Uint8Array([0])),
60
- toFile: vi.fn().mockResolvedValue(undefined),
61
- }),
62
- }));
63
-
64
- const mockSymlink = vi.fn().mockResolvedValue(undefined);
65
- vi.mock("fs/promises", () => ({
66
- symlink: mockSymlink,
67
- }));
68
-
69
- vi.mock("module", () => ({
70
- createRequire: () => ({
71
- resolve: (id: string) => {
72
- if (id.includes("capacitor-plugin-auto-update")) {
73
- return path.resolve("/fake/workspace/packages/capacitor-plugin-auto-update/package.json");
74
- }
75
- throw new Error(`Cannot find module '${id}'`);
76
- },
77
- }),
78
- }));
79
-
80
- //#endregion
81
-
82
- //#region Helpers
83
-
84
- const PKG_PATH = "/fake/pkg";
85
-
86
- function setupDefaultMocks() {
87
- mockFsxExists.mockImplementation((p: string) => {
88
- const n = p.replace(/\\/g, "/");
89
- if (n.includes(".capacitor.lock")) return false;
90
- return true;
91
- });
92
-
93
- mockFsxReadJson.mockImplementation((p: string) => {
94
- const normalized = p.replace(/\\/g, "/");
95
- if (normalized.includes(".capacitor/package.json")) {
96
- return {
97
- name: "com.test.app",
98
- version: "1.0.0",
99
- dependencies: {
100
- "@capacitor/core": "^7.0.0",
101
- "@capacitor/app": "^7.0.0",
102
- "@capacitor/android": "^7.0.0",
103
- },
104
- devDependencies: {
105
- "@capacitor/cli": "^7.0.0",
106
- "@capacitor/assets": "^3.0.0",
107
- },
108
- };
109
- }
110
- return {
111
- name: "test-pkg",
112
- version: "1.0.0",
113
- dependencies: {
114
- "@simplysm/capacitor-plugin-auto-update": "workspace:*",
115
- "@capacitor/camera": "^7.0.0",
116
- },
117
- };
118
- });
119
-
120
- mockFsxRead.mockImplementation((p: string) => {
121
- if (p.includes("AndroidManifest.xml")) {
122
- return '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n<application>\n<activity android:name=".MainActivity">\n</activity>\n</application>\n</manifest>';
123
- }
124
- if (p.includes("build.gradle")) {
125
- return `android {
126
- defaultConfig {
127
- versionCode 1
128
- versionName "1.0"
129
- minSdkVersion rootProject.ext.minSdkVersion
130
- targetSdkVersion rootProject.ext.targetSdkVersion
131
- }
132
- buildTypes {
133
- release {
134
- }
135
- }
136
- }`;
137
- }
138
- if (p.includes("gradle.properties")) {
139
- return "org.gradle.jvmargs=-Xmx2048m";
140
- }
141
- return "";
142
- });
143
-
144
- mockFsxGlob.mockResolvedValue(["C:/Program Files/Amazon Corretto/jdk21.0.1"]);
145
- process.env["ANDROID_HOME"] = "C:/Android/Sdk";
146
- execaCalls.length = 0;
147
- }
148
-
149
- //#endregion
150
-
151
- let savedEnv: Record<string, string | undefined>;
152
- beforeEach(() => {
153
- savedEnv = { ...process.env };
154
- });
155
- afterEach(() => {
156
- process.env = savedEnv;
157
- });
158
-
159
- describe("workspace:* 플러그인 해석", () => {
160
- beforeEach(() => {
161
- vi.clearAllMocks();
162
- setupDefaultMocks();
163
- });
164
-
165
- it("workspace:* 플러그인이 link: 프로토콜로 package.json에 등록된다", async () => {
166
- const { Capacitor } = await import("../../src/capacitor/capacitor.js");
167
-
168
- const cap = await Capacitor.create(PKG_PATH, {
169
- appId: "com.test.app",
170
- appName: "Test App",
171
- plugins: {
172
- "@simplysm/capacitor-plugin-auto-update": true,
173
- "@capacitor/camera": true,
174
- },
175
- platform: { android: {} },
176
- });
177
-
178
- await cap.initialize();
179
-
180
- const writeJsonCalls = mockFsxWriteJson.mock.calls;
181
- const capPkgWrite = writeJsonCalls.find(
182
- (call) => typeof call[0] === "string" && call[0].includes("package.json"),
183
- );
184
- expect(capPkgWrite).toBeDefined();
185
- const deps = (capPkgWrite![1] as Record<string, unknown>)["dependencies"] as Record<
186
- string,
187
- string
188
- >;
189
-
190
- // 1. workspace:* 플러그인이 link: 프로토콜로 등록되어야 한다
191
- expect(deps["@simplysm/capacitor-plugin-auto-update"]).toMatch(/^link:/);
192
-
193
- // 2. link: 경로는 상대경로여야 한다 (절대경로가 아님)
194
- const linkPath = deps["@simplysm/capacitor-plugin-auto-update"].replace(/^link:/, "");
195
- expect(linkPath).not.toMatch(/^[A-Z]:|^\//i);
196
-
197
- // 3. 일반 npm 플러그인은 버전 문자열로 등록되어야 한다
198
- expect(deps["@capacitor/camera"]).toBe("^7.0.0");
199
-
200
- // 4. 수동 symlink 생성이 호출되지 않아야 한다
201
- expect(mockSymlink).not.toHaveBeenCalled();
202
- });
203
- });
@@ -1,237 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { Capacitor } from "../../src/capacitor/capacitor";
3
- import { Electron } from "../../src/electron/electron";
4
- import * as sdConfigModule from "../../src/utils/sd-config";
5
- import { runDevice } from "../../src/commands/device";
6
-
7
- const mocks = vi.hoisted(() => ({
8
- readFileSync: vi.fn(),
9
- existsSync: vi.fn().mockReturnValue(false),
10
- httpGet: vi.fn(),
11
- }));
12
-
13
- vi.mock("node:fs", () => ({
14
- default: {
15
- readFileSync: (...args: any[]) => mocks.readFileSync(...args),
16
- existsSync: (...args: any[]) => mocks.existsSync(...args),
17
- },
18
- readFileSync: (...args: any[]) => mocks.readFileSync(...args),
19
- existsSync: (...args: any[]) => mocks.existsSync(...args),
20
- }));
21
-
22
- vi.mock("node:http", () => ({
23
- default: {
24
- get: (...args: any[]) => mocks.httpGet(...args),
25
- },
26
- get: (...args: any[]) => mocks.httpGet(...args),
27
- }));
28
-
29
- describe("runDevice", () => {
30
- const mockCapacitorInstance = {
31
- initialize: vi.fn().mockResolvedValue(undefined),
32
- run: vi.fn().mockResolvedValue(undefined),
33
- build: vi.fn().mockResolvedValue(undefined),
34
- };
35
- const mockElectronInstance = {
36
- initialize: vi.fn().mockResolvedValue(undefined),
37
- run: vi.fn().mockResolvedValue(undefined),
38
- build: vi.fn().mockResolvedValue(undefined),
39
- };
40
-
41
- beforeEach(() => {
42
- vi.clearAllMocks();
43
- vi.spyOn(Capacitor, "create").mockResolvedValue(mockCapacitorInstance as any);
44
- vi.spyOn(Electron, "create").mockResolvedValue(mockElectronInstance as any);
45
- vi.spyOn(sdConfigModule, "loadSdConfig");
46
- });
47
-
48
- afterEach(() => {
49
- vi.restoreAllMocks();
50
- });
51
-
52
- // Acceptance: Scenario "device 명령어로 Capacitor 앱 실행"
53
- it("runs Capacitor.create + run when capacitor config exists", async () => {
54
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
55
- packages: {
56
- "client-pda": {
57
- target: "client",
58
- server: 40480,
59
- capacitor: { appId: "com.test.app", appName: "TestApp" },
60
- },
61
- },
62
- });
63
-
64
- await runDevice({ target: "client-pda", options: [] });
65
-
66
- expect(Capacitor.create).toHaveBeenCalledWith(
67
- expect.stringContaining("client-pda"),
68
- { appId: "com.test.app", appName: "TestApp" },
69
- undefined,
70
- );
71
- expect(mockCapacitorInstance.run).toHaveBeenCalledWith("http://localhost:40480/client-pda/");
72
- });
73
-
74
- // Acceptance: Scenario "device 명령어로 Electron 앱 실행"
75
- it("runs Electron.create + run when electron config exists", async () => {
76
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
77
- packages: {
78
- "my-client": {
79
- target: "client",
80
- server: 4200,
81
- electron: { appId: "com.test.electron" },
82
- },
83
- },
84
- });
85
-
86
- await runDevice({ target: "my-client", options: [] });
87
-
88
- expect(Electron.create).toHaveBeenCalledWith(
89
- expect.stringContaining("my-client"),
90
- { appId: "com.test.electron" },
91
- undefined,
92
- );
93
- expect(mockElectronInstance.run).toHaveBeenCalledWith("http://localhost:4200/my-client/");
94
- });
95
-
96
- // Acceptance: Scenario "device 명령어에 URL 옵션 지정"
97
- it("uses provided URL instead of auto-generated one", async () => {
98
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
99
- packages: {
100
- "client-pda": {
101
- target: "client",
102
- server: 40480,
103
- capacitor: { appId: "com.test.app", appName: "TestApp" },
104
- },
105
- },
106
- });
107
-
108
- await runDevice({ target: "client-pda", url: "http://192.168.1.100:4200", options: [] });
109
-
110
- expect(mockCapacitorInstance.run).toHaveBeenCalledWith("http://192.168.1.100:4200");
111
- });
112
-
113
- // Unit: electron이 capacitor보다 우선 (v13 동작)
114
- it("prefers electron over capacitor when both are configured", async () => {
115
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
116
- packages: {
117
- "my-client": {
118
- target: "client",
119
- server: 4200,
120
- capacitor: { appId: "com.test.app", appName: "TestApp" },
121
- electron: { appId: "com.test.electron" },
122
- },
123
- },
124
- });
125
-
126
- await runDevice({ target: "my-client", options: [] });
127
-
128
- expect(Electron.create).toHaveBeenCalled();
129
- expect(Capacitor.create).not.toHaveBeenCalled();
130
- });
131
-
132
- // Unit: 존재하지 않는 패키지 에러
133
- it("throws when package does not exist", async () => {
134
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
135
- packages: {
136
- "other-pkg": { target: "node" },
137
- },
138
- });
139
-
140
- await expect(runDevice({ target: "nonexistent", options: [] })).rejects.toThrow();
141
- });
142
-
143
- // Unit: client가 아닌 패키지 에러
144
- it("throws when package is not a client target", async () => {
145
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
146
- packages: {
147
- "my-server": { target: "server" },
148
- },
149
- });
150
-
151
- await expect(runDevice({ target: "my-server", options: [] })).rejects.toThrow();
152
- });
153
-
154
- // Acceptance: Scenario "server가 string일 때 서버 패키지의 .dev-port에서 포트 읽기"
155
- it("reads .dev-port from server package directory when server is a string", async () => {
156
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
157
- packages: {
158
- "client-devtool": {
159
- target: "client",
160
- server: "my-server",
161
- electron: { appId: "com.test.electron" },
162
- },
163
- },
164
- });
165
-
166
- mocks.readFileSync.mockReturnValue("3000");
167
-
168
- // HTTP 헬스체크 성공 mock
169
- mocks.httpGet.mockImplementation((_url: string, cb: Function) => {
170
- const res = { resume: vi.fn() };
171
- cb(res);
172
- return { on: vi.fn(), setTimeout: vi.fn() };
173
- });
174
-
175
- await runDevice({ target: "client-devtool", options: [] });
176
-
177
- // 서버 패키지 경로의 .dev-port를 읽어야 함
178
- expect(mocks.readFileSync).toHaveBeenCalledWith(
179
- expect.stringContaining("my-server"),
180
- "utf-8",
181
- );
182
- expect(mockElectronInstance.run).toHaveBeenCalledWith(
183
- "http://localhost:3000/client-devtool/",
184
- );
185
- });
186
-
187
- // Acceptance: Scenario "dev 서버 미실행 시 에러"
188
- it("throws when .dev-port file does not exist and server is a string", async () => {
189
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
190
- packages: {
191
- "client-devtool": {
192
- target: "client",
193
- server: "my-server",
194
- electron: { appId: "com.test.electron" },
195
- },
196
- },
197
- });
198
-
199
- mocks.readFileSync.mockImplementation(() => {
200
- throw new Error("ENOENT");
201
- });
202
-
203
- await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
204
- "dev 서버가 실행 중이 아닙니다",
205
- );
206
- });
207
-
208
- // Acceptance: Scenario "stale 포트 파일 존재 시 헬스체크 실패 에러"
209
- it("throws when .dev-port exists but health check fails", async () => {
210
- vi.mocked(sdConfigModule.loadSdConfig).mockResolvedValue({
211
- packages: {
212
- "client-devtool": {
213
- target: "client",
214
- server: "my-server",
215
- electron: { appId: "com.test.electron" },
216
- },
217
- },
218
- });
219
-
220
- mocks.readFileSync.mockReturnValue("5173");
221
-
222
- // HTTP 헬스체크 실패 mock
223
- mocks.httpGet.mockImplementation((_url: string, _cb: Function) => {
224
- const req = {
225
- on: vi.fn((event: string, handler: Function) => {
226
- if (event === "error") handler(new Error("ECONNREFUSED"));
227
- }),
228
- setTimeout: vi.fn(),
229
- };
230
- return req;
231
- });
232
-
233
- await expect(runDevice({ target: "client-devtool", options: [] })).rejects.toThrow(
234
- "dev 서버가 응답하지 않습니다",
235
- );
236
- });
237
- });