@simplysm/sd-cli 14.2.2 → 14.2.3

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 (66) hide show
  1. package/dist/commands/init/generators/root.d.ts.map +1 -1
  2. package/dist/commands/init/generators/root.js +1 -8
  3. package/dist/commands/init/generators/root.js.map +1 -1
  4. package/dist/commands/init/init-client.js +1 -1
  5. package/dist/commands/init/init-client.js.map +1 -1
  6. package/dist/commands/init/patch.d.ts +1 -1
  7. package/dist/commands/init/patch.d.ts.map +1 -1
  8. package/dist/commands/init/patch.js +1 -1
  9. package/dist/commands/init/patch.js.map +1 -1
  10. package/dist/commands/publish/git-phase.d.ts +1 -1
  11. package/dist/commands/publish/git-phase.js +1 -1
  12. package/dist/deps/replace-deps/collect-deps.js +1 -1
  13. package/dist/deps/server-externals/server-production-files.d.ts +1 -1
  14. package/dist/deps/server-externals/server-production-files.js +1 -1
  15. package/dist/esbuild/esbuild-client-config.js +1 -1
  16. package/dist/esbuild/esbuild-client-config.js.map +1 -1
  17. package/dist/esbuild/esbuild-worker-plugin.js +1 -1
  18. package/dist/esbuild/esbuild-worker-plugin.js.map +1 -1
  19. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  20. package/dist/orchestrators/BuildOrchestrator.js +1 -3
  21. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  22. package/dist/orchestrators/TypecheckOrchestrator.js +1 -1
  23. package/dist/runtime/ResultCollector.d.ts +1 -1
  24. package/dist/runtime/ResultCollector.js +1 -1
  25. package/dist/sd-config.types.d.ts.map +1 -1
  26. package/dist/typecheck/typecheck-non-package.js +1 -1
  27. package/dist/typecheck/typecheck-non-package.js.map +1 -1
  28. package/dist/utils/import-config-module.d.ts +1 -1
  29. package/dist/utils/import-config-module.js +1 -1
  30. package/dist/utils/package-utils.d.ts +2 -2
  31. package/dist/utils/package-utils.js +2 -2
  32. package/package.json +6 -6
  33. package/src/commands/init/generators/root.ts +1 -8
  34. package/src/commands/init/init-client.ts +1 -1
  35. package/src/commands/init/patch.ts +1 -1
  36. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +1 -1
  37. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs +2 -2
  38. package/src/commands/init/templates/client/src/app/home/master/user.detail.ts.hbs +1 -1
  39. package/src/commands/init/templates/client/src/app/home/master/user.list.ts.hbs +3 -3
  40. package/src/commands/init/templates/client/src/main.server.ts.hbs +1 -1
  41. package/src/commands/init/templates/server/src/services/user.service.ts.hbs +3 -3
  42. package/src/commands/init/templates/workspace-root/package.json.hbs +1 -1
  43. package/src/commands/publish/git-phase.ts +1 -1
  44. package/src/deps/replace-deps/collect-deps.ts +1 -1
  45. package/src/deps/server-externals/server-production-files.ts +1 -1
  46. package/src/esbuild/esbuild-client-config.ts +1 -1
  47. package/src/esbuild/esbuild-worker-plugin.ts +1 -1
  48. package/src/orchestrators/BuildOrchestrator.ts +13 -11
  49. package/src/orchestrators/TypecheckOrchestrator.ts +1 -1
  50. package/src/runtime/ResultCollector.ts +1 -1
  51. package/src/sd-config.types.ts +4 -1
  52. package/src/typecheck/typecheck-non-package.ts +1 -1
  53. package/src/utils/import-config-module.ts +1 -1
  54. package/src/utils/package-utils.ts +2 -2
  55. package/tests/capacitor/capacitor-npm-config.spec.ts +106 -94
  56. package/tests/deps/replace-deps/collect-deps.acc.spec.ts +3 -3
  57. package/tests/esbuild/esbuild-worker-plugin.acc.spec.ts +1 -1
  58. package/tests/init/patch.spec.ts +1 -1
  59. package/tests/init/render.spec.ts +2 -2
  60. package/tests/orchestrators/build-orchestrator.spec.ts +32 -31
  61. package/tests/ts-compiler/fixtures/non-angular-pkg/.cache/typecheck-browser.tsbuildinfo +1 -0
  62. package/tests/ts-compiler/fixtures/non-angular-pkg/.cache/typecheck-node.tsbuildinfo +1 -0
  63. package/tests/ts-compiler/fixtures/non-angular-pkg/.cache/typecheck.tsbuildinfo +1 -0
  64. package/tests/utils/copy-src.spec.ts +2 -6
  65. package/tests/utils/package-utils.spec.ts +23 -36
  66. 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·권한 계산에서 신규 export 로 대체 (init 동등)
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
- /** 권한 계산·import 에서 신규 export 로 대체할 기본 export 이름 (기존 라우팅 클라이언트 0개 케이스) */
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
- // 미저장 권한 변경 보호: 페이지 이탈·역할 전환 전에 자식 detail 의 변경 가드 확인
88
+ // 미저장 권한 변경 보호: 페이지 이탈, 역할 전환 전에 자식 detail 의 변경 가드 확인
89
89
  protected readonly checkCanLeave = (): boolean => {
90
90
  const detail = this.detail();
91
91
  return detail == null || detail.checkIgnoreChanges();
@@ -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·권한을 재로드. roleId 는 이번 저장으로 바뀔 수 있어 {{userEntityCamel}}Id 로 타겟.
190
+ // → 해당 세션이 authInfo, 권한을 재로드. roleId 는 이번 저장으로 바뀔 수 있어 {{userEntityCamel}}Id 로 타겟.
191
191
  for (const { id } of saved) {
192
192
  await ctx.server.emitEvent(
193
193
  AuthInfoChangedEvent,
@@ -32,7 +32,7 @@
32
32
  "@vitest/coverage-v8": "^4.1.0",
33
33
  "eslint": "^10.6.0",
34
34
  "jiti": "^2.7.0",
35
- "prettier": "^3.8.0",
35
+ "oxfmt": "^0.59.0",
36
36
  "typescript": "~6.0.0",
37
37
  "vite": "^8.1.0",
38
38
  "vitest": "^4.1.0"
@@ -5,7 +5,7 @@ import { err as errNs } from "@simplysm/core-common";
5
5
  /**
6
6
  * 워킹트리에 미커밋 변경사항이 있으면 throw 한다.
7
7
  * 배포는 깨끗한 워킹트리에서만 진행한다 — 커밋은 사용자가 직접 수행해야 한다.
8
- * @throws 미커밋(unstaged·staged) 변경사항이 있을 때 Error
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/ 가 아닌 워크스페이스(testsplugins/ 등)는 제외된다.
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" · "@scope/name@version" · "name@version(peer@ver)..."
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() 로 참조하는 폰트·이미지 자산을 outdir로 복사하고 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
- // 호출 형태만 통과시킨다. 식별자·타입·interface로만 등장하는 Worker 키워드는 차단하여
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 { BuildOutput, BuildPackageInfo, ClientPackageInfo, ServerPackageInfo } from "../engines/types";
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(packageNames.map((name) => fsx.rm(pathx.posixResolve(cwd, "packages", name, "dist"))));
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 = config.capacitor != null
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
- * 대상 경로에서 패키지명을 추출한다. 워크스페이스 종류(packagestestsplugins/ 등)를 가리지 않는다.
57
+ * 대상 경로에서 패키지명을 추출한다. 워크스페이스 종류(packages/, tests/, plugins/ 등)를 가리지 않는다.
58
58
  * "packages/core-common" → "core-common"
59
59
  * "tests/orm" → "orm"
60
60
  * "plugins/sd" → "sd"
@@ -12,7 +12,7 @@ export interface BuildResult {
12
12
  }
13
13
 
14
14
  /**
15
- * 빌드 결과를 수집·관리하는 클래스
15
+ * 빌드 결과를 수집, 관리하는 클래스
16
16
  *
17
17
  * 여러 Builder의 빌드 결과를 중앙에서 관리하며,
18
18
  * 상태별 필터링 및 출력 기능을 제공한다.
@@ -32,7 +32,10 @@ export interface SdNpmPublishConfig {
32
32
  * - SdLocalDirectoryPublishConfig: 로컬 디렉토리로 복사
33
33
  * - SdStoragePublishConfig: FTP/FTPS/SFTP 서버에 업로드
34
34
  */
35
- export type SdPublishConfig = SdNpmPublishConfig | SdLocalDirectoryPublishConfig | SdStoragePublishConfig;
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/(배포 대상)가 아닌 워크스페이스(testsplugins/·향후 추가분)는 각자 엔진 타입체크가
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`·`eslint.config.ts` 등)을 동적 import 한다.
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
- * packagestestsplugins/ 등 종류를 가리지 않고 포함하며, 배포/검사 대상 구분은 소비자가 relPath로 판단한다.
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/ 가 아닌 testsplugins/ 등)를 sd.config.ts
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
- writeFileSync(path.join(tmpRoot, "package.json"), JSON.stringify({ private: true, workspaces: ["pkg"] }));
23
- PKG_PATH = path.join(tmpRoot, "pkg");
24
- CAP_PATH = path.join(PKG_PATH, ".capacitor");
25
- });
26
-
27
- afterAll(() => {
28
- rmSync(tmpRoot, { recursive: true, force: true });
29
- });
30
-
31
- describe("setupCapNpmConfig", () => {
32
- beforeEach(() => {
33
- vi.clearAllMocks();
34
- mockFsxExists.mockResolvedValue(true);
35
- mockFsxReadJson.mockImplementation(((p: string) => {
36
- const normalized = p.replace(/\\/g, "/");
37
- if (normalized.includes(".capacitor/package.json")) {
38
- return {
39
- name: "com.test.app",
40
- version: "1.0.0",
41
- dependencies: {
42
- "@capacitor/core": "^7",
43
- "@capacitor/app": "^7",
44
- "@capacitor/android": "^7",
45
- },
46
- devDependencies: {
47
- "@capacitor/cli": "^7",
48
- "@capacitor/assets": "^3",
49
- },
50
- };
51
- }
52
- return { name: "test-pkg", version: "1.0.0" };
53
- }) as never);
54
- });
55
-
56
- it("루트 package.json이 없으면 에러를 던진다", async () => {
57
- mockFsxExists.mockImplementation(((p: string) => {
58
- const n = p.replace(/\\/g, "/");
59
- if (n.endsWith("package.json") && !n.includes(".capacitor")) return false;
60
- return true;
61
- }) as never);
62
-
63
- const { setupCapNpmConfig } = await import(
64
- "../../src/capacitor/capacitor-npm-config.js"
65
- );
66
-
67
- await expect(
68
- setupCapNpmConfig(CAP_PATH, PKG_PATH, {
69
- appId: "com.test.app",
70
- appName: "Test App",
71
- }, { name: "test-pkg", version: "1.0.0" }, [], []),
72
- ).rejects.toThrow("루트 package.json");
73
- });
74
-
75
- it(".capacitor/package.json이 없으면 빈 설정으로 시작한다", async () => {
76
- mockFsxExists.mockImplementation(((p: string) => {
77
- const n = p.replace(/\\/g, "/");
78
- if (n.includes(".capacitor/package.json")) return false;
79
- return true;
80
- }) as never);
81
-
82
- const { setupCapNpmConfig } = await import(
83
- "../../src/capacitor/capacitor-npm-config.js"
84
- );
85
-
86
- const changed = await setupCapNpmConfig(CAP_PATH, PKG_PATH, {
87
- appId: "com.test.app",
88
- appName: "Test App",
89
- }, { name: "test-pkg", version: "1.0.0" }, [], []);
90
-
91
- // 설정에서 시작하므로 dependencies가 추가되어 변경됨
92
- expect(changed).toBe(true);
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는 package.json#workspaces 기반)
13
+ // 워크스페이스 루트 선언 (discoverWorkspacePackages는 pnpm-workspace.yaml 기반)
14
14
  fs.writeFileSync(
15
- pathx.posix(path.join(tmpDir, "package.json")),
16
- JSON.stringify({ private: true, workspaces: ["packages/*", "tests/*", "plugins/*"] }),
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 빌드에서 스킵)
@@ -195,7 +195,7 @@ describe("patchDevService", () => {
195
195
  expect(r.patched).toBe(expected);
196
196
  });
197
197
 
198
- it("0개 → 1개 (기본 export 워크스페이스) — import·권한 인자가 신규 단독으로 대체, init 동등 결과", async () => {
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·DbClass 에 반영", async () => {
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
  });