@simplysm/sd-cli 14.0.19 → 14.0.21

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 (89) hide show
  1. package/dist/angular/vite-postcss-inline-plugin.d.ts.map +1 -1
  2. package/dist/angular/vite-postcss-inline-plugin.js +4 -1
  3. package/dist/angular/vite-postcss-inline-plugin.js.map +1 -1
  4. package/dist/capacitor/capacitor-android.d.ts +16 -0
  5. package/dist/capacitor/capacitor-android.d.ts.map +1 -0
  6. package/dist/capacitor/capacitor-android.js +289 -0
  7. package/dist/capacitor/capacitor-android.js.map +1 -0
  8. package/dist/capacitor/capacitor.d.ts +0 -49
  9. package/dist/capacitor/capacitor.d.ts.map +1 -1
  10. package/dist/capacitor/capacitor.js +4 -244
  11. package/dist/capacitor/capacitor.js.map +1 -1
  12. package/dist/commands/check.js +2 -2
  13. package/dist/commands/check.js.map +1 -1
  14. package/dist/commands/lint.d.ts +1 -42
  15. package/dist/commands/lint.d.ts.map +1 -1
  16. package/dist/commands/lint.js +1 -151
  17. package/dist/commands/lint.js.map +1 -1
  18. package/dist/commands/publish.d.ts.map +1 -1
  19. package/dist/commands/publish.js +2 -1
  20. package/dist/commands/publish.js.map +1 -1
  21. package/dist/commands/typecheck.d.ts +3 -40
  22. package/dist/commands/typecheck.d.ts.map +1 -1
  23. package/dist/commands/typecheck.js +3 -232
  24. package/dist/commands/typecheck.js.map +1 -1
  25. package/dist/electron/electron.js +11 -4
  26. package/dist/electron/electron.js.map +1 -1
  27. package/dist/orchestrators/DevWatchOrchestrator.d.ts +1 -0
  28. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  29. package/dist/orchestrators/DevWatchOrchestrator.js +10 -6
  30. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  31. package/dist/orchestrators/TypecheckOrchestrator.d.ts +74 -0
  32. package/dist/orchestrators/TypecheckOrchestrator.d.ts.map +1 -0
  33. package/dist/orchestrators/TypecheckOrchestrator.js +285 -0
  34. package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -0
  35. package/dist/sd-cli.js +6 -1
  36. package/dist/sd-cli.js.map +1 -1
  37. package/dist/utils/lint-core.d.ts +43 -0
  38. package/dist/utils/lint-core.d.ts.map +1 -0
  39. package/dist/utils/lint-core.js +154 -0
  40. package/dist/utils/lint-core.js.map +1 -0
  41. package/dist/utils/lint-utils.d.ts +1 -1
  42. package/dist/utils/lint-utils.d.ts.map +1 -1
  43. package/dist/utils/server-production-files.d.ts +22 -0
  44. package/dist/utils/server-production-files.d.ts.map +1 -0
  45. package/dist/utils/server-production-files.js +162 -0
  46. package/dist/utils/server-production-files.js.map +1 -0
  47. package/dist/workers/lint.worker.d.ts +1 -1
  48. package/dist/workers/lint.worker.d.ts.map +1 -1
  49. package/dist/workers/lint.worker.js +1 -1
  50. package/dist/workers/lint.worker.js.map +1 -1
  51. package/dist/workers/server-build.worker.d.ts.map +1 -1
  52. package/dist/workers/server-build.worker.js +11 -161
  53. package/dist/workers/server-build.worker.js.map +1 -1
  54. package/package.json +4 -4
  55. package/src/angular/vite-postcss-inline-plugin.ts +5 -1
  56. package/src/capacitor/capacitor-android.ts +368 -0
  57. package/src/capacitor/capacitor.ts +4 -317
  58. package/src/commands/check.ts +2 -2
  59. package/src/commands/lint.ts +1 -201
  60. package/src/commands/publish.ts +2 -1
  61. package/src/commands/typecheck.ts +7 -292
  62. package/src/electron/electron.ts +4 -4
  63. package/src/orchestrators/DevWatchOrchestrator.ts +10 -6
  64. package/src/orchestrators/TypecheckOrchestrator.ts +364 -0
  65. package/src/sd-cli.ts +6 -1
  66. package/src/utils/lint-core.ts +205 -0
  67. package/src/utils/lint-utils.ts +1 -1
  68. package/src/utils/server-production-files.ts +186 -0
  69. package/src/workers/lint.worker.ts +1 -1
  70. package/src/workers/server-build.worker.ts +10 -185
  71. package/tests/angular/vite-postcss-inline-plugin.spec.ts +10 -0
  72. package/tests/capacitor/capacitor-android-exports.verify.md +11 -0
  73. package/tests/capacitor/capacitor-android.spec.ts +219 -0
  74. package/tests/capacitor/capacitor-build.spec.ts +17 -21
  75. package/tests/capacitor/capacitor-icon.spec.ts +17 -19
  76. package/tests/capacitor/capacitor-init.spec.ts +18 -14
  77. package/tests/capacitor/capacitor-run.spec.ts +10 -24
  78. package/tests/capacitor/capacitor-workspace.spec.ts +10 -15
  79. package/tests/commands/check.spec.ts +2 -2
  80. package/tests/commands/lint.spec.ts +33 -194
  81. package/tests/commands/publish-set.verify.md +7 -0
  82. package/tests/electron/electron-symlink-cleanup.verify.md +8 -0
  83. package/tests/orchestrators/dist-delete-watcher.verify.md +10 -0
  84. package/tests/orchestrators/typecheck-orchestrator.spec.ts +180 -0
  85. package/tests/sd-cli-catch-all.verify.md +7 -0
  86. package/tests/utils/lint-core-import-paths.verify.md +10 -0
  87. package/tests/utils/lint-core.spec.ts +188 -0
  88. package/tests/utils/server-production-files-import-paths.verify.md +14 -0
  89. package/tests/workers/server-build-context-dispose.verify.md +8 -0
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ //#region Mocks
4
+
5
+ const mocks = vi.hoisted(() => ({
6
+ loadSdConfig: vi.fn(),
7
+ deserializeDiagnostic: vi.fn((d: any) => d),
8
+ typecheckNonPackageFiles: vi.fn(),
9
+ createBuildEngine: vi.fn(),
10
+ discoverWorkspacePackages: vi.fn(),
11
+ mergeTestsPackagesIntoConfig: vi.fn(),
12
+ }));
13
+
14
+ const mockEngines: Array<{
15
+ run: ReturnType<typeof vi.fn>;
16
+ stop: ReturnType<typeof vi.fn>;
17
+ }> = [];
18
+
19
+ vi.mock("../../src/utils/sd-config", () => ({
20
+ loadSdConfig: mocks.loadSdConfig,
21
+ }));
22
+
23
+ vi.mock("../../src/utils/typecheck-serialization", () => ({
24
+ deserializeDiagnostic: mocks.deserializeDiagnostic,
25
+ }));
26
+
27
+ vi.mock("../../src/utils/typecheck-non-package", () => ({
28
+ typecheckNonPackageFiles: mocks.typecheckNonPackageFiles,
29
+ }));
30
+
31
+ vi.mock("../../src/engines/index", () => ({
32
+ createBuildEngine: mocks.createBuildEngine,
33
+ }));
34
+
35
+ vi.mock("../../src/utils/package-utils", async (importOriginal) => {
36
+ const actual = await importOriginal<typeof import("../../src/utils/package-utils")>();
37
+ return {
38
+ ...actual,
39
+ discoverWorkspacePackages: mocks.discoverWorkspacePackages,
40
+ mergeTestsPackagesIntoConfig: mocks.mergeTestsPackagesIntoConfig,
41
+ };
42
+ });
43
+
44
+ vi.mock("typescript", async (importOriginal) => {
45
+ const orig = await importOriginal<Record<string, unknown>>();
46
+ const origDefault = (orig["default"] ?? orig) as Record<string, unknown>;
47
+ return {
48
+ ...orig,
49
+ default: {
50
+ ...origDefault,
51
+ sortAndDeduplicateDiagnostics: vi.fn((d: unknown[]) => d),
52
+ formatDiagnosticsWithColorAndContext: vi.fn((diags: Array<{ messageText: string }>) =>
53
+ diags.map((d) => `formatted: ${d.messageText}`).join("\n"),
54
+ ),
55
+ },
56
+ };
57
+ });
58
+
59
+ vi.mock("consola", () => {
60
+ const fns = (): Record<string, unknown> => ({
61
+ debug: vi.fn(), start: vi.fn(), success: vi.fn(),
62
+ info: vi.fn(), error: vi.fn(), warn: vi.fn(), log: vi.fn(),
63
+ withTag: vi.fn(() => fns()),
64
+ level: 0,
65
+ });
66
+ const c = fns();
67
+ return { consola: c, default: c, LogLevels: {} };
68
+ });
69
+
70
+ const { TypecheckOrchestrator } = await import(
71
+ "../../src/orchestrators/TypecheckOrchestrator"
72
+ );
73
+
74
+ //#endregion
75
+
76
+ //#region Helpers
77
+
78
+ function createMockEngine() {
79
+ const engine = {
80
+ run: vi.fn().mockResolvedValue({
81
+ build: { success: true, errors: [], warnings: [], diagnostics: [] },
82
+ }),
83
+ startWatch: vi.fn(),
84
+ stop: vi.fn().mockResolvedValue(undefined),
85
+ };
86
+ mockEngines.push(engine);
87
+ return engine;
88
+ }
89
+
90
+ function setupDefaults(packages: Record<string, any> = {}) {
91
+ mocks.loadSdConfig.mockResolvedValue({ packages });
92
+ mocks.createBuildEngine.mockImplementation(() => createMockEngine() as any);
93
+ mocks.typecheckNonPackageFiles.mockReturnValue({
94
+ success: true, errorCount: 0, warningCount: 0, diagnostics: [],
95
+ });
96
+
97
+ mocks.discoverWorkspacePackages.mockReturnValue(new Map<string, string>());
98
+ const pathMap = new Map<string, string>();
99
+ for (const name of Object.keys(packages)) {
100
+ pathMap.set(name, `packages/${name}`);
101
+ }
102
+ mocks.mergeTestsPackagesIntoConfig.mockReturnValue({
103
+ merged: packages,
104
+ pathMap,
105
+ });
106
+ }
107
+
108
+ //#endregion
109
+
110
+ //#region Tests
111
+
112
+ beforeEach(() => {
113
+ vi.clearAllMocks();
114
+ mockEngines.length = 0;
115
+ });
116
+
117
+ describe("TypecheckOrchestrator", () => {
118
+ // Acceptance: Scenario "TypecheckOrchestrator 정상 실행"
119
+ it("produces correct TypecheckResult through init → start → shutdown lifecycle", async () => {
120
+ setupDefaults({
121
+ "core-common": { target: "neutral" },
122
+ "core-node": { target: "node" },
123
+ });
124
+
125
+ const orchestrator = new TypecheckOrchestrator({ targets: [], options: [] });
126
+ await orchestrator.initialize();
127
+ const result = await orchestrator.start();
128
+ await orchestrator.shutdown();
129
+
130
+ expect(result.success).toBe(true);
131
+ expect(result.errorCount).toBe(0);
132
+ expect(result.warningCount).toBe(0);
133
+ // neutral → 2 tasks (node + browser), node → 1 task = 3 engines
134
+ expect(mocks.createBuildEngine).toHaveBeenCalledTimes(3);
135
+ for (const engine of mockEngines) {
136
+ expect(engine.run).toHaveBeenCalledWith(
137
+ expect.objectContaining({ js: false, dts: false }),
138
+ );
139
+ }
140
+ });
141
+
142
+ // Acceptance: Scenario "엔진 성공 시 리소스 정리"
143
+ it("calls engine.stop() after successful run", async () => {
144
+ setupDefaults({ "core-node": { target: "node" } });
145
+
146
+ const orchestrator = new TypecheckOrchestrator({ targets: [], options: [] });
147
+ await orchestrator.initialize();
148
+ await orchestrator.start();
149
+ await orchestrator.shutdown();
150
+
151
+ expect(mockEngines[0].stop).toHaveBeenCalled();
152
+ });
153
+
154
+ // Acceptance: Scenario "엔진 실패 시 리소스 정리"
155
+ it("calls engine.stop() even when run fails", async () => {
156
+ setupDefaults({ "core-node": { target: "node" } });
157
+ mocks.createBuildEngine.mockImplementation(() => {
158
+ const engine = {
159
+ run: vi.fn().mockRejectedValue(new Error("build error")),
160
+ startWatch: vi.fn(),
161
+ stop: vi.fn().mockResolvedValue(undefined),
162
+ };
163
+ mockEngines.push(engine);
164
+ return engine as any;
165
+ });
166
+
167
+ const orchestrator = new TypecheckOrchestrator({ targets: [], options: [] });
168
+ await orchestrator.initialize();
169
+ const result = await orchestrator.start();
170
+ await orchestrator.shutdown();
171
+
172
+ expect(result.success).toBe(false);
173
+ expect(mockEngines[0].stop).toHaveBeenCalled();
174
+ });
175
+ });
176
+
177
+ // executeTypecheck 편의 함수 테스트는 tests/commands/typecheck.spec.ts에서
178
+ // 동일한 코드 경로를 통해 검증되므로 중복 삭제함
179
+
180
+ //#endregion
@@ -0,0 +1,7 @@
1
+ # replaceDeps catch-all 에러 필터링 -- LLM 검증
2
+
3
+ ## 검증 항목
4
+
5
+ - [x] catch 블록이 에러 코드를 확인하여 MODULE_NOT_FOUND/ERR_MODULE_NOT_FOUND만 무시: `sd-cli.ts:37-41` 확인 완료. `code` 프로퍼티를 체크하고 해당 코드가 아닌 경우 `console.warn`으로 경고 출력
6
+ - [x] 예상 외 에러(SyntaxError 등)에 경고 로그 출력: `console.warn("[sd-cli] replaceDeps 사전 설정 실패:", ...)` 확인
7
+ - [x] Phase 2로 정상 진행: catch 후 코드 흐름이 Phase 2(line 44~)로 계속됨 확인
@@ -0,0 +1,10 @@
1
+ # lint.worker/lint-utils/check.ts import 경로 검증 -- LLM 검증
2
+
3
+ ## 검증 항목
4
+
5
+ - [x] lint.worker.ts가 `../utils/lint-core`에서 import: `import { executeLint, type LintOptions, type LintResult } from "../utils/lint-core";` (line 2) 확인
6
+ - [x] lint-utils.ts가 `./lint-core`에서 import: `import type { LintOptions, LintResult } from "./lint-core";` (line 2) 확인
7
+ - [x] check.ts가 `../utils/lint-core`에서 import: `import { executeLint, type LintResult } from "../utils/lint-core";` (line 4) 확인
8
+ - [x] commands/lint.ts�� `../commands/lint` import가 없음: `import { executeLint, type LintOptions } from "../utils/lint-core";` (line 1)만 존재
9
+ - [x] lint-core.ts에 runLint가 없음: `executeLint`, `loadIgnorePatterns`, `LintOptions`, `LintResult`만 export
10
+ - [x] commands/lint.ts에 runLint만 존재: 13줄의 thin wrapper
@@ -0,0 +1,188 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+
3
+ // Hoisted mock references — available inside vi.mock factories
4
+ const mocks = vi.hoisted(() => ({
5
+ fsxExists: vi.fn<(path: string) => Promise<boolean>>(),
6
+ fsxGlob: vi.fn<(...args: unknown[]) => Promise<string[]>>(),
7
+ filterByTargets: vi.fn(
8
+ (files: string[], _targets: string[], _cwd: string) => files,
9
+ ),
10
+ lintFiles: vi.fn<() => Promise<Array<{ errorCount: number; warningCount: number }>>>(),
11
+ loadFormatter: vi.fn(),
12
+ outputFixes: vi.fn(),
13
+ jitiImport: vi.fn(),
14
+ eslintCtor: vi.fn(),
15
+ }));
16
+
17
+ vi.mock("@simplysm/core-node", () => ({
18
+ fsx: { exists: mocks.fsxExists, glob: mocks.fsxGlob },
19
+ pathx: { filterByTargets: mocks.filterByTargets },
20
+ }));
21
+
22
+ vi.mock("eslint", () => ({
23
+ ESLint: class MockESLint {
24
+ constructor(options: unknown) { mocks.eslintCtor(options); }
25
+ lintFiles = mocks.lintFiles;
26
+ loadFormatter = mocks.loadFormatter;
27
+ static outputFixes = mocks.outputFixes;
28
+ },
29
+ }));
30
+
31
+ vi.mock("jiti", () => ({
32
+ createJiti: vi.fn(() => ({ import: mocks.jitiImport })),
33
+ }));
34
+
35
+ vi.mock("consola", () => {
36
+ const fns = (): Record<string, unknown> => ({
37
+ debug: vi.fn(), start: vi.fn(), success: vi.fn(),
38
+ info: vi.fn(), error: vi.fn(), warn: vi.fn(), log: vi.fn(),
39
+ withTag: vi.fn(() => fns()),
40
+ level: 0,
41
+ });
42
+ const c = fns();
43
+ return { consola: c, default: c, LogLevels: {} };
44
+ });
45
+
46
+ const { loadIgnorePatterns, executeLint } = await import("../../src/utils/lint-core");
47
+
48
+ //#region loadIgnorePatterns
49
+
50
+ describe("loadIgnorePatterns", () => {
51
+ beforeEach(() => vi.clearAllMocks());
52
+
53
+ it("extracts globalIgnores patterns from eslint config", async () => {
54
+ mocks.fsxExists.mockImplementation((p) =>
55
+ Promise.resolve(typeof p === "string" && p.endsWith("eslint.config.ts")),
56
+ );
57
+ mocks.jitiImport.mockResolvedValue({
58
+ default: [
59
+ { ignores: ["dist/**", "node_modules/**"] },
60
+ { files: ["*.ts"], rules: {} }, // not globalIgnores — has files key
61
+ { ignores: [".cache/**"] },
62
+ ],
63
+ });
64
+
65
+ const result = await loadIgnorePatterns("/project");
66
+ expect(result).toEqual(["dist/**", "node_modules/**", ".cache/**"]);
67
+ });
68
+
69
+ it("ignores config objects that have files key", async () => {
70
+ mocks.fsxExists.mockImplementation((p) =>
71
+ Promise.resolve(typeof p === "string" && p.endsWith("eslint.config.ts")),
72
+ );
73
+ mocks.jitiImport.mockResolvedValue({
74
+ default: [{ files: ["*.ts"], ignores: ["dist/**"] }],
75
+ });
76
+
77
+ const result = await loadIgnorePatterns("/project");
78
+ expect(result).toEqual([]);
79
+ });
80
+
81
+ it("throws when no eslint config file found", async () => {
82
+ mocks.fsxExists.mockResolvedValue(false);
83
+
84
+ await expect(loadIgnorePatterns("/project")).rejects.toThrow(
85
+ "ESLint 설정 파일",
86
+ );
87
+ });
88
+ });
89
+
90
+ //#endregion
91
+
92
+ //#region executeLint
93
+
94
+ describe("executeLint", () => {
95
+ beforeEach(() => {
96
+ vi.clearAllMocks();
97
+ // Default: eslint config exists with no ignores
98
+ mocks.fsxExists.mockImplementation((p) =>
99
+ Promise.resolve(typeof p === "string" && p.endsWith("eslint.config.ts")),
100
+ );
101
+ mocks.jitiImport.mockResolvedValue({ default: [] });
102
+ mocks.fsxGlob.mockResolvedValue(["/project/src/a.ts", "/project/src/b.ts"]);
103
+ mocks.filterByTargets.mockImplementation((files) => files);
104
+ mocks.lintFiles.mockResolvedValue([{ errorCount: 0, warningCount: 0 }]);
105
+ mocks.loadFormatter.mockResolvedValue({
106
+ format: vi.fn().mockResolvedValue(""),
107
+ });
108
+ });
109
+
110
+ it("lints all collected files and returns success when no errors", async () => {
111
+ const result = await executeLint({ targets: [], fix: false, timing: false });
112
+
113
+ expect(result.success).toBe(true);
114
+ expect(result.errorCount).toBe(0);
115
+ expect(result.warningCount).toBe(0);
116
+ expect(mocks.lintFiles).toHaveBeenCalled();
117
+ });
118
+
119
+ it("filters files by targets via pathx.filterByTargets", async () => {
120
+ const filteredFiles = ["/project/packages/core-common/src/a.ts"];
121
+ mocks.filterByTargets.mockReturnValue(filteredFiles);
122
+
123
+ await executeLint({ targets: ["packages/core-common"], fix: false, timing: false });
124
+
125
+ expect(mocks.filterByTargets).toHaveBeenCalledWith(
126
+ expect.any(Array),
127
+ ["packages/core-common"],
128
+ expect.any(String),
129
+ );
130
+ });
131
+
132
+ it("applies auto-fix when fix option is true", async () => {
133
+ await executeLint({ targets: [], fix: true, timing: false });
134
+
135
+ expect(mocks.outputFixes).toHaveBeenCalled();
136
+ });
137
+
138
+ it("sets TIMING env variable when timing option is true", async () => {
139
+ const origTiming = process.env["TIMING"];
140
+
141
+ await executeLint({ targets: [], fix: false, timing: true });
142
+
143
+ expect(process.env["TIMING"]).toBe("1");
144
+
145
+ // Cleanup
146
+ if (origTiming == null) delete process.env["TIMING"];
147
+ else process.env["TIMING"] = origTiming;
148
+ });
149
+
150
+ it("creates ESLint with cache enabled and correct cache location", async () => {
151
+ await executeLint({ targets: [], fix: false, timing: false });
152
+
153
+ expect(mocks.eslintCtor).toHaveBeenCalledWith(
154
+ expect.objectContaining({
155
+ cache: true,
156
+ cacheLocation: expect.stringContaining("eslint.cache"),
157
+ }),
158
+ );
159
+ });
160
+
161
+ it("returns error count and formatted output when lint errors found", async () => {
162
+ mocks.lintFiles.mockResolvedValue([
163
+ { errorCount: 3, warningCount: 1 },
164
+ { errorCount: 1, warningCount: 2 },
165
+ ]);
166
+ mocks.loadFormatter.mockResolvedValue({
167
+ format: vi.fn().mockResolvedValue("error details"),
168
+ });
169
+
170
+ const result = await executeLint({ targets: [], fix: false, timing: false });
171
+
172
+ expect(result.success).toBe(false);
173
+ expect(result.errorCount).toBe(4);
174
+ expect(result.warningCount).toBe(3);
175
+ expect(result.formattedOutput).toBe("error details");
176
+ });
177
+
178
+ it("returns success when no files to lint", async () => {
179
+ mocks.fsxGlob.mockResolvedValue([]);
180
+
181
+ const result = await executeLint({ targets: [], fix: false, timing: false });
182
+
183
+ expect(result.success).toBe(true);
184
+ expect(mocks.lintFiles).not.toHaveBeenCalled();
185
+ });
186
+ });
187
+
188
+ //#endregion
@@ -0,0 +1,14 @@
1
+ # server-build.worker 비즈니스 로직 분리 — LLM 검증
2
+
3
+ ## 검증 항목
4
+
5
+ - [x] 4개 함수가 utils/server-production-files.ts에 export되어 있다: line 14, 27, 68, 87에 각각 export function 확인
6
+ - [x] worker에서 4개 함수 정의가 제거되었다: worker에 함수 정의 없음, import와 호출만 존재
7
+ - [x] worker에서 collectAllExternals, generateProductionFiles를 새 모듈에서 import한다: line 19 `import { collectAllExternals, generateProductionFiles } from "../utils/server-production-files"`
8
+ - [x] collectAllExternals 시그니처: `(pkgDir: string, manualExternals?: string[]) => string[]` — line 14 확인
9
+ - [x] parseLockfileVersions 시그니처: `(cwd: string) => Map<string, string>` — line 27 확인
10
+ - [x] resolveLockedVersions 시그니처: `(cwd: string, pkgNames: string[]) => Record<string, string>` — line 68 확인
11
+ - [x] generateProductionFiles 시그니처: `(info: ServerBuildInfo, externals: string[]) => void` — line 87 확인
12
+ - [x] worker에 build, rebuildAll, startWatch, stopWatch, cleanup, createEsbuildWatchContext가 존재한다: line 116, 148, 259, 336, 359, 488 확인
13
+ - [x] worker에서 cpx import가 제거되었다: grep 결과 매칭 없음
14
+ - [x] worker에서 collectAllDependencyExternals import가 제거되었다: grep 결과 매칭 없음
@@ -0,0 +1,8 @@
1
+ # esbuild context dispose 안전성 -- LLM 검증
2
+
3
+ ## 검증 항목
4
+
5
+ - [x] try-finally 패턴 적용: `server-build.worker.ts:434-442` — oldContext 캡처 후 try 블록에서 새 context 생성, finally에서 oldContext.dispose() 호출
6
+ - [x] 성공 시 dispose: try 블록에서 새 context 생성 성공 → finally에서 oldContext.dispose() 실행
7
+ - [x] 실패 시 dispose: createEsbuildWatchContext() 예외 시 → finally에서 oldContext.dispose() 실행 보장
8
+ - [x] null 안전: `if (oldContext != null)` 가드로 oldContext가 없는 경우(output.js=false 등) 안전