@simplysm/sd-cli 14.0.96 → 14.0.97
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/commands/init/generators/client.d.ts.map +1 -1
- package/dist/commands/init/generators/client.js +3 -0
- package/dist/commands/init/generators/client.js.map +1 -1
- package/dist/commands/init/normalize.d.ts.map +1 -1
- package/dist/commands/init/normalize.js +1 -0
- package/dist/commands/init/normalize.js.map +1 -1
- package/dist/commands/init/prompts.d.ts.map +1 -1
- package/dist/commands/init/prompts.js +8 -1
- package/dist/commands/init/prompts.js.map +1 -1
- package/dist/commands/init/types.d.ts +3 -0
- package/dist/commands/init/types.d.ts.map +1 -1
- package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
- package/dist/engines/EsbuildClientEngine.js +1 -0
- package/dist/engines/EsbuildClientEngine.js.map +1 -1
- package/dist/esbuild/esbuild-client-config.d.ts.map +1 -1
- package/dist/esbuild/esbuild-client-config.js +2 -11
- package/dist/esbuild/esbuild-client-config.js.map +1 -1
- package/dist/esbuild/esbuild-postcss-plugin.d.ts +4 -0
- package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -1
- package/dist/esbuild/esbuild-postcss-plugin.js +15 -0
- package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -1
- package/dist/esbuild/esbuild-ssr-config.d.ts +27 -0
- package/dist/esbuild/esbuild-ssr-config.d.ts.map +1 -0
- package/dist/esbuild/esbuild-ssr-config.js +113 -0
- package/dist/esbuild/esbuild-ssr-config.js.map +1 -0
- package/dist/sd-config.types.d.ts +7 -0
- package/dist/sd-config.types.d.ts.map +1 -1
- package/dist/ssg/prerender.d.ts +19 -0
- package/dist/ssg/prerender.d.ts.map +1 -0
- package/dist/ssg/prerender.js +43 -0
- package/dist/ssg/prerender.js.map +1 -0
- package/dist/workers/client.worker.d.ts +2 -0
- package/dist/workers/client.worker.d.ts.map +1 -1
- package/dist/workers/client.worker.js +21 -0
- package/dist/workers/client.worker.js.map +1 -1
- package/package.json +6 -6
- package/src/commands/init/generators/client.ts +8 -0
- package/src/commands/init/normalize.ts +1 -0
- package/src/commands/init/prompts.ts +9 -1
- package/src/commands/init/templates/client/package.json.hbs +2 -1
- package/src/commands/init/templates/client/src/main.server.ts.hbs +24 -0
- package/src/commands/init/templates/client/src/main.ts.hbs +13 -0
- package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +3 -0
- package/src/commands/init/types.ts +3 -0
- package/src/engines/EsbuildClientEngine.ts +1 -0
- package/src/esbuild/esbuild-client-config.ts +2 -12
- package/src/esbuild/esbuild-postcss-plugin.ts +18 -0
- package/src/esbuild/esbuild-ssr-config.ts +149 -0
- package/src/sd-config.types.ts +7 -0
- package/src/ssg/prerender.ts +65 -0
- package/src/workers/client.worker.ts +26 -0
- package/tests/engines/base-engine.spec.ts +1 -26
- package/tests/init/__snapshots__/render.spec.ts.snap +38 -20
- package/tests/init/render.spec.ts +113 -33
- package/tests/utils/hmr-client-script.acc.spec.ts +0 -21
- package/tests/angular/vite-angular-plugin.spec.ts +0 -102
- package/tests/engines/engine-adapter-isolation.spec.ts +0 -79
- package/tests/runtime/signal-handler.spec.ts +0 -21
- package/tests/utils/angular-build.spec.ts +0 -109
- package/tests/utils/esbuild-client-config.acc.spec.ts +0 -438
- package/tests/utils/esbuild-client-config.spec.ts +0 -659
- package/tests/utils/hmr-client-script.spec.ts +0 -44
- package/tests/utils/tsconfig-angular.spec.ts +0 -9
|
@@ -14,8 +14,7 @@ vi.spyOn(Worker, "create").mockReturnValue(mockWorker as any);
|
|
|
14
14
|
import { TscEngine } from "../../src/engines/TscEngine";
|
|
15
15
|
import { BaseEngine } from "../../src/engines/BaseEngine";
|
|
16
16
|
|
|
17
|
-
import type { BuildPackageInfo
|
|
18
|
-
import type { BuildResult } from "../../src/runtime/ResultCollector";
|
|
17
|
+
import type { BuildPackageInfo } from "../../src/engines/types";
|
|
19
18
|
|
|
20
19
|
// --- Helpers ---
|
|
21
20
|
|
|
@@ -373,18 +372,6 @@ describe("BaseEngine", () => {
|
|
|
373
372
|
});
|
|
374
373
|
|
|
375
374
|
describe("lint 통합", () => {
|
|
376
|
-
describe("BuildOutput.lint flag controls lint execution", () => {
|
|
377
|
-
it("BuildOutput이 lint 불리언 플래그를 수용", () => {
|
|
378
|
-
const output: BuildOutput = { js: true, dts: true, lint: true };
|
|
379
|
-
expect(output.lint).toBe(true);
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it("BuildOutput.lint는 미설정 시 undefined가 기본값", () => {
|
|
383
|
-
const output: BuildOutput = { js: true, dts: true };
|
|
384
|
-
expect(output.lint).toBeUndefined();
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
|
|
388
375
|
describe("EngineResult includes lint field", () => {
|
|
389
376
|
it("run() returns EngineResult with lint field when worker provides it", async () => {
|
|
390
377
|
mockWorker.build.mockResolvedValue({
|
|
@@ -496,17 +483,5 @@ describe("BaseEngine", () => {
|
|
|
496
483
|
await engine.stop();
|
|
497
484
|
});
|
|
498
485
|
});
|
|
499
|
-
|
|
500
|
-
describe("ResultCollector supports lint type", () => {
|
|
501
|
-
it("BuildResult 타입에 lint 포함", () => {
|
|
502
|
-
const lintResult: BuildResult = {
|
|
503
|
-
name: "test-pkg",
|
|
504
|
-
target: "node",
|
|
505
|
-
type: "lint",
|
|
506
|
-
status: "success",
|
|
507
|
-
};
|
|
508
|
-
expect(lintResult.type).toBe("lint");
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
486
|
});
|
|
512
487
|
});
|
|
@@ -1,68 +1,82 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
3
|
exports[`client/src/main.ts.hbs > 라우팅 N (mobile) 1`] = `
|
|
4
|
-
"import
|
|
5
|
-
import { enableProdMode, inject, provideAppInitializer } from "@angular/core";
|
|
4
|
+
"import { enableProdMode, inject, provideAppInitializer } from "@angular/core";
|
|
6
5
|
import { bootstrapApplication } from "@angular/platform-browser";
|
|
7
6
|
import { provideHttpClient, withFetch } from "@angular/common/http";
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
provideSdAngular,
|
|
9
|
+
} from "@simplysm/angular";
|
|
10
|
+
import { createLogger } from "@simplysm/core-common";
|
|
10
11
|
import { AppRoot } from "./app.root";
|
|
11
12
|
import "./styles.scss";
|
|
13
|
+
import {
|
|
14
|
+
AppServiceProvider,
|
|
15
|
+
} from "@demo/client-common";
|
|
16
|
+
|
|
17
|
+
const CLIENT_NAME = "client-pda";
|
|
18
|
+
|
|
19
|
+
const logger = createLogger(CLIENT_NAME);
|
|
12
20
|
|
|
13
21
|
if (typeof ngDevMode !== "undefined" && !ngDevMode) {
|
|
14
22
|
enableProdMode();
|
|
15
23
|
} else {
|
|
16
|
-
|
|
24
|
+
logger.info("dev mode");
|
|
17
25
|
}
|
|
18
26
|
|
|
19
27
|
bootstrapApplication(AppRoot, {
|
|
20
28
|
providers: [
|
|
21
29
|
provideHttpClient(withFetch()),
|
|
22
|
-
provideSdAngular({ clientName:
|
|
30
|
+
provideSdAngular({ clientName: CLIENT_NAME }),
|
|
23
31
|
|
|
24
|
-
//-- app-service 연결
|
|
25
32
|
provideAppInitializer(async () => {
|
|
26
33
|
await inject(AppServiceProvider).connectAsync();
|
|
27
34
|
}),
|
|
28
35
|
],
|
|
29
36
|
}).catch((err: unknown) => {
|
|
30
|
-
|
|
37
|
+
logger.error("부트스트랩 실패", err);
|
|
31
38
|
});
|
|
32
39
|
"
|
|
33
40
|
`;
|
|
34
41
|
|
|
35
42
|
exports[`client/src/main.ts.hbs > 라우팅 Y (web) 1`] = `
|
|
36
|
-
"import
|
|
37
|
-
import { enableProdMode, inject, provideAppInitializer } from "@angular/core";
|
|
43
|
+
"import { enableProdMode, inject, provideAppInitializer } from "@angular/core";
|
|
38
44
|
import { bootstrapApplication } from "@angular/platform-browser";
|
|
39
45
|
import { provideHttpClient, withFetch } from "@angular/common/http";
|
|
40
46
|
import { provideRouter, withHashLocation } from "@angular/router";
|
|
41
|
-
import {
|
|
42
|
-
|
|
47
|
+
import {
|
|
48
|
+
provideSdAngular,
|
|
49
|
+
} from "@simplysm/angular";
|
|
50
|
+
import { createLogger } from "@simplysm/core-common";
|
|
43
51
|
import { AppRoot } from "./app.root";
|
|
44
52
|
import { routes } from "./routes";
|
|
45
53
|
import "./styles.scss";
|
|
54
|
+
import {
|
|
55
|
+
AppServiceProvider,
|
|
56
|
+
} from "@demo/client-common";
|
|
57
|
+
|
|
58
|
+
const CLIENT_NAME = "client-admin";
|
|
59
|
+
|
|
60
|
+
const logger = createLogger(CLIENT_NAME);
|
|
46
61
|
|
|
47
62
|
if (typeof ngDevMode !== "undefined" && !ngDevMode) {
|
|
48
63
|
enableProdMode();
|
|
49
64
|
} else {
|
|
50
|
-
|
|
65
|
+
logger.info("dev mode");
|
|
51
66
|
}
|
|
52
67
|
|
|
53
68
|
bootstrapApplication(AppRoot, {
|
|
54
69
|
providers: [
|
|
55
70
|
provideHttpClient(withFetch()),
|
|
56
71
|
provideRouter(routes, withHashLocation()),
|
|
57
|
-
provideSdAngular({ clientName:
|
|
72
|
+
provideSdAngular({ clientName: CLIENT_NAME }),
|
|
58
73
|
|
|
59
|
-
//-- app-service 연결
|
|
60
74
|
provideAppInitializer(async () => {
|
|
61
75
|
await inject(AppServiceProvider).connectAsync();
|
|
62
76
|
}),
|
|
63
77
|
],
|
|
64
78
|
}).catch((err: unknown) => {
|
|
65
|
-
|
|
79
|
+
logger.error("부트스트랩 실패", err);
|
|
66
80
|
});
|
|
67
81
|
"
|
|
68
82
|
`;
|
|
@@ -177,7 +191,7 @@ exports[`server/src/main.ts.hbs > DB=N — OrmService 없음 1`] = `
|
|
|
177
191
|
import { env, num, parseBoolEnv } from "@simplysm/core-common";
|
|
178
192
|
import { setupConsola } from "@simplysm/core-node";
|
|
179
193
|
import { createServiceServer, getConfig } from "@simplysm/service-server";
|
|
180
|
-
import {EventEmitter} from "node:events";
|
|
194
|
+
import { EventEmitter } from "node:events";
|
|
181
195
|
|
|
182
196
|
Error.stackTraceLimit = Infinity;
|
|
183
197
|
EventEmitter.defaultMaxListeners = 0;
|
|
@@ -194,7 +208,8 @@ if (authConfig?.jwtSecret == null) {
|
|
|
194
208
|
|
|
195
209
|
export const server = createServiceServer({
|
|
196
210
|
rootPath: import.meta.dirname,
|
|
197
|
-
services: [
|
|
211
|
+
services: [
|
|
212
|
+
],
|
|
198
213
|
port: num.parseInt(env("SERVER_PORT"))!,
|
|
199
214
|
auth: { jwtSecret: authConfig.jwtSecret },
|
|
200
215
|
});
|
|
@@ -210,7 +225,7 @@ exports[`server/src/main.ts.hbs > DB=Y — OrmService 포함 1`] = `
|
|
|
210
225
|
import { env, num, parseBoolEnv } from "@simplysm/core-common";
|
|
211
226
|
import { setupConsola } from "@simplysm/core-node";
|
|
212
227
|
import { createServiceServer, getConfig, OrmService } from "@simplysm/service-server";
|
|
213
|
-
import {EventEmitter} from "node:events";
|
|
228
|
+
import { EventEmitter } from "node:events";
|
|
214
229
|
import { DevService } from "./services/dev.service";
|
|
215
230
|
|
|
216
231
|
Error.stackTraceLimit = Infinity;
|
|
@@ -228,7 +243,10 @@ if (authConfig?.jwtSecret == null) {
|
|
|
228
243
|
|
|
229
244
|
export const server = createServiceServer({
|
|
230
245
|
rootPath: import.meta.dirname,
|
|
231
|
-
services: [
|
|
246
|
+
services: [
|
|
247
|
+
OrmService,
|
|
248
|
+
...(parseBoolEnv(env("DEV")) ? [DevService] : []),
|
|
249
|
+
],
|
|
232
250
|
port: num.parseInt(env("SERVER_PORT"))!,
|
|
233
251
|
auth: { jwtSecret: authConfig.jwtSecret },
|
|
234
252
|
});
|
|
@@ -141,9 +141,8 @@ describe("server/src/main.ts.hbs", () => {
|
|
|
141
141
|
serverPort: 40080,
|
|
142
142
|
});
|
|
143
143
|
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/main.ts.hbs"), data);
|
|
144
|
-
expect(out).toContain(
|
|
145
|
-
|
|
146
|
-
);
|
|
144
|
+
expect(out).toContain("OrmService,");
|
|
145
|
+
expect(out).toContain('...(parseBoolEnv(env("DEV")) ? [DevService] : []),');
|
|
147
146
|
expect(out).not.toContain("AuthService");
|
|
148
147
|
});
|
|
149
148
|
|
|
@@ -161,9 +160,9 @@ describe("server/src/main.ts.hbs", () => {
|
|
|
161
160
|
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/main.ts.hbs"), data);
|
|
162
161
|
expect(out).toContain('import { AuthService } from "./services/auth.service";');
|
|
163
162
|
expect(out).toContain('import { DevService } from "./services/dev.service";');
|
|
164
|
-
expect(out).toContain(
|
|
165
|
-
|
|
166
|
-
);
|
|
163
|
+
expect(out).toContain("OrmService,");
|
|
164
|
+
expect(out).toContain("AuthService,");
|
|
165
|
+
expect(out).toContain('...(parseBoolEnv(env("DEV")) ? [DevService] : []),');
|
|
167
166
|
});
|
|
168
167
|
});
|
|
169
168
|
|
|
@@ -208,8 +207,8 @@ describe("server 인증 템플릿", () => {
|
|
|
208
207
|
expect(out).toContain("roleName: employee.role!.name,");
|
|
209
208
|
expect(out).toContain(".include((item) => item.role)");
|
|
210
209
|
expect(out).toContain(".from(EmployeeConfig)");
|
|
211
|
-
expect(out).toContain("expr.eq(
|
|
212
|
-
expect(out).toContain("(configs as Record<string, unknown>)[
|
|
210
|
+
expect(out).toContain("expr.eq(ec.employeeId, e.id)");
|
|
211
|
+
expect(out).toContain("(configs as Record<string, unknown>)[c.code] = JSON.parse(c.valueJson);");
|
|
213
212
|
expect(out).toContain("유효하지 않은 직원입니다.");
|
|
214
213
|
|
|
215
214
|
// update(내정보수정) 메서드 + 인증정보 변경 이벤트 emit
|
|
@@ -371,8 +370,24 @@ describe("client 인증 로그인 (routes / login.view / ng-icons)", () => {
|
|
|
371
370
|
expect(out).toContain('"@ng-icons/tabler-icons": "^33.2.3"');
|
|
372
371
|
});
|
|
373
372
|
|
|
374
|
-
it("client/package.json: 인증 OFF → @ng-icons
|
|
373
|
+
it("client/package.json: 인증 OFF + DB ON → dev 모달용 @ng-icons 포함", async () => {
|
|
375
374
|
const out = await renderTemplate(path.join(TPL_ROOT, "client/package.json.hbs"), authClientCtx(false));
|
|
375
|
+
expect(out).toContain('"@ng-icons/core": "^33.2.3"');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("client/package.json: 인증 OFF + DB OFF → @ng-icons 없음", async () => {
|
|
379
|
+
const data = buildData({
|
|
380
|
+
workspaceName: "demo",
|
|
381
|
+
description: "Demo",
|
|
382
|
+
hasServer: true,
|
|
383
|
+
hasDb: false,
|
|
384
|
+
clients: [{ name: "admin", type: "web", hasRouter: true }],
|
|
385
|
+
serverPort: 40080,
|
|
386
|
+
});
|
|
387
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/package.json.hbs"), {
|
|
388
|
+
...data,
|
|
389
|
+
client: data.clients[0],
|
|
390
|
+
});
|
|
376
391
|
expect(out).not.toContain("@ng-icons");
|
|
377
392
|
});
|
|
378
393
|
});
|
|
@@ -397,7 +412,7 @@ describe("관리자 셸 (home/main/app-structure/main.ts 와이어링)", () => {
|
|
|
397
412
|
it("common/app-structure: 사용자 엔티티 메뉴 항목에 선택 네이밍 반영", async () => {
|
|
398
413
|
const out = await renderTemplate(path.join(TPL_ROOT, "common/src/app-structure.ts.hbs"), authClientCtx(true));
|
|
399
414
|
expect(out).toContain('import type { AppStructureItem } from "@simplysm/service-common";');
|
|
400
|
-
expect(out).toContain("export const
|
|
415
|
+
expect(out).toContain("export const adminAppStructureItems: AppStructureItem[] = [");
|
|
401
416
|
expect(out).toContain('{ code: "employee", title: "직원", perms: ["use", "edit"] }');
|
|
402
417
|
expect(out).toContain('{ code: "role-permission", title: "역할/권한", perms: ["use", "edit"] }');
|
|
403
418
|
});
|
|
@@ -448,8 +463,8 @@ describe("관리자 셸 (home/main/app-structure/main.ts 와이어링)", () => {
|
|
|
448
463
|
it("client/main.ts: 인증 ON + router → app-structure 초기화 와이어링", async () => {
|
|
449
464
|
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/main.ts.hbs"), authClientCtx(true));
|
|
450
465
|
expect(out).toContain("SdAppStructureProvider");
|
|
451
|
-
expect(out).toContain('import {
|
|
452
|
-
expect(out).toContain("inject(SdAppStructureProvider).initialize(
|
|
466
|
+
expect(out).toContain('import { adminAppStructureItems } from "@demo/common";');
|
|
467
|
+
expect(out).toContain("inject(SdAppStructureProvider).initialize(adminAppStructureItems);");
|
|
453
468
|
});
|
|
454
469
|
|
|
455
470
|
it("client/main.ts: 인증 OFF → app-structure 초기화 없음", async () => {
|
|
@@ -534,7 +549,8 @@ describe("client-common/src/providers/app-service.provider.ts.hbs", () => {
|
|
|
534
549
|
serverPort: 40080,
|
|
535
550
|
});
|
|
536
551
|
const out = await renderTemplate(path.join(TPL_ROOT, svcTpl), data);
|
|
537
|
-
expect(out).toContain(
|
|
552
|
+
expect(out).toContain("AuthServiceMethods,");
|
|
553
|
+
expect(out).toContain('} from "@demo/server";');
|
|
538
554
|
expect(out).toContain("type ServiceProxy,");
|
|
539
555
|
expect(out).toContain("private _auth?: ServiceProxy<AuthServiceMethods>;");
|
|
540
556
|
expect(out).toContain("get auth(): ServiceProxy<AuthServiceMethods> {");
|
|
@@ -564,8 +580,8 @@ describe("client-common/src/providers/app-auth.provider.ts.hbs", () => {
|
|
|
564
580
|
expect(out).toContain("export class AppAuthProvider {");
|
|
565
581
|
expect(out).toContain("authInfo = signal<IAuthData | undefined>(undefined);");
|
|
566
582
|
expect(out).toContain("inject(SdAppStructureProvider)");
|
|
567
|
-
expect(out).toContain("this._sdAppStructure.permRecord.set(
|
|
568
|
-
expect(out).toContain("async login(
|
|
583
|
+
expect(out).toContain("this._sdAppStructure.permRecord.set(authData.permissions);");
|
|
584
|
+
expect(out).toContain("async login(loginId: string, password: string): Promise<void>");
|
|
569
585
|
expect(out).toContain("async tryReloadAuth(): Promise<boolean>");
|
|
570
586
|
expect(out).toContain("await this._appService.client.auth(token);");
|
|
571
587
|
});
|
|
@@ -584,7 +600,7 @@ describe("client-common/src/index.ts.hbs auth export", () => {
|
|
|
584
600
|
serverPort: 40080,
|
|
585
601
|
});
|
|
586
602
|
const out = await renderTemplate(path.join(TPL_ROOT, "client-common/src/index.ts.hbs"), data);
|
|
587
|
-
expect(out).toContain('export
|
|
603
|
+
expect(out).toContain('export * from "./providers/app-auth.provider";');
|
|
588
604
|
});
|
|
589
605
|
|
|
590
606
|
it("인증 OFF → AppAuthProvider export 없음", async () => {
|
|
@@ -616,9 +632,7 @@ describe("server/src/index.ts.hbs", () => {
|
|
|
616
632
|
serverPort: 40080,
|
|
617
633
|
});
|
|
618
634
|
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/index.ts.hbs"), data);
|
|
619
|
-
expect(out).toContain(
|
|
620
|
-
'export type { AuthServiceMethods, IAuthData, IAuthResult } from "./services/auth.service";',
|
|
621
|
-
);
|
|
635
|
+
expect(out).toContain('export type * from "./services/auth.service";');
|
|
622
636
|
});
|
|
623
637
|
});
|
|
624
638
|
|
|
@@ -928,6 +942,68 @@ describe("common/src/index.ts.hbs", () => {
|
|
|
928
942
|
});
|
|
929
943
|
});
|
|
930
944
|
|
|
945
|
+
describe("SSG 클라이언트 스캐폴드", () => {
|
|
946
|
+
function ssgCtx(useSsg: boolean) {
|
|
947
|
+
const data = buildData({
|
|
948
|
+
workspaceName: "demo",
|
|
949
|
+
description: "Demo",
|
|
950
|
+
hasServer: true,
|
|
951
|
+
hasDb: false,
|
|
952
|
+
clients: [{ name: "portal", type: "web", hasRouter: true, useSsg }],
|
|
953
|
+
serverPort: 40080,
|
|
954
|
+
});
|
|
955
|
+
return { ...data, client: data.clients[0] };
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
it("sd.config: SSG ON → prerender 설정 포함", async () => {
|
|
959
|
+
const ctx = ssgCtx(true);
|
|
960
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "workspace-root/sd.config.ts.hbs"), ctx);
|
|
961
|
+
expect(out).toContain('prerender: ["/"],');
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it("sd.config: SSG OFF → prerender 없음", async () => {
|
|
965
|
+
const ctx = ssgCtx(false);
|
|
966
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "workspace-root/sd.config.ts.hbs"), ctx);
|
|
967
|
+
expect(out).not.toContain("prerender");
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it("client/package.json: SSG ON → @angular/platform-server 의존성 포함", async () => {
|
|
971
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/package.json.hbs"), ssgCtx(true));
|
|
972
|
+
expect(out).toContain('"@angular/platform-server": "^21.2.0"');
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
it("client/package.json: SSG OFF → @angular/platform-server 없음", async () => {
|
|
976
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/package.json.hbs"), ssgCtx(false));
|
|
977
|
+
expect(out).not.toContain("@angular/platform-server");
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it("client/main.ts: SSG ON → path 라우팅 + hydration", async () => {
|
|
981
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/main.ts.hbs"), ssgCtx(true));
|
|
982
|
+
expect(out).toContain('import { provideRouter } from "@angular/router";');
|
|
983
|
+
expect(out).not.toContain("withHashLocation");
|
|
984
|
+
expect(out).toContain("provideClientHydration");
|
|
985
|
+
expect(out).toContain("provideRouter(routes),");
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
it("client/main.ts: SSG OFF → hash 라우팅 유지 + hydration 없음", async () => {
|
|
989
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/main.ts.hbs"), ssgCtx(false));
|
|
990
|
+
expect(out).toContain("provideRouter(routes, withHashLocation()),");
|
|
991
|
+
expect(out).not.toContain("provideClientHydration");
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it("client/main.server.ts: 서버 부트스트랩 default export + 최소 provider", async () => {
|
|
995
|
+
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/main.server.ts.hbs"), ssgCtx(true));
|
|
996
|
+
expect(out).toContain('import { provideServerRendering } from "@angular/platform-server";');
|
|
997
|
+
expect(out).toContain("const bootstrap = (context: BootstrapContext) =>");
|
|
998
|
+
expect(out).toContain("provideRouter(routes),");
|
|
999
|
+
expect(out).toContain('provideSdAngular({ clientName: "client-portal" }),');
|
|
1000
|
+
expect(out).toContain("provideServerRendering(),");
|
|
1001
|
+
expect(out).toContain("export default bootstrap;");
|
|
1002
|
+
// 프리렌더는 빌드 시점 — 서버 연결·로그 배선 제외
|
|
1003
|
+
expect(out).not.toContain("connectAsync");
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
|
|
931
1007
|
describe("root/package.json.hbs", () => {
|
|
932
1008
|
it("hasMobile=true 시 run-device 스크립트 포함", async () => {
|
|
933
1009
|
const data = buildData({
|
|
@@ -984,8 +1060,9 @@ describe("common/src/auth-info-changed.event.ts.hbs", () => {
|
|
|
984
1060
|
);
|
|
985
1061
|
expect(out).toContain('import { defineEvent } from "@simplysm/service-common";');
|
|
986
1062
|
expect(out).toContain(
|
|
987
|
-
|
|
1063
|
+
"export const AuthInfoChangedEvent = defineEvent<{ employeeId: number; roleId: number }, void>(",
|
|
988
1064
|
);
|
|
1065
|
+
expect(out).toContain('"AuthInfoChanged",');
|
|
989
1066
|
});
|
|
990
1067
|
});
|
|
991
1068
|
|
|
@@ -1057,8 +1134,10 @@ describe("client-common 인증정보 변경 구독 배선", () => {
|
|
|
1057
1134
|
);
|
|
1058
1135
|
expect(out).toContain("async logout(): Promise<void> {");
|
|
1059
1136
|
expect(out).toContain("await this._unregisterAuthEvent();");
|
|
1060
|
-
expect(out).toContain("await this._registerAuthEvent(authData.employeeId);");
|
|
1061
|
-
expect(out).toContain(
|
|
1137
|
+
expect(out).toContain("await this._registerAuthEvent(authData.employeeId, authData.roleId);");
|
|
1138
|
+
expect(out).toContain(
|
|
1139
|
+
"private async _registerAuthEvent(employeeId: number, roleId: number): Promise<void> {",
|
|
1140
|
+
);
|
|
1062
1141
|
expect(out).toContain("this._appService.authInfoChangedEvent.addListener(");
|
|
1063
1142
|
});
|
|
1064
1143
|
});
|
|
@@ -1094,7 +1173,7 @@ describe("client/src/app/home/my-info/my-info.detail.ts.hbs", () => {
|
|
|
1094
1173
|
expect(out).toContain("this._appAuth.authInfo()?.employeeId");
|
|
1095
1174
|
expect(out).toContain("lastModifiedBy: item.lastDataLog?.employeeName,");
|
|
1096
1175
|
expect(out).toContain("await this._appService.auth.update({");
|
|
1097
|
-
expect(out).toContain('{{ flatMenu.titleChain.join("
|
|
1176
|
+
expect(out).toContain('{{ flatMenu.titleChain.join("» ") }}');
|
|
1098
1177
|
expect(out).not.toContain("\\{{");
|
|
1099
1178
|
});
|
|
1100
1179
|
});
|
|
@@ -1142,8 +1221,8 @@ describe("server/src/services/dev.service.ts.hbs", () => {
|
|
|
1142
1221
|
expect(out).toContain('import { ExcelWorkbook } from "@simplysm/excel";');
|
|
1143
1222
|
expect(out).toContain('import { appStructureItems } from "@demo/common";');
|
|
1144
1223
|
expect(out).toContain(".employee().insert(");
|
|
1145
|
-
expect(out).toContain("getFlatPermissions(
|
|
1146
|
-
expect(out).toContain("
|
|
1224
|
+
expect(out).toContain("getFlatPermissions(");
|
|
1225
|
+
expect(out).toContain("appStructureItems,");
|
|
1147
1226
|
});
|
|
1148
1227
|
|
|
1149
1228
|
it("인증 OFF (DB만) → 시드 없이 db.initialize + TODO", async () => {
|
|
@@ -1182,7 +1261,7 @@ describe("server/src/index.ts.hbs dev export", () => {
|
|
|
1182
1261
|
serverPort: 40080,
|
|
1183
1262
|
});
|
|
1184
1263
|
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/index.ts.hbs"), data);
|
|
1185
|
-
expect(out).toContain('export type
|
|
1264
|
+
expect(out).toContain('export type * from "./services/dev.service";');
|
|
1186
1265
|
expect(out).not.toContain("auth.service");
|
|
1187
1266
|
});
|
|
1188
1267
|
|
|
@@ -1198,8 +1277,8 @@ describe("server/src/index.ts.hbs dev export", () => {
|
|
|
1198
1277
|
serverPort: 40080,
|
|
1199
1278
|
});
|
|
1200
1279
|
const out = await renderTemplate(path.join(TPL_ROOT, "server/src/index.ts.hbs"), data);
|
|
1201
|
-
expect(out).toContain('from "./services/auth.service";');
|
|
1202
|
-
expect(out).toContain('export type
|
|
1280
|
+
expect(out).toContain('export type * from "./services/auth.service";');
|
|
1281
|
+
expect(out).toContain('export type * from "./services/dev.service";');
|
|
1203
1282
|
});
|
|
1204
1283
|
});
|
|
1205
1284
|
|
|
@@ -1219,7 +1298,8 @@ describe("client-common app-service dev getter", () => {
|
|
|
1219
1298
|
path.join(TPL_ROOT, "client-common/src/providers/app-service.provider.ts.hbs"),
|
|
1220
1299
|
data,
|
|
1221
1300
|
);
|
|
1222
|
-
expect(out).toContain(
|
|
1301
|
+
expect(out).toContain("DevServiceMethods,");
|
|
1302
|
+
expect(out).toContain('} from "@demo/server";');
|
|
1223
1303
|
expect(out).toContain("type ServiceProxy,");
|
|
1224
1304
|
expect(out).toContain("get dev(): ServiceProxy<DevServiceMethods> {");
|
|
1225
1305
|
expect(out).toContain('this._dev ??= this.client.getService<DevServiceMethods>("DevService")');
|
|
@@ -1259,7 +1339,7 @@ describe("client/src/app.root.ts.hbs dev 도구 배선", () => {
|
|
|
1259
1339
|
|
|
1260
1340
|
it("DB ON → HostListener keydown + DevModal 호출", async () => {
|
|
1261
1341
|
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app.root.ts.hbs"), devCtx(true));
|
|
1262
|
-
expect(out).toContain('
|
|
1342
|
+
expect(out).toContain('"(document:keydown)": "onKeydown($event)",');
|
|
1263
1343
|
expect(out).toContain('import { DevModal } from "./modals/dev.modal";');
|
|
1264
1344
|
expect(out).toContain("this._sdModal.showAsync({ type: DevModal");
|
|
1265
1345
|
expect(out).toContain("export class AppRoot {");
|
|
@@ -1268,13 +1348,13 @@ describe("client/src/app.root.ts.hbs dev 도구 배선", () => {
|
|
|
1268
1348
|
it("DB OFF → 빈 AppRoot (dev 배선 없음)", async () => {
|
|
1269
1349
|
const out = await renderTemplate(path.join(TPL_ROOT, "client/src/app.root.ts.hbs"), devCtx(false));
|
|
1270
1350
|
expect(out).toContain("export class AppRoot {}");
|
|
1271
|
-
expect(out).not.toContain("
|
|
1351
|
+
expect(out).not.toContain("onKeydown");
|
|
1272
1352
|
expect(out).not.toContain("DevModal");
|
|
1273
1353
|
});
|
|
1274
1354
|
});
|
|
1275
1355
|
|
|
1276
1356
|
describe("client/src/modals/dev.modal.ts.hbs", () => {
|
|
1277
|
-
it("dev 모달: client-common import + initDb 호출
|
|
1357
|
+
it("dev 모달: client-common import + initDb 호출 + 아이콘 사용", async () => {
|
|
1278
1358
|
const data = buildData({
|
|
1279
1359
|
workspaceName: "demo",
|
|
1280
1360
|
description: "Demo",
|
|
@@ -1293,6 +1373,6 @@ describe("client/src/modals/dev.modal.ts.hbs", () => {
|
|
|
1293
1373
|
expect(out).toContain("export class DevModal implements SdModalContentDef<void> {");
|
|
1294
1374
|
expect(out).toContain('import { AppServiceProvider } from "@demo/client-common";');
|
|
1295
1375
|
expect(out).toContain("await this._appService.dev.initDb();");
|
|
1296
|
-
expect(out).
|
|
1376
|
+
expect(out).toContain('import { NgIcon } from "@ng-icons/core";');
|
|
1297
1377
|
});
|
|
1298
1378
|
});
|
|
@@ -14,27 +14,6 @@ describe("HMR 클라이언트 스크립트 통합", () => {
|
|
|
14
14
|
// logical assignment (&&=, ||=, ??=) 미사용
|
|
15
15
|
expect(script).not.toMatch(/&&=|(\|\|=)|(\?\?=)/);
|
|
16
16
|
});
|
|
17
|
-
|
|
18
|
-
it("WebSocket 연결 코드를 포함한다", () => {
|
|
19
|
-
const script = getHmrClientScript("/app/", 4200);
|
|
20
|
-
expect(script).toContain("WebSocket");
|
|
21
|
-
expect(script).toContain("ws://");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("component-update, css-update, full-reload 메시지 핸들러를 포함한다", () => {
|
|
25
|
-
const script = getHmrClientScript("/app/", 4200);
|
|
26
|
-
expect(script).toContain("component-update");
|
|
27
|
-
expect(script).toContain("css-update");
|
|
28
|
-
expect(script).toContain("full-reload");
|
|
29
|
-
expect(script).toContain("__hmr_dispatch");
|
|
30
|
-
expect(script).toContain("location.reload");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("자동 재연결 로직을 포함한다", () => {
|
|
34
|
-
const script = getHmrClientScript("/app/", 4200);
|
|
35
|
-
expect(script).toContain("setTimeout");
|
|
36
|
-
expect(script).toContain("connect");
|
|
37
|
-
});
|
|
38
17
|
});
|
|
39
18
|
|
|
40
19
|
describe("Scenario: css-update 메시지의 files와 매칭되는 link만 업데이트", () => {
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import {
|
|
4
|
-
FIXTURE_DIR,
|
|
5
|
-
PKG_DIR,
|
|
6
|
-
initPlugin,
|
|
7
|
-
} from "./_vite-angular-plugin-test-setup";
|
|
8
|
-
|
|
9
|
-
const { sdAngularPlugin } = await import("../../src/angular/vite-angular-plugin.js");
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
vi.clearAllMocks();
|
|
13
|
-
vi.spyOn(process, "cwd").mockReturnValue(FIXTURE_DIR);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe("sdAngularPlugin", () => {
|
|
17
|
-
// Scenario: non-Angular .ts 파일은 기본 처리
|
|
18
|
-
it("returns undefined for non-emitted .ts files", async () => {
|
|
19
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
20
|
-
await initPlugin(plugin);
|
|
21
|
-
|
|
22
|
-
const result = (plugin as any).transform?.call(
|
|
23
|
-
{},
|
|
24
|
-
"export const x = 1;",
|
|
25
|
-
"/some/unknown/file.ts",
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
expect(result).toBeUndefined();
|
|
29
|
-
(plugin as any).buildEnd?.call({});
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// Scenario: buildStart에서 초기화 및 emit + buildEnd에서 리소스 정리
|
|
33
|
-
it("initializes facade in buildStart and disposes in buildEnd", async () => {
|
|
34
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
35
|
-
await initPlugin(plugin);
|
|
36
|
-
(plugin as any).buildEnd?.call({});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Scenario: .mjs/.js 파일은 처리하지 않는다
|
|
40
|
-
it("returns undefined for .mjs files", async () => {
|
|
41
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
42
|
-
await initPlugin(plugin);
|
|
43
|
-
|
|
44
|
-
const result = (plugin as any).transform?.call(
|
|
45
|
-
{},
|
|
46
|
-
"export const x = 1;",
|
|
47
|
-
"/some/library/module.mjs",
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
expect(result).toBeUndefined();
|
|
51
|
-
(plugin as any).buildEnd?.call({});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("returns undefined for .js files", async () => {
|
|
55
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
56
|
-
await initPlugin(plugin);
|
|
57
|
-
|
|
58
|
-
const result = (plugin as any).transform?.call(
|
|
59
|
-
{},
|
|
60
|
-
"export const y = 2;",
|
|
61
|
-
"/some/library/module.js",
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
expect(result).toBeUndefined();
|
|
65
|
-
(plugin as any).buildEnd?.call({});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Scenario: 비대상 파일(.css 등)은 transform하지 않는다
|
|
69
|
-
it("returns undefined for non-JS files like .css", async () => {
|
|
70
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
71
|
-
await initPlugin(plugin);
|
|
72
|
-
|
|
73
|
-
const result = (plugin as any).transform?.call(
|
|
74
|
-
{},
|
|
75
|
-
"body { color: red; }",
|
|
76
|
-
"/some/styles.css",
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
expect(result).toBeUndefined();
|
|
80
|
-
(plugin as any).buildEnd?.call({});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Scenario: Angular .ts 파일 transform
|
|
84
|
-
it("transforms emitted .ts files with compiled JS", async () => {
|
|
85
|
-
const plugin = sdAngularPlugin({ pkg: "basic-app" });
|
|
86
|
-
await initPlugin(plugin);
|
|
87
|
-
|
|
88
|
-
const appComponentPath = path
|
|
89
|
-
.join(PKG_DIR, "src/app.component.ts")
|
|
90
|
-
.replace(/\\/g, "/");
|
|
91
|
-
|
|
92
|
-
const result = (plugin as any).transform?.call({}, "", appComponentPath);
|
|
93
|
-
|
|
94
|
-
if (result != null) {
|
|
95
|
-
expect(result).toHaveProperty("code");
|
|
96
|
-
expect(typeof result.code).toBe("string");
|
|
97
|
-
expect(result.code.length).toBeGreaterThan(0);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
(plugin as any).buildEnd?.call({});
|
|
101
|
-
});
|
|
102
|
-
});
|