@simplysm/sd-cli 14.0.37 → 14.0.39
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.
- package/dist/angular/angular-build-pipeline.d.ts +1 -1
- package/dist/angular/angular-build-pipeline.js +1 -1
- package/dist/angular/client-transform-stylesheet.d.ts +1 -1
- package/dist/angular/client-transform-stylesheet.js +3 -3
- package/dist/angular/vite-angular-plugin.d.ts +2 -5
- package/dist/angular/vite-angular-plugin.d.ts.map +1 -1
- package/dist/angular/vite-angular-plugin.js +19 -72
- package/dist/angular/vite-angular-plugin.js.map +1 -1
- package/dist/commands/publish/storage-publisher.js +1 -0
- package/dist/commands/publish/storage-publisher.js.map +1 -1
- package/dist/dev-server/hmr-service.d.ts +2 -0
- package/dist/dev-server/hmr-service.d.ts.map +1 -1
- package/dist/dev-server/hmr-service.js +24 -10
- package/dist/dev-server/hmr-service.js.map +1 -1
- package/dist/electron/electron.js +4 -4
- package/dist/electron/electron.js.map +1 -1
- package/dist/engines/BaseEngine.d.ts.map +1 -1
- package/dist/engines/BaseEngine.js +10 -1
- package/dist/engines/BaseEngine.js.map +1 -1
- package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
- package/dist/engines/EsbuildClientEngine.js +12 -1
- package/dist/engines/EsbuildClientEngine.js.map +1 -1
- package/dist/engines/engine-factory.d.ts +0 -4
- package/dist/engines/engine-factory.d.ts.map +1 -1
- package/dist/engines/engine-factory.js.map +1 -1
- package/dist/esbuild/esbuild-client-config.d.ts +0 -2
- package/dist/esbuild/esbuild-client-config.d.ts.map +1 -1
- package/dist/esbuild/esbuild-client-config.js +24 -14
- package/dist/esbuild/esbuild-client-config.js.map +1 -1
- package/dist/esbuild/esbuild-postcss-plugin.d.ts +8 -0
- package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -0
- package/dist/esbuild/esbuild-postcss-plugin.js +105 -0
- package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -0
- package/dist/esbuild/esbuild-tsc-plugin.d.ts +23 -0
- package/dist/esbuild/esbuild-tsc-plugin.d.ts.map +1 -0
- package/dist/esbuild/esbuild-tsc-plugin.js +60 -0
- package/dist/esbuild/esbuild-tsc-plugin.js.map +1 -0
- package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/DevOrchestrator.js +0 -5
- package/dist/orchestrators/DevOrchestrator.js.map +1 -1
- package/dist/orchestrators/TypecheckOrchestrator.js +2 -2
- package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -1
- package/dist/sd-cli.js +2 -1
- package/dist/sd-cli.js.map +1 -1
- package/dist/utils/output-utils.d.ts.map +1 -1
- package/dist/utils/output-utils.js +3 -2
- package/dist/utils/output-utils.js.map +1 -1
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +39 -3
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +129 -90
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-esbuild-context.d.ts +27 -0
- package/dist/workers/server-esbuild-context.d.ts.map +1 -1
- package/dist/workers/server-esbuild-context.js +43 -3
- package/dist/workers/server-esbuild-context.js.map +1 -1
- package/package.json +6 -4
- package/src/angular/angular-build-pipeline.ts +2 -2
- package/src/angular/client-transform-stylesheet.ts +4 -4
- package/src/angular/vite-angular-plugin.ts +19 -82
- package/src/commands/publish/storage-publisher.ts +1 -0
- package/src/dev-server/hmr-service.ts +28 -13
- package/src/electron/electron.ts +5 -5
- package/src/engines/BaseEngine.ts +10 -1
- package/src/engines/EsbuildClientEngine.ts +13 -1
- package/src/engines/engine-factory.ts +0 -1
- package/src/esbuild/esbuild-client-config.ts +27 -18
- package/src/esbuild/esbuild-postcss-plugin.ts +117 -0
- package/src/esbuild/esbuild-tsc-plugin.ts +83 -0
- package/src/orchestrators/DevOrchestrator.ts +0 -6
- package/src/orchestrators/TypecheckOrchestrator.ts +2 -2
- package/src/sd-cli.ts +2 -1
- package/src/utils/output-utils.ts +3 -2
- package/src/workers/client.worker.ts +40 -3
- package/src/workers/server-build.worker.ts +136 -97
- package/src/workers/server-esbuild-context.ts +59 -3
- package/tests/angular/_vite-angular-plugin-test-setup.ts +3 -30
- package/tests/angular/client-transform-stylesheet.spec.ts +1 -1
- package/tests/angular/vite-angular-plugin-legacy-watch.spec.ts +14 -31
- package/tests/angular/vite-angular-plugin-vitest.spec.ts +4 -34
- package/tests/angular/vite-angular-plugin.spec.ts +24 -60
- package/tests/commands/typecheck.spec.ts +1 -1
- package/tests/engines/base-engine.spec.ts +25 -0
- package/tests/engines/engine-adapter-isolation.spec.ts +3 -3
- package/tests/engines/esbuild-client-engine.acc.spec.ts +29 -0
- package/tests/engines/esbuild-client-engine.spec.ts +26 -0
- package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +349 -0
- package/tests/esbuild/esbuild-tsc-plugin.spec.ts +230 -0
- package/tests/orchestrators/build-orchestrator.spec.ts +1 -1
- package/tests/orchestrators/dev-orchestrator.spec.ts +1 -1
- package/tests/orchestrators/typecheck-orchestrator.spec.ts +1 -1
- package/tests/orchestrators/watch-orchestrator.spec.ts +1 -1
- package/tests/utils/esbuild-client-config-postcss.verify.md +6 -0
- package/tests/utils/esbuild-client-config.acc.spec.ts +30 -15
- package/tests/utils/esbuild-client-config.spec.ts +73 -11
- package/tests/utils/esbuild-postcss-plugin.acc.spec.ts +299 -0
- package/tests/utils/esbuild-postcss-plugin.spec.ts +290 -0
- package/tests/utils/esbuild-scss-plugin.acc.spec.ts +1 -0
- package/tests/utils/hmr-service-dispatcher.acc.spec.ts +70 -0
- package/tests/workers/client-worker-initial-build-error.verify.md +8 -0
- package/tests/workers/server-build-lint.spec.ts +43 -0
- package/tests/workers/server-build-worker-refactoring.verify.md +14 -0
- package/tests/workers/server-build-worker.spec.ts +122 -9
- package/tests/workers/server-esbuild-context-tsc.verify.md +7 -0
- package/tests/workers/server-esbuild-context.acc.spec.ts +156 -2
- package/tests/workers/server-esbuild-context.spec.ts +320 -2
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
1
2
|
import esbuild from "esbuild";
|
|
2
3
|
import {
|
|
3
4
|
createServerEsbuildOptions,
|
|
4
5
|
writeChangedOutputFiles,
|
|
5
6
|
} from "../esbuild/esbuild-config";
|
|
7
|
+
import { createTscPlugin, type TscPluginResult } from "../esbuild/esbuild-tsc-plugin";
|
|
8
|
+
import type { TypecheckEnv } from "../utils/tsconfig";
|
|
9
|
+
import type { SerializedDiagnostic } from "../typecheck/typecheck-serialization";
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* esbuild watch context 생성 옵션
|
|
@@ -12,6 +16,13 @@ export interface EsbuildContextOptions {
|
|
|
12
16
|
entryPoints: string[];
|
|
13
17
|
env?: Record<string, string>;
|
|
14
18
|
external: string[];
|
|
19
|
+
/** tsc 플러그인 옵션. 제공 시 createTscPlugin으로 플러그인을 생성하여 esbuild context에 포함한다. */
|
|
20
|
+
tsc?: {
|
|
21
|
+
cwd: string;
|
|
22
|
+
output: { dts: boolean };
|
|
23
|
+
env?: TypecheckEnv;
|
|
24
|
+
includeTests?: boolean;
|
|
25
|
+
};
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
/** esbuild watch context (모듈 스코프 상태) */
|
|
@@ -20,11 +31,24 @@ let context: esbuild.BuildContext | undefined;
|
|
|
20
31
|
/** 마지막 빌드의 metafile (변경 필터링용) */
|
|
21
32
|
let lastMetafile: esbuild.Metafile | undefined;
|
|
22
33
|
|
|
34
|
+
/** tsc 플러그인 인스턴스 (모듈 스코프 상태) */
|
|
35
|
+
let tscPlugin: TscPluginResult | undefined;
|
|
36
|
+
|
|
23
37
|
/**
|
|
24
38
|
* esbuild watch context를 생성한다.
|
|
25
39
|
* dev 모드 전용 (metafile:true, write:false).
|
|
26
40
|
*/
|
|
27
41
|
export async function createContext(options: EsbuildContextOptions): Promise<void> {
|
|
42
|
+
if (options.tsc != null) {
|
|
43
|
+
tscPlugin = createTscPlugin({
|
|
44
|
+
pkgDir: options.pkgDir,
|
|
45
|
+
cwd: options.tsc.cwd,
|
|
46
|
+
output: options.tsc.output,
|
|
47
|
+
env: options.tsc.env,
|
|
48
|
+
includeTests: options.tsc.includeTests,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
28
52
|
const baseOptions = createServerEsbuildOptions({
|
|
29
53
|
pkgDir: options.pkgDir,
|
|
30
54
|
entryPoints: options.entryPoints,
|
|
@@ -35,6 +59,7 @@ export async function createContext(options: EsbuildContextOptions): Promise<voi
|
|
|
35
59
|
|
|
36
60
|
context = await esbuild.context({
|
|
37
61
|
...baseOptions,
|
|
62
|
+
plugins: tscPlugin != null ? [tscPlugin.plugin] : [],
|
|
38
63
|
metafile: true,
|
|
39
64
|
write: false,
|
|
40
65
|
});
|
|
@@ -61,12 +86,14 @@ export async function rebuild(): Promise<{
|
|
|
61
86
|
await writeChangedOutputFiles(result.outputFiles);
|
|
62
87
|
}
|
|
63
88
|
|
|
64
|
-
const
|
|
89
|
+
const esbuildErrors = result.errors.map((e) => e.text);
|
|
90
|
+
const tscErrors = tscPlugin?.getErrors() ?? [];
|
|
91
|
+
const allErrors = [...esbuildErrors, ...tscErrors];
|
|
65
92
|
const warnings = result.warnings.map((w) => w.text);
|
|
66
93
|
|
|
67
94
|
return {
|
|
68
|
-
success:
|
|
69
|
-
errors:
|
|
95
|
+
success: allErrors.length === 0,
|
|
96
|
+
errors: allErrors.length > 0 ? allErrors : undefined,
|
|
70
97
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
71
98
|
};
|
|
72
99
|
}
|
|
@@ -85,6 +112,10 @@ export async function recreateContext(options: EsbuildContextOptions): Promise<v
|
|
|
85
112
|
context = undefined;
|
|
86
113
|
lastMetafile = undefined;
|
|
87
114
|
|
|
115
|
+
if (tscPlugin != null) {
|
|
116
|
+
tscPlugin.resetBuilderProgram();
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
try {
|
|
89
120
|
await createContext(options);
|
|
90
121
|
} finally {
|
|
@@ -101,6 +132,7 @@ export async function dispose(): Promise<void> {
|
|
|
101
132
|
const contextToDispose = context;
|
|
102
133
|
context = undefined;
|
|
103
134
|
lastMetafile = undefined;
|
|
135
|
+
tscPlugin = undefined;
|
|
104
136
|
|
|
105
137
|
if (contextToDispose != null) {
|
|
106
138
|
await contextToDispose.dispose();
|
|
@@ -120,3 +152,27 @@ export function getMetafile(): esbuild.Metafile | undefined {
|
|
|
120
152
|
export function hasContext(): boolean {
|
|
121
153
|
return context != null;
|
|
122
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* tsc 플러그인의 ts.Program을 반환한다.
|
|
158
|
+
* 플러그인이 없으면 undefined를 반환한다.
|
|
159
|
+
*/
|
|
160
|
+
export function getTscProgram(): ts.Program | undefined {
|
|
161
|
+
return tscPlugin?.getProgram();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* tsc 플러그인의 affected files를 반환한다.
|
|
166
|
+
* 플러그인이 없으면 undefined를 반환한다.
|
|
167
|
+
*/
|
|
168
|
+
export function getTscAffectedFiles(): ReadonlySet<string> | undefined {
|
|
169
|
+
return tscPlugin?.getAffectedFiles();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* tsc 플러그인의 diagnostics를 반환한다.
|
|
174
|
+
* 플러그인이 없으면 빈 배열을 반환한다.
|
|
175
|
+
*/
|
|
176
|
+
export function getTscDiagnostics(): SerializedDiagnostic[] {
|
|
177
|
+
return tscPlugin?.getDiagnostics() ?? [];
|
|
178
|
+
}
|
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
}
|
|
@@ -118,7 +118,7 @@ describe("createClientTransformStylesheet", () => {
|
|
|
118
118
|
const deps = new Map<string, Set<string>>();
|
|
119
119
|
const transform = createClientTransformStylesheet({
|
|
120
120
|
loadPaths: [],
|
|
121
|
-
|
|
121
|
+
postcssPlugins: [testPlugin],
|
|
122
122
|
scssErrors: errors,
|
|
123
123
|
scssDependencies: deps,
|
|
124
124
|
});
|
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 없이 재빌드
|
|
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 =
|
|
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
|
-
|
|
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",
|
|
23
|
+
it("compiles @Component source and serves ɵcmp runtime code", () => {
|
|
34
24
|
const filePath = resolve(PKG_DIR, "src/app.component.ts");
|
|
35
|
-
const result =
|
|
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",
|
|
44
|
-
const result =
|
|
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
|
});
|
|
@@ -1,114 +1,95 @@
|
|
|
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
11
|
beforeEach(() => {
|
|
20
12
|
vi.clearAllMocks();
|
|
21
13
|
vi.spyOn(process, "cwd").mockReturnValue(FIXTURE_DIR);
|
|
22
|
-
mockLoadSdConfig.mockResolvedValue(createTestSdConfig());
|
|
23
14
|
});
|
|
24
15
|
|
|
25
16
|
describe("sdAngularPlugin", () => {
|
|
26
|
-
// config() define 반환은 Vitest 축소로 제거됨 (esbuild-client-config에서 처리)
|
|
27
|
-
|
|
28
17
|
// Scenario: non-Angular .ts 파일은 기본 처리
|
|
29
18
|
it("returns undefined for non-emitted .ts files", async () => {
|
|
30
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app"});
|
|
19
|
+
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
31
20
|
await initPlugin(plugin);
|
|
32
21
|
|
|
33
|
-
const result =
|
|
22
|
+
const result = (plugin as any).transform?.call(
|
|
34
23
|
{},
|
|
35
24
|
"export const x = 1;",
|
|
36
25
|
"/some/unknown/file.ts",
|
|
37
26
|
);
|
|
38
27
|
|
|
39
28
|
expect(result).toBeUndefined();
|
|
40
|
-
|
|
29
|
+
(plugin as any).buildEnd?.call({});
|
|
41
30
|
});
|
|
42
31
|
|
|
43
32
|
// Scenario: buildStart에서 초기화 및 emit + buildEnd에서 리소스 정리
|
|
44
33
|
it("initializes facade in buildStart and disposes in buildEnd", async () => {
|
|
45
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app"});
|
|
34
|
+
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
46
35
|
await initPlugin(plugin);
|
|
47
|
-
|
|
36
|
+
(plugin as any).buildEnd?.call({});
|
|
48
37
|
});
|
|
49
38
|
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Scenario: .mjs 파일이 JavaScriptTransformer를 통과한다
|
|
54
|
-
it("transforms .mjs files through JavaScriptTransformer", async () => {
|
|
55
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app"});
|
|
39
|
+
// Scenario: .mjs/.js 파일은 처리하지 않는다
|
|
40
|
+
it("returns undefined for .mjs files", async () => {
|
|
41
|
+
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
56
42
|
await initPlugin(plugin);
|
|
57
43
|
|
|
58
|
-
const result =
|
|
44
|
+
const result = (plugin as any).transform?.call(
|
|
59
45
|
{},
|
|
60
46
|
"export const x = 1;",
|
|
61
47
|
"/some/library/module.mjs",
|
|
62
48
|
);
|
|
63
49
|
|
|
64
|
-
expect(result).
|
|
65
|
-
|
|
66
|
-
expect(typeof result.code).toBe("string");
|
|
67
|
-
await (plugin as any).buildEnd?.call({});
|
|
50
|
+
expect(result).toBeUndefined();
|
|
51
|
+
(plugin as any).buildEnd?.call({});
|
|
68
52
|
});
|
|
69
53
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app"});
|
|
54
|
+
it("returns undefined for .js files", async () => {
|
|
55
|
+
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
73
56
|
await initPlugin(plugin);
|
|
74
57
|
|
|
75
|
-
const result =
|
|
58
|
+
const result = (plugin as any).transform?.call(
|
|
76
59
|
{},
|
|
77
60
|
"export const y = 2;",
|
|
78
61
|
"/some/library/module.js",
|
|
79
62
|
);
|
|
80
63
|
|
|
81
|
-
expect(result).
|
|
82
|
-
|
|
83
|
-
expect(typeof result.code).toBe("string");
|
|
84
|
-
await (plugin as any).buildEnd?.call({});
|
|
64
|
+
expect(result).toBeUndefined();
|
|
65
|
+
(plugin as any).buildEnd?.call({});
|
|
85
66
|
});
|
|
86
67
|
|
|
87
68
|
// Scenario: 비대상 파일(.css 등)은 transform하지 않는다
|
|
88
69
|
it("returns undefined for non-JS files like .css", async () => {
|
|
89
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app"});
|
|
70
|
+
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
90
71
|
await initPlugin(plugin);
|
|
91
72
|
|
|
92
|
-
const result =
|
|
73
|
+
const result = (plugin as any).transform?.call(
|
|
93
74
|
{},
|
|
94
75
|
"body { color: red; }",
|
|
95
76
|
"/some/styles.css",
|
|
96
77
|
);
|
|
97
78
|
|
|
98
79
|
expect(result).toBeUndefined();
|
|
99
|
-
|
|
80
|
+
(plugin as any).buildEnd?.call({});
|
|
100
81
|
});
|
|
101
82
|
|
|
102
|
-
// Scenario: Angular .ts 파일 transform
|
|
83
|
+
// Scenario: Angular .ts 파일 transform
|
|
103
84
|
it("transforms emitted .ts files with compiled JS", async () => {
|
|
104
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app"});
|
|
105
|
-
await initPlugin(plugin
|
|
85
|
+
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
86
|
+
await initPlugin(plugin);
|
|
106
87
|
|
|
107
88
|
const appComponentPath = path
|
|
108
89
|
.join(PKG_DIR, "src/app.component.ts")
|
|
109
90
|
.replace(/\\/g, "/");
|
|
110
91
|
|
|
111
|
-
const result =
|
|
92
|
+
const result = (plugin as any).transform?.call({}, "", appComponentPath);
|
|
112
93
|
|
|
113
94
|
if (result != null) {
|
|
114
95
|
expect(result).toHaveProperty("code");
|
|
@@ -116,23 +97,6 @@ describe("sdAngularPlugin", () => {
|
|
|
116
97
|
expect(result.code.length).toBeGreaterThan(0);
|
|
117
98
|
}
|
|
118
99
|
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// config() define 반환값은 Vitest 축소로 제거됨 (Feature 3.3에서 별도 테스트)
|
|
123
|
-
|
|
124
|
-
// Scenario: browserSupport 미설정 시 기본값 동작
|
|
125
|
-
it("works when browserSupport is not set in sd.config.ts", async () => {
|
|
126
|
-
mockLoadSdConfig.mockResolvedValue(createTestSdConfig());
|
|
127
|
-
|
|
128
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
129
|
-
|
|
130
|
-
await initPlugin(plugin);
|
|
131
|
-
|
|
132
|
-
const appComponentPath = PKG_DIR.replace(/\\/g, "/") + "/src/app.component.ts";
|
|
133
|
-
const result = await (plugin as any).transform?.call({}, "", appComponentPath);
|
|
134
|
-
expect(result).toBeDefined();
|
|
135
|
-
|
|
136
|
-
await (plugin as any).buildEnd?.call({});
|
|
100
|
+
(plugin as any).buildEnd?.call({});
|
|
137
101
|
});
|
|
138
102
|
});
|
|
@@ -27,7 +27,7 @@ vi.mock("../../src/typecheck/typecheck-non-package", () => ({
|
|
|
27
27
|
typecheckNonPackageFiles: mocks.typecheckNonPackageFiles,
|
|
28
28
|
}));
|
|
29
29
|
|
|
30
|
-
vi.mock("../../src/engines/
|
|
30
|
+
vi.mock("../../src/engines/engine-factory", () => ({
|
|
31
31
|
createTypecheckEngine: mocks.createTypecheckEngine,
|
|
32
32
|
}));
|
|
33
33
|
|
|
@@ -254,6 +254,31 @@ describe("BaseEngine", () => {
|
|
|
254
254
|
await engine.stop();
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
+
it("_callStartWatch 실패 시 에러를 ResultCollector에 보고하고 resolveInitialBuild를 호출한다", async () => {
|
|
258
|
+
const mockResultCollector = { add: vi.fn() };
|
|
259
|
+
|
|
260
|
+
mockWorker.startWatch.mockRejectedValue(new Error("Worker RPC failed"));
|
|
261
|
+
|
|
262
|
+
const engine = new TscEngine({
|
|
263
|
+
cwd: "/root",
|
|
264
|
+
pkg: createMockPkg(),
|
|
265
|
+
resultCollector: mockResultCollector as any,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// startWatch()가 hang하지 않고 resolve되어야 한다
|
|
269
|
+
await engine.startWatch({ js: true, dts: true });
|
|
270
|
+
|
|
271
|
+
// ResultCollector에 에러가 보고되어야 한다
|
|
272
|
+
const errorReport = mockResultCollector.add.mock.calls.find(
|
|
273
|
+
(c: any[]) => c[0].type === "build" && c[0].status === "error",
|
|
274
|
+
);
|
|
275
|
+
expect(errorReport).toBeDefined();
|
|
276
|
+
expect(errorReport![0].name).toBe("test-pkg");
|
|
277
|
+
expect(errorReport![0].message).toContain("Worker RPC failed");
|
|
278
|
+
|
|
279
|
+
await engine.stop();
|
|
280
|
+
});
|
|
281
|
+
|
|
257
282
|
it("calls resolver on error event to release RebuildManager batch", async () => {
|
|
258
283
|
const mockResolver = vi.fn();
|
|
259
284
|
const mockRebuildManager = { registerBuild: vi.fn(() => mockResolver) };
|
|
@@ -25,7 +25,7 @@ describe("EsbuildClientEngine adapter isolation", () => {
|
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it("vite-angular-plugin.ts
|
|
28
|
+
it("vite-angular-plugin.ts does not import @angular/* directly", () => {
|
|
29
29
|
|
|
30
30
|
const pluginFile = path.resolve(
|
|
31
31
|
import.meta.dirname,
|
|
@@ -33,8 +33,8 @@ describe("EsbuildClientEngine adapter isolation", () => {
|
|
|
33
33
|
);
|
|
34
34
|
const content = fs.readFileSync(pluginFile, "utf-8");
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
expect(content).
|
|
36
|
+
const angularImportPattern = /from\s+["']@angular\/(build|compiler-cli)/;
|
|
37
|
+
expect(angularImportPattern.test(content)).toBe(false);
|
|
38
38
|
expect(content).not.toContain("createAngularCompilation");
|
|
39
39
|
expect(content).not.toMatch(/\bSourceFileCache\b(?<!AngularSourceFileCache)/);
|
|
40
40
|
expect(content).not.toContain("ComponentStylesheetBundler");
|
|
@@ -134,6 +134,35 @@ describe("EsbuildClientEngine Acceptance", () => {
|
|
|
134
134
|
await engine.stop();
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
// Scenario: 초기 빌드 실패 시 ResultCollector에 에러 보고
|
|
138
|
+
it("초기 빌드 실패 시 ResultCollector에 에러가 보고된다", async () => {
|
|
139
|
+
// Given: client 패키지가 정의되어 있다
|
|
140
|
+
const mockResultCollector = { add: vi.fn() };
|
|
141
|
+
|
|
142
|
+
mockWorker.startWatch.mockResolvedValue({
|
|
143
|
+
success: false,
|
|
144
|
+
errors: ["esbuild compilation failed"],
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const engine = new EsbuildClientEngine({
|
|
148
|
+
cwd: "/root",
|
|
149
|
+
pkg: createMockPkg(),
|
|
150
|
+
resultCollector: mockResultCollector as any,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// When: startWatch()를 호출한다
|
|
154
|
+
await engine.startWatch({ js: true, dts: false });
|
|
155
|
+
|
|
156
|
+
// Then: ResultCollector에 에러가 보고된다
|
|
157
|
+
const errorReport = mockResultCollector.add.mock.calls.find(
|
|
158
|
+
(c: any[]) => c[0].type === "build" && c[0].status === "error",
|
|
159
|
+
);
|
|
160
|
+
expect(errorReport).toBeDefined();
|
|
161
|
+
expect(errorReport![0].name).toBe("my-client");
|
|
162
|
+
|
|
163
|
+
await engine.stop();
|
|
164
|
+
});
|
|
165
|
+
|
|
137
166
|
// Scenario: 엔진 중지
|
|
138
167
|
it("stop()으로 worker를 종료하고 .dev-port를 삭제한다", async () => {
|
|
139
168
|
// Given: dev watch 모드가 실행 중이다
|
|
@@ -271,6 +271,32 @@ describe("EsbuildClientEngine", () => {
|
|
|
271
271
|
await engine.stop();
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
+
it("초기 빌드 실패 시 에러를 logger.error로 출력한다", async () => {
|
|
275
|
+
mockWorker.startWatch.mockResolvedValue({
|
|
276
|
+
success: false,
|
|
277
|
+
errors: ["Module not found: @angular/core", "Syntax error in app.ts"],
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const engine = new EsbuildClientEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
281
|
+
await engine.startWatch({ js: true, dts: false });
|
|
282
|
+
|
|
283
|
+
// startWatch가 reject하지 않고 정상 완료되는지 확인 (기존 동작 유지)
|
|
284
|
+
// 에러 로깅은 verify.md에서 검증
|
|
285
|
+
expect(mockWorker.startWatch).toHaveBeenCalled();
|
|
286
|
+
|
|
287
|
+
await engine.stop();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("초기 빌드 성공 시 에러 로깅을 하지 않는다", async () => {
|
|
291
|
+
mockWorker.startWatch.mockResolvedValue({ success: true });
|
|
292
|
+
|
|
293
|
+
const engine = new EsbuildClientEngine({ cwd: "/root", pkg: createMockPkg() });
|
|
294
|
+
// 예외 없이 완료
|
|
295
|
+
await expect(engine.startWatch({ js: true, dts: false })).resolves.toBeUndefined();
|
|
296
|
+
|
|
297
|
+
await engine.stop();
|
|
298
|
+
});
|
|
299
|
+
|
|
274
300
|
it("scopeRebuild 이벤트를 구독하지 않는다", async () => {
|
|
275
301
|
mockWorker.startWatch.mockResolvedValue({ success: true });
|
|
276
302
|
|