@simplysm/sd-cli 13.0.68 → 13.0.70

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 (201) hide show
  1. package/README.md +10 -957
  2. package/dist/builders/BaseBuilder.d.ts +23 -23
  3. package/dist/builders/BaseBuilder.d.ts.map +1 -1
  4. package/dist/builders/BaseBuilder.js +15 -15
  5. package/dist/builders/DtsBuilder.d.ts +4 -4
  6. package/dist/builders/DtsBuilder.js +1 -1
  7. package/dist/builders/LibraryBuilder.d.ts +3 -3
  8. package/dist/builders/types.d.ts +10 -10
  9. package/dist/capacitor/capacitor.d.ts +36 -36
  10. package/dist/capacitor/capacitor.js +63 -63
  11. package/dist/capacitor/capacitor.js.map +1 -1
  12. package/dist/commands/add-client.d.ts +8 -8
  13. package/dist/commands/add-client.js +15 -15
  14. package/dist/commands/add-client.js.map +1 -1
  15. package/dist/commands/add-server.d.ts +9 -9
  16. package/dist/commands/add-server.js +13 -13
  17. package/dist/commands/add-server.js.map +1 -1
  18. package/dist/commands/build.d.ts +9 -9
  19. package/dist/commands/check.js +3 -3
  20. package/dist/commands/check.js.map +1 -1
  21. package/dist/commands/dev.d.ts +9 -9
  22. package/dist/commands/device.d.ts +9 -9
  23. package/dist/commands/device.d.ts.map +1 -1
  24. package/dist/commands/device.js +17 -17
  25. package/dist/commands/device.js.map +1 -1
  26. package/dist/commands/init.d.ts +6 -6
  27. package/dist/commands/init.js +12 -12
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/lint.d.ts +23 -23
  30. package/dist/commands/lint.d.ts.map +1 -1
  31. package/dist/commands/lint.js +25 -25
  32. package/dist/commands/lint.js.map +1 -1
  33. package/dist/commands/publish.d.ts +13 -13
  34. package/dist/commands/publish.d.ts.map +1 -1
  35. package/dist/commands/publish.js +61 -61
  36. package/dist/commands/publish.js.map +1 -1
  37. package/dist/commands/replace-deps.d.ts +3 -3
  38. package/dist/commands/replace-deps.d.ts.map +1 -1
  39. package/dist/commands/replace-deps.js +1 -1
  40. package/dist/commands/replace-deps.js.map +1 -1
  41. package/dist/commands/typecheck.d.ts +20 -20
  42. package/dist/commands/typecheck.d.ts.map +1 -1
  43. package/dist/commands/typecheck.js +20 -20
  44. package/dist/commands/typecheck.js.map +1 -1
  45. package/dist/commands/watch.d.ts +7 -7
  46. package/dist/electron/electron.d.ts +27 -27
  47. package/dist/electron/electron.js +32 -32
  48. package/dist/electron/electron.js.map +1 -1
  49. package/dist/infra/ResultCollector.d.ts +9 -9
  50. package/dist/infra/ResultCollector.js +5 -5
  51. package/dist/infra/SignalHandler.d.ts +7 -7
  52. package/dist/infra/SignalHandler.js +4 -4
  53. package/dist/infra/WorkerManager.d.ts +14 -14
  54. package/dist/infra/WorkerManager.js +11 -11
  55. package/dist/orchestrators/BuildOrchestrator.d.ts +19 -19
  56. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  57. package/dist/orchestrators/BuildOrchestrator.js +26 -26
  58. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  59. package/dist/orchestrators/DevOrchestrator.d.ts +25 -25
  60. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  61. package/dist/orchestrators/DevOrchestrator.js +30 -30
  62. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  63. package/dist/orchestrators/WatchOrchestrator.d.ts +13 -13
  64. package/dist/orchestrators/WatchOrchestrator.js +17 -17
  65. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  66. package/dist/sd-cli-entry.d.ts +2 -2
  67. package/dist/sd-cli-entry.js +38 -38
  68. package/dist/sd-cli-entry.js.map +1 -1
  69. package/dist/sd-cli.d.ts +2 -2
  70. package/dist/sd-cli.js +1 -1
  71. package/dist/sd-cli.js.map +1 -1
  72. package/dist/sd-config.types.d.ts +84 -84
  73. package/dist/sd-config.types.d.ts.map +1 -1
  74. package/dist/utils/build-env.d.ts +1 -1
  75. package/dist/utils/config-editor.d.ts +5 -5
  76. package/dist/utils/config-editor.js +2 -2
  77. package/dist/utils/config-editor.js.map +1 -1
  78. package/dist/utils/copy-public.d.ts +9 -9
  79. package/dist/utils/copy-src.d.ts +9 -9
  80. package/dist/utils/esbuild-config.d.ts +30 -30
  81. package/dist/utils/esbuild-config.d.ts.map +1 -1
  82. package/dist/utils/output-utils.d.ts +6 -6
  83. package/dist/utils/package-utils.d.ts +6 -6
  84. package/dist/utils/package-utils.js +1 -1
  85. package/dist/utils/package-utils.js.map +1 -1
  86. package/dist/utils/rebuild-manager.js +3 -3
  87. package/dist/utils/rebuild-manager.js.map +1 -1
  88. package/dist/utils/replace-deps.d.ts +25 -25
  89. package/dist/utils/replace-deps.js +3 -3
  90. package/dist/utils/replace-deps.js.map +1 -1
  91. package/dist/utils/sd-config.d.ts +3 -3
  92. package/dist/utils/sd-config.js +3 -3
  93. package/dist/utils/sd-config.js.map +1 -1
  94. package/dist/utils/tailwind-config-deps.d.ts +3 -3
  95. package/dist/utils/template.d.ts +8 -8
  96. package/dist/utils/tsconfig.d.ts +16 -16
  97. package/dist/utils/tsconfig.js +2 -2
  98. package/dist/utils/tsconfig.js.map +1 -1
  99. package/dist/utils/typecheck-serialization.d.ts +8 -8
  100. package/dist/utils/vite-config.d.ts +8 -8
  101. package/dist/utils/vite-config.d.ts.map +1 -1
  102. package/dist/utils/vite-config.js +3 -3
  103. package/dist/utils/worker-events.d.ts +12 -12
  104. package/dist/utils/worker-events.d.ts.map +1 -1
  105. package/dist/utils/worker-utils.d.ts +3 -3
  106. package/dist/utils/worker-utils.js +2 -2
  107. package/dist/utils/worker-utils.js.map +1 -1
  108. package/dist/workers/client.worker.d.ts +14 -14
  109. package/dist/workers/client.worker.d.ts.map +1 -1
  110. package/dist/workers/client.worker.js +1 -1
  111. package/dist/workers/client.worker.js.map +1 -1
  112. package/dist/workers/dts.worker.d.ts +13 -13
  113. package/dist/workers/dts.worker.d.ts.map +1 -1
  114. package/dist/workers/dts.worker.js +3 -3
  115. package/dist/workers/dts.worker.js.map +1 -1
  116. package/dist/workers/library.worker.d.ts +12 -12
  117. package/dist/workers/library.worker.js +1 -1
  118. package/dist/workers/library.worker.js.map +1 -1
  119. package/dist/workers/lint.worker.d.ts +1 -1
  120. package/dist/workers/server-runtime.worker.d.ts +6 -6
  121. package/dist/workers/server-runtime.worker.js +6 -6
  122. package/dist/workers/server-runtime.worker.js.map +1 -1
  123. package/dist/workers/server.worker.d.ts +20 -20
  124. package/dist/workers/server.worker.d.ts.map +1 -1
  125. package/dist/workers/server.worker.js +6 -6
  126. package/dist/workers/server.worker.js.map +1 -1
  127. package/package.json +8 -7
  128. package/src/builders/BaseBuilder.ts +33 -33
  129. package/src/builders/DtsBuilder.ts +5 -5
  130. package/src/builders/LibraryBuilder.ts +9 -9
  131. package/src/builders/types.ts +10 -10
  132. package/src/capacitor/capacitor.ts +119 -119
  133. package/src/commands/add-client.ts +31 -31
  134. package/src/commands/add-server.ts +34 -34
  135. package/src/commands/build.ts +9 -9
  136. package/src/commands/check.ts +5 -5
  137. package/src/commands/dev.ts +9 -9
  138. package/src/commands/device.ts +30 -30
  139. package/src/commands/init.ts +25 -25
  140. package/src/commands/lint.ts +64 -64
  141. package/src/commands/publish.ts +139 -139
  142. package/src/commands/replace-deps.ts +4 -4
  143. package/src/commands/typecheck.ts +74 -74
  144. package/src/commands/watch.ts +7 -7
  145. package/src/electron/electron.ts +51 -51
  146. package/src/infra/ResultCollector.ts +9 -9
  147. package/src/infra/SignalHandler.ts +7 -7
  148. package/src/infra/WorkerManager.ts +14 -14
  149. package/src/orchestrators/BuildOrchestrator.ts +76 -76
  150. package/src/orchestrators/DevOrchestrator.ts +88 -88
  151. package/src/orchestrators/WatchOrchestrator.ts +39 -39
  152. package/src/sd-cli-entry.ts +43 -43
  153. package/src/sd-cli.ts +15 -15
  154. package/src/sd-config.types.ts +85 -85
  155. package/src/utils/build-env.ts +1 -1
  156. package/src/utils/config-editor.ts +19 -19
  157. package/src/utils/copy-public.ts +17 -17
  158. package/src/utils/copy-src.ts +11 -11
  159. package/src/utils/esbuild-config.ts +33 -33
  160. package/src/utils/output-utils.ts +11 -11
  161. package/src/utils/package-utils.ts +12 -12
  162. package/src/utils/rebuild-manager.ts +3 -3
  163. package/src/utils/replace-deps.ts +361 -361
  164. package/src/utils/sd-config.ts +44 -44
  165. package/src/utils/tailwind-config-deps.ts +98 -98
  166. package/src/utils/template.ts +56 -56
  167. package/src/utils/tsconfig.ts +127 -127
  168. package/src/utils/typecheck-serialization.ts +86 -86
  169. package/src/utils/vite-config.ts +341 -341
  170. package/src/utils/worker-events.ts +16 -16
  171. package/src/utils/worker-utils.ts +45 -45
  172. package/src/workers/client.worker.ts +34 -34
  173. package/src/workers/dts.worker.ts +467 -467
  174. package/src/workers/library.worker.ts +314 -314
  175. package/src/workers/lint.worker.ts +16 -16
  176. package/src/workers/server-runtime.worker.ts +157 -157
  177. package/src/workers/server.worker.ts +572 -572
  178. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  179. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  180. package/templates/init/package.json.hbs +3 -3
  181. package/tests/config-editor.spec.ts +160 -0
  182. package/tests/copy-src.spec.ts +50 -0
  183. package/tests/get-compiler-options-for-package.spec.ts +139 -0
  184. package/tests/get-package-source-files.spec.ts +181 -0
  185. package/tests/get-types-from-package-json.spec.ts +107 -0
  186. package/tests/infra/ResultCollector.spec.ts +39 -0
  187. package/tests/infra/SignalHandler.spec.ts +38 -0
  188. package/tests/infra/WorkerManager.spec.ts +97 -0
  189. package/tests/load-ignore-patterns.spec.ts +188 -0
  190. package/tests/load-sd-config.spec.ts +137 -0
  191. package/tests/package-utils.spec.ts +188 -0
  192. package/tests/parse-root-tsconfig.spec.ts +89 -0
  193. package/tests/replace-deps.spec.ts +308 -0
  194. package/tests/run-lint.spec.ts +415 -0
  195. package/tests/run-typecheck.spec.ts +653 -0
  196. package/tests/run-watch.spec.ts +75 -0
  197. package/tests/sd-cli.spec.ts +330 -0
  198. package/tests/tailwind-config-deps.spec.ts +30 -0
  199. package/tests/template.spec.ts +70 -0
  200. package/tests/utils/rebuild-manager.spec.ts +43 -0
  201. package/tests/write-changed-output-files.spec.ts +97 -0
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ResultCollector, type BuildResult } from "../../src/infra/ResultCollector";
3
+
4
+ describe("ResultCollector", () => {
5
+ it("adds and retrieves result", () => {
6
+ const collector = new ResultCollector();
7
+ const result: BuildResult = {
8
+ name: "core-common",
9
+ target: "neutral",
10
+ type: "build",
11
+ status: "success",
12
+ };
13
+
14
+ collector.add(result);
15
+
16
+ expect(collector.get("core-common:build")).toEqual(result);
17
+ });
18
+
19
+ it("overwrites result when added with same key", () => {
20
+ const collector = new ResultCollector();
21
+ collector.add({ name: "pkg1", target: "node", type: "build", status: "pending" });
22
+ collector.add({ name: "pkg1", target: "node", type: "build", status: "success" });
23
+
24
+ const result = collector.get("pkg1:build");
25
+
26
+ expect(result?.status).toBe("success");
27
+ expect(collector.toMap().size).toBe(1);
28
+ });
29
+
30
+ it("returns internal Map", () => {
31
+ const collector = new ResultCollector();
32
+ collector.add({ name: "pkg1", target: "node", type: "build", status: "success" });
33
+
34
+ const map = collector.toMap();
35
+
36
+ expect(map).toBeInstanceOf(Map);
37
+ expect(map.size).toBe(1);
38
+ });
39
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { SignalHandler } from "../../src/infra/SignalHandler";
3
+
4
+ describe("SignalHandler", () => {
5
+ afterEach(() => {
6
+ // Clean up process listeners between tests
7
+ process.removeAllListeners("SIGINT");
8
+ process.removeAllListeners("SIGTERM");
9
+ });
10
+
11
+ it("resolves waitForTermination when termination is requested", async () => {
12
+ const handler = new SignalHandler();
13
+
14
+ // Request termination asynchronously
15
+ setTimeout(() => handler.requestTermination(), 10);
16
+
17
+ await expect(handler.waitForTermination()).resolves.toBeUndefined();
18
+ });
19
+
20
+ it("returns termination status correctly", () => {
21
+ const handler = new SignalHandler();
22
+
23
+ expect(handler.isTerminated()).toBe(false);
24
+
25
+ handler.requestTermination();
26
+
27
+ expect(handler.isTerminated()).toBe(true);
28
+ });
29
+
30
+ it("ignores duplicate termination requests", () => {
31
+ const handler = new SignalHandler();
32
+
33
+ handler.requestTermination();
34
+ handler.requestTermination(); // Second call is ignored
35
+
36
+ expect(handler.isTerminated()).toBe(true);
37
+ });
38
+ });
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { WorkerManager } from "../../src/infra/WorkerManager";
3
+
4
+ // Worker 모킹
5
+ vi.mock("@simplysm/core-node", () => ({
6
+ Worker: {
7
+ create: vi.fn(() => ({
8
+ on: vi.fn(),
9
+ off: vi.fn(),
10
+ terminate: vi.fn().mockResolvedValue(undefined),
11
+ })),
12
+ },
13
+ }));
14
+
15
+ describe("WorkerManager", () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+
20
+ it("creates a Worker and retrieves it by ID", () => {
21
+ const manager = new WorkerManager();
22
+
23
+ const worker = manager.create("test-worker", "/path/to/worker.ts");
24
+
25
+ expect(worker).toBeDefined();
26
+ expect(manager.get("test-worker")).toBe(worker);
27
+ });
28
+
29
+ it("returns undefined when retrieving a non-existent Worker", () => {
30
+ const manager = new WorkerManager();
31
+
32
+ expect(manager.get("nonexistent")).toBeUndefined();
33
+ });
34
+
35
+ it("terminates all Workers", async () => {
36
+ const manager = new WorkerManager();
37
+ const worker1 = manager.create("worker1", "/path/to/worker.ts");
38
+ const worker2 = manager.create("worker2", "/path/to/worker.ts");
39
+
40
+ await manager.terminateAll();
41
+
42
+ expect(worker1.terminate).toHaveBeenCalled();
43
+ expect(worker2.terminate).toHaveBeenCalled();
44
+ expect(manager.size).toBe(0);
45
+ });
46
+
47
+ it("terminates a specific Worker only", async () => {
48
+ const manager = new WorkerManager();
49
+ const worker1 = manager.create("worker1", "/path/to/worker.ts");
50
+ const worker2 = manager.create("worker2", "/path/to/worker.ts");
51
+
52
+ await manager.terminate("worker1");
53
+
54
+ expect(worker1.terminate).toHaveBeenCalled();
55
+ expect(worker2.terminate).not.toHaveBeenCalled();
56
+ expect(manager.get("worker1")).toBeUndefined();
57
+ expect(manager.get("worker2")).toBe(worker2);
58
+ });
59
+
60
+ it("handles terminating a non-existent Worker without error", async () => {
61
+ const manager = new WorkerManager();
62
+
63
+ await expect(manager.terminate("nonexistent")).resolves.toBeUndefined();
64
+ });
65
+
66
+ it("retrieves the count of managed Workers", () => {
67
+ const manager = new WorkerManager();
68
+
69
+ expect(manager.size).toBe(0);
70
+
71
+ manager.create("worker1", "/path/to/worker.ts");
72
+ expect(manager.size).toBe(1);
73
+
74
+ manager.create("worker2", "/path/to/worker.ts");
75
+ expect(manager.size).toBe(2);
76
+ });
77
+
78
+ it("retrieves list of all Worker IDs", () => {
79
+ const manager = new WorkerManager();
80
+ manager.create("worker1", "/path/to/worker.ts");
81
+ manager.create("worker2", "/path/to/worker.ts");
82
+
83
+ const ids = manager.ids;
84
+
85
+ expect(ids).toEqual(["worker1", "worker2"]);
86
+ });
87
+
88
+ it("overwrites existing Worker when creating with the same ID", () => {
89
+ const manager = new WorkerManager();
90
+ const worker1 = manager.create("same-id", "/path/to/worker1.ts");
91
+ const worker2 = manager.create("same-id", "/path/to/worker2.ts");
92
+
93
+ expect(manager.get("same-id")).toBe(worker2);
94
+ expect(manager.get("same-id")).not.toBe(worker1);
95
+ expect(manager.size).toBe(1);
96
+ });
97
+ });
@@ -0,0 +1,188 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2
+ import { loadIgnorePatterns } from "../src/commands/lint";
3
+ import path from "path";
4
+
5
+ // Mock core-node functions and jiti
6
+ vi.mock("@simplysm/core-node", () => ({
7
+ fsExists: vi.fn(),
8
+ pathPosix: vi.fn(),
9
+ }));
10
+
11
+ const mockJitiImportFn = vi.fn();
12
+ vi.mock("jiti", () => ({
13
+ createJiti: vi.fn(() => ({
14
+ import: mockJitiImportFn,
15
+ })),
16
+ }));
17
+
18
+ import { fsExists } from "@simplysm/core-node";
19
+
20
+ describe("loadIgnorePatterns", () => {
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ });
24
+
25
+ afterEach(() => {
26
+ vi.restoreAllMocks();
27
+ });
28
+
29
+ it("extracts globalIgnores pattern from eslint.config.ts", async () => {
30
+ const cwd = "/project";
31
+ const mockExists = vi.mocked(fsExists);
32
+
33
+ mockExists.mockImplementation((filePath: string) => {
34
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.ts"));
35
+ });
36
+
37
+ mockJitiImportFn.mockResolvedValue({
38
+ default: [{ ignores: ["node_modules/**", "dist/**"] }, { files: ["**/*.ts"], rules: {} }],
39
+ });
40
+
41
+ const patterns = await loadIgnorePatterns(cwd);
42
+
43
+ expect(patterns).toEqual(["node_modules/**", "dist/**"]);
44
+ });
45
+
46
+ it("does not extract as globalIgnores if files is present", async () => {
47
+ const cwd = "/project";
48
+ const mockExists = vi.mocked(fsExists);
49
+
50
+ mockExists.mockImplementation((filePath: string) => {
51
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.ts"));
52
+ });
53
+
54
+ mockJitiImportFn.mockResolvedValue({
55
+ default: [
56
+ { ignores: ["global/**"] },
57
+ { files: ["**/*.ts"], ignores: ["local/**"], rules: {} },
58
+ ],
59
+ });
60
+
61
+ const patterns = await loadIgnorePatterns(cwd);
62
+
63
+ expect(patterns).toEqual(["global/**"]);
64
+ expect(patterns).not.toContain("local/**");
65
+ });
66
+
67
+ it("throws error if config file not found", async () => {
68
+ const cwd = "/project";
69
+ const mockExists = vi.mocked(fsExists);
70
+
71
+ mockExists.mockResolvedValue(false);
72
+
73
+ await expect(loadIgnorePatterns(cwd)).rejects.toThrow("Cannot find ESLint config file");
74
+ });
75
+
76
+ it("throws error if config is not array", async () => {
77
+ const cwd = "/project";
78
+ const mockExists = vi.mocked(fsExists);
79
+
80
+ mockExists.mockImplementation((filePath: string) => {
81
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.ts"));
82
+ });
83
+
84
+ mockJitiImportFn.mockResolvedValue({
85
+ default: { rules: {} },
86
+ });
87
+
88
+ await expect(loadIgnorePatterns(cwd)).rejects.toThrow("ESLint config is not an array");
89
+ });
90
+
91
+ it("merges multiple globalIgnores settings", async () => {
92
+ const cwd = "/project";
93
+ const mockExists = vi.mocked(fsExists);
94
+
95
+ mockExists.mockImplementation((filePath: string) => {
96
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.ts"));
97
+ });
98
+
99
+ mockJitiImportFn.mockResolvedValue({
100
+ default: [{ ignores: ["node_modules/**"] }, { ignores: ["dist/**", ".cache/**"] }],
101
+ });
102
+
103
+ const patterns = await loadIgnorePatterns(cwd);
104
+
105
+ expect(patterns).toEqual(["node_modules/**", "dist/**", ".cache/**"]);
106
+ });
107
+
108
+ it("handles config that directly exports array", async () => {
109
+ const cwd = "/project";
110
+ const mockExists = vi.mocked(fsExists);
111
+
112
+ mockExists.mockImplementation((filePath: string) => {
113
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.js"));
114
+ });
115
+
116
+ mockJitiImportFn.mockResolvedValue([{ ignores: ["build/**"] }]);
117
+
118
+ const patterns = await loadIgnorePatterns(cwd);
119
+
120
+ expect(patterns).toEqual(["build/**"]);
121
+ });
122
+
123
+ it("throws error if config has no default and is not array", async () => {
124
+ const cwd = "/project";
125
+ const mockExists = vi.mocked(fsExists);
126
+
127
+ mockExists.mockImplementation((filePath: string) => {
128
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.ts"));
129
+ });
130
+
131
+ mockJitiImportFn.mockResolvedValue({
132
+ config: [{ ignores: ["test/**"] }],
133
+ });
134
+
135
+ await expect(loadIgnorePatterns(cwd)).rejects.toThrow(
136
+ /ESLint config file is not .* format/,
137
+ );
138
+ });
139
+
140
+ it("uses mts file if eslint.config.ts not found", async () => {
141
+ const cwd = "/project";
142
+ const mockExists = vi.mocked(fsExists);
143
+
144
+ mockExists.mockImplementation((filePath: string) => {
145
+ // eslint.config.ts does not exist, only eslint.config.mts
146
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.mts"));
147
+ });
148
+
149
+ mockJitiImportFn.mockResolvedValue({
150
+ default: [{ ignores: ["mts-ignore/**"] }],
151
+ });
152
+
153
+ const patterns = await loadIgnorePatterns(cwd);
154
+
155
+ expect(patterns).toEqual(["mts-ignore/**"]);
156
+ expect(mockJitiImportFn).toHaveBeenCalledWith(expect.stringContaining("eslint.config.mts"));
157
+ });
158
+
159
+ it("returns empty pattern array if empty array exported", async () => {
160
+ const cwd = "/project";
161
+ const mockExists = vi.mocked(fsExists);
162
+
163
+ mockExists.mockImplementation((filePath: string) => {
164
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.ts"));
165
+ });
166
+
167
+ mockJitiImportFn.mockResolvedValue({
168
+ default: [],
169
+ });
170
+
171
+ const patterns = await loadIgnorePatterns(cwd);
172
+
173
+ expect(patterns).toEqual([]);
174
+ });
175
+
176
+ it("propagates error if jiti import fails", async () => {
177
+ const cwd = "/project";
178
+ const mockExists = vi.mocked(fsExists);
179
+
180
+ mockExists.mockImplementation((filePath: string) => {
181
+ return Promise.resolve(filePath === path.join(cwd, "eslint.config.ts"));
182
+ });
183
+
184
+ mockJitiImportFn.mockRejectedValue(new Error("Syntax error in config file"));
185
+
186
+ await expect(loadIgnorePatterns(cwd)).rejects.toThrow("Syntax error in config file");
187
+ });
188
+ });
@@ -0,0 +1,137 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2
+
3
+ const mockJitiImport = vi.fn();
4
+
5
+ vi.mock("@simplysm/core-node", () => ({
6
+ fsExists: vi.fn(),
7
+ }));
8
+
9
+ vi.mock("jiti", () => ({
10
+ createJiti: vi.fn(() => ({
11
+ import: mockJitiImport,
12
+ })),
13
+ }));
14
+
15
+ import { fsExists } from "@simplysm/core-node";
16
+ import { loadSdConfig } from "../src/utils/sd-config";
17
+
18
+ describe("loadSdConfig", () => {
19
+ beforeEach(() => {
20
+ vi.clearAllMocks();
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.restoreAllMocks();
25
+ });
26
+
27
+ it("throws error if sd.config.ts file not found", async () => {
28
+ vi.mocked(fsExists).mockResolvedValue(false);
29
+
30
+ await expect(loadSdConfig({ cwd: "/project", dev: false, opt: [] })).rejects.toThrow(
31
+ "sd.config.ts file not found",
32
+ );
33
+ });
34
+
35
+ it("throws error if no default export", async () => {
36
+ vi.mocked(fsExists).mockResolvedValue(true);
37
+ mockJitiImport.mockResolvedValue({
38
+ someOtherExport: () => ({}),
39
+ });
40
+
41
+ await expect(loadSdConfig({ cwd: "/project", dev: false, opt: [] })).rejects.toThrow(
42
+ "sd.config.ts must export a function as default",
43
+ );
44
+ });
45
+
46
+ it("throws error if default export is not function", async () => {
47
+ vi.mocked(fsExists).mockResolvedValue(true);
48
+ mockJitiImport.mockResolvedValue({
49
+ default: { packages: {} }, // object not function
50
+ });
51
+
52
+ await expect(loadSdConfig({ cwd: "/project", dev: false, opt: [] })).rejects.toThrow(
53
+ "sd.config.ts must export a function as default",
54
+ );
55
+ });
56
+
57
+ it("throws error if return value is wrong format (missing packages)", async () => {
58
+ vi.mocked(fsExists).mockResolvedValue(true);
59
+ mockJitiImport.mockResolvedValue({
60
+ default: () => ({}), // missing packages property
61
+ });
62
+
63
+ await expect(loadSdConfig({ cwd: "/project", dev: false, opt: [] })).rejects.toThrow(
64
+ /sd\.config\.ts return value is not in .* correct format/,
65
+ );
66
+ });
67
+
68
+ it("throws error if return value is wrong format (packages is array)", async () => {
69
+ vi.mocked(fsExists).mockResolvedValue(true);
70
+ mockJitiImport.mockResolvedValue({
71
+ default: () => ({ packages: [] }), // packages is array
72
+ });
73
+
74
+ await expect(loadSdConfig({ cwd: "/project", dev: false, opt: [] })).rejects.toThrow(
75
+ /sd\.config\.ts return value is not in .* correct format/,
76
+ );
77
+ });
78
+
79
+ it("throws error if return value is wrong format (packages is null)", async () => {
80
+ vi.mocked(fsExists).mockResolvedValue(true);
81
+ mockJitiImport.mockResolvedValue({
82
+ default: () => ({ packages: null }),
83
+ });
84
+
85
+ await expect(loadSdConfig({ cwd: "/project", dev: false, opt: [] })).rejects.toThrow(
86
+ /sd\.config\.ts return value is not in .* correct format/,
87
+ );
88
+ });
89
+
90
+ it("returns correct config", async () => {
91
+ vi.mocked(fsExists).mockResolvedValue(true);
92
+ mockJitiImport.mockResolvedValue({
93
+ default: () => ({
94
+ packages: {
95
+ "core-common": { target: "neutral" },
96
+ "core-node": { target: "node" },
97
+ },
98
+ }),
99
+ });
100
+
101
+ const config = await loadSdConfig({ cwd: "/project", dev: false, opt: [] });
102
+
103
+ expect(config.packages).toEqual({
104
+ "core-common": { target: "neutral" },
105
+ "core-node": { target: "node" },
106
+ });
107
+ });
108
+
109
+ it("empty packages object is valid", async () => {
110
+ vi.mocked(fsExists).mockResolvedValue(true);
111
+ mockJitiImport.mockResolvedValue({
112
+ default: () => ({ packages: {} }),
113
+ });
114
+
115
+ const config = await loadSdConfig({ cwd: "/project", dev: false, opt: [] });
116
+
117
+ expect(config.packages).toEqual({});
118
+ });
119
+
120
+ it("handles async function default export correctly", async () => {
121
+ vi.mocked(fsExists).mockResolvedValue(true);
122
+ mockJitiImport.mockResolvedValue({
123
+ // eslint-disable-next-line @typescript-eslint/require-await
124
+ default: async () => ({
125
+ packages: {
126
+ "core-common": { target: "neutral" },
127
+ },
128
+ }),
129
+ });
130
+
131
+ const config = await loadSdConfig({ cwd: "/project", dev: false, opt: [] });
132
+
133
+ expect(config.packages).toEqual({
134
+ "core-common": { target: "neutral" },
135
+ });
136
+ });
137
+ });
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { collectDeps } from "../src/utils/package-utils";
3
+ import fs from "fs";
4
+ import os from "os";
5
+ import path from "path";
6
+
7
+ describe("collectDeps", () => {
8
+ let tmpDir: string;
9
+
10
+ function writePkgJson(dir: string, content: object): void {
11
+ fs.mkdirSync(dir, { recursive: true });
12
+ fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(content));
13
+ }
14
+
15
+ beforeEach(() => {
16
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "collectDeps-"));
17
+ });
18
+
19
+ afterEach(() => {
20
+ fs.rmSync(tmpDir, { recursive: true, force: true });
21
+ });
22
+
23
+ it("collects workspace direct dependencies", () => {
24
+ // root package.json with scope
25
+ writePkgJson(tmpDir, { name: "@myapp/root" });
26
+
27
+ // workspace package: packages/core
28
+ writePkgJson(path.join(tmpDir, "packages", "core"), {
29
+ name: "@myapp/core",
30
+ });
31
+
32
+ // target package that depends on @myapp/core
33
+ const pkgDir = path.join(tmpDir, "packages", "app");
34
+ writePkgJson(pkgDir, {
35
+ name: "@myapp/app",
36
+ dependencies: { "@myapp/core": "workspace:*" },
37
+ });
38
+
39
+ const result = collectDeps(pkgDir, tmpDir);
40
+ expect(result.workspaceDeps).toEqual(["core"]);
41
+ expect(result.replaceDeps).toEqual([]);
42
+ });
43
+
44
+ it("collects workspace transitive dependencies", () => {
45
+ writePkgJson(tmpDir, { name: "@myapp/root" });
46
+
47
+ // packages/utils (no deps)
48
+ writePkgJson(path.join(tmpDir, "packages", "utils"), {
49
+ name: "@myapp/utils",
50
+ });
51
+
52
+ // packages/core depends on @myapp/utils
53
+ writePkgJson(path.join(tmpDir, "packages", "core"), {
54
+ name: "@myapp/core",
55
+ dependencies: { "@myapp/utils": "workspace:*" },
56
+ });
57
+
58
+ // packages/app depends on @myapp/core
59
+ const pkgDir = path.join(tmpDir, "packages", "app");
60
+ writePkgJson(pkgDir, {
61
+ name: "@myapp/app",
62
+ dependencies: { "@myapp/core": "workspace:*" },
63
+ });
64
+
65
+ const result = collectDeps(pkgDir, tmpDir);
66
+ expect(result.workspaceDeps).toContain("core");
67
+ expect(result.workspaceDeps).toContain("utils");
68
+ expect(result.replaceDeps).toEqual([]);
69
+ });
70
+
71
+ it("handles replaceDeps glob pattern matching", () => {
72
+ writePkgJson(tmpDir, { name: "@myapp/root" });
73
+
74
+ const pkgDir = path.join(tmpDir, "packages", "app");
75
+ writePkgJson(pkgDir, {
76
+ name: "@myapp/app",
77
+ dependencies: { "@external/lib-a": "^1.0.0" },
78
+ });
79
+
80
+ const result = collectDeps(pkgDir, tmpDir, { "@external/*": "../external/packages/*" });
81
+ expect(result.replaceDeps).toEqual(["@external/lib-a"]);
82
+ expect(result.workspaceDeps).toEqual([]);
83
+ });
84
+
85
+ it("handles workspace to replaceDeps transitive tracking", () => {
86
+ writePkgJson(tmpDir, { name: "@myapp/root" });
87
+
88
+ // packages/core depends on @external/lib-a
89
+ writePkgJson(path.join(tmpDir, "packages", "core"), {
90
+ name: "@myapp/core",
91
+ dependencies: { "@external/lib-a": "^1.0.0" },
92
+ });
93
+
94
+ const pkgDir = path.join(tmpDir, "packages", "app");
95
+ writePkgJson(pkgDir, {
96
+ name: "@myapp/app",
97
+ dependencies: { "@myapp/core": "workspace:*" },
98
+ });
99
+
100
+ const result = collectDeps(pkgDir, tmpDir, { "@external/*": "../external/packages/*" });
101
+ expect(result.workspaceDeps).toEqual(["core"]);
102
+ expect(result.replaceDeps).toEqual(["@external/lib-a"]);
103
+ });
104
+
105
+ it("handles replaceDeps to replaceDeps transitive tracking", () => {
106
+ writePkgJson(tmpDir, { name: "@myapp/root" });
107
+
108
+ // node_modules/@external/lib-a depends on @external/lib-b
109
+ writePkgJson(path.join(tmpDir, "node_modules", "@external", "lib-a"), {
110
+ name: "@external/lib-a",
111
+ dependencies: { "@external/lib-b": "^1.0.0" },
112
+ });
113
+
114
+ // node_modules/@external/lib-b exists
115
+ writePkgJson(path.join(tmpDir, "node_modules", "@external", "lib-b"), {
116
+ name: "@external/lib-b",
117
+ });
118
+
119
+ const pkgDir = path.join(tmpDir, "packages", "app");
120
+ writePkgJson(pkgDir, {
121
+ name: "@myapp/app",
122
+ dependencies: { "@external/lib-a": "^1.0.0" },
123
+ });
124
+
125
+ const result = collectDeps(pkgDir, tmpDir, { "@external/*": "../external/packages/*" });
126
+ expect(result.replaceDeps).toContain("@external/lib-a");
127
+ expect(result.replaceDeps).toContain("@external/lib-b");
128
+ });
129
+
130
+ it("prevents infinite loop in circular dependency (visited set)", () => {
131
+ writePkgJson(tmpDir, { name: "@myapp/root" });
132
+
133
+ // packages/a depends on @myapp/b
134
+ writePkgJson(path.join(tmpDir, "packages", "a"), {
135
+ name: "@myapp/a",
136
+ dependencies: { "@myapp/b": "workspace:*" },
137
+ });
138
+
139
+ // packages/b depends on @myapp/a (circular)
140
+ writePkgJson(path.join(tmpDir, "packages", "b"), {
141
+ name: "@myapp/b",
142
+ dependencies: { "@myapp/a": "workspace:*" },
143
+ });
144
+
145
+ const pkgDir = path.join(tmpDir, "packages", "a");
146
+ const result = collectDeps(pkgDir, tmpDir);
147
+ expect(result.workspaceDeps).toContain("b");
148
+ // Should not infinite loop - a is already visited
149
+ });
150
+
151
+ it("handles replaceDeps exact pattern matching", () => {
152
+ writePkgJson(tmpDir, { name: "@myapp/root" });
153
+
154
+ const pkgDir = path.join(tmpDir, "packages", "app");
155
+ writePkgJson(pkgDir, {
156
+ name: "@myapp/app",
157
+ dependencies: {
158
+ "exact-pkg": "^1.0.0",
159
+ "exact-pkg-extra": "^1.0.0",
160
+ },
161
+ });
162
+
163
+ const result = collectDeps(pkgDir, tmpDir, { "exact-pkg": "../exact-pkg" });
164
+ expect(result.replaceDeps).toEqual(["exact-pkg"]);
165
+ });
166
+
167
+ it("ignores external packages", () => {
168
+ writePkgJson(tmpDir, { name: "@myapp/root" });
169
+
170
+ const pkgDir = path.join(tmpDir, "packages", "app");
171
+ writePkgJson(pkgDir, {
172
+ name: "@myapp/app",
173
+ dependencies: {
174
+ "lodash": "^4.0.0",
175
+ "express": "^4.0.0",
176
+ "@myapp/core": "workspace:*",
177
+ },
178
+ });
179
+
180
+ writePkgJson(path.join(tmpDir, "packages", "core"), {
181
+ name: "@myapp/core",
182
+ });
183
+
184
+ const result = collectDeps(pkgDir, tmpDir);
185
+ expect(result.workspaceDeps).toEqual(["core"]);
186
+ expect(result.replaceDeps).toEqual([]);
187
+ });
188
+ });