@bstage-sdk/cli 0.5.0 → 0.6.0

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/MIGRATION.md CHANGED
@@ -50,6 +50,51 @@
50
50
  > **정렬**: 릴리즈 **시점 최신이 위**. 패키지가 독립 버전이라 헤더 숫자만으로는 시간순 정렬이 되지 않으므로 릴리즈 순서를 기준으로 한다.
51
51
 
52
52
  ---
53
+
54
+ ## → cli 0.6.0
55
+
56
+ ### sdk와 liquid 템플릿이 한 레포에 있어도 빌드·배포된다(혼합 레포) — `자동` · `선택`
57
+
58
+ **영향**
59
+
60
+ - **단일 종류(sdk만·liquid만) 레포가 할 일은 없다.** 명령·산출물이 그대로다.
61
+ - `mixed`(sdk 템플릿 + liquid 템플릿)로 판정돼 `bstage build`·`dev`·`deploy`가 종료 코드 2로 멈추던 레포는 이제 진행한다.
62
+ - 감지: `bstage build`가 "liquid 템플릿 N개 … 포털이 … 패키징합니다" 안내를 낸다. `bstage deploy`도 혼합 안내 한 줄을 낸다.
63
+
64
+ **변경 내용**
65
+
66
+ - **`bstage build`**: 혼합 레포에서 sdk 엔트리만 번들하고 liquid는 검증만 한다 — `dist/`에 liquid를 넣지 않는다(포털 빌더가 push된 커밋의 `public/{user|admin}/{이름}/`을 sdk 산출물과 함께 패키징한다). 검증은 번들보다 먼저 하고, 오류가 있으면 번들 없이 종료 코드 2다. `--json`은 `{ "kind": "mixed", "issues", "sdk": { "templates", "outputs" }, "liquid": { "templates" } }` 객체 하나를 stdout에 쓰고 진행 로그는 stderr로 보낸다.
67
+ - **예약 이름**: 혼합 레포에서 `src/pages/user/…`·`src/pages/admin/…` 페이지는 error다. 산출물이 `dist/{user|admin}/…`로 나가 liquid 자리와 겹치고, 같은 디렉터리에 `template.js`와 `template.liquid`가 함께 있으면 포털이 sdk로만 판정해 liquid가 조용히 사라진다. 포털 빌더도 같은 자리에서 막는다. 위젯(`src/slots`)은 해당 없다.
68
+ - **`bstage deploy`**: 혼합 레포를 막지 않는다. 위젯(SLOT) 배치 사전점검은 liquid 전용 레포에서만 하고, 혼합 레포의 항목별 판정(liquid 항목에 SLOT을 걸면 400)은 포털이 한다.
69
+ - **`bstage dev --kind <sdk|liquid>`**: 혼합 레포에서 어느 프리뷰를 띄울지 고른다. 기본은 sdk(Vite)이고 시작할 때 liquid 프리뷰 방법을 안내한다. 두 서버를 함께 띄우지 않는다. 단일 종류 레포에서 판정과 어긋난 값은 종료 코드 2다.
70
+ - **liquid 검증기**(`bstage build`·`bstage doctor`의 liquid 절)는 sdk와 섞인 것을 더 이상 error로 보고하지 않는다.
71
+ - **포털 빌더는 별도로 갱신된다.** 갱신 전 포털은 혼합 레포에서 liquid만 패키징하므로, CLI와 빌더가 모두 반영된 뒤 혼합 레포를 배포한다.
72
+
73
+ **적용**
74
+
75
+ 1. (자동) 단일 종류 레포는 그대로 둔다 — 올리기만 하면 된다.
76
+ 2. (판단) 혼합 레포를 만들 때: liquid는 `public/{user|admin}/{이름}/template.liquid`, sdk 페이지는 `src/pages/{이름}/template.tsx`(`user`·`admin` 제외), 위젯은 `src/slots/…`. `bstage build`가 겹치는 자리를 짚어 준다.
77
+ 3. (자동) 혼합 레포의 liquid 프리뷰는 `bstage dev --kind liquid`.
78
+
79
+ ### AGENTS.md 관리 영역 v14 · 혼합 레포 스킬 세트(`bstage ai install`·`doctor`) — `자동` · `선택`
80
+
81
+ **영향**
82
+
83
+ - `bstage init`으로 만든 모든 프로젝트. 관리 영역이 v14보다 낮으면 해당한다(본문 문구 변경 — liquid 본문의 "sdk와 섞지 않는다"가 공존 규칙으로 바뀌었다).
84
+ - `mixed`로 판정돼 `bstage ai install`·`update`가 종료 코드 2로 멈추고 `bstage doctor`가 스킬 검사를 건너뛰던 혼합 레포.
85
+ - 감지: `bstage ai doctor`가 `AGENTS.md`를 `stale`로 보고한다. 혼합 레포에서는 `bstage-template`·`bstage-liquid` 둘 다 검사 대상이 된다.
86
+
87
+ **변경 내용**
88
+
89
+ - `bstage ai install`·`update`·`doctor`가 혼합 레포를 지원한다 — 스킬은 sdk 세트와 liquid 세트의 **합집합**(5종)을 설치하고, `AGENTS.md` 관리 영역은 두 구조와 혼합 규칙(어느 방식으로 만들지 · sdk 페이지 `user`·`admin` 금지 · `bstage build`·`dev --kind` 동작)을 담은 **혼합 본문**이 된다. `mixed`는 판정으로만 나오며 `--kind`로 지정하는 값이 아니다 — 혼합 레포에 `--kind sdk|liquid`를 주면 판정과 어긋나 멈춘다.
90
+ - `bstage doctor`는 혼합 레포에서 스킬 검사를 건너뛰지 않고 두 세트를 모두 보며, liquid 검증 결과도 함께 낸다. `--json`의 `skillsSkipped`는 항상 `false`다(필드는 비파괴적으로 남긴다).
91
+ - `bstage-onboarding`·`bstage-liquid` 스킬과 `AGENTS.md`의 "한 레포에 한 방식만" 문구를 공존 규칙(예약 이름 · `dev --kind liquid`)으로 바꿨다.
92
+
93
+ **적용**
94
+
95
+ 1. (자동) `npx @bstage-sdk/cli@latest ai update` — 스킬·`AGENTS.md` 관리 영역을 v14로 갱신한다. 혼합 레포에서는 없는 스킬도 만들어야 하므로 `ai install`을 쓴다.
96
+ 2. (판단) 혼합 레포의 `AGENTS.md` 자유 영역에 "한 레포에 한 방식만" 류의 자체 규칙을 적어 두었다면 지운다 — 관리 영역과 어긋난다.
97
+
53
98
  ## → cli 0.5.0
54
99
 
55
100
  ### liquid 에셋은 포털 미디어에 올린다 — 레포 상대 경로는 배포 후 깨진다 — `판단` · `필수`
package/dist/index.js CHANGED
@@ -662,6 +662,13 @@ function detectProjectKind(root) {
662
662
  hasSdkDependency: sdkDependency
663
663
  };
664
664
  }
665
+ function collidingSdkPages(report2) {
666
+ if (report2.liquidTemplates.length === 0) return [];
667
+ return report2.sdkTemplates.filter((t) => {
668
+ const [root, first] = t.split("/");
669
+ return root === "pages" && first !== void 0 && LIQUID_SURFACES.has(first.toLowerCase());
670
+ });
671
+ }
665
672
 
666
673
  // src/commands/ai/context.ts
667
674
  var DEFAULT_SKILLS_DIR = ".claude/skills";
@@ -703,10 +710,13 @@ function resolveKind(root, requested) {
703
710
  const wanted = parseKindOption(requested);
704
711
  const detected = detectProjectKind(root).kind;
705
712
  if (detected === "mixed") {
706
- fail(
707
- ExitCode.PRECONDITION,
708
- "sdk\uC640 liquid\uAC00 \uD55C \uB808\uD3EC\uC5D0 \uC11E\uC5EC \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uD3EC\uD138\uC774 \uBE4C\uB4DC\uD558\uC9C0 \uBABB\uD569\uB2C8\uB2E4. \uD558\uB098\uB9CC \uB0A8\uAE30\uC138\uC694."
709
- );
713
+ if (wanted) {
714
+ fail(
715
+ ExitCode.PRECONDITION,
716
+ `--kind ${wanted}\uB85C \uC9C0\uC815\uD588\uC9C0\uB9CC \uC774 \uB808\uD3EC\uB294 mixed(sdk + liquid \uD63C\uD569)\uB85C \uD310\uC815\uB429\uB2C8\uB2E4 \u2014 \uC635\uC158\uC744 \uBE7C\uACE0 \uC2E4\uD589\uD558\uC138\uC694(\uB450 \uC138\uD2B8\uB97C \uBAA8\uB450 \uC124\uCE58\uD569\uB2C8\uB2E4).`
717
+ );
718
+ }
719
+ return "mixed";
710
720
  }
711
721
  if (detected === "unknown") {
712
722
  if (!wanted) {
@@ -1099,7 +1109,7 @@ async function aiDoctorCommand(o, deps = {}) {
1099
1109
  }
1100
1110
 
1101
1111
  // src/commands/build.ts
1102
- import { resolve as resolve2, join as join6, relative as relative2 } from "path";
1112
+ import { resolve as resolve2, join as join6, relative as relative2, sep as sep3 } from "path";
1103
1113
  import { readFile as readFile2, access, readdir, rename, mkdir, rm } from "fs/promises";
1104
1114
  import { build as viteBuild, loadEnv } from "vite";
1105
1115
  import pc7 from "picocolors";
@@ -1523,13 +1533,6 @@ function findMisnamedTemplates(root) {
1523
1533
  function validateLiquid(root) {
1524
1534
  const detected = detectProjectKind(root);
1525
1535
  const issues = [];
1526
- if (detected.kind === "mixed") {
1527
- issues.push({
1528
- level: "error",
1529
- path: ".",
1530
- message: "sdk \uD15C\uD50C\uB9BF(src/pages\xB7src/slots\uC758 template.tsx)\uACFC liquid \uD15C\uD50C\uB9BF\uC774 \uD55C \uB808\uD3EC\uC5D0 \uC11E\uC5EC \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uD3EC\uD138 \uBE4C\uB354\uB294 \uB458\uC744 \uD568\uAED8 \uD328\uD0A4\uC9D5\uD558\uC9C0 \uBABB\uD569\uB2C8\uB2E4. \uD558\uB098\uB9CC \uB0A8\uACA8 \uC8FC\uC138\uC694."
1531
- });
1532
- }
1533
1536
  if (detected.liquidTemplates.length === 0) {
1534
1537
  issues.push({
1535
1538
  level: "error",
@@ -1746,8 +1749,9 @@ ${pc7.red("Error")} ${label}: name "${elementName}"\uC744 ${elementNameOwner}\uA
1746
1749
  const finalDir = outputName ? join6(cwd, DIST_DIR, outputName) : join6(cwd, DIST_DIR);
1747
1750
  await mkdir(finalDir, { recursive: true });
1748
1751
  await rename(join6(cwd, outDir, "template.js"), join6(finalDir, "template.js"));
1749
- console.log(` ${relative2(cwd, finalDir)}/template.js (${formatSize(bundleSize)})`);
1750
- return { usesClient };
1752
+ const output = relative2(cwd, finalDir).split(sep3).join("/");
1753
+ console.log(` ${output}/template.js (${formatSize(bundleSize)})`);
1754
+ return { usesClient, output };
1751
1755
  }
1752
1756
  function printCredentialReport(env2, referenced) {
1753
1757
  const issues = checkCredentials(env2, referenced);
@@ -1813,19 +1817,101 @@ function printLiquidIssues(issues) {
1813
1817
  console.log(pc7.green("\u2713 liquid \uAC80\uC99D \uD1B5\uACFC"));
1814
1818
  }
1815
1819
  }
1820
+ async function bundleAll(cwd, entries, bundleEntry) {
1821
+ await rm(join6(cwd, DIST_DIR), { recursive: true, force: true });
1822
+ const ctx = {
1823
+ cwd,
1824
+ takenOutputs: /* @__PURE__ */ new Map(),
1825
+ takenElementNames: /* @__PURE__ */ new Map()
1826
+ };
1827
+ console.log(
1828
+ `Found ${entries.length} template${entries.length > 1 ? "s" : ""}: ${entries.map(entryLabel).join(", ")}`
1829
+ );
1830
+ const results = [];
1831
+ for (const entry of entries) {
1832
+ results.push(await bundleEntry(ctx, entry));
1833
+ }
1834
+ await rm(join6(cwd, DIST_DIR, STAGING_DIR), { recursive: true, force: true });
1835
+ return results;
1836
+ }
1837
+ async function reportCredentials(cwd, results) {
1838
+ if (!results.some((r) => r.usesClient)) return;
1839
+ const referenced = collectReferencedVars(await readProjectSources(cwd));
1840
+ printCredentialReport(loadEnv("production", cwd, "VITE_"), referenced);
1841
+ }
1842
+ function collisionIssues(report2) {
1843
+ return collidingSdkPages(report2).map((t) => ({
1844
+ level: "error",
1845
+ path: `src/${t}`,
1846
+ message: `\uC0B0\uCD9C\uBB3C\uC774 ${DIST_DIR}/${t.slice("pages/".length)}/ \uB85C \uB098\uAC00 liquid \uD15C\uD50C\uB9BF \uC790\uB9AC(${DIST_DIR}/{user|admin}/{\uC774\uB984}/)\uC640 \uACB9\uCE69\uB2C8\uB2E4 \u2014 ${PAGES_ROOT}/ \uBC14\uB85C \uC544\uB798 \uD3F4\uB354 \uC774\uB984\uC5D0 user\xB7admin \uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD3F4\uB354 \uC774\uB984\uC744 \uBC14\uAFD4 \uC8FC\uC138\uC694.`
1847
+ }));
1848
+ }
1849
+ async function withStdoutQuiet(fn) {
1850
+ const { log: log3, info } = console;
1851
+ console.log = (...args) => console.error(...args);
1852
+ console.info = (...args) => console.error(...args);
1853
+ try {
1854
+ return await fn();
1855
+ } finally {
1856
+ console.log = log3;
1857
+ console.info = info;
1858
+ }
1859
+ }
1860
+ async function buildMixed(cwd, options, report2, bundleEntry) {
1861
+ const issues = [...validateLiquid(cwd), ...collisionIssues(report2)];
1862
+ const hasError = issues.some((i) => i.level === "error");
1863
+ const liquid = { templates: report2.liquidTemplates };
1864
+ if (options.json) {
1865
+ if (hasError) {
1866
+ printJson({ kind: "mixed", issues, liquid });
1867
+ process.exitCode = ExitCode.PRECONDITION;
1868
+ return;
1869
+ }
1870
+ const entries2 = await discoverEntries(cwd);
1871
+ const results2 = await withStdoutQuiet(() => bundleAll(cwd, entries2, bundleEntry));
1872
+ printJson({
1873
+ kind: "mixed",
1874
+ issues,
1875
+ sdk: { templates: entries2.map(entryLabel), outputs: results2.map((r) => r.output) },
1876
+ liquid
1877
+ });
1878
+ return;
1879
+ }
1880
+ printLiquidIssues(issues);
1881
+ if (hasError) {
1882
+ console.error(
1883
+ `
1884
+ ${pc7.red("\u2717")} \uAC80\uC99D \uC2E4\uD328 \u2014 \uC704 \uD56D\uBAA9\uC744 \uACE0\uCE5C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694. \uC774\uB300\uB85C push\uD558\uBA74 \uD3EC\uD138 \uBE4C\uB4DC\uB3C4 \uAC19\uC740 \uC790\uB9AC\uC5D0\uC11C \uB9C9\uD799\uB2C8\uB2E4.`
1885
+ );
1886
+ process.exit(ExitCode.PRECONDITION);
1887
+ }
1888
+ const entries = await discoverEntries(cwd);
1889
+ const results = await bundleAll(cwd, entries, bundleEntry);
1890
+ console.log(`
1891
+ Build complete.`);
1892
+ console.log(
1893
+ `liquid \uD15C\uD50C\uB9BF ${report2.liquidTemplates.length}\uAC1C(${report2.liquidTemplates.join(", ")})\uB294 \uB85C\uCEEC \uC0B0\uCD9C\uBB3C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uD3EC\uD138\uC774 push\uB41C \uCEE4\uBC0B\uC758 public/{user|admin}/{name}/ \uC744 \uADF8\uB300\uB85C \uD328\uD0A4\uC9D5\uD569\uB2C8\uB2E4(data.json\xB7layout.json \uC81C\uC678)`
1894
+ );
1895
+ await reportCredentials(cwd, results);
1896
+ }
1816
1897
  async function buildCommand(options = {}, deps = {}) {
1817
1898
  const cwd = deps.cwd ?? process.cwd();
1899
+ const bundleEntry = deps.bundleEntry ?? buildEntry;
1818
1900
  const report2 = detectProjectKind(cwd);
1819
1901
  const kind = report2.kind;
1820
- if (kind === "liquid" || kind === "mixed") {
1902
+ if (kind === "liquid") {
1821
1903
  reportLiquid(cwd, options);
1822
1904
  return;
1823
1905
  }
1906
+ if (kind === "mixed") {
1907
+ await buildMixed(cwd, options, report2, bundleEntry);
1908
+ return;
1909
+ }
1824
1910
  const entries = await discoverEntries(cwd);
1825
1911
  if (entries.length === 0) {
1826
- const ignored = ignoredLiquidNotice(report2.ignoredLiquid);
1827
- if (ignored !== null) {
1828
- console.error(`Error: ${ignored}`);
1912
+ const ignored2 = ignoredLiquidNotice(report2.ignoredLiquid);
1913
+ if (ignored2 !== null) {
1914
+ console.error(`Error: ${ignored2}`);
1829
1915
  await printLegacyHint(cwd);
1830
1916
  process.exit(ExitCode.PRECONDITION);
1831
1917
  }
@@ -1835,26 +1921,13 @@ async function buildCommand(options = {}, deps = {}) {
1835
1921
  await printLegacyHint(cwd);
1836
1922
  process.exit(ExitCode.PRECONDITION);
1837
1923
  }
1838
- await rm(join6(cwd, DIST_DIR), { recursive: true, force: true });
1839
- const ctx = {
1840
- cwd,
1841
- takenOutputs: /* @__PURE__ */ new Map(),
1842
- takenElementNames: /* @__PURE__ */ new Map()
1843
- };
1844
- console.log(
1845
- `Found ${entries.length} template${entries.length > 1 ? "s" : ""}: ${entries.map(entryLabel).join(", ")}`
1846
- );
1847
- const results = [];
1848
- for (const entry of entries) {
1849
- results.push(await buildEntry(ctx, entry));
1850
- }
1851
- await rm(join6(cwd, DIST_DIR, STAGING_DIR), { recursive: true, force: true });
1924
+ const ignored = ignoredLiquidNotice(report2.ignoredLiquid);
1925
+ if (ignored !== null) console.log(`${pc7.yellow("\u26A0")} ${ignored}
1926
+ `);
1927
+ const results = await bundleAll(cwd, entries, bundleEntry);
1852
1928
  console.log(`
1853
1929
  Build complete.`);
1854
- if (results.some((r) => r.usesClient)) {
1855
- const referenced = collectReferencedVars(await readProjectSources(cwd));
1856
- printCredentialReport(loadEnv("production", cwd, "VITE_"), referenced);
1857
- }
1930
+ await reportCredentials(cwd, results);
1858
1931
  }
1859
1932
  function formatSize(bytes) {
1860
1933
  if (bytes < 1024) return `${bytes} B`;
@@ -2511,13 +2584,8 @@ async function deployCommand(options, deps = {}) {
2511
2584
  fail(ExitCode.PRECONDITION, "--json \uC740 --yes \uC640 \uD568\uAED8 \uC4F0\uC138\uC694.");
2512
2585
  }
2513
2586
  if (!options.yes) assertCanConfirm(deps);
2514
- const kind = detectProjectKind(deps.cwd ?? process.cwd()).kind;
2515
- if (kind === "mixed") {
2516
- fail(
2517
- ExitCode.PRECONDITION,
2518
- "sdk \uD15C\uD50C\uB9BF\uACFC liquid \uD15C\uD50C\uB9BF\uC774 \uD55C \uB808\uD3EC\uC5D0 \uC11E\uC5EC \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uD3EC\uD138 \uBE4C\uB354\uAC00 \uD328\uD0A4\uC9D5\uD558\uC9C0 \uBABB\uD569\uB2C8\uB2E4. bstage build \uB85C \uC5B4\uB290 \uD30C\uC77C\uC774 \uAC78\uB9AC\uB294\uC9C0 \uD655\uC778\uD55C \uB4A4 \uD558\uB098\uB9CC \uB0A8\uACA8 \uC8FC\uC138\uC694."
2519
- );
2520
- }
2587
+ const report2 = detectProjectKind(deps.cwd ?? process.cwd());
2588
+ const kind = report2.kind;
2521
2589
  const out = deps.out ?? console.log;
2522
2590
  const ctx = await resolveContext({ cwd: deps.cwd, env: deps.env, fetch: deps.fetch });
2523
2591
  const defaultBranch = await repoDefaultBranch(ctx);
@@ -2525,13 +2593,20 @@ async function deployCommand(options, deps = {}) {
2525
2593
  const targets = await fetchTargets(ctx, options.placement);
2526
2594
  if (kind === "liquid") {
2527
2595
  assertNoSlotPlacement(targets);
2528
- if (!options.json) {
2529
- out(
2530
- pc10.dim(
2531
- "[bstage] liquid \uB808\uD3EC \u2014 \uB85C\uCEEC \uBE4C\uB4DC \uC5C6\uC774 \uD3EC\uD138\uC774 push\uB41C \uCEE4\uBC0B\uC758 public/{user|admin}/ \uC744 \uADF8\uB300\uB85C \uD328\uD0A4\uC9D5\uD569\uB2C8\uB2E4."
2532
- )
2533
- );
2534
- }
2596
+ }
2597
+ if (!options.json && kind === "liquid") {
2598
+ out(
2599
+ pc10.dim(
2600
+ "[bstage] liquid \uB808\uD3EC \u2014 \uB85C\uCEEC \uBE4C\uB4DC \uC5C6\uC774 \uD3EC\uD138\uC774 push\uB41C \uCEE4\uBC0B\uC758 public/{user|admin}/ \uC744 \uADF8\uB300\uB85C \uD328\uD0A4\uC9D5\uD569\uB2C8\uB2E4."
2601
+ )
2602
+ );
2603
+ }
2604
+ if (!options.json && kind === "mixed") {
2605
+ out(
2606
+ pc10.dim(
2607
+ `[bstage] \uD63C\uD569 \uB808\uD3EC \u2014 sdk \uD15C\uD50C\uB9BF\uC740 \uD3EC\uD138\uC774 \uBE4C\uB4DC\uD558\uACE0, liquid \uD15C\uD50C\uB9BF ${report2.liquidTemplates.length}\uAC1C\uB294 push\uB41C \uCEE4\uBC0B\uC758 public/{user|admin}/ \uC744 \uADF8\uB300\uB85C \uD328\uD0A4\uC9D5\uD569\uB2C8\uB2E4.`
2608
+ )
2609
+ );
2535
2610
  }
2536
2611
  const created = await createAndAnnounceBuild(ctx, options);
2537
2612
  if (options.noWait) {
@@ -2555,7 +2630,7 @@ import pc11 from "picocolors";
2555
2630
  import { existsSync as existsSync5, realpathSync as realpathSync2, statSync, watch } from "fs";
2556
2631
  import { readFile as readFile5 } from "fs/promises";
2557
2632
  import { createServer } from "http";
2558
- import { join as join9, relative as relative3, sep as sep3 } from "path";
2633
+ import { join as join9, relative as relative3, sep as sep4 } from "path";
2559
2634
 
2560
2635
  // src/liquid/routes.ts
2561
2636
  var RELOAD_PATH = "/__bstage__/reload";
@@ -2788,7 +2863,7 @@ function isInside(publicDir, full) {
2788
2863
  try {
2789
2864
  const base = realpathSync2(publicDir);
2790
2865
  const real = realpathSync2(full);
2791
- return real === base || real.startsWith(base + sep3);
2866
+ return real === base || real.startsWith(base + sep4);
2792
2867
  } catch {
2793
2868
  return false;
2794
2869
  }
@@ -2961,6 +3036,27 @@ async function startLiquidDevServer(options) {
2961
3036
  }
2962
3037
 
2963
3038
  // src/commands/dev.ts
3039
+ function resolveDevMode(kind, requested) {
3040
+ if (requested !== void 0 && requested !== "sdk" && requested !== "liquid") {
3041
+ fail(ExitCode.PRECONDITION, `--kind \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${requested} (sdk \uB610\uB294 liquid)`);
3042
+ }
3043
+ const detected = kind === "liquid" ? "liquid" : "sdk";
3044
+ if (kind === "mixed") {
3045
+ if (requested === "liquid") return { mode: "liquid" };
3046
+ if (requested === "sdk") return { mode: "sdk" };
3047
+ return {
3048
+ mode: "sdk",
3049
+ hint: "sdk\uC640 liquid \uD15C\uD50C\uB9BF\uC774 \uD568\uAED8 \uC788\uB294 \uB808\uD3EC\uC785\uB2C8\uB2E4 \u2014 \uC9C0\uAE08\uC740 sdk(Vite) \uD504\uB9AC\uBDF0\uB97C \uB744\uC6C1\uB2C8\uB2E4. liquid \uD504\uB9AC\uBDF0\uB294 `bstage dev --kind liquid` \uB85C \uB530\uB85C \uB744\uC6B0\uC138\uC694."
3050
+ };
3051
+ }
3052
+ if (requested !== void 0 && requested !== detected) {
3053
+ fail(
3054
+ ExitCode.PRECONDITION,
3055
+ `--kind ${requested}\uB85C \uC9C0\uC815\uD588\uC9C0\uB9CC \uC774 \uB808\uD3EC\uB294 ${kind}\uB85C \uD310\uC815\uB429\uB2C8\uB2E4 \u2014 \uC635\uC158\uC744 \uBE7C\uACE0 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`
3056
+ );
3057
+ }
3058
+ return { mode: detected };
3059
+ }
2964
3060
  async function runLiquidPreview(root, port, phase) {
2965
3061
  if (phase !== void 0) {
2966
3062
  console.log("[bstage] liquid \uD504\uB9AC\uBDF0\uB294 phase\uB97C \uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 --phase \uB97C \uBB34\uC2DC\uD569\uB2C8\uB2E4.");
@@ -2977,7 +3073,6 @@ async function runLiquidPreview(root, port, phase) {
2977
3073
  console.log(` admin \uBAA9\uB85D: ${base}/admin`);
2978
3074
  console.log(" phase\uC640 \uC778\uC99D \uD504\uB85D\uC2DC\uB294 liquid \uD504\uB9AC\uBDF0\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4. \uAC12\uC740 data.json\uC5D0\uC11C \uC635\uB2C8\uB2E4.\n");
2979
3075
  }
2980
- var MIXED_MESSAGE = "liquid \uD15C\uD50C\uB9BF(public/{user|admin}/{\uC774\uB984}/template.liquid)\uACFC React \uD15C\uD50C\uB9BF(src/**/template.tsx)\uC774 \uD55C \uB808\uD3EC\uC5D0 \uC11E\uC5EC \uC788\uC2B5\uB2C8\uB2E4. \uD3EC\uD138 \uBE4C\uB354\uAC00 \uBE4C\uB4DC\uD558\uC9C0 \uBABB\uD558\uBBC0\uB85C \uD55C\uCABD\uC73C\uB85C \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.";
2981
3076
  async function runViteDevServer(root, port, phaseOption) {
2982
3077
  let resolved;
2983
3078
  try {
@@ -3020,12 +3115,13 @@ async function devCommand(options) {
3020
3115
  const port = parseInt(options.port, 10);
3021
3116
  const root = process.cwd();
3022
3117
  const report2 = detectProjectKind(root);
3023
- const kind = report2.kind;
3118
+ const decision = resolveDevMode(report2.kind, options.kind);
3024
3119
  const ignored = ignoredLiquidNotice(report2.ignoredLiquid);
3025
3120
  if (ignored !== null) console.log(`[bstage] ${pc11.yellow("\uACBD\uACE0")}: ${ignored}
3026
3121
  `);
3027
- if (kind === "liquid") return await runLiquidPreview(root, port, options.phase);
3028
- if (kind === "mixed") fail(ExitCode.PRECONDITION, MIXED_MESSAGE);
3122
+ if (decision.hint !== void 0) console.log(`[bstage] ${decision.hint}
3123
+ `);
3124
+ if (decision.mode === "liquid") return await runLiquidPreview(root, port, options.phase);
3029
3125
  await runViteDevServer(root, port, options.phase);
3030
3126
  }
3031
3127
 
@@ -4530,6 +4626,7 @@ import {
4530
4626
  parseAgentsIdentity as parseAgentsIdentity2,
4531
4627
  readSkillStamp as readSkillStamp2,
4532
4628
  renderManagedBlock,
4629
+ sanitizeIdentity as sanitizeIdentity2,
4533
4630
  skillsFor as skillsFor4
4534
4631
  } from "@bstage-sdk/ai-toolkit";
4535
4632
  async function autoSyncSkills(root, report2) {
@@ -4555,10 +4652,10 @@ async function diagnoseSkills(root, kind) {
4555
4652
  );
4556
4653
  }
4557
4654
  function resolveSkillKind(detected) {
4558
- if (detected === "mixed") return null;
4655
+ if (detected === "mixed") return "mixed";
4559
4656
  return detected === "liquid" ? "liquid" : "sdk";
4560
4657
  }
4561
- async function diagnoseAgentsMd(root, pkgName) {
4658
+ async function diagnoseAgentsMd(root, pkgName, kind = "sdk") {
4562
4659
  let content;
4563
4660
  try {
4564
4661
  content = await readFile8(join14(root, "AGENTS.md"), "utf-8");
@@ -4568,10 +4665,10 @@ async function diagnoseAgentsMd(root, pkgName) {
4568
4665
  const status = agentsMdStatus(content);
4569
4666
  if (status === "ok") return { status, managedBlock: null };
4570
4667
  const parsed = parseAgentsIdentity2(content);
4571
- const projectName = parsed.projectName ?? pkgName ?? "custom-templates";
4572
- const space = parsed.space ?? projectName.split("-custom-templates-")[0] ?? projectName;
4668
+ const projectName = sanitizeIdentity2(parsed.projectName) ?? sanitizeIdentity2(pkgName) ?? "custom-templates";
4669
+ const space = sanitizeIdentity2(parsed.space) ?? projectName.split("-custom-templates-")[0] ?? projectName;
4573
4670
  const target = resolveProjectTarget(root);
4574
- return { status, managedBlock: renderManagedBlock({ space, projectName, target }) };
4671
+ return { status, managedBlock: renderManagedBlock({ space, projectName, target, kind }) };
4575
4672
  }
4576
4673
 
4577
4674
  // src/commands/doctor/report.ts
@@ -4646,7 +4743,8 @@ async function diagnose2(root) {
4646
4743
  const deps = pkg.deps;
4647
4744
  const kind = detectProjectKind(root).kind;
4648
4745
  const isLiquid = kind === "liquid";
4649
- const isBstageProject = isLiquid || SDK_PACKAGES2.some((p5) => p5 in deps);
4746
+ const hasLiquid = isLiquid || kind === "mixed";
4747
+ const isBstageProject = hasLiquid || SDK_PACKAGES2.some((p5) => p5 in deps);
4650
4748
  const installed = await collectInstalledVersions(root, deps);
4651
4749
  const { versions, latestChecked } = await diagnoseVersions(
4652
4750
  deps,
@@ -4667,11 +4765,11 @@ async function diagnose2(root) {
4667
4765
  scripts: diagnoseScripts(pkg.scripts, tables.scripts),
4668
4766
  recommendedDeps: diagnoseRecommendedDeps(deps, tables.deps),
4669
4767
  migrations: await readApplicableMigrations(migrationResolver(root, deps, installed)),
4670
- agentsMd: await diagnoseAgentsMd(root, pkg.name),
4671
- skills: skillKind === null ? [] : await diagnoseSkills(root, skillKind),
4672
- skillsSkipped: skillKind === null,
4768
+ agentsMd: await diagnoseAgentsMd(root, pkg.name, skillKind),
4769
+ skills: await diagnoseSkills(root, skillKind),
4770
+ skillsSkipped: false,
4673
4771
  latestChecked,
4674
- liquid: isLiquid ? { issues: validateLiquid(root) } : null
4772
+ liquid: hasLiquid ? { issues: validateLiquid(root) } : null
4675
4773
  };
4676
4774
  }
4677
4775
  async function readInstalledVersion(root, pkg) {
@@ -4943,8 +5041,8 @@ function printReport(r) {
4943
5041
  }
4944
5042
  printVersions(r);
4945
5043
  printFiles(r);
4946
- if (r.liquid === null) printLayout(r);
4947
- else printLiquid(r);
5044
+ if (r.kind !== "liquid") printLayout(r);
5045
+ if (r.liquid !== null) printLiquid(r);
4948
5046
  printPnpmWorkspace(r);
4949
5047
  printScripts(r);
4950
5048
  printRecommendedDeps(r);
@@ -5882,8 +5980,8 @@ function readVersion() {
5882
5980
  var program = new Command().name("bstage").description("b.stage \uC11C\uB4DC\uD30C\uD2F0 \uD15C\uD50C\uB9BF SDK CLI \u2014 \uD15C\uD50C\uB9BF \uAC1C\uBC1C\xB7\uBE4C\uB4DC\xB7\uB85C\uCEEC \uC11C\uBC84\xB7\uB9C8\uC774\uADF8\uB808\uC774\uC158").version(readVersion(), "-v, --version", "\uBC84\uC804 \uCD9C\uB825");
5883
5981
  program.command("init").description("\uC0C8 bstage \uD15C\uD50C\uB9BF \uD504\uB85C\uC81D\uD2B8 \uC0DD\uC131").option("-y, --yes", "\uAE30\uBCF8\uAC12\uC73C\uB85C \uBE44\uC778\uD130\uB799\uD2F0\uBE0C \uC2E4\uD589").option("--space <space>", "Space \uC774\uB984").option("--target <target>", "\uC2A4\uCE90\uD3F4\uB4DC \uB300\uC0C1 (user, admin)", "user").option("--kind <kind>", "\uD504\uB85C\uC81D\uD2B8 \uC885\uB958 (sdk: React \uCEF4\uD3EC\uB10C\uD2B8, liquid: liquid \uD15C\uD50C\uB9BF)", "sdk").option("--template <template>", "\uCCAB \uBC88\uC9F8 \uD15C\uD50C\uB9BF \uC774\uB984 (\uAE30\uBCF8: sdk {space}-hello, liquid hello)").option("--phase <phase>", "\uBC30\uD3EC \uD658\uACBD (dev, qa, real, sandbox)", "sandbox").option("--pm <pm>", "\uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800 (npm, pnpm)", "npm").action(initCommand);
5884
5982
  program.command("build").description(
5885
- "\uD15C\uD50C\uB9BF\uC744 IIFE \uBC88\uB4E4\uB85C \uBE4C\uB4DC (\uD398\uC774\uC9C0\uB294 \uACBD\uB85C\uBCC4, \uC704\uC82F\uC740 \uC2AC\uB86F\uBCC4 \uB514\uB809\uD1A0\uB9AC) \u2014 liquid \uB808\uD3EC\uB294 \uAC80\uC99D\uB9CC"
5886
- ).option("--json", "\uAC80\uC99D \uACB0\uACFC\uB97C JSON\uC73C\uB85C \uCD9C\uB825 (liquid \uB808\uD3EC \uC804\uC6A9)").action((options) => buildCommand(options));
5983
+ "\uD15C\uD50C\uB9BF\uC744 IIFE \uBC88\uB4E4\uB85C \uBE4C\uB4DC (\uD398\uC774\uC9C0\uB294 \uACBD\uB85C\uBCC4, \uC704\uC82F\uC740 \uC2AC\uB86F\uBCC4 \uB514\uB809\uD1A0\uB9AC) \u2014 liquid \uB808\uD3EC\uB294 \uAC80\uC99D\uB9CC, \uD63C\uD569 \uB808\uD3EC\uB294 sdk \uBC88\uB4E4 + liquid \uAC80\uC99D"
5984
+ ).option("--json", "\uAC80\uC99D \uACB0\uACFC\xB7\uC0B0\uCD9C\uBB3C \uC694\uC57D\uC744 JSON\uC73C\uB85C \uCD9C\uB825 (liquid\xB7\uD63C\uD569 \uB808\uD3EC)").action((options) => buildCommand(options));
5887
5985
  program.command("docs").description("\uC124\uCE58\uB41C SDK \uBB38\uC11C \uBAA9\uB85D\xB7\uACBD\uB85C \uCD9C\uB825").action(docsCommand);
5888
5986
  program.command("doctor").description(
5889
5987
  "SDK \uBC84\uC804\xB7\uBCF4\uC77C\uB7EC\uD50C\uB808\uC774\uD2B8 \uB4DC\uB9AC\uD504\uD2B8 \uC9C4\uB2E8 (\uC9C4\uB2E8 + SDK \uC18C\uC720 \uC2A4\uD0AC \uC790\uB3D9 \uB3D9\uAE30\uD654; package.json\xB7\uD6C5\uC740 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC74C)"
@@ -5891,6 +5989,9 @@ program.command("doctor").description(
5891
5989
  program.command("dev").description("\uC778\uC99D \uD504\uB85D\uC2DC \uD3EC\uD568 \uB85C\uCEEC \uAC1C\uBC1C \uC11C\uBC84 (liquid \uB808\uD3EC\uB294 liquidjs \uD504\uB9AC\uBDF0)").option("-p, --port <port>", "Dev server port", "5173").option(
5892
5990
  "--phase <phase>",
5893
5991
  "Target phase (dev, qa, real, sandbox). \uC0DD\uB7B5 \uC2DC .env\uC758 VITE_BSTAGE_PHASE, \uADF8\uAC83\uB3C4 \uC5C6\uC73C\uBA74 sandbox"
5992
+ ).option(
5993
+ "--kind <kind>",
5994
+ "\uD63C\uD569 \uB808\uD3EC(sdk + liquid)\uC5D0\uC11C \uB744\uC6B8 \uD504\uB9AC\uBDF0 (sdk: Vite \uAE30\uBCF8, liquid: liquidjs)"
5894
5995
  ).action((o) => runCommand(() => devCommand(o)));
5895
5996
  var ai = program.command("ai").description("\uC5D0\uC774\uC804\uD2B8\uC6A9 \uC2A4\uD0AC\xB7AGENTS.md \uC124\uCE58\xB7\uB3D9\uAE30\uD654\xB7\uC9C4\uB2E8");
5896
5997
  ai.command("install").description("\uC2A4\uD0AC(.claude/skills)\xB7AGENTS.md\xB7CLAUDE.md \uC124\uCE58 \uBC0F \uCD5C\uC2E0\uD654 + pre-commit \uC2DC\uD06C\uB9BF \uAC00\uB4DC").option("--dir <dir>", "\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC", ".claude/skills").option("--kind <kind>", "\uD504\uB85C\uC81D\uD2B8 \uC885\uB958(sdk|liquid) \u2014 \uC790\uB3D9 \uD310\uC815\uC774 \uC548 \uB420 \uB54C").action((o) => runCommand(() => aiInstallCommand(o)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bstage-sdk/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "bstage SDK CLI — build b.stage templates",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -32,14 +32,14 @@
32
32
  "liquidjs": "^10.24.0",
33
33
  "picocolors": "^1.1.1",
34
34
  "vite": "^7.0.0",
35
- "@bstage-sdk/ai-toolkit": "0.5.0",
36
- "@bstage-sdk/core": "0.5.0"
35
+ "@bstage-sdk/ai-toolkit": "0.6.0",
36
+ "@bstage-sdk/core": "0.6.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^25.5.0",
40
40
  "tsup": "^8.0.0",
41
41
  "typescript": "^5.5.0",
42
- "@bstage-sdk/design": "0.5.0"
42
+ "@bstage-sdk/design": "0.6.0"
43
43
  },
44
44
  "license": "MIT",
45
45
  "scripts": {