@simplysm/sd-cli 14.0.36 → 14.0.38

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 (66) hide show
  1. package/dist/angular/vite-angular-plugin.d.ts +2 -5
  2. package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
  3. package/dist/angular/vite-angular-plugin.js +19 -72
  4. package/dist/angular/vite-angular-plugin.js.map +1 -1
  5. package/dist/commands/publish/storage-publisher.js +1 -0
  6. package/dist/commands/publish/storage-publisher.js.map +1 -1
  7. package/dist/dev-server/hmr-service.d.ts +2 -0
  8. package/dist/dev-server/hmr-service.d.ts.map +1 -1
  9. package/dist/dev-server/hmr-service.js +24 -10
  10. package/dist/dev-server/hmr-service.js.map +1 -1
  11. package/dist/electron/electron.js +4 -4
  12. package/dist/electron/electron.js.map +1 -1
  13. package/dist/engines/BaseEngine.d.ts.map +1 -1
  14. package/dist/engines/BaseEngine.js +10 -1
  15. package/dist/engines/BaseEngine.js.map +1 -1
  16. package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
  17. package/dist/engines/EsbuildClientEngine.js +12 -1
  18. package/dist/engines/EsbuildClientEngine.js.map +1 -1
  19. package/dist/engines/engine-factory.d.ts +0 -4
  20. package/dist/engines/engine-factory.d.ts.map +1 -1
  21. package/dist/engines/engine-factory.js.map +1 -1
  22. package/dist/esbuild/esbuild-client-config.js +5 -5
  23. package/dist/esbuild/esbuild-client-config.js.map +1 -1
  24. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  25. package/dist/orchestrators/DevOrchestrator.js +0 -5
  26. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  27. package/dist/orchestrators/TypecheckOrchestrator.js +2 -2
  28. package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -1
  29. package/dist/sd-cli.js +2 -1
  30. package/dist/sd-cli.js.map +1 -1
  31. package/dist/utils/output-utils.d.ts.map +1 -1
  32. package/dist/utils/output-utils.js +3 -2
  33. package/dist/utils/output-utils.js.map +1 -1
  34. package/dist/workers/client.worker.d.ts.map +1 -1
  35. package/dist/workers/client.worker.js +7 -1
  36. package/dist/workers/client.worker.js.map +1 -1
  37. package/package.json +4 -4
  38. package/src/angular/vite-angular-plugin.ts +19 -82
  39. package/src/commands/publish/storage-publisher.ts +1 -0
  40. package/src/dev-server/hmr-service.ts +28 -13
  41. package/src/electron/electron.ts +5 -5
  42. package/src/engines/BaseEngine.ts +10 -1
  43. package/src/engines/EsbuildClientEngine.ts +13 -1
  44. package/src/engines/engine-factory.ts +0 -1
  45. package/src/esbuild/esbuild-client-config.ts +6 -6
  46. package/src/orchestrators/DevOrchestrator.ts +0 -6
  47. package/src/orchestrators/TypecheckOrchestrator.ts +2 -2
  48. package/src/sd-cli.ts +2 -1
  49. package/src/utils/output-utils.ts +3 -2
  50. package/src/workers/client.worker.ts +8 -1
  51. package/tests/angular/_vite-angular-plugin-test-setup.ts +3 -30
  52. package/tests/angular/vite-angular-plugin-legacy-watch.spec.ts +14 -31
  53. package/tests/angular/vite-angular-plugin-vitest.spec.ts +4 -34
  54. package/tests/angular/vite-angular-plugin.spec.ts +24 -60
  55. package/tests/commands/typecheck.spec.ts +1 -1
  56. package/tests/engines/base-engine.spec.ts +25 -0
  57. package/tests/engines/engine-adapter-isolation.spec.ts +3 -3
  58. package/tests/engines/esbuild-client-engine.acc.spec.ts +29 -0
  59. package/tests/engines/esbuild-client-engine.spec.ts +26 -0
  60. package/tests/orchestrators/build-orchestrator.spec.ts +1 -1
  61. package/tests/orchestrators/dev-orchestrator.spec.ts +1 -1
  62. package/tests/orchestrators/typecheck-orchestrator.spec.ts +1 -1
  63. package/tests/orchestrators/watch-orchestrator.spec.ts +1 -1
  64. package/tests/utils/esbuild-client-config.acc.spec.ts +4 -1
  65. package/tests/utils/hmr-service-dispatcher.acc.spec.ts +70 -0
  66. package/tests/workers/client-worker-initial-build-error.verify.md +8 -0
@@ -1,5 +1,8 @@
1
1
  import type http from "node:http";
2
2
  import type { IncomingMessage, ServerResponse } from "node:http";
3
+ import fs from "node:fs";
4
+ import path from "path";
5
+ import crypto from "crypto";
3
6
  import type esbuild from "esbuild";
4
7
  import { WebSocketServer, type WebSocket } from "ws";
5
8
 
@@ -10,6 +13,8 @@ export interface HmrServiceOptions {
10
13
  basePath: string;
11
14
  /** templateUpdates Map (createCompilerPlugin과 공유) */
12
15
  templateUpdates: Map<string, string>;
16
+ /** 빌드 출력 디렉토리 경로. 설정 시 파일 내용 hash로 변경 감지 (bytes 대신) */
17
+ outDir?: string;
13
18
  }
14
19
 
15
20
  export interface HmrService {
@@ -24,7 +29,7 @@ export interface HmrService {
24
29
  }
25
30
 
26
31
  export function createHmrService(options: HmrServiceOptions): HmrService {
27
- const { httpServer, basePath, templateUpdates } = options;
32
+ const { httpServer, basePath, templateUpdates, outDir } = options;
28
33
  const clients = new Set<WebSocket>();
29
34
 
30
35
  const wss = new WebSocketServer({ server: httpServer });
@@ -36,7 +41,7 @@ export function createHmrService(options: HmrServiceOptions): HmrService {
36
41
  });
37
42
  });
38
43
 
39
- let prevOutputs: Map<string, number> | undefined;
44
+ let prevOutputs: Map<string, string> | undefined;
40
45
  let debounceTimer: ReturnType<typeof setTimeout> | undefined;
41
46
  let pendingMetafile: esbuild.Metafile | undefined;
42
47
 
@@ -49,12 +54,22 @@ export function createHmrService(options: HmrServiceOptions): HmrService {
49
54
  }, 100);
50
55
  }
51
56
 
52
- function collectOutputs(metafile: esbuild.Metafile): Map<string, number> {
53
- const outputs = new Map<string, number>();
57
+ function collectOutputs(metafile: esbuild.Metafile): Map<string, string> {
58
+ const outputs = new Map<string, string>();
54
59
  for (const [outputPath, output] of Object.entries(metafile.outputs)) {
55
60
  const normalizedPath = outputPath.replace(/\\/g, "/");
56
61
  if (normalizedPath.endsWith(".js") || normalizedPath.endsWith(".css")) {
57
- outputs.set(normalizedPath, output.bytes);
62
+ let fingerprint = String(output.bytes);
63
+ if (outDir != null) {
64
+ try {
65
+ const filePath = path.resolve(outDir, normalizedPath);
66
+ const content = fs.readFileSync(filePath);
67
+ fingerprint = crypto.createHash("md5").update(content).digest("hex");
68
+ } catch {
69
+ // 파일 읽기 실패 시 bytes 폴백
70
+ }
71
+ }
72
+ outputs.set(normalizedPath, fingerprint);
58
73
  }
59
74
  }
60
75
  return outputs;
@@ -91,12 +106,12 @@ export function createHmrService(options: HmrServiceOptions): HmrService {
91
106
  let cssChanged = false;
92
107
  const changedCssFiles: string[] = [];
93
108
 
94
- for (const [path, bytes] of currentOutputs) {
95
- const prevBytes = prevOutputs.get(path);
96
- if (prevBytes !== bytes) {
97
- if (path.endsWith(".css")) {
109
+ for (const [outputPath, fingerprint] of currentOutputs) {
110
+ const prevFingerprint = prevOutputs.get(outputPath);
111
+ if (prevFingerprint !== fingerprint) {
112
+ if (outputPath.endsWith(".css")) {
98
113
  cssChanged = true;
99
- changedCssFiles.push(path.split("/").pop() ?? path);
114
+ changedCssFiles.push(outputPath.split("/").pop() ?? outputPath);
100
115
  } else {
101
116
  jsChanged = true;
102
117
  }
@@ -104,9 +119,9 @@ export function createHmrService(options: HmrServiceOptions): HmrService {
104
119
  }
105
120
 
106
121
  // 삭제된 파일 체크
107
- for (const [path] of prevOutputs) {
108
- if (!currentOutputs.has(path)) {
109
- if (path.endsWith(".css")) {
122
+ for (const [outputPath] of prevOutputs) {
123
+ if (!currentOutputs.has(outputPath)) {
124
+ if (outputPath.endsWith(".css")) {
110
125
  cssChanged = true;
111
126
  } else {
112
127
  jsChanged = true;
@@ -97,7 +97,7 @@ export class Electron {
97
97
 
98
98
  let currentElectron: cpx.SpawnProcess | null = null;
99
99
  let isRestarting = false;
100
- let resolveTermination: (() => void) | null = null;
100
+ let resolveTermination: (() => void | Promise<void>) | null = null;
101
101
 
102
102
  const spawnElectron = () => {
103
103
  Electron._logger.debug("Electron 프로세스 시작");
@@ -112,7 +112,7 @@ export class Electron {
112
112
  currentElectron = null;
113
113
  if (!isRestarting && resolveTermination != null) {
114
114
  Electron._logger.info("Electron이 종료되었습니다.");
115
- resolveTermination();
115
+ void resolveTermination();
116
116
  }
117
117
  });
118
118
  };
@@ -160,13 +160,13 @@ export class Electron {
160
160
  await new Promise<void>((resolve) => {
161
161
  let disposed = false;
162
162
 
163
- const cleanup = () => {
163
+ const cleanup = async () => {
164
164
  if (disposed) return;
165
165
  disposed = true;
166
166
  Electron._logger.debug("cleanup 시작");
167
167
  process.removeListener("SIGINT", signalHandler);
168
168
  process.removeListener("SIGTERM", signalHandler);
169
- void ctx.dispose();
169
+ await ctx.dispose();
170
170
  resolve();
171
171
  };
172
172
 
@@ -175,7 +175,7 @@ export class Electron {
175
175
  const signalHandler = () => {
176
176
  Electron._logger.debug("시그널 수신, Electron 종료 중");
177
177
  if (currentElectron != null) currentElectron.kill();
178
- cleanup();
178
+ void cleanup();
179
179
  };
180
180
 
181
181
  process.once("SIGINT", signalHandler);
@@ -1,4 +1,5 @@
1
1
  import { Worker, type WorkerProxy } from "@simplysm/core-node";
2
+ import { err as errNs } from "@simplysm/core-common";
2
3
  import { consola } from "consola";
3
4
  import type { BuildResult, ResultCollector } from "../runtime/ResultCollector";
4
5
  import { stopEngineWorker } from "../runtime/engine-stop";
@@ -182,7 +183,15 @@ export abstract class BaseEngine<
182
183
  }
183
184
  });
184
185
 
185
- this._callStartWatch(output).catch(() => {
186
+ this._callStartWatch(output).catch((err: unknown) => {
187
+ logger.error(`[${this._pkg.name}] startWatch 실패:`, errNs.message(err));
188
+ this._resultCollector?.add({
189
+ name: this._pkg.name,
190
+ target: this._getTarget(),
191
+ type: "build",
192
+ status: "error",
193
+ message: errNs.message(err),
194
+ });
186
195
  resolveInitialBuild();
187
196
  });
188
197
 
@@ -121,7 +121,7 @@ export class EsbuildClientEngine implements BuildEngine {
121
121
  ? this._pkg.config.server
122
122
  : undefined;
123
123
 
124
- await this._worker!.startWatch({
124
+ const result = await this._worker!.startWatch({
125
125
  name: this._pkg.name,
126
126
  cwd: this._cwd,
127
127
  pkgDir: this._pkg.dir,
@@ -131,6 +131,18 @@ export class EsbuildClientEngine implements BuildEngine {
131
131
  pwa: this._pkg.config.pwa,
132
132
  browserSupport: this._pkg.config.browserSupport,
133
133
  });
134
+
135
+ if (!result.success) {
136
+ const errorDetail = result.errors?.join("; ") ?? "unknown error";
137
+ logger.error(`[${this._pkg.name}] 초기 빌드 실패: ${errorDetail}`);
138
+ this._resultCollector?.add({
139
+ name: this._pkg.name,
140
+ target: "client",
141
+ type: "build",
142
+ status: "error",
143
+ message: errorDetail,
144
+ });
145
+ }
134
146
  }
135
147
 
136
148
  /**
@@ -24,7 +24,6 @@ export function createBuildEngine(
24
24
  options: {
25
25
  cwd: string;
26
26
  replaceDeps?: Record<string, string>;
27
- resolvedReplaceDeps?: Array<{ packageName: string; sourcePath: string }>;
28
27
  resultCollector?: ResultCollector;
29
28
  rebuildManager?: RebuildManager;
30
29
  /** 클라이언트 빌드 출력 경로 (EsbuildClientEngine에만 적용) */
@@ -65,16 +65,16 @@ export async function createClientEsbuildContext(
65
65
  const sourceFileCache = new SourceFileCache(cachePath);
66
66
 
67
67
  // CompilerPluginOptions
68
- const pluginOptions: CompilerPluginOptions & { browserOnlyBuild?: boolean } = {
68
+ const pluginOptions: CompilerPluginOptions = {
69
69
  tsconfig: options.tsconfig ?? path.join(options.pkgDir, "tsconfig.json"),
70
70
  sourcemap: isDev,
71
71
  advancedOptimizations: !isDev,
72
72
  thirdPartySourcemaps: isDev,
73
- incremental: true,
73
+ incremental: isDev,
74
74
  sourceFileCache,
75
75
  loadResultCache: sourceFileCache.loadResultCache,
76
76
  templateUpdates: options.templateUpdates,
77
- browserOnlyBuild: true,
77
+ includeTestMetadata: isDev,
78
78
  };
79
79
 
80
80
  // BundleStylesheetOptions
@@ -106,7 +106,7 @@ export async function createClientEsbuildContext(
106
106
  // SCSS side-effect import 처리 플러그인
107
107
  const scssPlugin = createScssPlugin({
108
108
  loadPaths: [
109
- path.join(options.pkgDir, "scss"),
109
+ path.join(options.pkgDir, "node_modules"),
110
110
  path.join(options.cwd, "node_modules"),
111
111
  ],
112
112
  });
@@ -156,7 +156,7 @@ export async function createClientEsbuildContext(
156
156
  ],
157
157
  target: esbuildTarget,
158
158
  entryNames: isDev ? "[name]" : "[name]-[hash]",
159
- chunkNames: isDev ? "[name]" : "[name]-[hash]",
159
+ chunkNames: "[name]-[hash]",
160
160
  assetNames: isDev ? "[name]" : "[name]-[hash]",
161
161
  bundle: true,
162
162
  splitting: options.legacyModule !== true,
@@ -166,7 +166,7 @@ export async function createClientEsbuildContext(
166
166
  metafile: true,
167
167
  write: true,
168
168
  sourcemap: isDev ? "linked" : false,
169
- logLevel: "silent",
169
+ logLevel: isDev ? "warning" : "silent",
170
170
  tsconfig: options.tsconfig ?? path.join(options.pkgDir, "tsconfig.json"),
171
171
  define,
172
172
  banner: hmrBanner != null ? { js: hmrBanner } : undefined,
@@ -97,11 +97,6 @@ export class DevOrchestrator extends BaseOrchestrator implements OrchestratorLif
97
97
  }
98
98
 
99
99
  // 클라이언트 패키지용 BuildEngine 생성
100
- const resolvedReplaceDeps = this._replaceDepWatcher?.entries.map((e) => ({
101
- packageName: e.targetName,
102
- sourcePath: e.resolvedSourcePath,
103
- }));
104
-
105
100
  for (const { name, dir, config } of this._clientPackages) {
106
101
  const engineConfig = { ...config, env: { ...this._baseEnv, ...config.env } };
107
102
  const engine = createBuildEngine(
@@ -109,7 +104,6 @@ export class DevOrchestrator extends BaseOrchestrator implements OrchestratorLif
109
104
  {
110
105
  cwd: this._cwd,
111
106
  replaceDeps: this._replaceDeps,
112
- resolvedReplaceDeps,
113
107
  resultCollector: this._resultCollector,
114
108
  rebuildManager: this._rebuildManager,
115
109
  },
@@ -262,8 +262,8 @@ export class TypecheckOrchestrator implements OrchestratorLifecycle<TypecheckRes
262
262
  deserializeDiagnostic(d, fileCache),
263
263
  );
264
264
  allDiagnostics.push(...buildDiags);
265
- totalErrorCount += buildDiags.filter((d) => d.category === 1).length;
266
- totalWarningCount += buildDiags.filter((d) => d.category === 0).length;
265
+ totalErrorCount += buildDiags.filter((d) => d.category === ts.DiagnosticCategory.Error).length;
266
+ totalWarningCount += buildDiags.filter((d) => d.category === ts.DiagnosticCategory.Warning).length;
267
267
  if (!engineResult.build.success && buildDiags.length === 0) {
268
268
  for (const errMsg of engineResult.build.errors) {
269
269
  allDiagnostics.push({
package/src/sd-cli.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * .js 실행 (프로덕션): replaceDeps 실행 후 새 프로세스에서 sd-cli-entry 실행
8
8
  */
9
9
 
10
- import { cpx } from "@simplysm/core-node";
10
+ import { cpx, setupConsola } from "@simplysm/core-node";
11
11
  import { consola } from "consola";
12
12
  import os from "os";
13
13
  import path from "path";
@@ -26,6 +26,7 @@ if (isDev) {
26
26
  await createCliParser(process.argv.slice(2)).parse();
27
27
  } else {
28
28
  // Production mode (.js): two-stage execution
29
+ setupConsola({ cli: true });
29
30
 
30
31
  // Phase 1: replaceDeps (인라인 — 설치된 버전으로 복사)
31
32
  try {
@@ -50,8 +50,9 @@ export function printServers(
50
50
  if (server.target === "server") {
51
51
  // 서버에 연결된 클라이언트가 있으면 클라이언트 URL만 출력
52
52
  const clients = serverClientsMap?.get(server.name) ?? [];
53
- if (clients.length > 0) {
54
- for (const clientName of clients) {
53
+ const activeClients = clients.filter((c) => results.get(`${c}:build`)?.status !== "error");
54
+ if (activeClients.length > 0) {
55
+ for (const clientName of activeClients) {
55
56
  consola.info(`[server] http://localhost:${server.port}/${clientName}/`);
56
57
  }
57
58
  } else {
@@ -282,7 +282,13 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
282
282
  // 초기 빌드 완료 시 resolve
283
283
  if (isInitialBuild) {
284
284
  isInitialBuild = false;
285
- initialBuildResolve?.({ success });
285
+ initialBuildResolve?.({
286
+ success,
287
+ errors:
288
+ result.errors.length > 0
289
+ ? result.errors.map((e) => e.text)
290
+ : undefined,
291
+ });
286
292
  }
287
293
  } catch (err) {
288
294
  const message = errNs.message(err);
@@ -309,6 +315,7 @@ async function startWatch(info: ClientBuildInfo): Promise<ClientBuildResult> {
309
315
  httpServer: httpDevServer.httpServer,
310
316
  basePath,
311
317
  templateUpdates,
318
+ outDir: outdir,
312
319
  });
313
320
 
314
321
  // 7. esbuild watch 시작 + 초기 빌드 대기
@@ -1,37 +1,10 @@
1
- import { vi } from "vitest";
2
1
  import path from "path";
3
- import type { SdConfig, SdClientPackageConfig } from "../../src/sd-config.types";
4
2
 
5
3
  export const FIXTURE_DIR = path.resolve(import.meta.dirname, "fixtures");
6
4
  export const PKG_DIR = path.resolve(FIXTURE_DIR, "packages/basic-app");
7
5
 
8
- export function createTestSdConfig(
9
- overrides?: Partial<SdClientPackageConfig>,
10
- ): SdConfig {
11
- return {
12
- packages: {
13
- "basic-app": { target: "client", server: 3000, ...overrides },
14
- },
15
- };
16
- }
17
-
18
- /** Vite lifecycle 시뮬레이션: config → configResolved → [configureServer] → buildStart */
19
- export async function initPlugin(
20
- plugin: any,
21
- options?: { mode?: string; command?: string; sourcemap?: boolean; withServer?: boolean },
22
- ): Promise<void> {
23
- const mode = options?.mode ?? "development";
24
- const command = options?.command ?? "serve";
25
- const sourcemap = options?.sourcemap ?? false;
26
- await plugin.config?.({}, { mode, command });
27
- plugin.configResolved?.({ build: { sourcemap } });
28
- if (options?.withServer === true) {
29
- plugin.configureServer?.({
30
- middlewares: { use: vi.fn() },
31
- httpServer: { on: vi.fn() },
32
- config: { base: "/" },
33
- hot: { send: vi.fn() },
34
- });
35
- }
6
+ /** Vite lifecycle 시뮬레이션: config → buildStart */
7
+ export async function initPlugin(plugin: any): Promise<void> {
8
+ await plugin.config?.({}, { mode: "development", command: "serve" });
36
9
  await plugin.buildStart?.call({});
37
10
  }
@@ -1,35 +1,22 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import path from "path";
3
- import type { SdConfig } from "../../src/sd-config.types";
4
3
  import {
5
4
  FIXTURE_DIR,
6
5
  PKG_DIR,
7
- createTestSdConfig,
8
6
  initPlugin,
9
7
  } from "./_vite-angular-plugin-test-setup";
10
8
 
11
- const mockLoadSdConfig = vi.fn<(...args: unknown[]) => Promise<SdConfig>>();
12
-
13
- vi.mock("../../src/utils/sd-config", () => ({
14
- loadSdConfig: (...args: unknown[]) => mockLoadSdConfig(...args),
15
- }));
16
-
17
9
  const { sdAngularPlugin } = await import("../../src/angular/vite-angular-plugin.js");
18
10
 
19
- describe("sdAngularPlugin legacy watch rebuild", () => {
11
+ describe("sdAngularPlugin watch rebuild", () => {
20
12
  beforeEach(() => {
21
13
  vi.clearAllMocks();
22
14
  vi.spyOn(process, "cwd").mockReturnValue(FIXTURE_DIR);
23
- mockLoadSdConfig.mockResolvedValue(
24
- createTestSdConfig({ browserSupport: { legacyModule: true } }),
25
- );
26
15
  });
27
16
 
28
17
  // Acceptance: 재빌드 시 변경 파일의 캐시가 무효화되고 증분 컴파일된다
29
18
  it("invalidates cache and produces updated output on watch rebuild", async () => {
30
- const plugin = sdAngularPlugin({
31
- pkg: "basic-app",
32
- });
19
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
33
20
 
34
21
  // 초기 빌드
35
22
  await initPlugin(plugin);
@@ -39,31 +26,29 @@ describe("sdAngularPlugin legacy watch rebuild", () => {
39
26
  .replace(/\\/g, "/");
40
27
 
41
28
  // 초기 transform 결과 캡처
42
- const initialResult = await (plugin as any).transform?.call({}, "", appComponentPath);
29
+ const initialResult = (plugin as any).transform?.call({}, "", appComponentPath);
43
30
  expect(initialResult).toBeDefined();
44
31
  expect(initialResult.code.length).toBeGreaterThan(0);
45
32
 
46
33
  // watchChange 호출 (파일 변경 알림)
47
34
  expect((plugin as any).watchChange).toBeDefined();
48
- await (plugin as any).watchChange?.call({}, appComponentPath, { event: "update" });
35
+ (plugin as any).watchChange?.call({}, appComponentPath, { event: "update" });
49
36
 
50
37
  // 재빌드 (buildStart 재호출)
51
38
  await (plugin as any).buildStart?.call({});
52
39
 
53
40
  // 재빌드 후 transform — 컴파일러가 재실행되어 결과를 반환해야 한다
54
- const rebuiltResult = await (plugin as any).transform?.call({}, "", appComponentPath);
41
+ const rebuiltResult = (plugin as any).transform?.call({}, "", appComponentPath);
55
42
  expect(rebuiltResult).toBeDefined();
56
43
  expect(rebuiltResult.code).toBeDefined();
57
44
  expect(rebuiltResult.code.length).toBeGreaterThan(0);
58
45
 
59
- await (plugin as any).buildEnd?.call({});
46
+ (plugin as any).buildEnd?.call({});
60
47
  });
61
48
 
62
- // Acceptance: 첫 buildStart는 기존 로직으로 전체 컴파일한다
49
+ // Acceptance: 첫 buildStart는 전체 컴파일한다
63
50
  it("performs full compilation on first buildStart", async () => {
64
- const plugin = sdAngularPlugin({
65
- pkg: "basic-app",
66
- });
51
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
67
52
 
68
53
  // watchChange 없이 첫 buildStart
69
54
  await initPlugin(plugin);
@@ -72,33 +57,31 @@ describe("sdAngularPlugin legacy watch rebuild", () => {
72
57
  .join(PKG_DIR, "src/app.component.ts")
73
58
  .replace(/\\/g, "/");
74
59
 
75
- const result = await (plugin as any).transform?.call({}, "", appComponentPath);
60
+ const result = (plugin as any).transform?.call({}, "", appComponentPath);
76
61
  expect(result).toBeDefined();
77
62
  expect(result.code.length).toBeGreaterThan(0);
78
63
 
79
- await (plugin as any).buildEnd?.call({});
64
+ (plugin as any).buildEnd?.call({});
80
65
  });
81
66
 
82
67
  // Acceptance: watchChange 없이 buildStart 재호출 시에도 정상 동작
83
68
  it("handles buildStart re-invocation without watchChange gracefully", async () => {
84
- const plugin = sdAngularPlugin({
85
- pkg: "basic-app",
86
- });
69
+ const plugin = sdAngularPlugin({ pkg: "basic-app" });
87
70
 
88
71
  // 초기 빌드
89
72
  await initPlugin(plugin);
90
73
 
91
- // watchChange 없이 재빌드 (예: Rolldown이 변경 없이 재빌드 트리거)
74
+ // watchChange 없이 재빌드
92
75
  await (plugin as any).buildStart?.call({});
93
76
 
94
77
  const appComponentPath = path
95
78
  .join(PKG_DIR, "src/app.component.ts")
96
79
  .replace(/\\/g, "/");
97
80
 
98
- const result = await (plugin as any).transform?.call({}, "", appComponentPath);
81
+ const result = (plugin as any).transform?.call({}, "", appComponentPath);
99
82
  expect(result).toBeDefined();
100
83
  expect(result.code.length).toBeGreaterThan(0);
101
84
 
102
- await (plugin as any).buildEnd?.call({});
85
+ (plugin as any).buildEnd?.call({});
103
86
  });
104
87
  });
@@ -1,19 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeAll } from "vitest";
2
2
  import type { Plugin } from "vite";
3
3
  import { resolve } from "node:path";
4
- import type { SdConfig } from "../../src/sd-config.types";
5
4
  import {
6
5
  FIXTURE_DIR,
7
6
  PKG_DIR,
8
- createTestSdConfig,
9
7
  } from "./_vite-angular-plugin-test-setup";
10
8
 
11
- const mockLoadSdConfig = vi.fn<(...args: unknown[]) => Promise<SdConfig>>();
12
-
13
- vi.mock("../../src/utils/sd-config", () => ({
14
- loadSdConfig: (...args: unknown[]) => mockLoadSdConfig(...args),
15
- }));
16
-
17
9
  const { sdAngularPlugin } = await import("../../src/angular/vite-angular-plugin");
18
10
 
19
11
  describe("sdAngularPlugin Vitest 지원", () => {
@@ -21,18 +13,16 @@ describe("sdAngularPlugin Vitest 지원", () => {
21
13
 
22
14
  beforeAll(async () => {
23
15
  vi.spyOn(process, "cwd").mockReturnValue(FIXTURE_DIR);
24
- mockLoadSdConfig.mockResolvedValue(createTestSdConfig());
25
16
 
26
17
  plugin = sdAngularPlugin({ pkg: "basic-app" });
27
18
  await (plugin as any).config?.({}, { mode: "development", command: "serve" });
28
- (plugin as any).configResolved?.({ build: { sourcemap: false } });
29
19
  await (plugin as any).buildStart?.call({});
30
20
  }, 60_000);
31
21
 
32
22
  // Scenario: @Component 소스 컴파일 및 ɵcmp 런타임 코드 서빙
33
- it("compiles @Component source and serves ɵcmp runtime code", async () => {
23
+ it("compiles @Component source and serves ɵcmp runtime code", () => {
34
24
  const filePath = resolve(PKG_DIR, "src/app.component.ts");
35
- const result = await (plugin as any).transform?.call({}, "", filePath);
25
+ const result = (plugin as any).transform?.call({}, "", filePath);
36
26
 
37
27
  expect(result).toBeDefined();
38
28
  expect(result!.code).toContain("ɵcmp");
@@ -40,32 +30,12 @@ describe("sdAngularPlugin Vitest 지원", () => {
40
30
  });
41
31
 
42
32
  // Scenario: node_modules .ts 파일은 AOT 대상이 아니므로 undefined 반환
43
- it("returns undefined for node_modules .ts path", async () => {
44
- const result = await (plugin as any).transform?.call(
33
+ it("returns undefined for node_modules .ts path", () => {
34
+ const result = (plugin as any).transform?.call(
45
35
  {},
46
36
  "",
47
37
  resolve(FIXTURE_DIR, "node_modules/@angular/core/index.ts"),
48
38
  );
49
39
  expect(result).toBeUndefined();
50
40
  });
51
-
52
- // Scenario: browser target 패키지에서 플러그인 초기화
53
- it("initializes successfully with non-client (browser target) package", async () => {
54
- mockLoadSdConfig.mockResolvedValue(
55
- createTestSdConfig({ target: "browser" } as any),
56
- );
57
-
58
- const browserPlugin = sdAngularPlugin({ pkg: "basic-app" });
59
-
60
- await (browserPlugin as any).config?.({}, { mode: "development", command: "serve" });
61
- (browserPlugin as any).configResolved?.({ build: { sourcemap: false } });
62
- await (browserPlugin as any).buildStart?.call({});
63
-
64
- const appComponentPath = resolve(PKG_DIR, "src/app.component.ts");
65
- const result = await (browserPlugin as any).transform?.call({}, "", appComponentPath);
66
- expect(result).toBeDefined();
67
- expect(result.code.length).toBeGreaterThan(0);
68
-
69
- await (browserPlugin as any).buildEnd?.call({});
70
- });
71
41
  });