@simplysm/sd-cli 14.0.95 → 14.0.97

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 (99) hide show
  1. package/dist/commands/init/generators/client.d.ts.map +1 -1
  2. package/dist/commands/init/generators/client.js +12 -0
  3. package/dist/commands/init/generators/client.js.map +1 -1
  4. package/dist/commands/init/generators/server.d.ts.map +1 -1
  5. package/dist/commands/init/generators/server.js +1 -0
  6. package/dist/commands/init/generators/server.js.map +1 -1
  7. package/dist/commands/init/normalize.d.ts.map +1 -1
  8. package/dist/commands/init/normalize.js +1 -0
  9. package/dist/commands/init/normalize.js.map +1 -1
  10. package/dist/commands/init/prompts.d.ts.map +1 -1
  11. package/dist/commands/init/prompts.js +8 -1
  12. package/dist/commands/init/prompts.js.map +1 -1
  13. package/dist/commands/init/types.d.ts +3 -0
  14. package/dist/commands/init/types.d.ts.map +1 -1
  15. package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
  16. package/dist/engines/EsbuildClientEngine.js +1 -0
  17. package/dist/engines/EsbuildClientEngine.js.map +1 -1
  18. package/dist/esbuild/esbuild-client-config.d.ts.map +1 -1
  19. package/dist/esbuild/esbuild-client-config.js +2 -11
  20. package/dist/esbuild/esbuild-client-config.js.map +1 -1
  21. package/dist/esbuild/esbuild-postcss-plugin.d.ts +4 -0
  22. package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -1
  23. package/dist/esbuild/esbuild-postcss-plugin.js +15 -0
  24. package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -1
  25. package/dist/esbuild/esbuild-ssr-config.d.ts +27 -0
  26. package/dist/esbuild/esbuild-ssr-config.d.ts.map +1 -0
  27. package/dist/esbuild/esbuild-ssr-config.js +113 -0
  28. package/dist/esbuild/esbuild-ssr-config.js.map +1 -0
  29. package/dist/sd-config.types.d.ts +7 -0
  30. package/dist/sd-config.types.d.ts.map +1 -1
  31. package/dist/ssg/prerender.d.ts +19 -0
  32. package/dist/ssg/prerender.d.ts.map +1 -0
  33. package/dist/ssg/prerender.js +43 -0
  34. package/dist/ssg/prerender.js.map +1 -0
  35. package/dist/workers/client.worker.d.ts +2 -0
  36. package/dist/workers/client.worker.d.ts.map +1 -1
  37. package/dist/workers/client.worker.js +21 -0
  38. package/dist/workers/client.worker.js.map +1 -1
  39. package/package.json +6 -6
  40. package/src/commands/init/generators/client.ts +53 -0
  41. package/src/commands/init/generators/server.ts +5 -0
  42. package/src/commands/init/normalize.ts +1 -0
  43. package/src/commands/init/prompts.ts +9 -1
  44. package/src/commands/init/templates/client/package.json.hbs +2 -1
  45. package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +1 -1
  46. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.detail.ts.hbs +221 -0
  47. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +106 -0
  48. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.detail.ts.hbs +277 -0
  49. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs +537 -0
  50. package/src/commands/init/templates/client/src/app/home/master/user.detail.ts.hbs +337 -0
  51. package/src/commands/init/templates/client/src/app/home/master/user.list.ts.hbs +540 -0
  52. package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +4 -6
  53. package/src/commands/init/templates/client/src/app/home/system/data-log/data-log.list.ts.hbs +355 -0
  54. package/src/commands/init/templates/client/src/app/home/system/system-log/system-log.list.ts.hbs +382 -0
  55. package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +3 -4
  56. package/src/commands/init/templates/client/src/app.root.ts.hbs +9 -3
  57. package/src/commands/init/templates/client/src/index.html.hbs +1 -0
  58. package/src/commands/init/templates/client/src/main.server.ts.hbs +24 -0
  59. package/src/commands/init/templates/client/src/main.ts.hbs +36 -18
  60. package/src/commands/init/templates/client/src/modals/text-view.modal.ts.hbs +30 -0
  61. package/src/commands/init/templates/client/src/routes.ts.hbs +22 -0
  62. package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -4
  63. package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +3 -3
  64. package/src/commands/init/templates/client-common/src/providers/app-orm.provider.ts.hbs +0 -11
  65. package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +24 -10
  66. package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +2 -2
  67. package/src/commands/init/templates/common/package.json.hbs +2 -1
  68. package/src/commands/init/templates/common/src/app-structure.ts.hbs +7 -2
  69. package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -1
  70. package/src/commands/init/templates/common/src/db/db-context.ts.hbs +2 -2
  71. package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +2 -2
  72. package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +1 -0
  73. package/src/commands/init/templates/common/src/index.ts.hbs +1 -1
  74. package/src/commands/init/templates/server/src/index.ts.hbs +3 -0
  75. package/src/commands/init/templates/server/src/main.ts.hbs +15 -1
  76. package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +28 -22
  77. package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +5 -5
  78. package/src/commands/init/templates/server/src/services/user.service.ts.hbs +191 -0
  79. package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +3 -0
  80. package/src/commands/init/types.ts +3 -0
  81. package/src/engines/EsbuildClientEngine.ts +1 -0
  82. package/src/esbuild/esbuild-client-config.ts +2 -12
  83. package/src/esbuild/esbuild-postcss-plugin.ts +18 -0
  84. package/src/esbuild/esbuild-ssr-config.ts +149 -0
  85. package/src/sd-config.types.ts +7 -0
  86. package/src/ssg/prerender.ts +65 -0
  87. package/src/workers/client.worker.ts +26 -0
  88. package/tests/engines/base-engine.spec.ts +1 -26
  89. package/tests/init/__snapshots__/render.spec.ts.snap +38 -20
  90. package/tests/init/render.spec.ts +113 -33
  91. package/tests/utils/hmr-client-script.acc.spec.ts +0 -21
  92. package/tests/angular/vite-angular-plugin.spec.ts +0 -102
  93. package/tests/engines/engine-adapter-isolation.spec.ts +0 -79
  94. package/tests/runtime/signal-handler.spec.ts +0 -21
  95. package/tests/utils/angular-build.spec.ts +0 -109
  96. package/tests/utils/esbuild-client-config.acc.spec.ts +0 -438
  97. package/tests/utils/esbuild-client-config.spec.ts +0 -659
  98. package/tests/utils/hmr-client-script.spec.ts +0 -44
  99. package/tests/utils/tsconfig-angular.spec.ts +0 -9
@@ -1,659 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import path from "path";
3
- import type esbuildTypes from "esbuild";
4
-
5
- // 외부 npm + Node 표준 모듈은 ESM namespace immutable이라 vi.mock 유지
6
- const mockContext = {
7
- rebuild: vi.fn(),
8
- watch: vi.fn(),
9
- dispose: vi.fn(),
10
- };
11
-
12
- vi.mock("esbuild", () => ({
13
- default: {
14
- context: vi.fn(() => Promise.resolve(mockContext)),
15
- },
16
- }));
17
-
18
- vi.mock("browserslist-to-esbuild", () => ({
19
- default: vi.fn(() => ["chrome61"]),
20
- }));
21
-
22
- vi.mock("module", async (importOriginal) => {
23
- const actual = await importOriginal<typeof import("module")>();
24
- return {
25
- ...actual,
26
- createRequire: vi.fn(() => (name: string) => {
27
- if (name === "nonexistent-plugin") {
28
- throw new Error(`Cannot find module '${name}'`);
29
- }
30
- return (..._args: any[]) => ({ postcssPlugin: name });
31
- }),
32
- };
33
- });
34
-
35
- import * as angularPluginMod from "../../src/esbuild/esbuild-angular-compiler-plugin";
36
- import * as transformStylesheetMod from "../../src/angular/client-transform-stylesheet";
37
-
38
- const mockAngularPlugin = { name: "sd-angular-compiler" };
39
- const mockTransformStylesheet = vi.fn();
40
-
41
- vi.spyOn(angularPluginMod, "createAngularCompilerPlugin").mockReturnValue(mockAngularPlugin as any);
42
- vi.spyOn(transformStylesheetMod, "createClientTransformStylesheet").mockReturnValue(mockTransformStylesheet as any);
43
-
44
- import { createClientEsbuildContext, ClientSourceFileCache } from "../../src/esbuild/esbuild-client-config";
45
- import { createAngularCompilerPlugin } from "../../src/esbuild/esbuild-angular-compiler-plugin";
46
- import { createClientTransformStylesheet } from "../../src/angular/client-transform-stylesheet";
47
- const esbuild = (await import("esbuild")).default;
48
- const browserslistToEsbuild = (await import("browserslist-to-esbuild")).default;
49
-
50
- // --- Helpers ---
51
-
52
- const baseDev = {
53
- pkgDir: "/workspace/packages/my-app",
54
- cwd: "/workspace",
55
- mode: "dev" as const,
56
- };
57
-
58
- const baseBuild = {
59
- ...baseDev,
60
- mode: "build" as const,
61
- };
62
-
63
- describe("createClientEsbuildContext — define 생성", () => {
64
- beforeEach(() => vi.clearAllMocks());
65
-
66
- it("dev 모드: ngJitMode=false, ngDevMode/ngHmrMode 미주입", async () => {
67
- await createClientEsbuildContext(baseDev);
68
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
69
- expect(opts.define!["ngJitMode"]).toBe("false");
70
- expect(opts.define!["ngDevMode"]).toBeUndefined();
71
- expect(opts.define!["ngHmrMode"]).toBeUndefined();
72
- });
73
-
74
- it("build 모드: ngDevMode, ngJitMode, ngHmrMode 모두 false", async () => {
75
- await createClientEsbuildContext(baseBuild);
76
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
77
- expect(opts.define!["ngDevMode"]).toBe("false");
78
- expect(opts.define!["ngJitMode"]).toBe("false");
79
- expect(opts.define!["ngHmrMode"]).toBe("false");
80
- });
81
-
82
- it("dev + templateUpdates + non-legacy → ngHmrMode = true", async () => {
83
- await createClientEsbuildContext({
84
- ...baseDev,
85
- templateUpdates: new Map<string, string>(),
86
- });
87
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
88
- expect(opts.define!["ngHmrMode"]).toBe("true");
89
- });
90
-
91
- it("dev + templateUpdates + legacyModule → ngHmrMode 미정의", async () => {
92
- await createClientEsbuildContext({
93
- ...baseDev,
94
- templateUpdates: new Map<string, string>(),
95
- legacyModule: true,
96
- });
97
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
98
- expect(opts.define!["ngHmrMode"]).toBeUndefined();
99
- });
100
-
101
- it("build + templateUpdates → ngHmrMode = false (build 모드 우선)", async () => {
102
- await createClientEsbuildContext({
103
- ...baseBuild,
104
- templateUpdates: new Map<string, string>(),
105
- });
106
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
107
- expect(opts.define!["ngHmrMode"]).toBe("false");
108
- });
109
-
110
- it("env가 없으면 import.meta.env define 없음", async () => {
111
- await createClientEsbuildContext(baseDev);
112
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
113
- expect(opts.define!["import.meta.env"]).toBeUndefined();
114
- });
115
-
116
- it("env 설정 시 import.meta.env 객체로 define에 주입", async () => {
117
- await createClientEsbuildContext({
118
- ...baseBuild,
119
- env: { MSG: 'hello "world"' },
120
- });
121
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
122
- expect(opts.define!["import.meta.env"]).toBe(JSON.stringify({ MSG: 'hello "world"' }));
123
- });
124
- });
125
-
126
- describe("createClientEsbuildContext — 소스맵 설정", () => {
127
- beforeEach(() => vi.clearAllMocks());
128
-
129
- it("dev 모드: esbuild sourcemap=linked, AngularCompilerPlugin sourcemap=true", async () => {
130
- await createClientEsbuildContext(baseDev);
131
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
132
- expect(opts.sourcemap).toBe("linked");
133
-
134
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
135
- expect(pluginOpts.sourcemap).toBe(true);
136
- });
137
-
138
- it("build 모드: esbuild sourcemap=false, AngularCompilerPlugin sourcemap=false", async () => {
139
- await createClientEsbuildContext(baseBuild);
140
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
141
- expect(opts.sourcemap).toBe(false);
142
-
143
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
144
- expect(pluginOpts.sourcemap).toBe(false);
145
- });
146
- });
147
-
148
- describe("createClientEsbuildContext — transformStylesheet 설정", () => {
149
- beforeEach(() => vi.clearAllMocks());
150
-
151
- it("transformStylesheet 콜백이 createAngularCompilerPlugin에 전달된다", async () => {
152
- await createClientEsbuildContext(baseBuild);
153
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
154
- expect(pluginOpts.transformStylesheet).toBe(mockTransformStylesheet);
155
- });
156
-
157
- it("stylesheetDependencies와 stylesheetErrors가 createAngularCompilerPlugin에 전달된다", async () => {
158
- await createClientEsbuildContext(baseBuild);
159
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
160
- expect(pluginOpts.stylesheetDependencies).toBeInstanceOf(Map);
161
- expect(pluginOpts.stylesheetErrors).toBeInstanceOf(Array);
162
- });
163
-
164
- it("postcssPlugins가 createClientTransformStylesheet에 전달된다", async () => {
165
- await createClientEsbuildContext({
166
- ...baseBuild,
167
- postcssPlugins: [["autoprefixer"]],
168
- });
169
- const transformOpts = vi.mocked(createClientTransformStylesheet).mock.calls[0][0];
170
- expect(transformOpts.postcssPlugins).toBeDefined();
171
- expect(transformOpts.postcssPlugins).toHaveLength(1);
172
- });
173
-
174
- it("postcssPlugins 미전달 시 createClientTransformStylesheet에 postcssPlugins가 undefined", async () => {
175
- await createClientEsbuildContext(baseBuild);
176
- const transformOpts = vi.mocked(createClientTransformStylesheet).mock.calls[0][0];
177
- expect(transformOpts.postcssPlugins).toBeUndefined();
178
- });
179
- });
180
-
181
- describe("createClientEsbuildContext — PostCSS 플러그인 통합", () => {
182
- beforeEach(() => vi.clearAllMocks());
183
-
184
- it("postcssPlugins 전달 시 sd-postcss 플러그인이 plugins에 등록된다", async () => {
185
- await createClientEsbuildContext({
186
- ...baseBuild,
187
- postcssPlugins: [["autoprefixer"]],
188
- });
189
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
190
- const pluginNames = opts.plugins!.map((p: any) => p.name);
191
- expect(pluginNames).toContain("sd-postcss");
192
- });
193
-
194
- it("postcssPlugins 미전달 시 sd-postcss 미등록", async () => {
195
- await createClientEsbuildContext(baseBuild);
196
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
197
- const pluginNames = opts.plugins!.map((p: any) => p.name);
198
- expect(pluginNames).not.toContain("sd-postcss");
199
- });
200
-
201
- it("sd-postcss가 customPlugins 뒤, sd-legacy-strip-dynamic-import 앞에 배치된다", async () => {
202
- const customPlugin = { name: "custom", setup: vi.fn() };
203
- await createClientEsbuildContext({
204
- ...baseBuild,
205
- postcssPlugins: [["autoprefixer"]],
206
- plugins: [customPlugin],
207
- legacyModule: true,
208
- onEnd: vi.fn(),
209
- });
210
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
211
- const pluginNames = opts.plugins!.map((p: any) => p.name);
212
-
213
- const customIdx = pluginNames.indexOf("custom");
214
- const postcssIdx = pluginNames.indexOf("sd-postcss");
215
- const stripIdx = pluginNames.indexOf("sd-legacy-strip-dynamic-import");
216
- const onEndIdx = pluginNames.indexOf("sd-on-end");
217
-
218
- expect(postcssIdx).toBeGreaterThan(customIdx);
219
- expect(postcssIdx).toBeLessThan(stripIdx);
220
- expect(postcssIdx).toBeLessThan(onEndIdx);
221
- });
222
-
223
- it("존재하지 않는 플러그인 이름으로 에러가 throw된다", async () => {
224
- await expect(
225
- createClientEsbuildContext({
226
- ...baseBuild,
227
- postcssPlugins: [["nonexistent-plugin"]],
228
- }),
229
- ).rejects.toThrow("nonexistent-plugin");
230
- });
231
- });
232
-
233
- describe("createClientEsbuildContext — ClientSourceFileCache", () => {
234
- beforeEach(() => vi.clearAllMocks());
235
-
236
- it("반환된 sourceFileCache가 ClientSourceFileCache 인스턴스", async () => {
237
- const result = await createClientEsbuildContext(baseDev);
238
- expect(result.sourceFileCache).toBeInstanceOf(ClientSourceFileCache);
239
- });
240
-
241
- it("sourceFileCache의 typeScriptFileCache와 loadResultCache가 AngularCompilerPlugin에 전달됨", async () => {
242
- const result = await createClientEsbuildContext(baseDev);
243
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
244
- expect(pluginOpts.sourceFileCache).toBe(result.sourceFileCache);
245
- expect(pluginOpts.typeScriptFileCache).toBe(result.sourceFileCache.typeScriptFileCache);
246
- expect(pluginOpts.loadResultCache).toBe(result.sourceFileCache.loadResultCache);
247
- });
248
-
249
- it("persistentCachePath가 .angular/cache 경로로 전달됨", async () => {
250
- await createClientEsbuildContext(baseDev);
251
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
252
- expect(pluginOpts.persistentCachePath).toBe(
253
- path.join("/workspace/packages/my-app", ".angular", "cache"),
254
- );
255
- });
256
- });
257
-
258
- describe("createClientEsbuildContext — 추가 옵션", () => {
259
- beforeEach(() => vi.clearAllMocks());
260
-
261
- it("tsconfig 커스텀 경로 전달", async () => {
262
- await createClientEsbuildContext({
263
- ...baseDev,
264
- tsconfig: "/workspace/packages/my-app/tsconfig.build.json",
265
- });
266
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
267
- expect(pluginOpts.tsconfig).toBe(
268
- "/workspace/packages/my-app/tsconfig.build.json",
269
- );
270
- });
271
-
272
- it("outdir 커스텀 경로 전달", async () => {
273
- await createClientEsbuildContext({
274
- ...baseDev,
275
- outdir: "/custom/output",
276
- });
277
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
278
- expect(opts.outdir).toBe("/custom/output");
279
- });
280
-
281
- it("추가 plugins가 esbuild plugins 배열에 포함됨", async () => {
282
- const customPlugin = { name: "custom", setup: vi.fn() };
283
- await createClientEsbuildContext({
284
- ...baseDev,
285
- plugins: [customPlugin],
286
- });
287
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
288
- expect(opts.plugins).toContainEqual(customPlugin);
289
- });
290
-
291
- it("templateUpdates가 AngularCompilerPluginOptions에 전달됨", async () => {
292
- const updates = new Map([["comp1", "template1"]]);
293
- await createClientEsbuildContext({
294
- ...baseDev,
295
- templateUpdates: updates,
296
- });
297
- const pluginOpts = vi.mocked(createAngularCompilerPlugin).mock.calls[0][0];
298
- expect(pluginOpts.templateUpdates).toBe(updates);
299
- });
300
- });
301
-
302
- describe("createClientEsbuildContext — browserslist → target 변환", () => {
303
- beforeEach(() => vi.clearAllMocks());
304
-
305
- it("browserslist 미설정 시 esbuild target이 [es2022]", async () => {
306
- await createClientEsbuildContext(baseDev);
307
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
308
- expect(opts.target).toEqual(["es2022"]);
309
- });
310
-
311
- it("browserslist 미설정 시 createClientTransformStylesheet의 loadPaths가 설정됨", async () => {
312
- await createClientEsbuildContext(baseDev);
313
- const transformOpts = vi.mocked(createClientTransformStylesheet).mock.calls[0][0];
314
- expect(transformOpts.loadPaths).toEqual([
315
- path.join("/workspace/packages/my-app", "node_modules"),
316
- path.join("/workspace", "node_modules"),
317
- ]);
318
- });
319
-
320
- it("browserslist 문자열은 배열로 변환하여 browserslistToEsbuild에 전달", async () => {
321
- vi.mocked(browserslistToEsbuild).mockReturnValueOnce(["chrome61"]);
322
- await createClientEsbuildContext({ ...baseDev, browserslist: "Chrome 61" });
323
- expect(browserslistToEsbuild).toHaveBeenCalledWith(["Chrome 61"]);
324
- });
325
-
326
- it("browserslist 배열은 그대로 browserslistToEsbuild에 전달", async () => {
327
- vi.mocked(browserslistToEsbuild).mockReturnValueOnce(["chrome61", "firefox60"]);
328
- await createClientEsbuildContext({
329
- ...baseDev,
330
- browserslist: ["Chrome 61", "Firefox 60"],
331
- });
332
- expect(browserslistToEsbuild).toHaveBeenCalledWith(["Chrome 61", "Firefox 60"]);
333
- });
334
-
335
- it("browserslistToEsbuild 결과가 esbuild target에 적용", async () => {
336
- vi.mocked(browserslistToEsbuild).mockReturnValueOnce(["chrome61", "firefox60"]);
337
- await createClientEsbuildContext({
338
- ...baseDev,
339
- browserslist: ["Chrome 61", "Firefox 60"],
340
- });
341
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
342
- expect(opts.target).toEqual(["chrome61", "firefox60"]);
343
- });
344
- });
345
-
346
- describe("createClientEsbuildContext — 출력 네이밍", () => {
347
- beforeEach(() => vi.clearAllMocks());
348
-
349
- it("dev 모드: entryNames이 [name]", async () => {
350
- await createClientEsbuildContext(baseDev);
351
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
352
- expect(opts.entryNames).toBe("[name]");
353
- });
354
-
355
- it("build 모드: entryNames이 [name]-[hash]", async () => {
356
- await createClientEsbuildContext(baseBuild);
357
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
358
- expect(opts.entryNames).toBe("[name]-[hash]");
359
- });
360
-
361
- it("chunkNames와 assetNames도 entryNames와 동일 패턴", async () => {
362
- await createClientEsbuildContext(baseBuild);
363
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
364
- expect(opts.chunkNames).toBe(opts.entryNames);
365
- expect(opts.assetNames).toBe(opts.entryNames);
366
- });
367
- });
368
-
369
- describe("createClientEsbuildContext — polyfills entryPoints", () => {
370
- beforeEach(() => vi.clearAllMocks());
371
-
372
- it("polyfills 1개 전달 시 main.ts 뒤에 추가", async () => {
373
- await createClientEsbuildContext({
374
- ...baseDev,
375
- polyfills: ["src/polyfills.ts"],
376
- });
377
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
378
- expect(opts.entryPoints).toEqual([
379
- path.join(baseDev.pkgDir, "src", "main.ts"),
380
- path.join(baseDev.pkgDir, "src/polyfills.ts"),
381
- ]);
382
- });
383
-
384
- it("polyfills 여러 개 전달 시 모두 절대경로로 추가", async () => {
385
- await createClientEsbuildContext({
386
- ...baseDev,
387
- polyfills: ["src/polyfills.ts", "src/zone-flags.ts"],
388
- });
389
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
390
- expect(opts.entryPoints).toEqual([
391
- path.join(baseDev.pkgDir, "src", "main.ts"),
392
- path.join(baseDev.pkgDir, "src/polyfills.ts"),
393
- path.join(baseDev.pkgDir, "src/zone-flags.ts"),
394
- ]);
395
- });
396
- });
397
-
398
- describe("createClientEsbuildContext — onEnd 플러그인", () => {
399
- beforeEach(() => vi.clearAllMocks());
400
-
401
- function captureOnEndCallback(
402
- options: Parameters<typeof createClientEsbuildContext>[0],
403
- ) {
404
- return (async () => {
405
- await createClientEsbuildContext(options);
406
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
407
- const sdOnEndPlugin = opts.plugins!.find(
408
- (p: any) => p.name === "sd-on-end",
409
- )!;
410
-
411
- let cb!: (result: any) => any;
412
- (sdOnEndPlugin as any).setup({
413
- onEnd(fn: (result: any) => any) {
414
- cb = fn;
415
- },
416
- });
417
- return cb;
418
- })();
419
- }
420
-
421
- it("sd-on-end가 async onEnd의 반환 Promise를 그대로 return한다", async () => {
422
- const asyncOnEnd = vi.fn().mockResolvedValue(undefined);
423
- const cb = await captureOnEndCallback({ ...baseDev, onEnd: asyncOnEnd });
424
-
425
- const result = cb({ errors: [], warnings: [] });
426
- expect(result).toBeInstanceOf(Promise);
427
- });
428
-
429
- it("sd-on-end가 sync onEnd의 반환값(undefined)을 그대로 return한다", async () => {
430
- const syncOnEnd = vi.fn();
431
- const cb = await captureOnEndCallback({ ...baseDev, onEnd: syncOnEnd });
432
-
433
- const result = cb({ errors: [], warnings: [] });
434
- expect(result).toBeUndefined();
435
- });
436
-
437
- it("onEnd 전달 시 sd-on-end 플러그인이 plugins 마지막에 추가됨", async () => {
438
- await createClientEsbuildContext({
439
- ...baseDev,
440
- onEnd: vi.fn(),
441
- });
442
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
443
- const pluginNames = opts.plugins!.map((p: any) => p.name);
444
- expect(pluginNames).toContain("sd-on-end");
445
- expect(pluginNames[pluginNames.length - 1]).toBe("sd-on-end");
446
- });
447
-
448
- it("customPlugins가 angularPlugin 이전에 위치 (onStart에서 sourceFileCache 무효화 선행)", async () => {
449
- await createClientEsbuildContext({
450
- ...baseDev,
451
- plugins: [{ name: "custom", setup: vi.fn() }],
452
- onEnd: vi.fn(),
453
- });
454
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
455
- const pluginNames = opts.plugins!.map((p: any) => p.name);
456
- const customIdx = pluginNames.indexOf("custom");
457
- const angularIdx = pluginNames.indexOf("sd-angular-compiler");
458
- expect(customIdx).toBeLessThan(angularIdx);
459
- });
460
- });
461
-
462
- describe("createClientEsbuildContext — tsconfig esbuild 옵션", () => {
463
- beforeEach(() => vi.clearAllMocks());
464
-
465
- it("tsconfig 미전달 시 esbuild에 pkgDir/tsconfig.json 전달", async () => {
466
- await createClientEsbuildContext(baseDev);
467
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
468
- expect(opts.tsconfig).toBe(
469
- path.join("/workspace/packages/my-app", "tsconfig.json"),
470
- );
471
- });
472
-
473
- it("tsconfig 커스텀 경로 전달 시 esbuild에도 동일 경로 전달", async () => {
474
- await createClientEsbuildContext({
475
- ...baseDev,
476
- tsconfig: "/workspace/packages/my-app/tsconfig.build.json",
477
- });
478
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
479
- expect(opts.tsconfig).toBe("/workspace/packages/my-app/tsconfig.build.json");
480
- });
481
- });
482
-
483
- describe("createClientEsbuildContext — SCSS 플러그인 통합", () => {
484
- beforeEach(() => vi.clearAllMocks());
485
-
486
- it("sd-scss 플러그인이 plugins 배열에 포함됨", async () => {
487
- await createClientEsbuildContext(baseDev);
488
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
489
- const pluginNames = opts.plugins!.map((p: any) => p.name);
490
- expect(pluginNames).toContain("sd-scss");
491
- });
492
-
493
- it("sd-scss 플러그인이 angularPlugin 다음에 위치", async () => {
494
- const customPlugin = { name: "custom", setup: vi.fn() };
495
- await createClientEsbuildContext({
496
- ...baseDev,
497
- plugins: [customPlugin],
498
- });
499
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
500
- const pluginNames = opts.plugins!.map((p: any) => p.name);
501
-
502
- const angularIdx = pluginNames.indexOf("sd-angular-compiler");
503
- const scssIdx = pluginNames.indexOf("sd-scss");
504
-
505
- expect(scssIdx).toBe(angularIdx + 1);
506
- });
507
- });
508
-
509
- describe("createClientEsbuildContext — legacyModule 설정", () => {
510
- beforeEach(() => vi.clearAllMocks());
511
-
512
- it("legacyModule true → splitting false", async () => {
513
- await createClientEsbuildContext({ ...baseDev, legacyModule: true });
514
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
515
- expect(opts.splitting).toBe(false);
516
- });
517
-
518
- it("legacyModule 미설정 → splitting true 유지", async () => {
519
- await createClientEsbuildContext(baseDev);
520
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
521
- expect(opts.splitting).toBe(true);
522
- });
523
-
524
- it("legacyModule true → supported['import-meta'] false", async () => {
525
- await createClientEsbuildContext({ ...baseDev, legacyModule: true });
526
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
527
- expect(opts.supported).toEqual({ "import-meta": false });
528
- });
529
-
530
- it("legacyModule 미설정 → supported 미설정", async () => {
531
- await createClientEsbuildContext(baseDev);
532
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
533
- expect(opts.supported).toBeUndefined();
534
- });
535
-
536
- it("legacyModule true + env 설정 → define과 supported 공존", async () => {
537
- await createClientEsbuildContext({
538
- ...baseDev,
539
- legacyModule: true,
540
- env: { API_URL: "https://api.example.com" },
541
- });
542
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
543
- expect(opts.define!["import.meta.env"]).toBe(
544
- JSON.stringify({ API_URL: "https://api.example.com" }),
545
- );
546
- expect(opts.supported).toEqual({ "import-meta": false });
547
- });
548
-
549
- it("legacyModule false → splitting true, supported 미설정", async () => {
550
- await createClientEsbuildContext({ ...baseDev, legacyModule: false });
551
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
552
- expect(opts.splitting).toBe(true);
553
- expect(opts.supported).toBeUndefined();
554
- });
555
- });
556
-
557
- describe("createClientEsbuildContext — legacyModule dynamic import 스트립 플러그인", () => {
558
- beforeEach(() => vi.clearAllMocks());
559
-
560
- it("legacyModule true → sd-legacy-strip-dynamic-import 플러그인 추가", async () => {
561
- await createClientEsbuildContext({ ...baseDev, legacyModule: true });
562
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
563
- const pluginNames = opts.plugins!.map((p: any) => p.name);
564
- expect(pluginNames).toContain("sd-legacy-strip-dynamic-import");
565
- });
566
-
567
- it("legacyModule 미설정 → sd-legacy-strip-dynamic-import 플러그인 미추가", async () => {
568
- await createClientEsbuildContext(baseDev);
569
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
570
- const pluginNames = opts.plugins!.map((p: any) => p.name);
571
- expect(pluginNames).not.toContain("sd-legacy-strip-dynamic-import");
572
- });
573
-
574
- async function captureStripOnEnd() {
575
- await createClientEsbuildContext({ ...baseDev, legacyModule: true });
576
- const opts = vi.mocked(esbuild.context).mock.calls[0][0];
577
- const stripPlugin = opts.plugins!.find(
578
- (p: any) => p.name === "sd-legacy-strip-dynamic-import",
579
- )!;
580
-
581
- let onEndCallback: (result: esbuildTypes.BuildResult) => Promise<void>;
582
- (stripPlugin as any).setup({
583
- onEnd(cb: (result: esbuildTypes.BuildResult) => Promise<void>) {
584
- onEndCallback = cb;
585
- },
586
- });
587
-
588
- const { promises: fsPromises } = await import("fs");
589
- const readSpy = vi.spyOn(fsPromises, "readFile");
590
- const writeSpy = vi.spyOn(fsPromises, "writeFile").mockResolvedValue();
591
-
592
- return {
593
- runOnEnd: (outputs: Record<string, unknown>, fileContent: string) => {
594
- readSpy.mockResolvedValue(fileContent);
595
- return onEndCallback!({
596
- metafile: { inputs: {}, outputs },
597
- errors: [],
598
- warnings: [],
599
- outputFiles: [],
600
- mangleCache: {},
601
- } as unknown as esbuildTypes.BuildResult);
602
- },
603
- readSpy,
604
- writeSpy,
605
- cleanup: () => {
606
- readSpy.mockRestore();
607
- writeSpy.mockRestore();
608
- },
609
- };
610
- }
611
-
612
- it("import() 포함 파일을 no-op 함수로 치환", async () => {
613
- const { runOnEnd, readSpy, writeSpy, cleanup } = await captureStripOnEnd();
614
-
615
- await runOnEnd(
616
- {
617
- "dist/main.js": { bytes: 100, inputs: {}, imports: [], exports: [] },
618
- "dist/main.css": { bytes: 50, inputs: {}, imports: [], exports: [] },
619
- },
620
- 'var x = import("./chunk.js");',
621
- );
622
-
623
- expect(readSpy).toHaveBeenCalledWith("dist/main.js", "utf-8");
624
- expect(readSpy).not.toHaveBeenCalledWith("dist/main.css", "utf-8");
625
- expect(writeSpy).toHaveBeenCalledWith(
626
- "dist/main.js",
627
- 'var x = (function(){return Promise.reject(new Error("Dynamic import not supported"))})("./chunk.js");',
628
- );
629
- cleanup();
630
- });
631
-
632
- it("import() 없는 파일은 수정하지 않음", async () => {
633
- const { runOnEnd, writeSpy, cleanup } = await captureStripOnEnd();
634
-
635
- await runOnEnd(
636
- { "dist/main.js": { bytes: 100, inputs: {}, imports: [], exports: [] } },
637
- 'var x = "no dynamic imports here";',
638
- );
639
-
640
- expect(writeSpy).not.toHaveBeenCalled();
641
- cleanup();
642
- });
643
-
644
- it("여러 import() 호출을 모두 치환", async () => {
645
- const { runOnEnd, writeSpy, cleanup } = await captureStripOnEnd();
646
-
647
- await runOnEnd(
648
- { "dist/main.js": { bytes: 100, inputs: {}, imports: [], exports: [] } },
649
- 'import("./a.js"); import ("./b.js");',
650
- );
651
-
652
- const written = writeSpy.mock.calls[0][1] as string;
653
- const noopFn =
654
- '(function(){return Promise.reject(new Error("Dynamic import not supported"))})';
655
- expect(written).toContain(`${noopFn}("./a.js");`);
656
- expect(written).toContain(`${noopFn}("./b.js");`);
657
- cleanup();
658
- });
659
- });