@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,18 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { cpx } from "@simplysm/core-node";
2
3
 
3
- const mocks = vi.hoisted(() => ({
4
- execa: vi.fn(),
5
- }));
4
+ const mocks = {
5
+ execa: vi.spyOn(cpx, "spawn"),
6
+ };
6
7
 
7
- vi.mock("@simplysm/core-node", () => ({
8
- cpx: {
9
- spawn: mocks.execa,
10
- },
11
- }));
12
-
13
- const { ensureCleanWorkingTree, commitTagAndPush } = await import(
14
- "../../src/commands/publish/git-phase"
15
- );
8
+ import { ensureCleanWorkingTree, commitTagAndPush } from "../../src/commands/publish/git-phase";
16
9
 
17
10
  function createLogger() {
18
11
  return {
@@ -29,65 +22,23 @@ describe("ensureCleanWorkingTree", () => {
29
22
  vi.clearAllMocks();
30
23
  });
31
24
 
32
- it("auto-commits with codex when uncommitted changes detected", async () => {
33
- const logger = createLogger();
34
- mocks.execa.mockImplementation((cmd: string, args?: string[]) => {
35
- if (cmd === "git" && args?.[0] === "diff") {
36
- return { stdout: "file.txt", stderr: "", exitCode: 0 };
37
- }
38
- return { stdout: "", stderr: "", exitCode: 0 };
39
- });
40
-
41
- await ensureCleanWorkingTree(true, logger);
42
-
43
- const codexCalls = mocks.execa.mock.calls.filter(
44
- (c: unknown[]) => c[0] === "codex",
45
- );
46
- expect(codexCalls).toHaveLength(1);
47
- expect((codexCalls[0][1] as string[])).toContain("exec");
48
- expect((codexCalls[0][1] as string[])).toContain("gpt-5.3-codex-spark");
49
- expect((codexCalls[0][1] as string[])).toContain('model_reasoning_effort="low"');
50
- expect((codexCalls[0][1] as string[]).some((arg) => arg.includes("$sd-commit"))).toBe(true);
51
- });
52
-
53
- it("skips auto-commit when no uncommitted changes", async () => {
25
+ it("throws when auto-commit fails", async () => {
54
26
  const logger = createLogger();
55
- mocks.execa.mockImplementation(() => {
56
- return { stdout: "", stderr: "", exitCode: 0 };
57
- });
58
-
59
- await ensureCleanWorkingTree(true, logger);
60
-
61
- const codexCalls = mocks.execa.mock.calls.filter(
62
- (c: unknown[]) => c[0] === "codex",
63
- );
64
- expect(codexCalls).toHaveLength(0);
65
- });
66
-
67
- it("throws when codex auto-commit fails", async () => {
68
- const logger = createLogger();
69
- mocks.execa.mockImplementation((cmd: string, args?: string[]) => {
27
+ mocks.execa.mockImplementation(((cmd: string, args?: string[]) => {
70
28
  if (cmd === "git" && args?.[0] === "diff") {
71
29
  return { stdout: "file.txt", stderr: "", exitCode: 0 };
72
30
  }
73
- if (cmd === "codex") {
74
- throw new Error("codex commit failed");
31
+ // 자동 커밋 명령 실패 시뮬레이션
32
+ if (cmd !== "git") {
33
+ throw new Error("auto-commit failed");
75
34
  }
76
35
  return { stdout: "", stderr: "", exitCode: 0 };
77
- });
36
+ }) as never);
78
37
 
79
38
  await expect(ensureCleanWorkingTree(true, logger)).rejects.toThrow(
80
39
  "자동 커밋에 실패했습니다",
81
40
  );
82
41
  });
83
-
84
- it("does nothing when hasGit is false", async () => {
85
- const logger = createLogger();
86
-
87
- await ensureCleanWorkingTree(false, logger);
88
-
89
- expect(mocks.execa).not.toHaveBeenCalled();
90
- });
91
42
  });
92
43
 
93
44
  describe("commitTagAndPush", () => {
@@ -95,67 +46,17 @@ describe("commitTagAndPush", () => {
95
46
  vi.clearAllMocks();
96
47
  });
97
48
 
98
- it("commits, tags, and pushes version changes", async () => {
99
- const logger = createLogger();
100
- mocks.execa.mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 });
101
-
102
- await commitTagAndPush(true, "14.0.1", ["package.json"], logger, false);
103
-
104
- const gitCalls = mocks.execa.mock.calls.filter(
105
- (c: unknown[]) => c[0] === "git",
106
- );
107
-
108
- // git add
109
- const addCall = gitCalls.find((c: unknown[]) => (c[1] as string[])[0] === "add");
110
- expect(addCall).toBeDefined();
111
- expect((addCall![1] as string[])).toContain("package.json");
112
-
113
- // git commit
114
- const commitCall = gitCalls.find((c: unknown[]) => (c[1] as string[])[0] === "commit");
115
- expect(commitCall).toBeDefined();
116
- expect((commitCall![1] as string[])).toContain("v14.0.1");
117
-
118
- // git tag
119
- const tagCall = gitCalls.find((c: unknown[]) => (c[1] as string[])[0] === "tag");
120
- expect(tagCall).toBeDefined();
121
- expect((tagCall![1] as string[])).toContain("-a");
122
- expect((tagCall![1] as string[])).toContain("v14.0.1");
123
-
124
- // git push (2 calls: push + push --tags)
125
- const pushCalls = gitCalls.filter((c: unknown[]) => (c[1] as string[])[0] === "push");
126
- expect(pushCalls).toHaveLength(2);
127
- });
128
-
129
- it("outputs simulation logs in dry-run mode without executing git", async () => {
130
- const logger = createLogger();
131
-
132
- await commitTagAndPush(true, "14.0.1", ["package.json"], logger, true);
133
-
134
- // No actual git commands
135
- expect(mocks.execa).not.toHaveBeenCalled();
136
- // Simulation logs
137
- expect(logger.info).toHaveBeenCalled();
138
- });
139
-
140
49
  it("throws with recovery guide when git commit fails", async () => {
141
50
  const logger = createLogger();
142
- mocks.execa.mockImplementation((_cmd: string, args?: string[]) => {
51
+ mocks.execa.mockImplementation(((_cmd: string, args?: string[]) => {
143
52
  if (args?.[0] === "commit") {
144
53
  throw new Error("git commit failed");
145
54
  }
146
55
  return { stdout: "", stderr: "", exitCode: 0 };
147
- });
56
+ }) as never);
148
57
 
149
58
  await expect(
150
59
  commitTagAndPush(true, "14.0.1", ["package.json"], logger, false),
151
60
  ).rejects.toThrow("Git 작업 실패");
152
61
  });
153
-
154
- it("does nothing when hasGit is false", async () => {
155
- const logger = createLogger();
156
-
157
- await commitTagAndPush(false, "14.0.1", ["package.json"], logger, false);
158
-
159
- expect(mocks.execa).not.toHaveBeenCalled();
160
- });
161
62
  });
@@ -1,33 +1,17 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
-
3
- //#region Mocks
4
-
5
- const mocks = vi.hoisted(() => ({
6
- executeLint: vi.fn(),
7
- }));
8
-
9
- vi.mock("../../src/lint/lint-core", () => ({
10
- executeLint: mocks.executeLint,
11
- }));
12
-
13
- const { runLint } = await import("../../src/commands/lint");
14
-
15
- //#endregion
16
-
17
- //#region runLint
2
+ import * as lintCore from "../../src/lint/lint-core";
3
+ import { runLint } from "../../src/commands/lint";
18
4
 
19
5
  describe("runLint", () => {
20
6
  let savedExitCode: typeof process.exitCode;
21
7
  let writeSpy: ReturnType<typeof vi.spyOn>;
8
+ let executeLintSpy: ReturnType<typeof vi.spyOn>;
22
9
 
23
10
  beforeEach(() => {
24
- vi.clearAllMocks();
25
11
  savedExitCode = process.exitCode;
26
12
  process.exitCode = undefined;
27
13
  writeSpy = vi.spyOn(process.stdout, "write").mockReturnValue(true);
28
-
29
- // Default: successful lint
30
- mocks.executeLint.mockResolvedValue({
14
+ executeLintSpy = vi.spyOn(lintCore, "executeLint").mockResolvedValue({
31
15
  success: true,
32
16
  errorCount: 0,
33
17
  warningCount: 0,
@@ -38,10 +22,11 @@ describe("runLint", () => {
38
22
  afterEach(() => {
39
23
  process.exitCode = savedExitCode;
40
24
  writeSpy.mockRestore();
25
+ executeLintSpy.mockRestore();
41
26
  });
42
27
 
43
28
  it("writes formatted output to stdout when there are results", async () => {
44
- mocks.executeLint.mockResolvedValue({
29
+ executeLintSpy.mockResolvedValue({
45
30
  success: false,
46
31
  errorCount: 1,
47
32
  warningCount: 0,
@@ -54,7 +39,7 @@ describe("runLint", () => {
54
39
  });
55
40
 
56
41
  it("sets exitCode to 1 when lint errors are found", async () => {
57
- mocks.executeLint.mockResolvedValue({
42
+ executeLintSpy.mockResolvedValue({
58
43
  success: false,
59
44
  errorCount: 1,
60
45
  warningCount: 0,
@@ -78,5 +63,3 @@ describe("runLint", () => {
78
63
  expect(writeSpy).not.toHaveBeenCalled();
79
64
  });
80
65
  });
81
-
82
- //#endregion
@@ -1,16 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { cpx } from "@simplysm/core-node";
2
3
 
3
- const mocks = vi.hoisted(() => ({
4
- execa: vi.fn(),
5
- }));
4
+ const mocks = {
5
+ execa: vi.spyOn(cpx, "spawn"),
6
+ };
6
7
 
7
- vi.mock("@simplysm/core-node", () => ({
8
- cpx: {
9
- spawn: mocks.execa,
10
- },
11
- }));
12
-
13
- const { runPostPublish } = await import("../../src/commands/publish/post-publish-phase");
8
+ import { runPostPublish } from "../../src/commands/publish/post-publish-phase";
14
9
 
15
10
  function createLogger() {
16
11
  return {
@@ -1,60 +1,28 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { consola } from "consola";
3
3
 
4
- const mocks = vi.hoisted(() => ({
5
- loadSdConfig: vi.fn(),
6
- deserializeDiagnostic: vi.fn((d: any) => d),
7
- typecheckNonPackageFiles: vi.fn(),
8
- createTypecheckEngine: vi.fn(),
9
- discoverWorkspacePackages: vi.fn(),
10
- mergeTestsPackagesIntoConfig: vi.fn(),
11
- }));
4
+ import * as sdConfigMod from "../../src/utils/sd-config";
5
+ import * as typecheckSerialization from "../../src/typecheck/typecheck-serialization";
6
+ import * as typecheckNonPackage from "../../src/typecheck/typecheck-non-package";
7
+ import * as engineFactory from "../../src/engines/engine-factory";
8
+ import * as packageUtils from "../../src/utils/package-utils";
9
+
10
+ import { executeTypecheck } from "../../src/commands/typecheck";
11
+
12
+ const mocks = {
13
+ loadSdConfig: undefined as unknown as ReturnType<typeof vi.spyOn>,
14
+ deserializeDiagnostic: undefined as unknown as ReturnType<typeof vi.spyOn>,
15
+ typecheckNonPackageFiles: undefined as unknown as ReturnType<typeof vi.spyOn>,
16
+ createTypecheckEngine: undefined as unknown as ReturnType<typeof vi.spyOn>,
17
+ discoverWorkspacePackages: undefined as unknown as ReturnType<typeof vi.spyOn>,
18
+ mergeTestsPackagesIntoConfig: undefined as unknown as ReturnType<typeof vi.spyOn>,
19
+ };
12
20
 
13
21
  const mockEngines: Array<{
14
22
  run: ReturnType<typeof vi.fn>;
15
23
  stop: ReturnType<typeof vi.fn>;
16
24
  }> = [];
17
25
 
18
- vi.mock("../../src/utils/sd-config", () => ({
19
- loadSdConfig: mocks.loadSdConfig,
20
- }));
21
-
22
- vi.mock("../../src/typecheck/typecheck-serialization", () => ({
23
- deserializeDiagnostic: mocks.deserializeDiagnostic,
24
- }));
25
-
26
- vi.mock("../../src/typecheck/typecheck-non-package", () => ({
27
- typecheckNonPackageFiles: mocks.typecheckNonPackageFiles,
28
- }));
29
-
30
- vi.mock("../../src/engines/engine-factory", () => ({
31
- createTypecheckEngine: mocks.createTypecheckEngine,
32
- }));
33
-
34
- vi.mock("../../src/utils/package-utils", async (importOriginal) => {
35
- const actual = await importOriginal<typeof import("../../src/utils/package-utils")>();
36
- return {
37
- ...actual,
38
- discoverWorkspacePackages: mocks.discoverWorkspacePackages,
39
- mergeTestsPackagesIntoConfig: mocks.mergeTestsPackagesIntoConfig,
40
- };
41
- });
42
-
43
- vi.mock("typescript", async (importOriginal) => {
44
- const orig = await importOriginal<Record<string, unknown>>();
45
- const origDefault = (orig["default"] ?? orig) as Record<string, unknown>;
46
- return {
47
- ...orig,
48
- default: {
49
- ...origDefault,
50
- sortAndDeduplicateDiagnostics: vi.fn((d: unknown[]) => d),
51
- formatDiagnosticsWithColorAndContext: vi.fn((diags: Array<{ messageText: string }>) =>
52
- diags.map((d) => `formatted: ${d.messageText}`).join("\n"),
53
- ),
54
- },
55
- };
56
- });
57
-
58
26
  const mockTypecheckLogger = {
59
27
  debug: vi.fn(),
60
28
  start: vi.fn(),
@@ -66,22 +34,6 @@ const mockTypecheckLogger = {
66
34
  withTag: vi.fn(),
67
35
  };
68
36
 
69
- vi.spyOn(consola, "withTag").mockImplementation((tag: string) => {
70
- if (tag === "sd:cli:typecheck") return mockTypecheckLogger as any;
71
- return {
72
- debug: vi.fn(),
73
- start: vi.fn(),
74
- success: vi.fn(),
75
- info: vi.fn(),
76
- error: vi.fn(),
77
- warn: vi.fn(),
78
- log: vi.fn(),
79
- withTag: vi.fn(),
80
- } as any;
81
- });
82
-
83
- const { executeTypecheck } = await import("../../src/commands/typecheck");
84
-
85
37
  function createMockEngine() {
86
38
  const engine = {
87
39
  run: vi.fn().mockResolvedValue({
@@ -118,8 +70,34 @@ function setupDefaults(packages: Record<string, any> = {}) {
118
70
  }
119
71
 
120
72
  beforeEach(() => {
121
- vi.clearAllMocks();
73
+ vi.restoreAllMocks();
122
74
  mockEngines.length = 0;
75
+
76
+ Object.values(mockTypecheckLogger).forEach((m) => {
77
+ if (typeof m === "function" && "mockReset" in m) (m as any).mockReset();
78
+ });
79
+
80
+ vi.spyOn(consola, "withTag").mockImplementation((tag: string) => {
81
+ if (tag === "sd:cli:typecheck") return mockTypecheckLogger as any;
82
+ return {
83
+ debug: vi.fn(),
84
+ start: vi.fn(),
85
+ success: vi.fn(),
86
+ info: vi.fn(),
87
+ error: vi.fn(),
88
+ warn: vi.fn(),
89
+ log: vi.fn(),
90
+ withTag: vi.fn(),
91
+ } as any;
92
+ });
93
+
94
+ mocks.loadSdConfig = vi.spyOn(sdConfigMod, "loadSdConfig");
95
+ mocks.deserializeDiagnostic = vi.spyOn(typecheckSerialization, "deserializeDiagnostic")
96
+ .mockImplementation((d: any) => d);
97
+ mocks.typecheckNonPackageFiles = vi.spyOn(typecheckNonPackage, "typecheckNonPackageFiles");
98
+ mocks.createTypecheckEngine = vi.spyOn(engineFactory, "createTypecheckEngine");
99
+ mocks.discoverWorkspacePackages = vi.spyOn(packageUtils, "discoverWorkspacePackages");
100
+ mocks.mergeTestsPackagesIntoConfig = vi.spyOn(packageUtils, "mergeTestsPackagesIntoConfig");
123
101
  });
124
102
 
125
103
  describe("executeTypecheck", () => {
@@ -1,48 +1,12 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { consola } from "consola";
3
+ import { fsx, cpx } from "@simplysm/core-node";
3
4
 
4
- //#region Mocks
5
-
6
- // fsx mock
7
- const mockFsxExists = vi.fn();
8
- const mockFsxReadJson = vi.fn();
9
- const mockFsxWriteJson = vi.fn().mockResolvedValue(undefined);
10
- const mockFsxWrite = vi.fn().mockResolvedValue(undefined);
11
- const mockFsxMkdir = vi.fn().mockResolvedValue(undefined);
12
- const mockFsxCopy = vi.fn().mockResolvedValue(undefined);
13
- const mockFsxReaddir = vi.fn();
14
- const mockFsxGlob = vi.fn();
15
-
16
- vi.mock("@simplysm/core-node", async (importOriginal) => {
17
- const original = await importOriginal<typeof import("@simplysm/core-node")>();
18
- return {
19
- ...original,
20
- fsx: {
21
- exists: mockFsxExists,
22
- readJson: mockFsxReadJson,
23
- writeJson: mockFsxWriteJson,
24
- write: mockFsxWrite,
25
- mkdir: mockFsxMkdir,
26
- copy: mockFsxCopy,
27
- readdir: mockFsxReaddir,
28
- glob: mockFsxGlob,
29
- },
30
- cpx: {
31
- spawn: mockCpxSpawn,
32
- spawnSync: vi.fn().mockReturnValue({ stdout: "", stderr: "", exitCode: 0 }),
33
- },
34
- };
35
- });
36
-
37
- // cpx mock (was execa)
38
- const mockCpxSpawn = vi.fn().mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 });
39
-
40
- // esbuild mock
5
+ // esbuild는 외부 npm으로 ESM namespace immutable이라 vi.mock 유지
41
6
  const mockEsbuildBuild = vi.fn().mockResolvedValue({});
42
7
  let mockEsbuildOnEndCallback: ((result: { errors: unknown[] }) => void | Promise<void>) | null =
43
8
  null;
44
9
  const mockEsbuildContext = vi.fn().mockImplementation((options: any) => {
45
- // Extract the electron-restart plugin's onEnd callback
46
10
  const plugin = options?.plugins?.find((p: any) => p.name === "electron-restart");
47
11
  if (plugin != null) {
48
12
  plugin.setup({
@@ -53,7 +17,6 @@ const mockEsbuildContext = vi.fn().mockImplementation((options: any) => {
53
17
  }
54
18
  return {
55
19
  watch: vi.fn().mockImplementation(async () => {
56
- // Simulate initial build success — trigger onEnd
57
20
  if (mockEsbuildOnEndCallback != null) {
58
21
  await mockEsbuildOnEndCallback({ errors: [] });
59
22
  }
@@ -66,7 +29,20 @@ vi.mock("esbuild", () => ({
66
29
  context: mockEsbuildContext,
67
30
  }));
68
31
 
69
- // consola mock
32
+ // @simplysm/core-node fsx, cpx는 spy로 전환
33
+ const mockFsxExists = vi.spyOn(fsx, "exists");
34
+ const mockFsxReadJson = vi.spyOn(fsx, "readJson");
35
+ const mockFsxWriteJson = vi.spyOn(fsx, "writeJson").mockResolvedValue(undefined);
36
+ vi.spyOn(fsx, "write").mockResolvedValue(undefined);
37
+ vi.spyOn(fsx, "mkdir").mockResolvedValue(undefined);
38
+ const mockFsxCopy = vi.spyOn(fsx, "copy").mockResolvedValue(undefined);
39
+ const mockFsxReaddir = vi.spyOn(fsx, "readdir");
40
+ const mockFsxGlob = vi.spyOn(fsx, "glob");
41
+
42
+ const mockCpxSpawn = vi.spyOn(cpx, "spawn").mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 });
43
+ vi.spyOn(cpx, "spawnSync").mockReturnValue({ stdout: "", stderr: "", exitCode: 0 });
44
+
45
+ // consola spy
70
46
  const mockLoggerDebug = vi.fn();
71
47
  const mockLoggerWarn = vi.fn();
72
48
  const mockLoggerInfo = vi.fn();
@@ -130,8 +106,8 @@ function findBuilderConfig(): Record<string, unknown> | undefined {
130
106
 
131
107
  function normalizedCopyCalls(): string[][] {
132
108
  return mockFsxCopy.mock.calls.map((c) => [
133
- (c[0] as string).replace(/\\/g, "/"),
134
- (c[1] as string).replace(/\\/g, "/"),
109
+ c[0].replace(/\\/g, "/"),
110
+ c[1].replace(/\\/g, "/"),
135
111
  ]);
136
112
  }
137
113
 
@@ -183,13 +159,13 @@ describe("Electron", () => {
183
159
 
184
160
  const spawnCalls = mockCpxSpawn.mock.calls;
185
161
  const installCall = spawnCalls.find(
186
- (c) => c[0] === "pnpm" && (c[1] as string[]).includes("install"),
162
+ (c) => c[0] === "pnpm" && c[1].includes("install"),
187
163
  );
188
164
  expect(installCall).toBeDefined();
189
165
  expect(installCall?.[2]).toEqual(expect.objectContaining({ shell: true }));
190
166
  expect(
191
167
  spawnCalls.find(
192
- (c) => c[0] === "pnpm" && (c[1] as string[]).includes("electron-rebuild"),
168
+ (c) => c[0] === "pnpm" && c[1].includes("electron-rebuild"),
193
169
  ),
194
170
  ).toBeDefined();
195
171
  });
@@ -201,7 +177,7 @@ describe("Electron", () => {
201
177
  await electron.initialize();
202
178
 
203
179
  const rebuildCall = mockCpxSpawn.mock.calls.find(
204
- (c) => c[0] === "pnpm" && (c[1] as string[]).includes("electron-rebuild"),
180
+ (c) => c[0] === "pnpm" && c[1].includes("electron-rebuild"),
205
181
  );
206
182
  expect(rebuildCall).toBeUndefined();
207
183
  });
@@ -523,7 +499,7 @@ describe("Electron", () => {
523
499
  expect(callArgs.bundle).toBe(true);
524
500
  expect(callArgs.external).toContain("electron");
525
501
  const electronCall = mockCpxSpawn.mock.calls.find(
526
- (c) => c[0] === "pnpm" && (c[1] as string[]).includes("electron"),
502
+ (c) => c[0] === "pnpm" && c[1].includes("electron"),
527
503
  );
528
504
  expect(electronCall?.[2]).toEqual(expect.objectContaining({ shell: true, reject: false }));
529
505
 
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
-
3
- // --- Mock factories (vi.mock is hoisted) ---
2
+ import { Worker } from "@simplysm/core-node";
4
3
 
5
4
  const mockWorker = {
6
5
  build: vi.fn(),
@@ -10,16 +9,10 @@ const mockWorker = {
10
9
  on: vi.fn(),
11
10
  };
12
11
 
13
- vi.mock("@simplysm/core-node", () => ({
14
- Worker: {
15
- create: vi.fn(() => mockWorker),
16
- },
17
- }));
18
-
19
- // --- Dynamic imports after mocking ---
12
+ vi.spyOn(Worker, "create").mockReturnValue(mockWorker as any);
20
13
 
21
- const { TscEngine } = await import("../../src/engines/TscEngine");
22
- const { BaseEngine } = await import("../../src/engines/BaseEngine");
14
+ import { TscEngine } from "../../src/engines/TscEngine";
15
+ import { BaseEngine } from "../../src/engines/BaseEngine";
23
16
 
24
17
  import type { BuildPackageInfo, BuildOutput } from "../../src/engines/types";
25
18
  import type { BuildResult } from "../../src/runtime/ResultCollector";
@@ -64,8 +57,6 @@ describe("BaseEngine", () => {
64
57
 
65
58
  describe("_createWorker resourceLimits", () => {
66
59
  it("Worker.create에 resourceLimits를 전달한다", async () => {
67
- const { Worker } = await import("@simplysm/core-node");
68
-
69
60
  const engine = new TscEngine({ cwd: "/root", pkg: createMockPkg() });
70
61
  await engine.run({ js: true, dts: true });
71
62
 
@@ -1,23 +1,20 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { Worker } from "@simplysm/core-node";
2
3
  import * as packageUtils from "../../src/utils/package-utils";
3
4
 
4
- vi.mock("@simplysm/core-node", () => ({
5
- Worker: {
6
- create: vi.fn(() => ({
7
- build: vi.fn(),
8
- startWatch: vi.fn(),
9
- stopWatch: vi.fn(),
10
- terminate: vi.fn(),
11
- on: vi.fn(),
12
- })),
13
- },
14
- }));
15
-
16
- const { createBuildEngine, createTypecheckEngine } = await import("../../src/engines/engine-factory");
17
- const { TscEngine } = await import("../../src/engines/TscEngine");
18
- const { NgtscEngine } = await import("../../src/engines/NgtscEngine");
19
- const { ServerEsbuildEngine } = await import("../../src/engines/ServerEsbuildEngine");
20
- const { EsbuildClientEngine } = await import("../../src/engines/EsbuildClientEngine");
5
+ vi.spyOn(Worker, "create").mockReturnValue({
6
+ build: vi.fn(),
7
+ startWatch: vi.fn(),
8
+ stopWatch: vi.fn(),
9
+ terminate: vi.fn(),
10
+ on: vi.fn(),
11
+ } as any);
12
+
13
+ import { createBuildEngine, createTypecheckEngine } from "../../src/engines/engine-factory";
14
+ import { TscEngine } from "../../src/engines/TscEngine";
15
+ import { NgtscEngine } from "../../src/engines/NgtscEngine";
16
+ import { ServerEsbuildEngine } from "../../src/engines/ServerEsbuildEngine";
17
+ import { EsbuildClientEngine } from "../../src/engines/EsbuildClientEngine";
21
18
 
22
19
  import type { BuildPackageInfo, ClientPackageInfo, ServerPackageInfo } from "../../src/engines/types";
23
20
 
@@ -1,22 +1,19 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { Worker } from "@simplysm/core-node";
2
3
  import * as packageUtils from "../../src/utils/package-utils";
3
4
 
4
- vi.mock("@simplysm/core-node", () => ({
5
- Worker: {
6
- create: vi.fn(() => ({
7
- build: vi.fn(),
8
- startWatch: vi.fn(),
9
- stopWatch: vi.fn(),
10
- terminate: vi.fn(),
11
- on: vi.fn(),
12
- })),
13
- },
14
- }));
15
-
16
- const { createTypecheckEngine } = await import("../../src/engines/engine-factory");
17
- const { TscEngine } = await import("../../src/engines/TscEngine");
18
- const { NgtscEngine } = await import("../../src/engines/NgtscEngine");
19
- const { ServerEsbuildEngine } = await import("../../src/engines/ServerEsbuildEngine");
5
+ vi.spyOn(Worker, "create").mockReturnValue({
6
+ build: vi.fn(),
7
+ startWatch: vi.fn(),
8
+ stopWatch: vi.fn(),
9
+ terminate: vi.fn(),
10
+ on: vi.fn(),
11
+ } as any);
12
+
13
+ import { createTypecheckEngine } from "../../src/engines/engine-factory";
14
+ import { TscEngine } from "../../src/engines/TscEngine";
15
+ import { NgtscEngine } from "../../src/engines/NgtscEngine";
16
+ import { ServerEsbuildEngine } from "../../src/engines/ServerEsbuildEngine";
20
17
 
21
18
  import type {
22
19
  BuildPackageInfo,