@simplysm/sd-cli 14.2.6 → 14.2.8

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 (49) hide show
  1. package/dist/commands/device.d.ts.map +1 -1
  2. package/dist/commands/device.js +6 -3
  3. package/dist/commands/device.js.map +1 -1
  4. package/dist/commands/init/normalize.d.ts.map +1 -1
  5. package/dist/commands/init/normalize.js +1 -0
  6. package/dist/commands/init/normalize.js.map +1 -1
  7. package/dist/commands/init/types.d.ts +1 -0
  8. package/dist/commands/init/types.d.ts.map +1 -1
  9. package/dist/esbuild/esbuild-config.d.ts.map +1 -1
  10. package/dist/esbuild/esbuild-config.js +2 -0
  11. package/dist/esbuild/esbuild-config.js.map +1 -1
  12. package/dist/sd-config.types.d.ts +2 -1
  13. package/dist/sd-config.types.d.ts.map +1 -1
  14. package/dist/utils/license-extractor.d.ts +53 -0
  15. package/dist/utils/license-extractor.d.ts.map +1 -0
  16. package/dist/utils/license-extractor.js +163 -0
  17. package/dist/utils/license-extractor.js.map +1 -0
  18. package/dist/workers/client.worker.d.ts.map +1 -1
  19. package/dist/workers/client.worker.js +6 -11
  20. package/dist/workers/client.worker.js.map +1 -1
  21. package/dist/workers/server-build.worker.d.ts.map +1 -1
  22. package/dist/workers/server-build.worker.js +14 -4
  23. package/dist/workers/server-build.worker.js.map +1 -1
  24. package/package.json +4 -4
  25. package/src/commands/device.ts +7 -3
  26. package/src/commands/init/normalize.ts +1 -0
  27. package/src/commands/init/templates/client/package.json.hbs +10 -8
  28. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +2 -2
  29. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.detail.ts.hbs +7 -5
  30. package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +2 -2
  31. package/src/commands/init/templates/client/src/app/home/system/system-log/system-log.list.ts.hbs +1 -1
  32. package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +1 -1
  33. package/src/commands/init/templates/client/src/modals/text-view.modal.ts.hbs +1 -1
  34. package/src/commands/init/templates/client/tsconfig.json +1 -0
  35. package/src/commands/init/templates/client-common/package.json.hbs +5 -5
  36. package/src/commands/init/templates/client-common/tsconfig.json +1 -0
  37. package/src/commands/init/templates/common/package.json.hbs +4 -4
  38. package/src/commands/init/templates/common/src/db/system-data-log.ext.ts.hbs +4 -4
  39. package/src/commands/init/templates/server/package.json.hbs +7 -7
  40. package/src/commands/init/templates/workspace-root/package.json.hbs +3 -2
  41. package/src/commands/init/templates/workspace-root/pnpm-workspace.yaml +13 -0
  42. package/src/commands/init/types.ts +1 -0
  43. package/src/esbuild/esbuild-config.ts +27 -32
  44. package/src/sd-config.types.ts +2 -1
  45. package/src/utils/license-extractor.ts +224 -0
  46. package/src/workers/client.worker.ts +16 -23
  47. package/src/workers/server-build.worker.ts +32 -12
  48. package/tests/init/render.spec.ts +21 -0
  49. package/tests/utils/license-extractor.spec.ts +273 -0
@@ -0,0 +1,273 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ import type esbuild from "esbuild";
6
+
7
+ const { extractLicenses, resolvePackageRef, resolveLicenseId } =
8
+ await import("../../src/utils/license-extractor");
9
+
10
+ let rootDir: string;
11
+
12
+ beforeEach(() => {
13
+ rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "sd-cli-license-"));
14
+ });
15
+
16
+ afterEach(() => {
17
+ fs.rmSync(rootDir, { recursive: true, force: true });
18
+ });
19
+
20
+ /** rootDir 하위에 패키지 디렉터리를 만든다 */
21
+ function writePackage(
22
+ relDir: string,
23
+ manifest: object | undefined,
24
+ files: Record<string, string> = {},
25
+ ): void {
26
+ const dir = path.join(rootDir, relDir);
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ if (manifest != null) {
29
+ fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify(manifest));
30
+ }
31
+ for (const [fileName, content] of Object.entries(files)) {
32
+ fs.writeFileSync(path.join(dir, fileName), content);
33
+ }
34
+ }
35
+
36
+ /** 입력 경로별 bytesInOutput 을 담은 단일 출력 metafile 을 만든다 */
37
+ function metafileOf(inputs: Record<string, number>): esbuild.Metafile {
38
+ return {
39
+ inputs: {},
40
+ outputs: {
41
+ "dist/main.js": {
42
+ bytes: 0,
43
+ inputs: Object.fromEntries(
44
+ Object.entries(inputs).map(([inputPath, bytesInOutput]) => [
45
+ inputPath,
46
+ { bytesInOutput },
47
+ ]),
48
+ ),
49
+ imports: [],
50
+ exports: [],
51
+ },
52
+ },
53
+ };
54
+ }
55
+
56
+ describe("resolvePackageRef", () => {
57
+ it("node_modules 아래 파일에서 패키지명을 역추적한다", () => {
58
+ const ref = resolvePackageRef(path.join(rootDir, "node_modules", "foo", "dist", "index.js"));
59
+ expect(ref?.packageName).toBe("foo");
60
+ expect(ref?.packageDir).toBe(path.join(rootDir, "node_modules", "foo"));
61
+ });
62
+
63
+ it("scope 가 있는 패키지는 scope 를 포함한 이름으로 역추적한다", () => {
64
+ const ref = resolvePackageRef(
65
+ path.join(rootDir, "node_modules", "@scope", "bar", "src", "index.js"),
66
+ );
67
+ expect(ref?.packageName).toBe("@scope/bar");
68
+ expect(ref?.packageDir).toBe(path.join(rootDir, "node_modules", "@scope", "bar"));
69
+ });
70
+
71
+ it("중첩된 node_modules 에서는 가장 가까운 패키지를 찾는다", () => {
72
+ const ref = resolvePackageRef(
73
+ path.join(rootDir, "node_modules", "outer", "node_modules", "inner", "index.js"),
74
+ );
75
+ expect(ref?.packageName).toBe("inner");
76
+ });
77
+
78
+ it("node_modules 를 거치지 않는 경로는 패키지가 아니다", () => {
79
+ expect(
80
+ resolvePackageRef(path.join(rootDir, "packages", "app", "src", "main.ts")),
81
+ ).toBeUndefined();
82
+ });
83
+ });
84
+
85
+ describe("resolveLicenseId", () => {
86
+ it("license 문자열을 그대로 사용한다", () => {
87
+ expect(resolveLicenseId({ license: "MIT" })).toBe("MIT");
88
+ });
89
+
90
+ it("공백뿐인 license 는 미상으로 본다", () => {
91
+ expect(resolveLicenseId({ license: " " })).toBeUndefined();
92
+ });
93
+
94
+ it("deprecated license 오브젝트의 type 을 인식한다", () => {
95
+ expect(resolveLicenseId({ license: { type: "BSD-3-Clause" } })).toBe("BSD-3-Clause");
96
+ });
97
+
98
+ it("deprecated licenses 배열의 type 들을 OR 로 잇는다", () => {
99
+ expect(resolveLicenseId({ licenses: [{ type: "MIT" }, { type: "Apache-2.0" }] })).toBe(
100
+ "MIT OR Apache-2.0",
101
+ );
102
+ });
103
+
104
+ it("어떤 표기도 없으면 미상이다", () => {
105
+ expect(resolveLicenseId({ name: "foo", version: "1.0.0" })).toBeUndefined();
106
+ });
107
+ });
108
+
109
+ describe("extractLicenses", () => {
110
+ it("LICENSE 파일이 있으면 본문까지 기록한다", async () => {
111
+ writePackage(
112
+ "node_modules/foo",
113
+ { name: "foo", version: "1.2.3", license: "MIT" },
114
+ { LICENSE: "Copyright (c) 2020 Foo Author" },
115
+ );
116
+
117
+ const content = await extractLicenses(metafileOf({ "node_modules/foo/index.js": 10 }), rootDir);
118
+
119
+ expect(content).toContain("Package: foo@1.2.3");
120
+ expect(content).toContain("License: MIT");
121
+ expect(content).toContain("Copyright (c) 2020 Foo Author");
122
+ });
123
+
124
+ it("LICENSE 파일이 없으면 라이선스 식별자만 기록한다", async () => {
125
+ writePackage("node_modules/bar", { name: "bar", version: "0.1.0", license: "ISC" });
126
+
127
+ const content = await extractLicenses(metafileOf({ "node_modules/bar/index.js": 10 }), rootDir);
128
+
129
+ expect(content).toContain("Package: bar@0.1.0");
130
+ expect(content).toContain("License: ISC");
131
+ });
132
+
133
+ it("LICENSE.md 등 다른 파일명도 찾는다", async () => {
134
+ writePackage(
135
+ "node_modules/baz",
136
+ { name: "baz", version: "1.0.0", license: "MIT" },
137
+ { "LICENSE.md": "# License\nMIT text here" },
138
+ );
139
+
140
+ const content = await extractLicenses(metafileOf({ "node_modules/baz/index.js": 10 }), rootDir);
141
+
142
+ expect(content).toContain("MIT text here");
143
+ });
144
+
145
+ it("scope 가 있는 패키지도 기록한다", async () => {
146
+ writePackage("node_modules/@scope/pkg", {
147
+ name: "@scope/pkg",
148
+ version: "2.0.0",
149
+ license: "Apache-2.0",
150
+ });
151
+
152
+ const content = await extractLicenses(
153
+ metafileOf({ "node_modules/@scope/pkg/index.js": 10 }),
154
+ rootDir,
155
+ );
156
+
157
+ expect(content).toContain("Package: @scope/pkg@2.0.0");
158
+ });
159
+
160
+ it("같은 패키지의 여러 파일이 포함돼도 한 번만 기록한다", async () => {
161
+ writePackage("node_modules/foo", { name: "foo", version: "1.0.0", license: "MIT" });
162
+
163
+ const content = await extractLicenses(
164
+ metafileOf({
165
+ "node_modules/foo/index.js": 10,
166
+ "node_modules/foo/util.js": 20,
167
+ "node_modules/foo/deep/inner.js": 30,
168
+ }),
169
+ rootDir,
170
+ );
171
+
172
+ expect(content.match(/Package: foo@1\.0\.0/g)).toHaveLength(1);
173
+ });
174
+
175
+ it("산출물에 남지 않은 입력(bytesInOutput 0)은 제외한다", async () => {
176
+ writePackage("node_modules/used", { name: "used", version: "1.0.0", license: "MIT" });
177
+ writePackage("node_modules/shaken", { name: "shaken", version: "1.0.0", license: "MIT" });
178
+
179
+ const content = await extractLicenses(
180
+ metafileOf({
181
+ "node_modules/used/index.js": 10,
182
+ "node_modules/shaken/index.js": 0,
183
+ }),
184
+ rootDir,
185
+ );
186
+
187
+ expect(content).toContain("Package: used@1.0.0");
188
+ expect(content).not.toContain("Package: shaken@1.0.0");
189
+ });
190
+
191
+ it("node_modules 밖의 자체 소스는 제외한다", async () => {
192
+ const content = await extractLicenses(metafileOf({ "packages/app/src/main.ts": 100 }), rootDir);
193
+
194
+ expect(content).not.toContain("Package:");
195
+ });
196
+
197
+ it("SEE LICENSE IN 표기는 지정한 파일을 읽는다", async () => {
198
+ writePackage(
199
+ "node_modules/custom",
200
+ { name: "custom", version: "1.0.0", license: "SEE LICENSE IN TERMS.txt" },
201
+ { "TERMS.txt": "사내 전용 라이선스 조건" },
202
+ );
203
+
204
+ const content = await extractLicenses(
205
+ metafileOf({ "node_modules/custom/index.js": 10 }),
206
+ rootDir,
207
+ );
208
+
209
+ expect(content).toContain("사내 전용 라이선스 조건");
210
+ });
211
+
212
+ it("SEE LICENSE IN 이 가리키는 파일이 없으면 실패한다", async () => {
213
+ writePackage("node_modules/custom", {
214
+ name: "custom",
215
+ version: "1.0.0",
216
+ license: "SEE LICENSE IN MISSING.txt",
217
+ });
218
+
219
+ await expect(
220
+ extractLicenses(metafileOf({ "node_modules/custom/index.js": 10 }), rootDir),
221
+ ).rejects.toThrow("custom");
222
+ });
223
+
224
+ it("SEE LICENSE IN 이 패키지 바깥을 가리키면 실패한다", async () => {
225
+ writePackage("node_modules/escape", {
226
+ name: "escape",
227
+ version: "1.0.0",
228
+ license: "SEE LICENSE IN ../../secret.txt",
229
+ });
230
+
231
+ await expect(
232
+ extractLicenses(metafileOf({ "node_modules/escape/index.js": 10 }), rootDir),
233
+ ).rejects.toThrow("패키지 바깥");
234
+ });
235
+
236
+ it("라이선스를 알 수 없는 패키지가 있으면 실패하고 그 패키지를 알린다", async () => {
237
+ writePackage("node_modules/ok", { name: "ok", version: "1.0.0", license: "MIT" });
238
+ writePackage("node_modules/nolicense", { name: "nolicense", version: "3.1.4" });
239
+
240
+ await expect(
241
+ extractLicenses(
242
+ metafileOf({
243
+ "node_modules/ok/index.js": 10,
244
+ "node_modules/nolicense/index.js": 10,
245
+ }),
246
+ rootDir,
247
+ ),
248
+ ).rejects.toThrow("nolicense@3.1.4");
249
+ });
250
+
251
+ it("package.json 이 없는 node_modules 경로는 실패한다", async () => {
252
+ writePackage("node_modules/broken", undefined, { "index.js": "" });
253
+
254
+ await expect(
255
+ extractLicenses(metafileOf({ "node_modules/broken/index.js": 10 }), rootDir),
256
+ ).rejects.toThrow("패키지 정보를 찾을 수 없습니다");
257
+ });
258
+
259
+ it("패키지를 이름순으로 정렬해 기록한다", async () => {
260
+ writePackage("node_modules/zeta", { name: "zeta", version: "1.0.0", license: "MIT" });
261
+ writePackage("node_modules/alpha", { name: "alpha", version: "1.0.0", license: "MIT" });
262
+
263
+ const content = await extractLicenses(
264
+ metafileOf({
265
+ "node_modules/zeta/index.js": 10,
266
+ "node_modules/alpha/index.js": 10,
267
+ }),
268
+ rootDir,
269
+ );
270
+
271
+ expect(content.indexOf("Package: alpha")).toBeLessThan(content.indexOf("Package: zeta"));
272
+ });
273
+ });