@simplysm/sd-cli 14.0.43 → 14.0.45
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/ngtsc-build-core.d.ts +12 -3
- package/dist/angular/ngtsc-build-core.d.ts.map +1 -1
- package/dist/angular/ngtsc-build-core.js +70 -6
- package/dist/angular/ngtsc-build-core.js.map +1 -1
- package/dist/commands/publish/version-upgrade.d.ts.map +1 -1
- package/dist/commands/publish/version-upgrade.js +15 -12
- package/dist/commands/publish/version-upgrade.js.map +1 -1
- package/dist/deps/replace-deps/replace-deps-resolve.d.ts.map +1 -1
- package/dist/deps/replace-deps/replace-deps-resolve.js +6 -7
- package/dist/deps/replace-deps/replace-deps-resolve.js.map +1 -1
- package/dist/deps/replace-deps/replace-deps.d.ts.map +1 -1
- package/dist/deps/replace-deps/replace-deps.js +79 -15
- package/dist/deps/replace-deps/replace-deps.js.map +1 -1
- package/dist/deps/server-externals/server-production-files.d.ts +10 -5
- package/dist/deps/server-externals/server-production-files.d.ts.map +1 -1
- package/dist/deps/server-externals/server-production-files.js +22 -26
- package/dist/deps/server-externals/server-production-files.js.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts +3 -8
- package/dist/esbuild/esbuild-angular-compiler-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-angular-compiler-plugin.js +57 -83
- package/dist/esbuild/esbuild-angular-compiler-plugin.js.map +1 -1
- package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-postcss-plugin.js +9 -6
- package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -1
- package/dist/esbuild/esbuild-worker-plugin.d.ts +30 -0
- package/dist/esbuild/esbuild-worker-plugin.d.ts.map +1 -0
- package/dist/esbuild/esbuild-worker-plugin.js +197 -0
- package/dist/esbuild/esbuild-worker-plugin.js.map +1 -0
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +4 -25
- package/dist/workers/client.worker.js.map +1 -1
- package/dist/workers/incremental-mtime-tracker.d.ts +13 -0
- package/dist/workers/incremental-mtime-tracker.d.ts.map +1 -0
- package/dist/workers/incremental-mtime-tracker.js +65 -0
- package/dist/workers/incremental-mtime-tracker.js.map +1 -0
- package/dist/workers/library-build.worker.d.ts.map +1 -1
- package/dist/workers/library-build.worker.js +37 -15
- package/dist/workers/library-build.worker.js.map +1 -1
- package/dist/workers/server-build.worker.d.ts.map +1 -1
- package/dist/workers/server-build.worker.js +6 -5
- package/dist/workers/server-build.worker.js.map +1 -1
- package/dist/workers/server-esbuild-context.d.ts.map +1 -1
- package/dist/workers/server-esbuild-context.js +3 -1
- package/dist/workers/server-esbuild-context.js.map +1 -1
- package/dist/workers/server-watch-manager.js +1 -1
- package/dist/workers/server-watch-manager.js.map +1 -1
- package/package.json +5 -4
- package/src/angular/ngtsc-build-core.ts +73 -5
- package/src/commands/publish/version-upgrade.ts +43 -34
- package/src/deps/replace-deps/replace-deps-resolve.ts +12 -7
- package/src/deps/replace-deps/replace-deps.ts +90 -16
- package/src/deps/server-externals/server-production-files.ts +26 -28
- package/src/esbuild/esbuild-angular-compiler-plugin.ts +82 -123
- package/src/esbuild/esbuild-postcss-plugin.ts +9 -6
- package/src/esbuild/esbuild-worker-plugin.ts +266 -0
- package/src/workers/client.worker.ts +4 -23
- package/src/workers/incremental-mtime-tracker.ts +68 -0
- package/src/workers/library-build.worker.ts +41 -14
- package/src/workers/server-build.worker.ts +6 -5
- package/src/workers/server-esbuild-context.ts +3 -1
- package/src/workers/server-watch-manager.ts +1 -1
- package/tests/angular/ngtsc-build-core.acc.spec.ts +210 -0
- package/tests/angular/ngtsc-build-core.spec.ts +52 -0
- package/tests/commands/version-upgrade.acc.spec.ts +210 -0
- package/tests/commands/version-upgrade.spec.ts +148 -0
- package/tests/deps/replace-deps/replace-deps-perf.verify.md +15 -0
- package/tests/deps/replace-deps/replace-deps-resolve.acc.spec.ts +124 -0
- package/tests/esbuild/esbuild-angular-compiler-plugin-worker.verify.md +56 -28
- package/tests/esbuild/esbuild-postcss-plugin-chunking.verify.md +17 -0
- package/tests/esbuild/esbuild-postcss-plugin.acc.spec.ts +152 -0
- package/tests/esbuild/esbuild-worker-plugin-node.verify.md +11 -0
- package/tests/esbuild/esbuild-worker-plugin.acc.spec.ts +318 -0
- package/tests/esbuild/esbuild-worker-plugin.spec.ts +297 -0
- package/tests/esbuild/esbuild-worker-plugin.verify.md +7 -0
- package/tests/esbuild/fixtures/worker-plugin/node-worker.js +2 -0
- package/tests/esbuild/fixtures/worker-plugin/shared-worker.js +6 -0
- package/tests/esbuild/fixtures/worker-plugin/worker-error.js +1 -0
- package/tests/esbuild/fixtures/worker-plugin/worker.js +3 -0
- package/tests/esbuild/fixtures/worker-plugin/worker2.js +3 -0
- package/tests/utils/ngtsc-build-core-write-emit.spec.ts +124 -0
- package/tests/workers/client-worker-mtime-incremental.verify.md +10 -0
- package/tests/workers/incremental-mtime-tracker.acc.spec.ts +144 -0
- package/tests/workers/incremental-mtime-tracker.spec.ts +102 -0
- package/tests/workers/library-build-worker.spec.ts +4 -0
- package/tests/workers/server-build-worker-plugin.verify.md +9 -0
- package/tests/workers/server-build-worker.spec.ts +26 -12
- package/tests/workers/server-esbuild-context.spec.ts +13 -5
- package/tests/workers/server-watch-manager.acc.spec.ts +2 -2
- package/tests/workers/server-watch-manager.spec.ts +2 -2
- package/dist/angular/web-worker-transformer.d.ts +0 -9
- package/dist/angular/web-worker-transformer.d.ts.map +0 -1
- package/dist/angular/web-worker-transformer.js +0 -73
- package/dist/angular/web-worker-transformer.js.map +0 -1
- package/src/angular/web-worker-transformer.ts +0 -117
- package/tests/angular/web-worker-transformer.spec.ts +0 -154
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/index.d.ts +0 -2
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/index.d.ts.map +0 -1
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/index.js +0 -4
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/index.js.map +0 -1
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/util.d.ts +0 -2
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/util.d.ts.map +0 -1
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/util.js +0 -4
- package/tests/ts-compiler/fixtures/non-angular-pkg/dist/util.js.map +0 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { upgradeVersion, computePublishLevels } from "../../src/commands/publish/version-upgrade";
|
|
6
|
+
|
|
7
|
+
function createTempDir(): string {
|
|
8
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "sd-cli-version-upgrade-"));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function writeJson(filePath: string, data: unknown): void {
|
|
12
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
13
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readJson<T>(filePath: string): T {
|
|
17
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8")) as T;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("upgradeVersion", () => {
|
|
21
|
+
let tmpDir: string;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
tmpDir = createTempDir();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("복수 패키지의 package.json을 병렬 업데이트한다", async () => {
|
|
32
|
+
// Given: allPkgPaths에 5개 패키지 경로가 있다
|
|
33
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
34
|
+
name: "@simplysm/root",
|
|
35
|
+
version: "14.0.0",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const pkgNames = ["pkg-a", "pkg-b", "pkg-c", "pkg-d", "pkg-e"];
|
|
39
|
+
const allPkgPaths = pkgNames.map((name) => {
|
|
40
|
+
const pkgDir = path.join(tmpDir, "packages", name);
|
|
41
|
+
writeJson(path.join(pkgDir, "package.json"), {
|
|
42
|
+
name: `@simplysm/${name}`,
|
|
43
|
+
version: "14.0.0",
|
|
44
|
+
});
|
|
45
|
+
return pkgDir;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// sd-cli/templates 디렉토리 생성 (glob 대상이 없도록)
|
|
49
|
+
fs.mkdirSync(path.join(tmpDir, "packages", "sd-cli", "templates"), {
|
|
50
|
+
recursive: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// When
|
|
54
|
+
const result = await upgradeVersion(tmpDir, allPkgPaths, false);
|
|
55
|
+
|
|
56
|
+
// Then: 5개 패키지의 package.json이 모두 newVersion으로 업데이트된다
|
|
57
|
+
expect(result.version).toBe("14.0.1");
|
|
58
|
+
for (const pkgDir of allPkgPaths) {
|
|
59
|
+
const pkg = readJson<{ version: string }>(path.join(pkgDir, "package.json"));
|
|
60
|
+
expect(pkg.version).toBe("14.0.1");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// And: changedFiles에 5개 패키지 경로가 모두 포함된다
|
|
64
|
+
for (const pkgDir of allPkgPaths) {
|
|
65
|
+
expect(result.changedFiles).toContain(path.resolve(pkgDir, "package.json"));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("패키지가 없으면 루트 package.json만 changedFiles에 포함된다", async () => {
|
|
70
|
+
// Given: allPkgPaths가 빈 배열이다
|
|
71
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
72
|
+
name: "@simplysm/root",
|
|
73
|
+
version: "14.0.0",
|
|
74
|
+
});
|
|
75
|
+
fs.mkdirSync(path.join(tmpDir, "packages", "sd-cli", "templates"), {
|
|
76
|
+
recursive: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// When
|
|
80
|
+
const result = await upgradeVersion(tmpDir, [], false);
|
|
81
|
+
|
|
82
|
+
// Then: 루트 package.json만 changedFiles에 포함된다
|
|
83
|
+
expect(result.changedFiles).toHaveLength(1);
|
|
84
|
+
expect(result.changedFiles[0]).toBe(path.resolve(tmpDir, "package.json"));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("복수 템플릿 파일을 병렬 업데이트한다", async () => {
|
|
88
|
+
// Given: 템플릿 3개 중 2개에 @simplysm 버전이 포함되어 있다
|
|
89
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
90
|
+
name: "@simplysm/root",
|
|
91
|
+
version: "14.0.0",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const templatesDir = path.join(tmpDir, "packages", "sd-cli", "templates");
|
|
95
|
+
fs.mkdirSync(templatesDir, { recursive: true });
|
|
96
|
+
|
|
97
|
+
// 템플릿 1: @simplysm 버전 포함
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(templatesDir, "a.hbs"),
|
|
100
|
+
`"@simplysm/core-common": "~14.0.0"`,
|
|
101
|
+
"utf-8",
|
|
102
|
+
);
|
|
103
|
+
// 템플릿 2: @simplysm 버전 포함
|
|
104
|
+
fs.writeFileSync(
|
|
105
|
+
path.join(templatesDir, "b.hbs"),
|
|
106
|
+
`"@simplysm/angular": "~14.0.0"`,
|
|
107
|
+
"utf-8",
|
|
108
|
+
);
|
|
109
|
+
// 템플릿 3: @simplysm 버전 미포함
|
|
110
|
+
fs.writeFileSync(path.join(templatesDir, "c.hbs"), `no version here`, "utf-8");
|
|
111
|
+
|
|
112
|
+
// When
|
|
113
|
+
const result = await upgradeVersion(tmpDir, [], false);
|
|
114
|
+
|
|
115
|
+
// Then: 2개 파일만 수정되고 changedFiles에 추가된다
|
|
116
|
+
const templateChanges = result.changedFiles.filter((f) => f.endsWith(".hbs"));
|
|
117
|
+
expect(templateChanges).toHaveLength(2);
|
|
118
|
+
|
|
119
|
+
// 수정된 파일들의 내용 확인
|
|
120
|
+
expect(fs.readFileSync(path.join(templatesDir, "a.hbs"), "utf-8")).toContain("~14.0.1");
|
|
121
|
+
expect(fs.readFileSync(path.join(templatesDir, "b.hbs"), "utf-8")).toContain("~14.0.1");
|
|
122
|
+
// 수정되지 않은 파일
|
|
123
|
+
expect(fs.readFileSync(path.join(templatesDir, "c.hbs"), "utf-8")).toBe("no version here");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("템플릿이 없으면 템플릿 관련 쓰기 없이 완료된다", async () => {
|
|
127
|
+
// Given: glob 결과가 빈 배열이다 (templates 디렉토리에 .hbs 파일 없음)
|
|
128
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
129
|
+
name: "@simplysm/root",
|
|
130
|
+
version: "14.0.0",
|
|
131
|
+
});
|
|
132
|
+
fs.mkdirSync(path.join(tmpDir, "packages", "sd-cli", "templates"), {
|
|
133
|
+
recursive: true,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// When
|
|
137
|
+
const result = await upgradeVersion(tmpDir, [], false);
|
|
138
|
+
|
|
139
|
+
// Then
|
|
140
|
+
const templateChanges = result.changedFiles.filter((f) => f.endsWith(".hbs"));
|
|
141
|
+
expect(templateChanges).toHaveLength(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("changedFiles[0]이 프로젝트 루트 package.json 경로이다", async () => {
|
|
145
|
+
// Given: allPkgPaths에 3개 패키지가 있다
|
|
146
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
147
|
+
name: "@simplysm/root",
|
|
148
|
+
version: "14.0.0",
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const allPkgPaths = ["pkg-a", "pkg-b", "pkg-c"].map((name) => {
|
|
152
|
+
const pkgDir = path.join(tmpDir, "packages", name);
|
|
153
|
+
writeJson(path.join(pkgDir, "package.json"), {
|
|
154
|
+
name: `@simplysm/${name}`,
|
|
155
|
+
version: "14.0.0",
|
|
156
|
+
});
|
|
157
|
+
return pkgDir;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
fs.mkdirSync(path.join(tmpDir, "packages", "sd-cli", "templates"), {
|
|
161
|
+
recursive: true,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// When
|
|
165
|
+
const result = await upgradeVersion(tmpDir, allPkgPaths, false);
|
|
166
|
+
|
|
167
|
+
// Then: changedFiles[0]이 프로젝트 루트 package.json 경로이다
|
|
168
|
+
expect(result.changedFiles[0]).toBe(path.resolve(tmpDir, "package.json"));
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("computePublishLevels", () => {
|
|
173
|
+
let tmpDir: string;
|
|
174
|
+
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
tmpDir = createTempDir();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
afterEach(() => {
|
|
180
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("의존 관계가 있는 패키지들의 레벨을 올바르게 계산한다", async () => {
|
|
184
|
+
// Given: A(의존 없음), B(A 의존), C(B 의존)
|
|
185
|
+
const pkgs = [
|
|
186
|
+
{ name: "pkg-a", deps: {} },
|
|
187
|
+
{ name: "pkg-b", deps: { "@simplysm/pkg-a": "~14.0.0" } },
|
|
188
|
+
{ name: "pkg-c", deps: { "@simplysm/pkg-b": "~14.0.0" } },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const publishPkgs = pkgs.map((p) => {
|
|
192
|
+
const pkgDir = path.join(tmpDir, "packages", p.name);
|
|
193
|
+
writeJson(path.join(pkgDir, "package.json"), {
|
|
194
|
+
name: `@simplysm/${p.name}`,
|
|
195
|
+
version: "14.0.0",
|
|
196
|
+
dependencies: p.deps,
|
|
197
|
+
});
|
|
198
|
+
return { name: p.name, path: pkgDir };
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// When
|
|
202
|
+
const levels = await computePublishLevels(publishPkgs);
|
|
203
|
+
|
|
204
|
+
// Then: 레벨은 [[A], [B], [C]]
|
|
205
|
+
expect(levels).toHaveLength(3);
|
|
206
|
+
expect(levels[0].map((p) => p.name)).toEqual(["pkg-a"]);
|
|
207
|
+
expect(levels[1].map((p) => p.name)).toEqual(["pkg-b"]);
|
|
208
|
+
expect(levels[2].map((p) => p.name)).toEqual(["pkg-c"]);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { upgradeVersion, computePublishLevels } from "../../src/commands/publish/version-upgrade";
|
|
6
|
+
|
|
7
|
+
function createTempDir(): string {
|
|
8
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "sd-cli-vu-unit-"));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function writeJson(filePath: string, data: unknown): void {
|
|
12
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
13
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readJson<T>(filePath: string): T {
|
|
17
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8")) as T;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("upgradeVersion — unit", () => {
|
|
21
|
+
let tmpDir: string;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
tmpDir = createTempDir();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("prerelease 버전은 prerelease로 증가한다", async () => {
|
|
32
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
33
|
+
name: "@simplysm/root",
|
|
34
|
+
version: "14.0.0-beta.3",
|
|
35
|
+
});
|
|
36
|
+
fs.mkdirSync(path.join(tmpDir, "packages", "sd-cli", "templates"), {
|
|
37
|
+
recursive: true,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const result = await upgradeVersion(tmpDir, [], false);
|
|
41
|
+
expect(result.version).toBe("14.0.0-beta.4");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("dryRun이면 파일을 수정하지 않고 새 버전만 반환한다", async () => {
|
|
45
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
46
|
+
name: "@simplysm/root",
|
|
47
|
+
version: "14.0.0",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const pkgDir = path.join(tmpDir, "packages", "pkg-a");
|
|
51
|
+
writeJson(path.join(pkgDir, "package.json"), {
|
|
52
|
+
name: "@simplysm/pkg-a",
|
|
53
|
+
version: "14.0.0",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = await upgradeVersion(tmpDir, [pkgDir], true);
|
|
57
|
+
expect(result.version).toBe("14.0.1");
|
|
58
|
+
expect(result.changedFiles).toHaveLength(0);
|
|
59
|
+
|
|
60
|
+
// 파일이 수정되지 않았는지 확인
|
|
61
|
+
const rootPkg = readJson<{ version: string }>(path.join(tmpDir, "package.json"));
|
|
62
|
+
expect(rootPkg.version).toBe("14.0.0");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("템플릿 파일에서 @simplysm 버전이 없으면 수정하지 않는다", async () => {
|
|
66
|
+
writeJson(path.join(tmpDir, "package.json"), {
|
|
67
|
+
name: "@simplysm/root",
|
|
68
|
+
version: "14.0.0",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const templatesDir = path.join(tmpDir, "packages", "sd-cli", "templates");
|
|
72
|
+
fs.mkdirSync(templatesDir, { recursive: true });
|
|
73
|
+
fs.writeFileSync(path.join(templatesDir, "no-version.hbs"), "plain template content", "utf-8");
|
|
74
|
+
|
|
75
|
+
const result = await upgradeVersion(tmpDir, [], false);
|
|
76
|
+
const templateChanges = result.changedFiles.filter((f) => f.endsWith(".hbs"));
|
|
77
|
+
expect(templateChanges).toHaveLength(0);
|
|
78
|
+
|
|
79
|
+
// 파일이 수정되지 않았는지 확인
|
|
80
|
+
expect(fs.readFileSync(path.join(templatesDir, "no-version.hbs"), "utf-8")).toBe(
|
|
81
|
+
"plain template content",
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("computePublishLevels — unit", () => {
|
|
87
|
+
let tmpDir: string;
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
tmpDir = createTempDir();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(() => {
|
|
94
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("의존성 없는 패키지들은 모두 Level 0이다", async () => {
|
|
98
|
+
const publishPkgs = ["pkg-a", "pkg-b", "pkg-c"].map((name) => {
|
|
99
|
+
const pkgDir = path.join(tmpDir, "packages", name);
|
|
100
|
+
writeJson(path.join(pkgDir, "package.json"), {
|
|
101
|
+
name: `@simplysm/${name}`,
|
|
102
|
+
version: "14.0.0",
|
|
103
|
+
dependencies: {},
|
|
104
|
+
});
|
|
105
|
+
return { name, path: pkgDir };
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const levels = await computePublishLevels(publishPkgs);
|
|
109
|
+
expect(levels).toHaveLength(1);
|
|
110
|
+
expect(levels[0].map((p) => p.name).sort()).toEqual(["pkg-a", "pkg-b", "pkg-c"]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("외부 의존성(@simplysm/ 아닌)은 레벨 계산에 영향 없다", async () => {
|
|
114
|
+
const pkgDir = path.join(tmpDir, "packages", "pkg-a");
|
|
115
|
+
writeJson(path.join(pkgDir, "package.json"), {
|
|
116
|
+
name: "@simplysm/pkg-a",
|
|
117
|
+
version: "14.0.0",
|
|
118
|
+
dependencies: { lodash: "^4.0.0", express: "^5.0.0" },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const levels = await computePublishLevels([{ name: "pkg-a", path: pkgDir }]);
|
|
122
|
+
expect(levels).toHaveLength(1);
|
|
123
|
+
expect(levels[0][0].name).toBe("pkg-a");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("peerDependencies와 optionalDependencies도 레벨에 반영된다", async () => {
|
|
127
|
+
const pkgADir = path.join(tmpDir, "packages", "pkg-a");
|
|
128
|
+
writeJson(path.join(pkgADir, "package.json"), {
|
|
129
|
+
name: "@simplysm/pkg-a",
|
|
130
|
+
version: "14.0.0",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const pkgBDir = path.join(tmpDir, "packages", "pkg-b");
|
|
134
|
+
writeJson(path.join(pkgBDir, "package.json"), {
|
|
135
|
+
name: "@simplysm/pkg-b",
|
|
136
|
+
version: "14.0.0",
|
|
137
|
+
peerDependencies: { "@simplysm/pkg-a": "~14.0.0" },
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const levels = await computePublishLevels([
|
|
141
|
+
{ name: "pkg-a", path: pkgADir },
|
|
142
|
+
{ name: "pkg-b", path: pkgBDir },
|
|
143
|
+
]);
|
|
144
|
+
expect(levels).toHaveLength(2);
|
|
145
|
+
expect(levels[0][0].name).toBe("pkg-a");
|
|
146
|
+
expect(levels[1][0].name).toBe("pkg-b");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# replace-deps 성능 최적화 — LLM 검증
|
|
2
|
+
|
|
3
|
+
## 검증 항목
|
|
4
|
+
|
|
5
|
+
### Slice 1: Set 중복 검사 + glob 병렬화
|
|
6
|
+
|
|
7
|
+
- [x] `resolveAllReplaceDepEntries`에서 `entries.some()` 대신 `Set<string>` 사용 확인: `replace-deps-resolve.ts:143`에 `seenTargetPaths = new Set<string>()` 선언, `:205`에서 `seenTargetPaths.has(actualTargetPath)` + `seenTargetPaths.add(actualTargetPath)` 사용. 기존 `entries.some()` 제거됨.
|
|
8
|
+
- [x] glob 병렬화 확인: `replace-deps-resolve.ts:167-171`에서 `Promise.all(Object.keys(replaceDeps).map(...))` 사용. 기존 순차 `for` 루프 제거됨. `:172-174`에서 `flatMap`으로 결과를 합침.
|
|
9
|
+
- [x] `seenTargetPaths`가 while 루프 바깥에 선언되어 재귀 탐색 전체에 걸쳐 중복 방지가 유지됨 확인.
|
|
10
|
+
|
|
11
|
+
### Slice 2: watch onChange entries 사전 필터링
|
|
12
|
+
|
|
13
|
+
- [x] `watchReplaceDeps`에서 사전 필터링 확인: `replace-deps.ts:240-242`에 `sourceEntries = entries.filter(e => e.resolvedSourcePath === entry.resolvedSourcePath)` 선언. watcher 생성 전에 필터링되어 클로저에 캡처됨.
|
|
14
|
+
- [x] onChange 내부에서 `sourceEntries`만 순회 확인: `replace-deps.ts:252`에서 `for (const e of sourceEntries)` 사용. 기존 `for (const e of entries)` + `if (e.resolvedSourcePath !== entry.resolvedSourcePath) continue;` 패턴이 제거됨.
|
|
15
|
+
- [x] `entries` 배열은 `resolveAllReplaceDepEntries` 호출 후 불변이므로, 필터링 결과가 watcher 생명주기 동안 유효함 확인 (`replace-deps.ts:202`에서 `entries` 할당 후 수정 없음).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { pathx } from "@simplysm/core-node";
|
|
6
|
+
import { resolveAllReplaceDepEntries } from "../../../src/deps/replace-deps/replace-deps-resolve";
|
|
7
|
+
import { consola } from "consola";
|
|
8
|
+
|
|
9
|
+
describe("resolveAllReplaceDepEntries", () => {
|
|
10
|
+
let tmpDir: string;
|
|
11
|
+
let projectRoot: string;
|
|
12
|
+
const logger = consola.withTag("test");
|
|
13
|
+
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
tmpDir = pathx.posix(await fs.promises.mkdtemp(path.join(os.tmpdir(), "sd-resolve-deps-")));
|
|
16
|
+
projectRoot = pathx.posix(path.join(tmpDir, "project"));
|
|
17
|
+
await fs.promises.mkdir(projectRoot, { recursive: true });
|
|
18
|
+
await fs.promises.writeFile(
|
|
19
|
+
pathx.posix(path.join(projectRoot, "pnpm-workspace.yaml")),
|
|
20
|
+
"packages:\n",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
await fs.promises.rm(tmpDir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* node_modules에 패키지 디렉토리를 생성하는 헬퍼
|
|
30
|
+
*/
|
|
31
|
+
async function createNodeModulesPkg(
|
|
32
|
+
nodeModulesDir: string,
|
|
33
|
+
pkgName: string,
|
|
34
|
+
): Promise<string> {
|
|
35
|
+
const pkgDir = pathx.posix(path.join(nodeModulesDir, pkgName));
|
|
36
|
+
await fs.promises.mkdir(pkgDir, { recursive: true });
|
|
37
|
+
await fs.promises.writeFile(
|
|
38
|
+
pathx.posix(path.join(pkgDir, "package.json")),
|
|
39
|
+
JSON.stringify({ name: pkgName }),
|
|
40
|
+
);
|
|
41
|
+
return pkgDir;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 소스 패키지를 생성하는 헬퍼
|
|
46
|
+
*/
|
|
47
|
+
async function createSourcePkg(name: string): Promise<string> {
|
|
48
|
+
const sourcePath = pathx.posix(path.join(tmpDir, name));
|
|
49
|
+
await fs.promises.mkdir(sourcePath, { recursive: true });
|
|
50
|
+
await fs.promises.writeFile(
|
|
51
|
+
pathx.posix(path.join(sourcePath, "package.json")),
|
|
52
|
+
JSON.stringify({ name, files: ["dist"] }),
|
|
53
|
+
);
|
|
54
|
+
await fs.promises.mkdir(pathx.posix(path.join(sourcePath, "dist")), { recursive: true });
|
|
55
|
+
await fs.promises.writeFile(
|
|
56
|
+
pathx.posix(path.join(sourcePath, "dist", "index.js")),
|
|
57
|
+
"module.exports = {};",
|
|
58
|
+
);
|
|
59
|
+
return sourcePath;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
it("동일 actualTargetPath를 가진 항목은 중복 등록되지 않는다", async () => {
|
|
63
|
+
// Given: 루트 node_modules와 workspace pkg의 node_modules에 동일 패키지가 존재하고
|
|
64
|
+
// 둘 다 같은 실제 경로(symlink 해석 후)를 가리킨다
|
|
65
|
+
const sourcePath = await createSourcePkg("@test/pkg");
|
|
66
|
+
const nodeModulesDir = pathx.posix(path.join(projectRoot, "node_modules"));
|
|
67
|
+
await createNodeModulesPkg(nodeModulesDir, "@test/pkg");
|
|
68
|
+
|
|
69
|
+
// workspace 패키지 설정 (pnpm-workspace.yaml에 추가)
|
|
70
|
+
const workspacePkgDir = pathx.posix(path.join(projectRoot, "packages", "my-app"));
|
|
71
|
+
await fs.promises.mkdir(workspacePkgDir, { recursive: true });
|
|
72
|
+
await fs.promises.writeFile(
|
|
73
|
+
pathx.posix(path.join(projectRoot, "pnpm-workspace.yaml")),
|
|
74
|
+
"packages:\n - packages/*\n",
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// workspace 패키지의 node_modules에도 동일 패키지 생성
|
|
78
|
+
const wsPkgNodeModules = pathx.posix(path.join(workspacePkgDir, "node_modules"));
|
|
79
|
+
await createNodeModulesPkg(wsPkgNodeModules, "@test/pkg");
|
|
80
|
+
|
|
81
|
+
// When
|
|
82
|
+
const entries = await resolveAllReplaceDepEntries(
|
|
83
|
+
projectRoot,
|
|
84
|
+
{ "@test/pkg": sourcePath },
|
|
85
|
+
logger,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Then: actualTargetPath가 다르므로 2개가 반환되어야 한다
|
|
89
|
+
// (symlink가 아닌 실제 디렉토리이므로 actualTargetPath가 각각 다름)
|
|
90
|
+
// 중복 방지 로직은 동일 actualTargetPath일 때만 작동한다
|
|
91
|
+
const actualTargetPaths = entries.map((e) => e.actualTargetPath);
|
|
92
|
+
const uniqueActualTargetPaths = new Set(actualTargetPaths);
|
|
93
|
+
expect(actualTargetPaths.length).toBe(uniqueActualTargetPaths.size);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("여러 replaceDeps 패턴이 올바르게 매칭되어 결과를 반환한다", async () => {
|
|
97
|
+
// Given: 2개의 다른 패턴과 각각의 소스 패키지
|
|
98
|
+
const sourceA = await createSourcePkg("@test/pkg-a");
|
|
99
|
+
const sourceB = await createSourcePkg("@other/lib");
|
|
100
|
+
const nodeModulesDir = pathx.posix(path.join(projectRoot, "node_modules"));
|
|
101
|
+
await createNodeModulesPkg(nodeModulesDir, "@test/pkg-a");
|
|
102
|
+
await createNodeModulesPkg(nodeModulesDir, "@other/lib");
|
|
103
|
+
|
|
104
|
+
// When: 2개 패턴으로 호출 (glob이 병렬 실행되어야 함)
|
|
105
|
+
const entries = await resolveAllReplaceDepEntries(
|
|
106
|
+
projectRoot,
|
|
107
|
+
{
|
|
108
|
+
"@test/pkg-a": sourceA,
|
|
109
|
+
"@other/lib": sourceB,
|
|
110
|
+
},
|
|
111
|
+
logger,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Then: 두 패턴 모두 매칭되어 2개 entries 반환
|
|
115
|
+
expect(entries).toHaveLength(2);
|
|
116
|
+
const targetNames = entries.map((e) => e.targetName).sort();
|
|
117
|
+
expect(targetNames).toEqual(["@other/lib", "@test/pkg-a"]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("빈 replaceDeps 설정이면 빈 배열을 반환한다", async () => {
|
|
121
|
+
const entries = await resolveAllReplaceDepEntries(projectRoot, {}, logger);
|
|
122
|
+
expect(entries).toEqual([]);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -1,31 +1,59 @@
|
|
|
1
|
-
# Web Worker
|
|
1
|
+
# Web Worker 통합 (Feature 1.2) — LLM 검증
|
|
2
2
|
|
|
3
3
|
## 검증 항목
|
|
4
4
|
|
|
5
|
-
###
|
|
6
|
-
|
|
7
|
-
- [x]
|
|
8
|
-
- [x]
|
|
9
|
-
- [x]
|
|
10
|
-
- [x]
|
|
11
|
-
- [x]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- [x]
|
|
19
|
-
- [x]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [x]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- [x]
|
|
28
|
-
- [x]
|
|
29
|
-
- [x]
|
|
30
|
-
- [x]
|
|
31
|
-
|
|
5
|
+
### 제거된 코드
|
|
6
|
+
|
|
7
|
+
- [x] `createWorkerTransformer` import가 제거됨: `esbuild-angular-compiler-plugin.ts`에 `web-worker-transformer.js`로부터의 import가 존재하지 않음
|
|
8
|
+
- [x] `AdditionalResult` 인터페이스 선언이 제거됨
|
|
9
|
+
- [x] `bundleWebWorker` 함수 및 `#region bundleWebWorker` 주석이 제거됨
|
|
10
|
+
- [x] `additionalResults` Map 선언이 제거됨
|
|
11
|
+
- [x] `createWebWorkerProcessor` 함수가 제거됨
|
|
12
|
+
- [x] onStart 내부의 `processWebWorker` + `workerTransformer` 생성 코드가 제거됨
|
|
13
|
+
- [x] `compileAsync` 호출에서 `additionalTransformers` 옵션이 제거됨
|
|
14
|
+
- [x] 증분 빌드 루프에서 `additionalResults.delete(file)` 호출이 제거됨
|
|
15
|
+
|
|
16
|
+
### 추가된 코드
|
|
17
|
+
|
|
18
|
+
- [x] `transformWorkerPatterns` import가 `./esbuild-worker-plugin`에서 추가됨 (같은 디렉토리, .js 확장자 미사용)
|
|
19
|
+
- [x] setup 스코프에 `workerResultsByContainingFile = new Map<string, { outputFiles?: esbuild.OutputFile[]; metafile?: esbuild.Metafile }>()` 선언이 존재함 (증분 빌드 시 변경되지 않은 Worker metafile 유지 목적)
|
|
20
|
+
|
|
21
|
+
### onStart — TS 파일 Worker 패턴 처리 (Rule 1)
|
|
22
|
+
|
|
23
|
+
- [x] 증분 빌드 시 `expandedModifiedFiles` 각 파일에 대해 `workerResultsByContainingFile.delete(file)` 호출 (선택적 제거)
|
|
24
|
+
- [x] `emitResults` 루프에서 각 파일에 대해 `transformWorkerPatterns(contents, normalized, build)` 호출
|
|
25
|
+
- [x] `workerResult != null`이면 `typeScriptFileCache.set(normalized, workerResult.contents)` 저장
|
|
26
|
+
- [x] `workerResult.errors`/`workerResult.warnings`를 onStart의 `errors`/`warnings` 배열에 push
|
|
27
|
+
- [x] `workerResult.workerMetafile != null`이면 `referencedFileTracker.add(normalized, Object.keys(workerResult.workerMetafile.inputs).map(input => path.join(cwd, input)))` 호출
|
|
28
|
+
- [x] `workerMetafile` 또는 `workerOutputFiles`가 있으면 `workerResultsByContainingFile.set(normalized, { outputFiles, metafile })` 저장
|
|
29
|
+
- [x] `workerResult == null`이면 기존처럼 `typeScriptFileCache.set(normalized, contents)` 저장
|
|
30
|
+
- [x] Worker 번들 에러가 errors 배열에 포함되어 onStart 결과에 반영됨 (Scenario: 에러 전파)
|
|
31
|
+
|
|
32
|
+
### JS onLoad — .js 파일 Worker 패턴 처리 (Rule 2)
|
|
33
|
+
|
|
34
|
+
- [x] `createCachedLoad` 콜백에서 `javascriptTransformer.transformFile` 호출 후 결과를 `TextDecoder`로 문자열로 변환
|
|
35
|
+
- [x] 변환된 문자열에 `transformWorkerPatterns(textContents, request, build)` 적용
|
|
36
|
+
- [x] `workerResult != null`일 때:
|
|
37
|
+
- `workerResult.workerMetafile`이 있으면 `referencedFileTracker.add` 호출
|
|
38
|
+
- `workerMetafile` 또는 `workerOutputFiles`가 있으면 `workerResultsByContainingFile.set(request, { outputFiles, metafile })` 저장
|
|
39
|
+
- 반환: `{ contents, loader: "js", resolveDir, errors (>0일때), warnings (>0일때) }` — TS/JS 에러 처리 일관성 확보 (L2 리뷰 반영)
|
|
40
|
+
- [x] `workerResult == null`일 때 기존과 동일한 `{ contents, loader: "js", resolveDir }` 반환
|
|
41
|
+
|
|
42
|
+
### onEnd — metafile 병합 (Rule 3)
|
|
43
|
+
|
|
44
|
+
- [x] onEnd에서 `workerResultsByContainingFile.values()` 순회
|
|
45
|
+
- [x] 각 항목의 `outputFiles`가 있으면 `result.outputFiles?.push(...outputFiles)`
|
|
46
|
+
- [x] 각 항목의 `metafile`이 있으면 `Object.assign(result.metafile.inputs, wr.metafile.inputs)` + `Object.assign(result.metafile.outputs, wr.metafile.outputs)`
|
|
47
|
+
- [x] onEnd에서 Map 전체 리셋 없음 — 증분 빌드에서 변경되지 않은 Worker 결과가 다음 빌드에서도 병합됨
|
|
48
|
+
- [x] 기존 `additionalResults` 순회 루프가 제거됨
|
|
49
|
+
|
|
50
|
+
### client config 무변경 (Rule 6)
|
|
51
|
+
|
|
52
|
+
- [x] `esbuild-client-config.ts`에 Worker 플러그인 추가 코드가 존재하지 않음 (plugins 배열에 `createWorkerBundlePlugin` 또는 `sd-worker-bundle` 미포함)
|
|
53
|
+
- [x] `esbuild-client-config.ts` 파일 자체가 Feature 1.2로 인해 변경되지 않음 (git diff 기준)
|
|
54
|
+
|
|
55
|
+
### 회귀 방지
|
|
56
|
+
|
|
57
|
+
- [x] `pnpm test --run packages/sd-cli/tests/esbuild/esbuild-angular-compiler-plugin.spec.ts` 통과 (기존 plugin 구조 테스트)
|
|
58
|
+
- [x] `pnpm test --run packages/sd-cli/tests/esbuild/esbuild-worker-plugin.spec.ts` 통과 (Feature 1.1 Unit 테스트)
|
|
59
|
+
- [x] `pnpm test --run packages/sd-cli/tests/esbuild/esbuild-worker-plugin.acc.spec.ts` 통과 (Feature 1.1 Acceptance 테스트)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# PostCSS 문자열 교체 청크화 — LLM 검증
|
|
2
|
+
|
|
3
|
+
## 검증 항목
|
|
4
|
+
|
|
5
|
+
- [x] 정방향 청크 배열 + join이 역순 slice와 수학적으로 동일한 결과를 생성하는가:
|
|
6
|
+
역순 slice 방식은 `code[0..start_n] + rep_n + code[end_n..start_{n-1}] + rep_{n-1} + ... + code[end_1..]` 순서로 최종 문자열을 구성한다.
|
|
7
|
+
정방향 청크 방식은 `code[0..start_1] + rep_1 + code[end_1..start_2] + rep_2 + ... + code[end_n..]` 순서로 청크를 수집하고 join한다.
|
|
8
|
+
두 방식 모두 원본 텍스트의 비교체 구간과 PostCSS 처리된 교체 텍스트를 동일한 순서로 결합하므로, replacements가 비중첩(acorn AST 노드 보장)인 한 결과가 동일하다.
|
|
9
|
+
검증 결과: **동일성 보장됨**
|
|
10
|
+
|
|
11
|
+
- [x] cursor 초기값 0과 마지막 `code.slice(cursor)` 호출이 파일 전체를 커버하는가:
|
|
12
|
+
cursor는 0에서 시작하여 각 replacement의 end로 갱신된다. 마지막 `chunks.push(code.slice(cursor))`가 마지막 replacement 이후의 원본 텍스트를 수집한다. replacements가 0건이면 `code.slice(0)` = 전체 코드가 되지만, 이 경우 `replacements.length === 0`에서 continue되므로 도달하지 않는다.
|
|
13
|
+
검증 결과: **전체 커버됨**
|
|
14
|
+
|
|
15
|
+
- [x] `replacements.sort()`가 원본 배열을 변경하는 것이 문제되지 않는가:
|
|
16
|
+
`replacements`는 각 파일 처리 시 새로 생성되는 로컬 배열이므로 in-place sort가 안전하다. 기존 코드도 동일하게 in-place sort를 사용했다.
|
|
17
|
+
검증 결과: **문제 없음**
|