@simplysm/sd-cli 14.0.38 → 14.0.40
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/dev-server/hmr-client-script.d.ts +2 -2
- package/dist/dev-server/hmr-client-script.d.ts.map +1 -1
- package/dist/dev-server/hmr-client-script.js +4 -4
- package/dist/dev-server/hmr-client-script.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 +20 -10
- 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/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +94 -26
- 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 +57 -4
- 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/dev-server/hmr-client-script.ts +4 -4
- package/src/esbuild/esbuild-client-config.ts +22 -13
- package/src/esbuild/esbuild-postcss-plugin.ts +117 -0
- package/src/esbuild/esbuild-tsc-plugin.ts +83 -0
- package/src/workers/client.worker.ts +96 -29
- package/src/workers/server-build.worker.ts +136 -97
- package/src/workers/server-esbuild-context.ts +72 -4
- package/tests/angular/client-transform-stylesheet.spec.ts +1 -1
- package/tests/esbuild/esbuild-tsc-plugin.acc.spec.ts +349 -0
- package/tests/esbuild/esbuild-tsc-plugin.spec.ts +230 -0
- package/tests/utils/esbuild-client-config-postcss.verify.md +6 -0
- package/tests/utils/esbuild-client-config.acc.spec.ts +34 -20
- package/tests/utils/esbuild-client-config.spec.ts +79 -16
- 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-client-script.acc.spec.ts +8 -8
- package/tests/utils/hmr-client-script.spec.ts +5 -5
- 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 +188 -2
- package/tests/workers/server-esbuild-context.spec.ts +401 -2
|
@@ -5,6 +5,15 @@ import { describe, it, expect, vi, afterEach } from "vitest";
|
|
|
5
5
|
const mockRebuild = vi.fn();
|
|
6
6
|
const mockDispose = vi.fn();
|
|
7
7
|
|
|
8
|
+
const mockTscPlugin = {
|
|
9
|
+
plugin: { name: "sd-tsc", setup: vi.fn() },
|
|
10
|
+
getProgram: vi.fn(),
|
|
11
|
+
getAffectedFiles: vi.fn(),
|
|
12
|
+
getDiagnostics: vi.fn((): unknown[] => []),
|
|
13
|
+
getErrors: vi.fn(),
|
|
14
|
+
resetBuilderProgram: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
8
17
|
vi.mock("esbuild", () => ({
|
|
9
18
|
default: {
|
|
10
19
|
context: vi.fn(() =>
|
|
@@ -21,11 +30,18 @@ vi.mock("../../src/esbuild/esbuild-config", async (importOriginal) => {
|
|
|
21
30
|
};
|
|
22
31
|
});
|
|
23
32
|
|
|
33
|
+
vi.mock("../../src/esbuild/esbuild-tsc-plugin", () => ({
|
|
34
|
+
createTscPlugin: vi.fn(() => mockTscPlugin),
|
|
35
|
+
}));
|
|
36
|
+
|
|
24
37
|
//#endregion
|
|
25
38
|
|
|
26
39
|
const esbuild = (await import("esbuild")).default;
|
|
27
|
-
const {
|
|
28
|
-
|
|
40
|
+
const {
|
|
41
|
+
createContext, rebuild, recreateContext, dispose, getMetafile, hasContext,
|
|
42
|
+
getTscProgram, getTscAffectedFiles, getTscDiagnostics,
|
|
43
|
+
} = await import("../../src/workers/server-esbuild-context");
|
|
44
|
+
const { createTscPlugin } = await import("../../src/esbuild/esbuild-tsc-plugin");
|
|
29
45
|
|
|
30
46
|
const baseOptions = {
|
|
31
47
|
pkgDir: "/workspace/packages/my-server",
|
|
@@ -33,11 +49,22 @@ const baseOptions = {
|
|
|
33
49
|
external: [],
|
|
34
50
|
};
|
|
35
51
|
|
|
52
|
+
const baseTscOptions = {
|
|
53
|
+
cwd: "/workspace",
|
|
54
|
+
output: { dts: true },
|
|
55
|
+
};
|
|
56
|
+
|
|
36
57
|
describe("server-esbuild-context lifecycle", () => {
|
|
37
58
|
afterEach(async () => {
|
|
38
59
|
mockRebuild.mockReset();
|
|
39
60
|
mockDispose.mockReset();
|
|
61
|
+
mockTscPlugin.getProgram.mockReset();
|
|
62
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
63
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
64
|
+
mockTscPlugin.getErrors.mockReset();
|
|
65
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
40
66
|
vi.mocked(esbuild.context).mockClear();
|
|
67
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
41
68
|
await dispose();
|
|
42
69
|
});
|
|
43
70
|
|
|
@@ -96,3 +123,162 @@ describe("server-esbuild-context lifecycle", () => {
|
|
|
96
123
|
expect(result).toBeNull();
|
|
97
124
|
});
|
|
98
125
|
});
|
|
126
|
+
|
|
127
|
+
describe("server-esbuild-context tsc integration lifecycle", () => {
|
|
128
|
+
afterEach(async () => {
|
|
129
|
+
mockRebuild.mockReset();
|
|
130
|
+
mockDispose.mockReset();
|
|
131
|
+
mockTscPlugin.getProgram.mockReset();
|
|
132
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
133
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
134
|
+
mockTscPlugin.getErrors.mockReset();
|
|
135
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
136
|
+
vi.mocked(esbuild.context).mockClear();
|
|
137
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
138
|
+
await dispose();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Acceptance: create with tsc → delegation works → dispose clears all
|
|
142
|
+
it("manages tsc plugin lifecycle: create with tsc → delegation returns plugin values → dispose clears", async () => {
|
|
143
|
+
const fakeProgram = { id: "fake-program" };
|
|
144
|
+
const fakeAffectedFiles = new Set(["/src/main.ts"]);
|
|
145
|
+
const fakeDiagnostics = [{ category: 1, code: 2322, messageText: "Type error" }];
|
|
146
|
+
mockTscPlugin.getProgram.mockReturnValue(fakeProgram);
|
|
147
|
+
mockTscPlugin.getAffectedFiles.mockReturnValue(fakeAffectedFiles);
|
|
148
|
+
mockTscPlugin.getDiagnostics.mockReturnValue(fakeDiagnostics);
|
|
149
|
+
|
|
150
|
+
// Create with tsc options
|
|
151
|
+
await createContext({ ...baseOptions, tsc: baseTscOptions });
|
|
152
|
+
expect(hasContext()).toBe(true);
|
|
153
|
+
|
|
154
|
+
// Delegation methods return plugin values
|
|
155
|
+
expect(getTscProgram()).toBe(fakeProgram);
|
|
156
|
+
expect(getTscAffectedFiles()).toBe(fakeAffectedFiles);
|
|
157
|
+
expect(getTscDiagnostics()).toEqual(fakeDiagnostics);
|
|
158
|
+
|
|
159
|
+
// Dispose clears everything
|
|
160
|
+
await dispose();
|
|
161
|
+
expect(hasContext()).toBe(false);
|
|
162
|
+
expect(getTscProgram()).toBeUndefined();
|
|
163
|
+
expect(getTscAffectedFiles()).toBeUndefined();
|
|
164
|
+
expect(getTscDiagnostics()).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Acceptance: create with tsc → recreate without tsc → plugin persists
|
|
168
|
+
it("persists tsc plugin across context recreation when tsc options omitted", async () => {
|
|
169
|
+
const fakeProgram = { id: "persisted-program" };
|
|
170
|
+
mockTscPlugin.getProgram.mockReturnValue(fakeProgram);
|
|
171
|
+
|
|
172
|
+
// Initial create with tsc
|
|
173
|
+
await createContext({ ...baseOptions, tsc: baseTscOptions });
|
|
174
|
+
expect(getTscProgram()).toBe(fakeProgram);
|
|
175
|
+
|
|
176
|
+
// Recreate without tsc options — plugin persists
|
|
177
|
+
await recreateContext(baseOptions);
|
|
178
|
+
expect(hasContext()).toBe(true);
|
|
179
|
+
expect(getTscProgram()).toBe(fakeProgram);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Acceptance: create without tsc → no plugin → delegation returns defaults
|
|
183
|
+
it("returns default values from delegation methods when no tsc plugin exists", async () => {
|
|
184
|
+
await createContext(baseOptions);
|
|
185
|
+
|
|
186
|
+
expect(hasContext()).toBe(true);
|
|
187
|
+
expect(getTscProgram()).toBeUndefined();
|
|
188
|
+
expect(getTscAffectedFiles()).toBeUndefined();
|
|
189
|
+
expect(getTscDiagnostics()).toEqual([]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Acceptance: rebuild merges esbuild + tsc errors
|
|
193
|
+
it("merges esbuild and tsc errors in rebuild result", async () => {
|
|
194
|
+
mockRebuild.mockResolvedValue({
|
|
195
|
+
errors: [{ text: "esbuild syntax error" }],
|
|
196
|
+
warnings: [{ text: "deprecation" }],
|
|
197
|
+
outputFiles: [],
|
|
198
|
+
metafile: undefined,
|
|
199
|
+
});
|
|
200
|
+
mockTscPlugin.getErrors.mockReturnValue(["TS2322: type mismatch"]);
|
|
201
|
+
|
|
202
|
+
await createContext({ ...baseOptions, tsc: baseTscOptions });
|
|
203
|
+
const result = await rebuild();
|
|
204
|
+
|
|
205
|
+
expect(result).toEqual({
|
|
206
|
+
success: false,
|
|
207
|
+
errors: ["esbuild syntax error", "TS2322: type mismatch"],
|
|
208
|
+
warnings: ["deprecation"],
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Acceptance: recreateContext resets tsc and persists plugin
|
|
213
|
+
it("resets tsc builderProgram on recreateContext and persists plugin for next rebuild", async () => {
|
|
214
|
+
mockRebuild.mockResolvedValue({
|
|
215
|
+
errors: [],
|
|
216
|
+
warnings: [],
|
|
217
|
+
outputFiles: [],
|
|
218
|
+
metafile: { inputs: {}, outputs: {} },
|
|
219
|
+
});
|
|
220
|
+
mockTscPlugin.getErrors.mockReturnValue(undefined);
|
|
221
|
+
|
|
222
|
+
await createContext({ ...baseOptions, tsc: baseTscOptions });
|
|
223
|
+
|
|
224
|
+
// recreateContext — resets tsc + creates new esbuild context
|
|
225
|
+
await recreateContext(baseOptions);
|
|
226
|
+
|
|
227
|
+
expect(mockTscPlugin.resetBuilderProgram).toHaveBeenCalled();
|
|
228
|
+
expect(hasContext()).toBe(true);
|
|
229
|
+
|
|
230
|
+
// rebuild still works with merged result
|
|
231
|
+
const result = await rebuild();
|
|
232
|
+
expect(result).toEqual({
|
|
233
|
+
success: true,
|
|
234
|
+
errors: undefined,
|
|
235
|
+
warnings: undefined,
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Acceptance: rebuild() reject 시 tsc 에러와 병합하여 정상 결과 반환
|
|
240
|
+
it("merges tsc errors when context.rebuild() rejects instead of propagating throw", async () => {
|
|
241
|
+
mockRebuild.mockResolvedValueOnce({
|
|
242
|
+
errors: [],
|
|
243
|
+
warnings: [],
|
|
244
|
+
outputFiles: [],
|
|
245
|
+
metafile: { inputs: { "src/main.ts": {} }, outputs: {} },
|
|
246
|
+
});
|
|
247
|
+
mockTscPlugin.getErrors.mockReturnValue(["TS2322: type mismatch"]);
|
|
248
|
+
|
|
249
|
+
await createContext({ ...baseOptions, tsc: baseTscOptions });
|
|
250
|
+
|
|
251
|
+
// 첫 빌드 성공 (metafile 설정)
|
|
252
|
+
await rebuild();
|
|
253
|
+
const savedMetafile = getMetafile();
|
|
254
|
+
expect(savedMetafile).toBeDefined();
|
|
255
|
+
|
|
256
|
+
// 두 번째 빌드 실패 — reject
|
|
257
|
+
mockRebuild.mockRejectedValueOnce(new Error("Build failed with 1 error"));
|
|
258
|
+
|
|
259
|
+
const result = await rebuild();
|
|
260
|
+
|
|
261
|
+
// throw 전파 대신 정상 결과 반환 — esbuild + tsc 에러 병합
|
|
262
|
+
expect(result).toEqual({
|
|
263
|
+
success: false,
|
|
264
|
+
errors: ["Build failed with 1 error", "TS2322: type mismatch"],
|
|
265
|
+
warnings: undefined,
|
|
266
|
+
});
|
|
267
|
+
// 이전 metafile 유지
|
|
268
|
+
expect(getMetafile()).toBe(savedMetafile);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Acceptance: LOGIC-001 — recreateContext failure preserves tsc plugin reset
|
|
272
|
+
it("resets tsc and disposes old context even when recreateContext fails (LOGIC-001)", async () => {
|
|
273
|
+
await createContext({ ...baseOptions, tsc: baseTscOptions });
|
|
274
|
+
|
|
275
|
+
// New context creation fails
|
|
276
|
+
vi.mocked(esbuild.context).mockRejectedValueOnce(new Error("creation failed"));
|
|
277
|
+
|
|
278
|
+
await expect(recreateContext(baseOptions)).rejects.toThrow("creation failed");
|
|
279
|
+
|
|
280
|
+
expect(mockTscPlugin.resetBuilderProgram).toHaveBeenCalled();
|
|
281
|
+
expect(mockDispose).toHaveBeenCalled();
|
|
282
|
+
expect(hasContext()).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -5,6 +5,15 @@ import { describe, it, expect, vi, afterEach } from "vitest";
|
|
|
5
5
|
const mockRebuild = vi.fn();
|
|
6
6
|
const mockDispose = vi.fn();
|
|
7
7
|
|
|
8
|
+
const mockTscPlugin = {
|
|
9
|
+
plugin: { name: "sd-tsc", setup: vi.fn() },
|
|
10
|
+
getProgram: vi.fn(),
|
|
11
|
+
getAffectedFiles: vi.fn(),
|
|
12
|
+
getDiagnostics: vi.fn((): unknown[] => []),
|
|
13
|
+
getErrors: vi.fn(),
|
|
14
|
+
resetBuilderProgram: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
8
17
|
vi.mock("esbuild", () => ({
|
|
9
18
|
default: {
|
|
10
19
|
context: vi.fn(() =>
|
|
@@ -21,12 +30,19 @@ vi.mock("../../src/esbuild/esbuild-config", async (importOriginal) => {
|
|
|
21
30
|
};
|
|
22
31
|
});
|
|
23
32
|
|
|
33
|
+
vi.mock("../../src/esbuild/esbuild-tsc-plugin", () => ({
|
|
34
|
+
createTscPlugin: vi.fn(() => mockTscPlugin),
|
|
35
|
+
}));
|
|
36
|
+
|
|
24
37
|
//#endregion
|
|
25
38
|
|
|
26
39
|
const esbuild = (await import("esbuild")).default;
|
|
27
40
|
const { writeChangedOutputFiles } = await import("../../src/esbuild/esbuild-config");
|
|
28
|
-
const {
|
|
29
|
-
|
|
41
|
+
const {
|
|
42
|
+
createContext, rebuild, recreateContext, dispose, getMetafile, hasContext,
|
|
43
|
+
getTscProgram, getTscAffectedFiles, getTscDiagnostics,
|
|
44
|
+
} = await import("../../src/workers/server-esbuild-context");
|
|
45
|
+
const { createTscPlugin } = await import("../../src/esbuild/esbuild-tsc-plugin");
|
|
30
46
|
|
|
31
47
|
const baseOptions = {
|
|
32
48
|
pkgDir: "/workspace/packages/my-server",
|
|
@@ -38,8 +54,14 @@ describe("createContext", () => {
|
|
|
38
54
|
afterEach(async () => {
|
|
39
55
|
mockRebuild.mockReset();
|
|
40
56
|
mockDispose.mockReset();
|
|
57
|
+
mockTscPlugin.getProgram.mockReset();
|
|
58
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
59
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
60
|
+
mockTscPlugin.getErrors.mockReset();
|
|
61
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
41
62
|
vi.mocked(esbuild.context).mockClear();
|
|
42
63
|
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
64
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
43
65
|
await dispose();
|
|
44
66
|
});
|
|
45
67
|
|
|
@@ -72,8 +94,14 @@ describe("rebuild", () => {
|
|
|
72
94
|
afterEach(async () => {
|
|
73
95
|
mockRebuild.mockReset();
|
|
74
96
|
mockDispose.mockReset();
|
|
97
|
+
mockTscPlugin.getProgram.mockReset();
|
|
98
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
99
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
100
|
+
mockTscPlugin.getErrors.mockReset();
|
|
101
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
75
102
|
vi.mocked(esbuild.context).mockClear();
|
|
76
103
|
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
104
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
77
105
|
await dispose();
|
|
78
106
|
});
|
|
79
107
|
|
|
@@ -124,12 +152,99 @@ describe("rebuild", () => {
|
|
|
124
152
|
});
|
|
125
153
|
});
|
|
126
154
|
|
|
155
|
+
describe("rebuild — error handling on reject", () => {
|
|
156
|
+
afterEach(async () => {
|
|
157
|
+
mockRebuild.mockReset();
|
|
158
|
+
mockDispose.mockReset();
|
|
159
|
+
mockTscPlugin.getProgram.mockReset();
|
|
160
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
161
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
162
|
+
mockTscPlugin.getErrors.mockReset();
|
|
163
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
164
|
+
vi.mocked(esbuild.context).mockClear();
|
|
165
|
+
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
166
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
167
|
+
await dispose();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("catches reject and returns merged esbuild + tsc errors", async () => {
|
|
171
|
+
mockRebuild.mockRejectedValue(new Error("Build failed"));
|
|
172
|
+
mockTscPlugin.getErrors.mockReturnValue(["type error"]);
|
|
173
|
+
|
|
174
|
+
await createContext({ ...baseOptions, tsc: { cwd: "/workspace", output: { dts: true } } });
|
|
175
|
+
const result = await rebuild();
|
|
176
|
+
|
|
177
|
+
expect(result).toEqual({
|
|
178
|
+
success: false,
|
|
179
|
+
errors: ["Build failed", "type error"],
|
|
180
|
+
warnings: undefined,
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("returns esbuild error only when tsc has no errors", async () => {
|
|
185
|
+
mockRebuild.mockRejectedValue(new Error("Build failed"));
|
|
186
|
+
mockTscPlugin.getErrors.mockReturnValue(undefined);
|
|
187
|
+
|
|
188
|
+
await createContext({ ...baseOptions, tsc: { cwd: "/workspace", output: { dts: true } } });
|
|
189
|
+
const result = await rebuild();
|
|
190
|
+
|
|
191
|
+
expect(result).toEqual({
|
|
192
|
+
success: false,
|
|
193
|
+
errors: ["Build failed"],
|
|
194
|
+
warnings: undefined,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("returns esbuild error when no tsc plugin exists", async () => {
|
|
199
|
+
mockRebuild.mockRejectedValue(new Error("Build failed"));
|
|
200
|
+
|
|
201
|
+
await createContext(baseOptions);
|
|
202
|
+
const result = await rebuild();
|
|
203
|
+
|
|
204
|
+
expect(result).toEqual({
|
|
205
|
+
success: false,
|
|
206
|
+
errors: ["Build failed"],
|
|
207
|
+
warnings: undefined,
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("preserves previous metafile when rebuild rejects", async () => {
|
|
212
|
+
const mockMetafile = { inputs: { "src/main.ts": {} }, outputs: {} };
|
|
213
|
+
mockRebuild.mockResolvedValueOnce({
|
|
214
|
+
errors: [],
|
|
215
|
+
warnings: [],
|
|
216
|
+
outputFiles: [],
|
|
217
|
+
metafile: mockMetafile,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await createContext(baseOptions);
|
|
221
|
+
await rebuild();
|
|
222
|
+
expect(getMetafile()).toBe(mockMetafile);
|
|
223
|
+
|
|
224
|
+
mockRebuild.mockRejectedValueOnce(new Error("Build failed"));
|
|
225
|
+
const result = await rebuild();
|
|
226
|
+
|
|
227
|
+
expect(result).toEqual({
|
|
228
|
+
success: false,
|
|
229
|
+
errors: ["Build failed"],
|
|
230
|
+
warnings: undefined,
|
|
231
|
+
});
|
|
232
|
+
expect(getMetafile()).toBe(mockMetafile);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
127
236
|
describe("recreateContext", () => {
|
|
128
237
|
afterEach(async () => {
|
|
129
238
|
mockRebuild.mockReset();
|
|
130
239
|
mockDispose.mockReset();
|
|
240
|
+
mockTscPlugin.getProgram.mockReset();
|
|
241
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
242
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
243
|
+
mockTscPlugin.getErrors.mockReset();
|
|
244
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
131
245
|
vi.mocked(esbuild.context).mockClear();
|
|
132
246
|
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
247
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
133
248
|
await dispose();
|
|
134
249
|
});
|
|
135
250
|
|
|
@@ -169,8 +284,14 @@ describe("dispose", () => {
|
|
|
169
284
|
afterEach(() => {
|
|
170
285
|
mockRebuild.mockReset();
|
|
171
286
|
mockDispose.mockReset();
|
|
287
|
+
mockTscPlugin.getProgram.mockReset();
|
|
288
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
289
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
290
|
+
mockTscPlugin.getErrors.mockReset();
|
|
291
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
172
292
|
vi.mocked(esbuild.context).mockClear();
|
|
173
293
|
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
294
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
174
295
|
});
|
|
175
296
|
|
|
176
297
|
it("is safe to call when no context exists", async () => {
|
|
@@ -195,4 +316,282 @@ describe("dispose", () => {
|
|
|
195
316
|
expect(hasContext()).toBe(false);
|
|
196
317
|
expect(getMetafile()).toBeUndefined();
|
|
197
318
|
});
|
|
319
|
+
|
|
320
|
+
it("clears tscPlugin reference on dispose", async () => {
|
|
321
|
+
mockTscPlugin.getProgram.mockReturnValue({ id: "prog" });
|
|
322
|
+
|
|
323
|
+
await createContext({
|
|
324
|
+
...baseOptions,
|
|
325
|
+
tsc: { cwd: "/workspace", output: { dts: true } },
|
|
326
|
+
});
|
|
327
|
+
expect(getTscProgram()).toEqual({ id: "prog" });
|
|
328
|
+
|
|
329
|
+
await dispose();
|
|
330
|
+
|
|
331
|
+
expect(getTscProgram()).toBeUndefined();
|
|
332
|
+
expect(getTscAffectedFiles()).toBeUndefined();
|
|
333
|
+
expect(getTscDiagnostics()).toEqual([]);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("createContext — tsc plugin", () => {
|
|
338
|
+
afterEach(async () => {
|
|
339
|
+
mockRebuild.mockReset();
|
|
340
|
+
mockDispose.mockReset();
|
|
341
|
+
mockTscPlugin.getProgram.mockReset();
|
|
342
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
343
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
344
|
+
mockTscPlugin.getErrors.mockReset();
|
|
345
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
346
|
+
vi.mocked(esbuild.context).mockClear();
|
|
347
|
+
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
348
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
349
|
+
await dispose();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("includes tsc plugin in esbuild context when tsc options provided", async () => {
|
|
353
|
+
await createContext({
|
|
354
|
+
...baseOptions,
|
|
355
|
+
tsc: { cwd: "/workspace", output: { dts: true } },
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
expect(esbuild.context).toHaveBeenCalledWith(
|
|
359
|
+
expect.objectContaining({
|
|
360
|
+
plugins: [mockTscPlugin.plugin],
|
|
361
|
+
}),
|
|
362
|
+
);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("creates esbuild context without plugins when no tsc options and no existing plugin", async () => {
|
|
366
|
+
await createContext(baseOptions);
|
|
367
|
+
|
|
368
|
+
expect(esbuild.context).toHaveBeenCalledWith(
|
|
369
|
+
expect.objectContaining({
|
|
370
|
+
plugins: [],
|
|
371
|
+
}),
|
|
372
|
+
);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("reuses existing tsc plugin when tsc options absent in subsequent createContext", async () => {
|
|
376
|
+
// First: create with tsc
|
|
377
|
+
await createContext({
|
|
378
|
+
...baseOptions,
|
|
379
|
+
tsc: { cwd: "/workspace", output: { dts: true } },
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Dispose context only (not module-level dispose — use recreateContext pattern)
|
|
383
|
+
vi.mocked(esbuild.context).mockClear();
|
|
384
|
+
|
|
385
|
+
// Second: create without tsc — should reuse existing plugin
|
|
386
|
+
await recreateContext(baseOptions);
|
|
387
|
+
|
|
388
|
+
expect(esbuild.context).toHaveBeenCalledWith(
|
|
389
|
+
expect.objectContaining({
|
|
390
|
+
plugins: [mockTscPlugin.plugin],
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("passes tsc env and includeTests options to createTscPlugin", async () => {
|
|
396
|
+
await createContext({
|
|
397
|
+
...baseOptions,
|
|
398
|
+
tsc: {
|
|
399
|
+
cwd: "/workspace",
|
|
400
|
+
output: { dts: false },
|
|
401
|
+
env: "node",
|
|
402
|
+
includeTests: true,
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
expect(createTscPlugin).toHaveBeenCalledWith({
|
|
407
|
+
pkgDir: baseOptions.pkgDir,
|
|
408
|
+
cwd: "/workspace",
|
|
409
|
+
output: { dts: false },
|
|
410
|
+
env: "node",
|
|
411
|
+
includeTests: true,
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe("delegation methods", () => {
|
|
417
|
+
afterEach(async () => {
|
|
418
|
+
mockTscPlugin.getProgram.mockReset();
|
|
419
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
420
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
421
|
+
mockTscPlugin.getErrors.mockReset();
|
|
422
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
423
|
+
vi.mocked(esbuild.context).mockClear();
|
|
424
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
425
|
+
await dispose();
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it("getTscProgram returns plugin value when plugin exists", async () => {
|
|
429
|
+
const fakeProgram = { getSourceFiles: () => [] };
|
|
430
|
+
mockTscPlugin.getProgram.mockReturnValue(fakeProgram);
|
|
431
|
+
|
|
432
|
+
await createContext({
|
|
433
|
+
...baseOptions,
|
|
434
|
+
tsc: { cwd: "/workspace", output: { dts: true } },
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
expect(getTscProgram()).toBe(fakeProgram);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it("getTscAffectedFiles returns plugin value when plugin exists", async () => {
|
|
441
|
+
const fakeFiles = new Set(["/src/main.ts"]);
|
|
442
|
+
mockTscPlugin.getAffectedFiles.mockReturnValue(fakeFiles);
|
|
443
|
+
|
|
444
|
+
await createContext({
|
|
445
|
+
...baseOptions,
|
|
446
|
+
tsc: { cwd: "/workspace", output: { dts: true } },
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(getTscAffectedFiles()).toBe(fakeFiles);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it("getTscDiagnostics returns plugin value when plugin exists", async () => {
|
|
453
|
+
const fakeDiag = [{ category: 1, code: 2322, messageText: "err" }];
|
|
454
|
+
mockTscPlugin.getDiagnostics.mockReturnValue(fakeDiag);
|
|
455
|
+
|
|
456
|
+
await createContext({
|
|
457
|
+
...baseOptions,
|
|
458
|
+
tsc: { cwd: "/workspace", output: { dts: true } },
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(getTscDiagnostics()).toEqual(fakeDiag);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
describe("rebuild — tsc error merge", () => {
|
|
467
|
+
afterEach(async () => {
|
|
468
|
+
mockRebuild.mockReset();
|
|
469
|
+
mockDispose.mockReset();
|
|
470
|
+
mockTscPlugin.getProgram.mockReset();
|
|
471
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
472
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
473
|
+
mockTscPlugin.getErrors.mockReset();
|
|
474
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
475
|
+
vi.mocked(esbuild.context).mockClear();
|
|
476
|
+
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
477
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
478
|
+
await dispose();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("returns success when both esbuild and tsc have no errors", async () => {
|
|
482
|
+
mockRebuild.mockResolvedValue({
|
|
483
|
+
errors: [],
|
|
484
|
+
warnings: [],
|
|
485
|
+
outputFiles: [],
|
|
486
|
+
metafile: { inputs: {}, outputs: {} },
|
|
487
|
+
});
|
|
488
|
+
mockTscPlugin.getErrors.mockReturnValue(undefined);
|
|
489
|
+
|
|
490
|
+
await createContext({ ...baseOptions, tsc: { cwd: "/workspace", output: { dts: true } } });
|
|
491
|
+
const result = await rebuild();
|
|
492
|
+
|
|
493
|
+
expect(result).toEqual({
|
|
494
|
+
success: true,
|
|
495
|
+
errors: undefined,
|
|
496
|
+
warnings: undefined,
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("returns tsc errors only when esbuild succeeds but tsc fails", async () => {
|
|
501
|
+
mockRebuild.mockResolvedValue({
|
|
502
|
+
errors: [],
|
|
503
|
+
warnings: [],
|
|
504
|
+
outputFiles: [],
|
|
505
|
+
metafile: { inputs: {}, outputs: {} },
|
|
506
|
+
});
|
|
507
|
+
mockTscPlugin.getErrors.mockReturnValue(["TS2322: type mismatch"]);
|
|
508
|
+
|
|
509
|
+
await createContext({ ...baseOptions, tsc: { cwd: "/workspace", output: { dts: true } } });
|
|
510
|
+
const result = await rebuild();
|
|
511
|
+
|
|
512
|
+
expect(result).toEqual({
|
|
513
|
+
success: false,
|
|
514
|
+
errors: ["TS2322: type mismatch"],
|
|
515
|
+
warnings: undefined,
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("returns esbuild errors only when esbuild fails but tsc succeeds", async () => {
|
|
520
|
+
mockRebuild.mockResolvedValue({
|
|
521
|
+
errors: [{ text: "syntax error" }],
|
|
522
|
+
warnings: [],
|
|
523
|
+
outputFiles: [],
|
|
524
|
+
metafile: undefined,
|
|
525
|
+
});
|
|
526
|
+
mockTscPlugin.getErrors.mockReturnValue(undefined);
|
|
527
|
+
|
|
528
|
+
await createContext({ ...baseOptions, tsc: { cwd: "/workspace", output: { dts: true } } });
|
|
529
|
+
const result = await rebuild();
|
|
530
|
+
|
|
531
|
+
expect(result).toEqual({
|
|
532
|
+
success: false,
|
|
533
|
+
errors: ["syntax error"],
|
|
534
|
+
warnings: undefined,
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("merges esbuild and tsc errors when both fail", async () => {
|
|
539
|
+
mockRebuild.mockResolvedValue({
|
|
540
|
+
errors: [{ text: "esbuild error" }],
|
|
541
|
+
warnings: [{ text: "warning" }],
|
|
542
|
+
outputFiles: [],
|
|
543
|
+
metafile: undefined,
|
|
544
|
+
});
|
|
545
|
+
mockTscPlugin.getErrors.mockReturnValue(["tsc error 1", "tsc error 2"]);
|
|
546
|
+
|
|
547
|
+
await createContext({ ...baseOptions, tsc: { cwd: "/workspace", output: { dts: true } } });
|
|
548
|
+
const result = await rebuild();
|
|
549
|
+
|
|
550
|
+
expect(result).toEqual({
|
|
551
|
+
success: false,
|
|
552
|
+
errors: ["esbuild error", "tsc error 1", "tsc error 2"],
|
|
553
|
+
warnings: ["warning"],
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it("returns esbuild-only result when no tsc plugin exists", async () => {
|
|
558
|
+
mockRebuild.mockResolvedValue({
|
|
559
|
+
errors: [{ text: "error" }],
|
|
560
|
+
warnings: [],
|
|
561
|
+
outputFiles: [],
|
|
562
|
+
metafile: undefined,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
await createContext(baseOptions);
|
|
566
|
+
const result = await rebuild();
|
|
567
|
+
|
|
568
|
+
expect(result).toEqual({
|
|
569
|
+
success: false,
|
|
570
|
+
errors: ["error"],
|
|
571
|
+
warnings: undefined,
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe("recreateContext — tsc reset", () => {
|
|
577
|
+
afterEach(async () => {
|
|
578
|
+
mockRebuild.mockReset();
|
|
579
|
+
mockDispose.mockReset();
|
|
580
|
+
mockTscPlugin.getProgram.mockReset();
|
|
581
|
+
mockTscPlugin.getAffectedFiles.mockReset();
|
|
582
|
+
mockTscPlugin.getDiagnostics.mockReset().mockReturnValue([]);
|
|
583
|
+
mockTscPlugin.getErrors.mockReset();
|
|
584
|
+
mockTscPlugin.resetBuilderProgram.mockReset();
|
|
585
|
+
vi.mocked(esbuild.context).mockClear();
|
|
586
|
+
vi.mocked(writeChangedOutputFiles).mockClear();
|
|
587
|
+
vi.mocked(createTscPlugin).mockClear();
|
|
588
|
+
await dispose();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it("does not fail when recreateContext is called without tsc plugin", async () => {
|
|
592
|
+
await createContext(baseOptions);
|
|
593
|
+
|
|
594
|
+
await expect(recreateContext(baseOptions)).resolves.not.toThrow();
|
|
595
|
+
expect(hasContext()).toBe(true);
|
|
596
|
+
});
|
|
198
597
|
});
|