@simplysm/sd-cli 14.2.2 → 14.2.4
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/root.d.ts.map +1 -1
- package/dist/commands/init/generators/root.js +1 -8
- package/dist/commands/init/generators/root.js.map +1 -1
- package/dist/commands/init/init-client.js +1 -1
- package/dist/commands/init/init-client.js.map +1 -1
- package/dist/commands/init/patch.d.ts +1 -1
- package/dist/commands/init/patch.d.ts.map +1 -1
- package/dist/commands/init/patch.js +1 -1
- package/dist/commands/init/patch.js.map +1 -1
- package/dist/commands/publish/git-phase.d.ts +1 -1
- package/dist/commands/publish/git-phase.js +1 -1
- package/dist/deps/replace-deps/collect-deps.js +1 -1
- package/dist/deps/server-externals/server-production-files.d.ts +1 -1
- package/dist/deps/server-externals/server-production-files.js +1 -1
- package/dist/esbuild/esbuild-client-config.js +1 -1
- package/dist/esbuild/esbuild-client-config.js.map +1 -1
- package/dist/esbuild/esbuild-worker-plugin.js +1 -1
- package/dist/esbuild/esbuild-worker-plugin.js.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
- package/dist/orchestrators/BuildOrchestrator.js +1 -3
- package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
- package/dist/orchestrators/TypecheckOrchestrator.js +1 -1
- package/dist/runtime/ResultCollector.d.ts +1 -1
- package/dist/runtime/ResultCollector.js +1 -1
- package/dist/sd-config.types.d.ts.map +1 -1
- package/dist/typecheck/typecheck-non-package.js +1 -1
- package/dist/typecheck/typecheck-non-package.js.map +1 -1
- package/dist/utils/import-config-module.d.ts +1 -1
- package/dist/utils/import-config-module.js +1 -1
- package/dist/utils/package-utils.d.ts +2 -2
- package/dist/utils/package-utils.js +2 -2
- package/package.json +6 -6
- package/src/commands/init/generators/root.ts +1 -8
- package/src/commands/init/init-client.ts +1 -1
- package/src/commands/init/patch.ts +1 -1
- package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +1 -1
- package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs +2 -2
- package/src/commands/init/templates/client/src/app/home/master/user.detail.ts.hbs +1 -1
- package/src/commands/init/templates/client/src/app/home/master/user.list.ts.hbs +3 -3
- package/src/commands/init/templates/client/src/main.server.ts.hbs +1 -1
- package/src/commands/init/templates/server/src/services/user.service.ts.hbs +3 -3
- package/src/commands/init/templates/workspace-root/package.json.hbs +1 -1
- package/src/commands/publish/git-phase.ts +1 -1
- package/src/deps/replace-deps/collect-deps.ts +1 -1
- package/src/deps/server-externals/server-production-files.ts +1 -1
- package/src/esbuild/esbuild-client-config.ts +1 -1
- package/src/esbuild/esbuild-worker-plugin.ts +1 -1
- package/src/orchestrators/BuildOrchestrator.ts +13 -11
- package/src/orchestrators/TypecheckOrchestrator.ts +1 -1
- package/src/runtime/ResultCollector.ts +1 -1
- package/src/sd-config.types.ts +4 -1
- package/src/typecheck/typecheck-non-package.ts +1 -1
- package/src/utils/import-config-module.ts +1 -1
- package/src/utils/package-utils.ts +2 -2
- package/tests/capacitor/capacitor-npm-config.spec.ts +106 -94
- package/tests/deps/replace-deps/collect-deps.acc.spec.ts +3 -3
- package/tests/esbuild/esbuild-worker-plugin.acc.spec.ts +1 -1
- package/tests/init/patch.spec.ts +1 -1
- package/tests/init/render.spec.ts +2 -2
- package/tests/orchestrators/build-orchestrator.spec.ts +32 -31
- package/tests/ts-compiler/fixtures/non-angular-pkg/.cache/typecheck-browser.tsbuildinfo +1 -0
- package/tests/ts-compiler/fixtures/non-angular-pkg/.cache/typecheck-node.tsbuildinfo +1 -0
- package/tests/ts-compiler/fixtures/non-angular-pkg/.cache/typecheck.tsbuildinfo +1 -0
- package/tests/utils/copy-src.spec.ts +2 -6
- package/tests/utils/package-utils.spec.ts +23 -36
- package/src/commands/init/templates/workspace-root/.prettierrc.yaml +0 -12
|
@@ -59,7 +59,7 @@ export async function runInitClient(opts: InitClientOptions): Promise<void> {
|
|
|
59
59
|
const needsAppStructure = data.hasAuth && client.hasRouter;
|
|
60
60
|
|
|
61
61
|
//-- 기존 라우팅 클라이언트 0개면 기본 메뉴 구조 export("appStructureItems")는
|
|
62
|
-
// 파일에 남기되 서버 초기화의 import
|
|
62
|
+
// 파일에 남기되 서버 초기화의 import, 권한 계산에서 신규 export 로 대체 (init 동등)
|
|
63
63
|
const isFirstRouterClient = recovered.input.clients.every((c) => !c.hasRouter);
|
|
64
64
|
const fallbackExportName =
|
|
65
65
|
isFirstRouterClient && recovered.appStructureExportNames.includes("appStructureItems")
|
|
@@ -225,7 +225,7 @@ export function patchDevService(
|
|
|
225
225
|
existingExportNames: string[],
|
|
226
226
|
data: RenderData,
|
|
227
227
|
client: ClientSpec,
|
|
228
|
-
/** 권한
|
|
228
|
+
/** 권한 계산, import 에서 신규 export 로 대체할 기본 export 이름 (기존 라우팅 클라이언트 0개 케이스) */
|
|
229
229
|
replaceExportName?: string,
|
|
230
230
|
): FilePatch {
|
|
231
231
|
const combinedNames = [...existingExportNames, client.appStructureName];
|
|
@@ -85,7 +85,7 @@ export class RolePermissionView {
|
|
|
85
85
|
|
|
86
86
|
detail = viewChild(RolePermissionDetail);
|
|
87
87
|
|
|
88
|
-
// 미저장 권한 변경 보호: 페이지
|
|
88
|
+
// 미저장 권한 변경 보호: 페이지 이탈, 역할 전환 전에 자식 detail 의 변경 가드 확인
|
|
89
89
|
protected readonly checkCanLeave = (): boolean => {
|
|
90
90
|
const detail = this.detail();
|
|
91
91
|
return detail == null || detail.checkIgnoreChanges();
|
package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs
CHANGED
|
@@ -248,7 +248,7 @@ export class RoleList implements SdModalContentDef<SelectModalOutputResult<numbe
|
|
|
248
248
|
|
|
249
249
|
this.busyCount.update((v) => v + 1);
|
|
250
250
|
await this._sdToast.try(async () => {
|
|
251
|
-
// 파생 컬럼(
|
|
251
|
+
// 파생 컬럼(수정일시, 수정자)은 업로드에서 제외하고 파싱. 삭제 여부는 읽어서 반영.
|
|
252
252
|
const records = await this._excelWrapper.read(files[0], 0, {
|
|
253
253
|
excludes: ["lastModifiedAt", "lastModifiedBy"],
|
|
254
254
|
});
|
|
@@ -448,7 +448,7 @@ export class RoleList implements SdModalContentDef<SelectModalOutputResult<numbe
|
|
|
448
448
|
.update(() => ({ isDeleted: false }));
|
|
449
449
|
|
|
450
450
|
// 복구 후 이름 비삭제 유니크 재검증 — 복구로 활성 중복이 생겼으면 throw(트랜잭션 롤백).
|
|
451
|
-
// 대상끼리
|
|
451
|
+
// 대상끼리 충돌, 기존 활성과의 충돌을 복구한 이름으로 한정해 한 번에 검사.
|
|
452
452
|
const conflicts = await db
|
|
453
453
|
.role()
|
|
454
454
|
.where((c) => [expr.in(c.name, names), expr.eq(c.isDeleted, false)])
|
|
@@ -281,7 +281,7 @@ export class {{userEntityPascal}}Detail implements SdModalContentDef<{ id: numbe
|
|
|
281
281
|
});
|
|
282
282
|
|
|
283
283
|
await this._appSharedData.emitAsync("{{userEntityLabel}}", [{{userEntityCamel}}Id]);
|
|
284
|
-
// 복구된 {{userEntityLabel}} 세션에 인증정보 변경 통지 → authInfo
|
|
284
|
+
// 복구된 {{userEntityLabel}} 세션에 인증정보 변경 통지 → authInfo, 권한 재로드
|
|
285
285
|
await this._appService.authInfoChangedEvent.emit((info) => info.{{userEntityCamel}}Id === {{userEntityCamel}}Id, undefined);
|
|
286
286
|
this._sdToast.success("복구되었습니다.");
|
|
287
287
|
await this._refresh();
|
|
@@ -278,7 +278,7 @@ export class {{userEntityPascal}}List {
|
|
|
278
278
|
|
|
279
279
|
this.busyCount.update((v) => v + 1);
|
|
280
280
|
await this._sdToast.try(async () => {
|
|
281
|
-
// 파생 컬럼(
|
|
281
|
+
// 파생 컬럼(수정일시, 수정자)은 업로드에서 제외하고 파싱. 삭제 여부는 읽어서 반영.
|
|
282
282
|
const records = await this._excelWrapper.read(files[0], 0, {
|
|
283
283
|
excludes: ["lastModifiedAt", "lastModifiedBy"],
|
|
284
284
|
});
|
|
@@ -440,7 +440,7 @@ export class {{userEntityPascal}}List {
|
|
|
440
440
|
.update(() => ({ isDeleted: false }));
|
|
441
441
|
|
|
442
442
|
// 복구 후 이름 비삭제 유니크 재검증 — 복구로 활성 중복이 생겼으면 throw(트랜잭션 롤백).
|
|
443
|
-
// 대상끼리
|
|
443
|
+
// 대상끼리 충돌, 기존 활성과의 충돌을 복구한 이름으로 한정해 한 번에 검사.
|
|
444
444
|
const conflicts = await db
|
|
445
445
|
.{{userEntityCamel}}()
|
|
446
446
|
.where((c) => [expr.in(c.name, names), expr.eq(c.isDeleted, false)])
|
|
@@ -458,7 +458,7 @@ export class {{userEntityPascal}}List {
|
|
|
458
458
|
}
|
|
459
459
|
});
|
|
460
460
|
await this._appSharedData.emitAsync("{{userEntityLabel}}", ids);
|
|
461
|
-
// 복구된 {{userEntityLabel}} 세션에 인증정보 변경 통지 → authInfo
|
|
461
|
+
// 복구된 {{userEntityLabel}} 세션에 인증정보 변경 통지 → authInfo, 권한 재로드
|
|
462
462
|
await this._appService.authInfoChangedEvent.emit((info) => ids.includes(info.{{userEntityCamel}}Id), undefined);
|
|
463
463
|
this._sdToast.success("복구되었습니다.");
|
|
464
464
|
await this._refresh();
|
|
@@ -6,7 +6,7 @@ import { provideSdAngular } from "@simplysm/angular";
|
|
|
6
6
|
import { AppRoot } from "./app.root";
|
|
7
7
|
import { routes } from "./routes";
|
|
8
8
|
|
|
9
|
-
// 프리렌더(빌드 시점) 전용 부트스트랩 — 서버
|
|
9
|
+
// 프리렌더(빌드 시점) 전용 부트스트랩 — 서버 연결, 로그 배선 등 브라우저 전용 초기화는 제외
|
|
10
10
|
const bootstrap = (context: BootstrapContext) =>
|
|
11
11
|
bootstrapApplication(
|
|
12
12
|
AppRoot,
|
|
@@ -82,7 +82,7 @@ export const {{userEntityPascal}}Service = defineService("{{userEntityPascal}}Se
|
|
|
82
82
|
const activeNames: string[] = [];
|
|
83
83
|
|
|
84
84
|
for (const { id, name, loginId, roleId, password, isDeleted } of rows) {
|
|
85
|
-
// 수정 대상이면 기존 레코드 확인 — id 존재 강제 +
|
|
85
|
+
// 수정 대상이면 기존 레코드 확인 — id 존재 강제 + 불변식, 이력전이 판정에 기존 상태 필요.
|
|
86
86
|
let existingEncryptedPassword: string | undefined;
|
|
87
87
|
let existingIsDeleted: boolean | undefined;
|
|
88
88
|
if (id != null) {
|
|
@@ -108,7 +108,7 @@ export const {{userEntityPascal}}Service = defineService("{{userEntityPascal}}Se
|
|
|
108
108
|
activeNames.push(name);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
// 불변식: 로그인ID 가 있으면
|
|
111
|
+
// 불변식: 로그인ID 가 있으면 역할, 비밀번호 필수 (비번은 이번 입력 또는 수정 시 기존 해시로 충족).
|
|
112
112
|
if (loginId != null) {
|
|
113
113
|
if (roleId == null) {
|
|
114
114
|
throw new Error(`${label(name)}로그인ID가 있으면 역할이 필수입니다.`);
|
|
@@ -187,7 +187,7 @@ export const {{userEntityPascal}}Service = defineService("{{userEntityPascal}}Se
|
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
// 저장으로 역할/로그인ID/비밀번호가 바뀌었을 수 있으므로, 저장된 각 사용자 세션에 인증정보 변경 통지
|
|
190
|
-
// → 해당 세션이 authInfo
|
|
190
|
+
// → 해당 세션이 authInfo, 권한을 재로드. roleId 는 이번 저장으로 바뀔 수 있어 {{userEntityCamel}}Id 로 타겟.
|
|
191
191
|
for (const { id } of saved) {
|
|
192
192
|
await ctx.server.emitEvent(
|
|
193
193
|
AuthInfoChangedEvent,
|
|
@@ -5,7 +5,7 @@ import { err as errNs } from "@simplysm/core-common";
|
|
|
5
5
|
/**
|
|
6
6
|
* 워킹트리에 미커밋 변경사항이 있으면 throw 한다.
|
|
7
7
|
* 배포는 깨끗한 워킹트리에서만 진행한다 — 커밋은 사용자가 직접 수행해야 한다.
|
|
8
|
-
* @throws 미커밋(unstaged
|
|
8
|
+
* @throws 미커밋(unstaged, staged) 변경사항이 있을 때 Error
|
|
9
9
|
*/
|
|
10
10
|
export async function ensureCleanWorkingTree(
|
|
11
11
|
hasGit: boolean,
|
|
@@ -12,7 +12,7 @@ export interface DepsResult {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* pnpm-workspace.yaml 기반으로 packages/(배포 대상) 패키지의 name → 상대 디렉토리 맵을 구성한다.
|
|
15
|
-
* packages/ 가 아닌 워크스페이스(tests
|
|
15
|
+
* packages/ 가 아닌 워크스페이스(tests/, plugins/ 등)는 제외된다.
|
|
16
16
|
* 예: "@simplysm/core-node" → "packages/core-node"
|
|
17
17
|
*/
|
|
18
18
|
function buildWorkspacePkgMap(cwd: string): Map<string, string> {
|
|
@@ -31,7 +31,7 @@ export function collectAllExternals(
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* pnpm-lock.yaml의 packages 섹션을 파싱하여 name→version 맵을 생성한다.
|
|
34
|
-
* 키 형태: "name@version"
|
|
34
|
+
* 키 형태: "name@version", "@scope/name@version", "name@version(peer@ver)..."
|
|
35
35
|
*/
|
|
36
36
|
export function parseLockfileVersions(cwd: string): Map<string, string> {
|
|
37
37
|
const lockfilePath = path.join(cwd, "pnpm-lock.yaml");
|
|
@@ -175,7 +175,7 @@ export async function createClientEsbuildContext(
|
|
|
175
175
|
entryNames: isDev ? "[name]" : "[name]-[hash]",
|
|
176
176
|
chunkNames: "[name]-[hash]",
|
|
177
177
|
assetNames: isDev ? "[name]" : "[name]-[hash]",
|
|
178
|
-
// CSS/SCSS url() 로 참조하는
|
|
178
|
+
// CSS/SCSS url() 로 참조하는 폰트, 이미지 자산을 outdir로 복사하고 url을 출력 경로로 재작성.
|
|
179
179
|
// esbuild 기본 loader 맵에는 js/ts/css/json/text만 있어, 미설정 시 url(*.woff) 등에서
|
|
180
180
|
// "No loader is configured" 에러가 발생한다.
|
|
181
181
|
loader: {
|
|
@@ -226,7 +226,7 @@ export function transformWorkerPatterns(
|
|
|
226
226
|
options?: TransformWorkerOptions,
|
|
227
227
|
): TransformWorkerResult | undefined {
|
|
228
228
|
// 빠른 사전 필터 — new Worker(), new SharedWorker(), import.meta.resolve()의 단어 경계
|
|
229
|
-
// 호출 형태만 통과시킨다.
|
|
229
|
+
// 호출 형태만 통과시킨다. 식별자, 타입, interface로만 등장하는 Worker 키워드는 차단하여
|
|
230
230
|
// TS transformSync 오버헤드를 줄인다. 정확성은 후속 AST 판별(findWorkerPatterns)이 담당한다.
|
|
231
231
|
// new 와 Worker/SharedWorker 사이에 주석이 낀 호출(예: `new /* c */ Worker`)은 의도된
|
|
232
232
|
// 트레이드오프로 탈락한다. 실발생 가능성이 희박하여 현행을 유지한다.
|
|
@@ -11,7 +11,12 @@ import { getVersion } from "../utils/build-env";
|
|
|
11
11
|
import { copySrcFiles } from "../utils/copy-src";
|
|
12
12
|
import { formatBuildMessages } from "../utils/output-utils";
|
|
13
13
|
import { createBuildEngine } from "../engines/engine-factory";
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
BuildOutput,
|
|
16
|
+
BuildPackageInfo,
|
|
17
|
+
ClientPackageInfo,
|
|
18
|
+
ServerPackageInfo,
|
|
19
|
+
} from "../engines/types";
|
|
15
20
|
import { runWithConcurrency, getMaxConcurrency } from "../utils/concurrency";
|
|
16
21
|
import { iteratePackages } from "../utils/package-classify";
|
|
17
22
|
import { Capacitor } from "../capacitor/capacitor";
|
|
@@ -99,7 +104,9 @@ export function classifyPackages(
|
|
|
99
104
|
* dist 폴더 삭제
|
|
100
105
|
*/
|
|
101
106
|
async function cleanDistFolders(cwd: string, packageNames: string[]): Promise<void> {
|
|
102
|
-
await Promise.all(
|
|
107
|
+
await Promise.all(
|
|
108
|
+
packageNames.map((name) => fsx.rm(pathx.posixResolve(cwd, "packages", name, "dist"))),
|
|
109
|
+
);
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
//#endregion
|
|
@@ -280,8 +287,7 @@ export class BuildOrchestrator implements OrchestratorLifecycle<boolean> {
|
|
|
280
287
|
type: "build",
|
|
281
288
|
success: engineResult.build.success,
|
|
282
289
|
errors: engineResult.build.errors.length > 0 ? engineResult.build.errors : undefined,
|
|
283
|
-
warnings:
|
|
284
|
-
engineResult.build.warnings.length > 0 ? engineResult.build.warnings : undefined,
|
|
290
|
+
warnings: engineResult.build.warnings.length > 0 ? engineResult.build.warnings : undefined,
|
|
285
291
|
};
|
|
286
292
|
} finally {
|
|
287
293
|
await engine.stop();
|
|
@@ -365,9 +371,8 @@ export class BuildOrchestrator implements OrchestratorLifecycle<boolean> {
|
|
|
365
371
|
this._logger.debug(`[${name}] (client) 빌드 시작`);
|
|
366
372
|
const pkgDir = pathx.posixResolve(this._cwd, "packages", name);
|
|
367
373
|
const isNativeBuild = config.capacitor != null || config.electron != null;
|
|
368
|
-
const outDir =
|
|
369
|
-
? pathx.posixResolve(pkgDir, ".capacitor/www")
|
|
370
|
-
: undefined;
|
|
374
|
+
const outDir =
|
|
375
|
+
config.capacitor != null ? pathx.posixResolve(pkgDir, ".capacitor/www") : undefined;
|
|
371
376
|
|
|
372
377
|
const result = await this._runEngineTask({
|
|
373
378
|
name,
|
|
@@ -446,10 +451,7 @@ export class BuildOrchestrator implements OrchestratorLifecycle<boolean> {
|
|
|
446
451
|
*
|
|
447
452
|
* @returns 에러 발생 여부 (true: 에러 있음)
|
|
448
453
|
*/
|
|
449
|
-
private _printBuildResults(
|
|
450
|
-
results: BuildStepResult[],
|
|
451
|
-
hasUntrackedError: boolean,
|
|
452
|
-
): boolean {
|
|
454
|
+
private _printBuildResults(results: BuildStepResult[], hasUntrackedError: boolean): boolean {
|
|
453
455
|
for (const result of results) {
|
|
454
456
|
const typeLabel = result.type === "lint" ? "lint" : result.target;
|
|
455
457
|
|
|
@@ -54,7 +54,7 @@ export interface TypecheckResult {
|
|
|
54
54
|
const TARGET_PATH_PATTERN = /^[^/]+\/([^/]+)/;
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* 대상 경로에서 패키지명을 추출한다. 워크스페이스 종류(packages
|
|
57
|
+
* 대상 경로에서 패키지명을 추출한다. 워크스페이스 종류(packages/, tests/, plugins/ 등)를 가리지 않는다.
|
|
58
58
|
* "packages/core-common" → "core-common"
|
|
59
59
|
* "tests/orm" → "orm"
|
|
60
60
|
* "plugins/sd" → "sd"
|
package/src/sd-config.types.ts
CHANGED
|
@@ -32,7 +32,10 @@ export interface SdNpmPublishConfig {
|
|
|
32
32
|
* - SdLocalDirectoryPublishConfig: 로컬 디렉토리로 복사
|
|
33
33
|
* - SdStoragePublishConfig: FTP/FTPS/SFTP 서버에 업로드
|
|
34
34
|
*/
|
|
35
|
-
export type SdPublishConfig =
|
|
35
|
+
export type SdPublishConfig =
|
|
36
|
+
| SdNpmPublishConfig
|
|
37
|
+
| SdLocalDirectoryPublishConfig
|
|
38
|
+
| SdStoragePublishConfig;
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* 로컬 디렉토리 배포 설정
|
|
@@ -28,7 +28,7 @@ export function typecheckNonPackageFiles(cwd: string): NonPackageTypecheckResult
|
|
|
28
28
|
const parsedConfig = parseTsconfig(cwd);
|
|
29
29
|
const packagesDir = path.join(cwd, "packages");
|
|
30
30
|
|
|
31
|
-
// packages/(배포 대상)가 아닌 워크스페이스(tests
|
|
31
|
+
// packages/(배포 대상)가 아닌 워크스페이스(tests/, plugins/, 향후 추가분)는 각자 엔진 타입체크가
|
|
32
32
|
// 자기 tsconfig로 담당하므로 루트 sweep에서 제외한다. 루트 tsconfig로 검사하면 그 워크스페이스
|
|
33
33
|
// 고유 옵션(allowImportingTsExtensions 등)이 무시돼 거짓 에러가 난다. 디렉터리명에 의존하지 않는다.
|
|
34
34
|
const nonDeployWorkspaceDirs = collectWorkspacePackages(cwd)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* 워크스페이스 TS 설정 파일(`sd.config.ts
|
|
4
|
+
* 워크스페이스 TS 설정 파일(`sd.config.ts`, `eslint.config.ts` 등)을 동적 import 한다.
|
|
5
5
|
*
|
|
6
6
|
* - `.ts` 로딩은 실행 런타임(개발: tsx, 프로덕션: Node 의 native type-stripping)에 위임한다.
|
|
7
7
|
* 별도 트랜스파일러(jiti)를 직접 의존하지 않는다 — jiti 는 워크스페이스(루트 / init 템플릿)가 제공.
|
|
@@ -22,7 +22,7 @@ export function findPackageRoot(startDir: string): string {
|
|
|
22
22
|
/**
|
|
23
23
|
* pnpm-workspace.yaml의 모든 워크스페이스 패키지를 탐색한다.
|
|
24
24
|
* 디렉토리명 → 상대 경로의 맵을 반환한다 (예: "orm" → "tests/orm", "sd" → "plugins/sd").
|
|
25
|
-
* packages
|
|
25
|
+
* packages/, tests/, plugins/ 등 종류를 가리지 않고 포함하며, 배포/검사 대상 구분은 소비자가 relPath로 판단한다.
|
|
26
26
|
*/
|
|
27
27
|
export function discoverWorkspacePackages(cwd: string): Map<string, string> {
|
|
28
28
|
logger.debug("워크스페이스 패키지 탐색 시작");
|
|
@@ -63,7 +63,7 @@ export function buildPathMapFromConfig(
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* workspace에서 발견된 비배포 워크스페이스(packages/ 가 아닌 tests
|
|
66
|
+
* workspace에서 발견된 비배포 워크스페이스(packages/ 가 아닌 tests/, plugins/ 등)를 sd.config.ts
|
|
67
67
|
* 패키지에 병합한다. 이들은 빌드 대상이 아니지만 각자 자기 tsconfig로 타입체크하기 위해
|
|
68
68
|
* `{ target: "node" }`가 할당된다. 모든 패키지의 pathMap(name → 상대 경로)도 함께 구성한다.
|
|
69
69
|
* 디렉터리명이 sd.config.ts 패키지명과 충돌하면 SdError를 던진다.
|
|
@@ -1,94 +1,106 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from "vitest";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { fsx, cpx } from "@simplysm/core-node";
|
|
6
|
-
|
|
7
|
-
const mockFsxExists = vi.spyOn(fsx, "exists");
|
|
8
|
-
vi.spyOn(fsx, "write").mockResolvedValue(undefined);
|
|
9
|
-
const mockFsxReadJson = vi.spyOn(fsx, "readJson");
|
|
10
|
-
vi.spyOn(fsx, "writeJson").mockResolvedValue(undefined);
|
|
11
|
-
vi.spyOn(fsx, "mkdir").mockResolvedValue(undefined);
|
|
12
|
-
vi.spyOn(fsx, "read");
|
|
13
|
-
|
|
14
|
-
vi.spyOn(cpx, "spawn").mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 });
|
|
15
|
-
|
|
16
|
-
let tmpRoot: string;
|
|
17
|
-
let CAP_PATH: string;
|
|
18
|
-
let PKG_PATH: string;
|
|
19
|
-
|
|
20
|
-
beforeAll(() => {
|
|
21
|
-
tmpRoot = mkdtempSync(path.join(tmpdir(), "cap-npm-config-"));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"@capacitor/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
await expect(
|
|
68
|
-
setupCapNpmConfig(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fsx, cpx } from "@simplysm/core-node";
|
|
6
|
+
|
|
7
|
+
const mockFsxExists = vi.spyOn(fsx, "exists");
|
|
8
|
+
vi.spyOn(fsx, "write").mockResolvedValue(undefined);
|
|
9
|
+
const mockFsxReadJson = vi.spyOn(fsx, "readJson");
|
|
10
|
+
vi.spyOn(fsx, "writeJson").mockResolvedValue(undefined);
|
|
11
|
+
vi.spyOn(fsx, "mkdir").mockResolvedValue(undefined);
|
|
12
|
+
vi.spyOn(fsx, "read");
|
|
13
|
+
|
|
14
|
+
vi.spyOn(cpx, "spawn").mockResolvedValue({ stdout: "", stderr: "", exitCode: 0 });
|
|
15
|
+
|
|
16
|
+
let tmpRoot: string;
|
|
17
|
+
let CAP_PATH: string;
|
|
18
|
+
let PKG_PATH: string;
|
|
19
|
+
|
|
20
|
+
beforeAll(() => {
|
|
21
|
+
tmpRoot = mkdtempSync(path.join(tmpdir(), "cap-npm-config-"));
|
|
22
|
+
// 워크스페이스 루트 판정은 pnpm-workspace.yaml 기반
|
|
23
|
+
writeFileSync(path.join(tmpRoot, "pnpm-workspace.yaml"), "packages:\n - pkg\n");
|
|
24
|
+
writeFileSync(path.join(tmpRoot, "package.json"), JSON.stringify({ private: true }));
|
|
25
|
+
PKG_PATH = path.join(tmpRoot, "pkg");
|
|
26
|
+
CAP_PATH = path.join(PKG_PATH, ".capacitor");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterAll(() => {
|
|
30
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("setupCapNpmConfig", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
mockFsxExists.mockResolvedValue(true);
|
|
37
|
+
mockFsxReadJson.mockImplementation(((p: string) => {
|
|
38
|
+
const normalized = p.replace(/\\/g, "/");
|
|
39
|
+
if (normalized.includes(".capacitor/package.json")) {
|
|
40
|
+
return {
|
|
41
|
+
name: "com.test.app",
|
|
42
|
+
version: "1.0.0",
|
|
43
|
+
dependencies: {
|
|
44
|
+
"@capacitor/core": "^7",
|
|
45
|
+
"@capacitor/app": "^7",
|
|
46
|
+
"@capacitor/android": "^7",
|
|
47
|
+
},
|
|
48
|
+
devDependencies: {
|
|
49
|
+
"@capacitor/cli": "^7",
|
|
50
|
+
"@capacitor/assets": "^3",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { name: "test-pkg", version: "1.0.0" };
|
|
55
|
+
}) as never);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("루트 package.json이 없으면 에러를 던진다", async () => {
|
|
59
|
+
mockFsxExists.mockImplementation(((p: string) => {
|
|
60
|
+
const n = p.replace(/\\/g, "/");
|
|
61
|
+
if (n.endsWith("package.json") && !n.includes(".capacitor")) return false;
|
|
62
|
+
return true;
|
|
63
|
+
}) as never);
|
|
64
|
+
|
|
65
|
+
const { setupCapNpmConfig } = await import("../../src/capacitor/capacitor-npm-config.js");
|
|
66
|
+
|
|
67
|
+
await expect(
|
|
68
|
+
setupCapNpmConfig(
|
|
69
|
+
CAP_PATH,
|
|
70
|
+
PKG_PATH,
|
|
71
|
+
{
|
|
72
|
+
appId: "com.test.app",
|
|
73
|
+
appName: "Test App",
|
|
74
|
+
},
|
|
75
|
+
{ name: "test-pkg", version: "1.0.0" },
|
|
76
|
+
[],
|
|
77
|
+
[],
|
|
78
|
+
),
|
|
79
|
+
).rejects.toThrow("루트 package.json");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it(".capacitor/package.json이 없으면 빈 설정으로 시작한다", async () => {
|
|
83
|
+
mockFsxExists.mockImplementation(((p: string) => {
|
|
84
|
+
const n = p.replace(/\\/g, "/");
|
|
85
|
+
if (n.includes(".capacitor/package.json")) return false;
|
|
86
|
+
return true;
|
|
87
|
+
}) as never);
|
|
88
|
+
|
|
89
|
+
const { setupCapNpmConfig } = await import("../../src/capacitor/capacitor-npm-config.js");
|
|
90
|
+
|
|
91
|
+
const changed = await setupCapNpmConfig(
|
|
92
|
+
CAP_PATH,
|
|
93
|
+
PKG_PATH,
|
|
94
|
+
{
|
|
95
|
+
appId: "com.test.app",
|
|
96
|
+
appName: "Test App",
|
|
97
|
+
},
|
|
98
|
+
{ name: "test-pkg", version: "1.0.0" },
|
|
99
|
+
[],
|
|
100
|
+
[],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// 빈 설정에서 시작하므로 dependencies가 추가되어 변경됨
|
|
104
|
+
expect(changed).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -10,10 +10,10 @@ describe("collectDeps — packages/ 외 워크스페이스 제외", () => {
|
|
|
10
10
|
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
tmpDir = pathx.posix(fs.mkdtempSync(path.join(os.tmpdir(), "sd-cli-test-")));
|
|
13
|
-
// 워크스페이스 루트 선언 (discoverWorkspacePackages는
|
|
13
|
+
// 워크스페이스 루트 선언 (discoverWorkspacePackages는 pnpm-workspace.yaml 기반)
|
|
14
14
|
fs.writeFileSync(
|
|
15
|
-
pathx.posix(path.join(tmpDir, "
|
|
16
|
-
|
|
15
|
+
pathx.posix(path.join(tmpDir, "pnpm-workspace.yaml")),
|
|
16
|
+
"packages:\n - packages/*\n - tests/*\n - plugins/*\n",
|
|
17
17
|
);
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -272,7 +272,7 @@ describe("esbuild Worker Bundle Plugin — Acceptance", () => {
|
|
|
272
272
|
const mainOutput = result.outputFiles!.find((f) => path.basename(f.path).startsWith("entry"));
|
|
273
273
|
const content = mainOutput!.text;
|
|
274
274
|
|
|
275
|
-
// 브라우저 워커는
|
|
275
|
+
// 브라우저 워커는 번들, 치환됨
|
|
276
276
|
expect(content).not.toContain('"./worker.js"');
|
|
277
277
|
expect(content).toMatch(/worker-[A-Z0-9]+\.js/i);
|
|
278
278
|
// node 워커는 그대로 남음 (browser 빌드에서 스킵)
|
package/tests/init/patch.spec.ts
CHANGED
|
@@ -195,7 +195,7 @@ describe("patchDevService", () => {
|
|
|
195
195
|
expect(r.patched).toBe(expected);
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
-
it("0개 → 1개 (기본 export 워크스페이스) — import
|
|
198
|
+
it("0개 → 1개 (기본 export 워크스페이스) — import, 권한 인자가 신규 단독으로 대체, init 동등 결과", async () => {
|
|
199
199
|
const serverOnly: InitInput = { ...baseInput, clients: [] };
|
|
200
200
|
const before = buildData(serverOnly); // appStructureNames = ["appStructureItems"]
|
|
201
201
|
const after = buildData({
|
|
@@ -496,7 +496,7 @@ describe("client-common/src/providers/app-orm.provider.ts.hbs", () => {
|
|
|
496
496
|
expect(out).toContain('configName: "MAIN"');
|
|
497
497
|
});
|
|
498
498
|
|
|
499
|
-
it("custom dbContextName=order → OrderDbContext 가 import
|
|
499
|
+
it("custom dbContextName=order → OrderDbContext 가 import, DbClass 에 반영", async () => {
|
|
500
500
|
const data = buildData({
|
|
501
501
|
workspaceName: "demo",
|
|
502
502
|
description: "Demo",
|
|
@@ -1018,7 +1018,7 @@ describe("SSG 클라이언트 스캐폴드", () => {
|
|
|
1018
1018
|
expect(out).toContain('provideSdAngular({ clientName: "client-portal" }),');
|
|
1019
1019
|
expect(out).toContain("provideServerRendering(),");
|
|
1020
1020
|
expect(out).toContain("export default bootstrap;");
|
|
1021
|
-
// 프리렌더는 빌드 시점 — 서버
|
|
1021
|
+
// 프리렌더는 빌드 시점 — 서버 연결, 로그 배선 제외
|
|
1022
1022
|
expect(out).not.toContain("connectAsync");
|
|
1023
1023
|
});
|
|
1024
1024
|
});
|