@bstage-sdk/cli 0.1.0 → 0.3.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/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ APP_KEY_VAR,
3
4
  DEFAULT_TRANSLATION_TIER,
5
+ LEGACY_APP_KEY_VAR,
4
6
  LOGIN_PATH,
7
+ PORTAL_HOSTS,
8
+ PORTAL_HOST_ENV_KEY,
5
9
  TRANSLATION_CDN_BASE_KEY,
6
10
  TRANSLATION_CDN_BUCKET_PREFIX,
7
11
  TRANSLATION_CDN_ORIGIN,
@@ -9,13 +13,14 @@ import {
9
13
  bstageDevPlugin,
10
14
  createViteConfig,
11
15
  describePhase,
16
+ isPublicPhase,
12
17
  resolvePhase,
13
18
  resolveProjectTarget
14
- } from "./chunk-S6HGR2U3.js";
19
+ } from "./chunk-COUDGZIA.js";
15
20
 
16
21
  // src/index.ts
17
22
  import { readFileSync as readFileSync2 } from "fs";
18
- import { dirname as dirname3, join as join7 } from "path";
23
+ import { dirname as dirname5, join as join9 } from "path";
19
24
  import { fileURLToPath as fileURLToPath2 } from "url";
20
25
  import { Command } from "commander";
21
26
 
@@ -154,10 +159,11 @@ __reg__.forEach(__registerTemplate__);
154
159
  // src/build/credentialCheck.ts
155
160
  var CLIENT_MARKER = "X-BSTAGE-APP-ID";
156
161
  var APP_ID_PREFIX = "bsa_";
157
- var APP_SECRET_PREFIX = "bsp_";
162
+ var APP_KEY_PREFIX = "bsp_";
158
163
  var CREDENTIAL_ENV_VARS = [
159
164
  "VITE_BSTAGE_APP_ID",
160
- "VITE_BSTAGE_APP_SECRET",
165
+ APP_KEY_VAR,
166
+ LEGACY_APP_KEY_VAR,
161
167
  "VITE_BSTAGE_TENANT_ID"
162
168
  ];
163
169
  function usesBstageClient(bundle) {
@@ -177,54 +183,77 @@ function collectReferencedVars(sources) {
177
183
  return referenced;
178
184
  }
179
185
  function checkCredentials(env2, referenced) {
180
- const issues = [];
181
- for (const varName of CREDENTIAL_ENV_VARS) {
182
- if (!referenced.has(varName)) continue;
183
- const value = env2[varName]?.trim();
186
+ const appId = referenced.has("VITE_BSTAGE_APP_ID") ? env2.VITE_BSTAGE_APP_ID?.trim() : void 0;
187
+ const appKeys = resolveAppKeys(env2, referenced);
188
+ const targets = [
189
+ ...referenced.has("VITE_BSTAGE_APP_ID") ? [{ varName: "VITE_BSTAGE_APP_ID", value: appId }] : [],
190
+ ...appKeys,
191
+ ...referenced.has("VITE_BSTAGE_TENANT_ID") ? [{ varName: "VITE_BSTAGE_TENANT_ID", value: env2.VITE_BSTAGE_TENANT_ID?.trim() }] : []
192
+ ];
193
+ const presenceIssues = targets.flatMap(({ varName, value }) => {
184
194
  if (!value) {
185
- issues.push({
186
- level: "error",
187
- varName,
188
- message: "\uAC12\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBC88\uB4E4\uC5D0 undefined\uAC00 \uBC15\uD600 \uBC30\uD3EC \uD6C4 401\uC774 \uB0A9\uB2C8\uB2E4."
189
- });
190
- continue;
195
+ return [
196
+ {
197
+ level: "error",
198
+ varName,
199
+ message: "\uAC12\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uBC88\uB4E4\uC5D0 undefined\uAC00 \uBC15\uD600 \uBC30\uD3EC \uD6C4 401\uC774 \uB0A9\uB2C8\uB2E4."
200
+ }
201
+ ];
191
202
  }
192
203
  if (isPlaceholderValue(value)) {
193
- issues.push({
194
- level: "error",
195
- varName,
196
- message: "\uC790\uB9AC\uD45C\uC2DC\uC790\uAC00 \uADF8\uB300\uB85C\uC785\uB2C8\uB2E4 \u2014 \uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 \uAC12\uC73C\uB85C \uBC14\uAFB8\uC138\uC694."
197
- });
204
+ return [
205
+ {
206
+ level: "error",
207
+ varName,
208
+ message: "\uC790\uB9AC\uD45C\uC2DC\uC790\uAC00 \uADF8\uB300\uB85C\uC785\uB2C8\uB2E4 \u2014 \uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 \uAC12\uC73C\uB85C \uBC14\uAFB8\uC138\uC694."
209
+ }
210
+ ];
198
211
  }
199
- }
200
- const appId = referenced.has("VITE_BSTAGE_APP_ID") ? env2.VITE_BSTAGE_APP_ID?.trim() : void 0;
201
- const appSecret = referenced.has("VITE_BSTAGE_APP_SECRET") ? env2.VITE_BSTAGE_APP_SECRET?.trim() : void 0;
202
- const appIdUsable = !!appId && !isPlaceholderValue(appId);
203
- const appSecretUsable = !!appSecret && !isPlaceholderValue(appSecret);
204
- const swapped = appIdUsable && appId.startsWith(APP_SECRET_PREFIX) || appSecretUsable && appSecret.startsWith(APP_ID_PREFIX);
212
+ return [];
213
+ });
214
+ const usableAppId = appId && !isPlaceholderValue(appId) ? appId : void 0;
215
+ const usableAppKeys = appKeys.filter(
216
+ (k) => !!k.value && !isPlaceholderValue(k.value)
217
+ );
218
+ const swappedKey = usableAppKeys.find((k) => k.value.startsWith(APP_ID_PREFIX));
219
+ const swapped = usableAppId?.startsWith(APP_KEY_PREFIX) || swappedKey !== void 0;
205
220
  if (swapped) {
206
- issues.push({
207
- level: "error",
208
- varName: "VITE_BSTAGE_APP_ID",
209
- message: `APP_ID\uC640 APP_SECRET\uC774 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4 \u2014 APP_ID\uB294 "${APP_ID_PREFIX}", APP_SECRET\uC740 "${APP_SECRET_PREFIX}"\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.`
210
- });
211
- return issues;
212
- }
213
- if (appIdUsable && !appId.startsWith(APP_ID_PREFIX)) {
214
- issues.push({
215
- level: "warn",
216
- varName: "VITE_BSTAGE_APP_ID",
217
- message: `"${APP_ID_PREFIX}"\uB85C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uD30C\uD2B8\uB108 \uCF58\uC194 \uBC1C\uAE09 \uAC12\uC774 \uB9DE\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`
218
- });
219
- }
220
- if (appSecretUsable && !appSecret.startsWith(APP_SECRET_PREFIX)) {
221
- issues.push({
222
- level: "warn",
223
- varName: "VITE_BSTAGE_APP_SECRET",
224
- message: `"${APP_SECRET_PREFIX}"\uB85C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uD30C\uD2B8\uB108 \uCF58\uC194 \uBC1C\uAE09 \uAC12\uC774 \uB9DE\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`
225
- });
221
+ const keyLabel = shortName(swappedKey?.varName ?? appKeys[0]?.varName ?? APP_KEY_VAR);
222
+ return [
223
+ ...presenceIssues,
224
+ {
225
+ level: "error",
226
+ varName: "VITE_BSTAGE_APP_ID",
227
+ message: `APP_ID\uC640 ${keyLabel}\uAC00 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4 \u2014 APP_ID\uB294 "${APP_ID_PREFIX}", ${keyLabel}\uB294 "${APP_KEY_PREFIX}"\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.`
228
+ }
229
+ ];
226
230
  }
227
- return issues;
231
+ const prefixWarnings = [
232
+ ...usableAppId && !usableAppId.startsWith(APP_ID_PREFIX) ? [
233
+ {
234
+ level: "warn",
235
+ varName: "VITE_BSTAGE_APP_ID",
236
+ message: `"${APP_ID_PREFIX}"\uB85C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uD30C\uD2B8\uB108 \uCF58\uC194 \uBC1C\uAE09 \uAC12\uC774 \uB9DE\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`
237
+ }
238
+ ] : [],
239
+ ...usableAppKeys.filter((k) => !k.value.startsWith(APP_KEY_PREFIX)).map(
240
+ (k) => ({
241
+ level: "warn",
242
+ varName: k.varName,
243
+ message: `"${APP_KEY_PREFIX}"\uB85C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uD30C\uD2B8\uB108 \uCF58\uC194 \uBC1C\uAE09 \uAC12\uC774 \uB9DE\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`
244
+ })
245
+ )
246
+ ];
247
+ return [...presenceIssues, ...prefixWarnings];
248
+ }
249
+ function resolveAppKeys(env2, referenced) {
250
+ return [APP_KEY_VAR, LEGACY_APP_KEY_VAR].filter((varName) => referenced.has(varName)).map((varName) => {
251
+ const other = varName === APP_KEY_VAR ? LEGACY_APP_KEY_VAR : APP_KEY_VAR;
252
+ return { varName, value: env2[varName]?.trim() || env2[other]?.trim() || void 0 };
253
+ });
254
+ }
255
+ function shortName(varName) {
256
+ return varName.replace(/^VITE_BSTAGE_/, "");
228
257
  }
229
258
 
230
259
  // src/commands/build.ts
@@ -474,20 +503,966 @@ async function buildCommand() {
474
503
  for (const entry of entries) {
475
504
  results.push(await buildEntry(ctx, entry));
476
505
  }
477
- await rm(join(cwd, DIST_DIR, STAGING_DIR), { recursive: true, force: true });
478
- console.log(`
479
- Build complete.`);
480
- if (results.some((r) => r.usesClient)) {
481
- const referenced = collectReferencedVars(await readProjectSources(cwd));
482
- printCredentialReport(loadEnv("production", cwd, "VITE_"), referenced);
506
+ await rm(join(cwd, DIST_DIR, STAGING_DIR), { recursive: true, force: true });
507
+ console.log(`
508
+ Build complete.`);
509
+ if (results.some((r) => r.usesClient)) {
510
+ const referenced = collectReferencedVars(await readProjectSources(cwd));
511
+ printCredentialReport(loadEnv("production", cwd, "VITE_"), referenced);
512
+ }
513
+ }
514
+ function formatSize(bytes) {
515
+ if (bytes < 1024) return `${bytes} B`;
516
+ const kb = bytes / 1024;
517
+ if (kb < 1024) return `${kb.toFixed(1)} KB`;
518
+ const mb = kb / 1024;
519
+ return `${mb.toFixed(2)} MB`;
520
+ }
521
+
522
+ // src/commands/deploy.ts
523
+ import pc5 from "picocolors";
524
+
525
+ // src/portal/applyPlacements.ts
526
+ import pc3 from "picocolors";
527
+
528
+ // src/portal/client.ts
529
+ var PortalError = class extends Error {
530
+ constructor(status, message, retryAfter) {
531
+ super(message);
532
+ this.status = status;
533
+ this.retryAfter = retryAfter;
534
+ this.name = "PortalError";
535
+ }
536
+ };
537
+ var MAX_RETRY = 3;
538
+ var DEFAULT_BACKOFF_MS = [5e3, 1e4, 2e4];
539
+ var MAX_RETRY_AFTER_MS = 6e4;
540
+ var DEFAULT_TIMEOUT_MS = 3e4;
541
+ var PortalClient = class {
542
+ constructor(opts) {
543
+ this.base = opts.portalUrl.replace(/\/+$/, "") + "/api/v1";
544
+ this.token = opts.token;
545
+ this.fetchFn = opts.fetch ?? globalThis.fetch;
546
+ this.sleep = opts.sleep ?? ((ms) => new Promise((resolve4) => setTimeout(resolve4, ms)));
547
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
548
+ }
549
+ /**
550
+ * 429는 `Retry-After`(초)가 있으면 그만큼, 없으면 5/10/20초로 최대 3회 재시도한다.
551
+ * 그 외 실패(4xx/5xx)는 즉시 `PortalError`로 던진다.
552
+ */
553
+ async request(method, path, body) {
554
+ for (let attempt = 0; ; attempt++) {
555
+ const res = await this.sendWithNetworkRetry(method, path, body);
556
+ if (res.status !== 429 || attempt >= MAX_RETRY) {
557
+ if (!res.ok) throw await toError(res);
558
+ return res;
559
+ }
560
+ await this.sleep(backoffMs(res.headers.get("Retry-After"), attempt));
561
+ }
562
+ }
563
+ /**
564
+ * 네트워크 계층 실패(`fetch failed`, 예: keep-alive 소켓이 서버 쪽에서 닫힌 뒤 재사용된
565
+ * UND_ERR_SOCKET)는 재시도해도 안전한 요청에 한해 한 번만 즉시 다시 보낸다. sandbox 실측:
566
+ * 디바이스 코드 폴링이 5초 간격이라 그 사이 유휴 연결이 닫혀 첫 재사용에서 실패했다.
567
+ * GET과 디바이스 흐름(code·token)만 대상이다 — deploy 같은 비멱등 POST는 재시도하지 않는다.
568
+ */
569
+ async sendWithNetworkRetry(method, path, body) {
570
+ try {
571
+ return await this.send(method, path, body);
572
+ } catch (err) {
573
+ if (!isNetworkError(err)) throw err;
574
+ if (!isSafeToRetry(method, path)) throw toNetworkError(err, method, path);
575
+ await this.sleep(NETWORK_RETRY_DELAY_MS);
576
+ try {
577
+ return await this.send(method, path, body);
578
+ } catch (again) {
579
+ throw isNetworkError(again) ? toNetworkError(again, method, path) : again;
580
+ }
581
+ }
582
+ }
583
+ /** fetch 한 번. 응답이 없는 실패(타임아웃·네트워크)는 `PortalError`로 바꿔 종료코드 1이 되게 한다. */
584
+ async send(method, path, body) {
585
+ try {
586
+ return await this.fetchFn(this.base + path, {
587
+ method,
588
+ headers: {
589
+ ...this.token ? { Authorization: `Bearer ${this.token}` } : {},
590
+ Accept: "application/json, text/plain",
591
+ ...body !== void 0 ? { "Content-Type": "application/json" } : {}
592
+ },
593
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
594
+ signal: AbortSignal.timeout(this.timeoutMs)
595
+ });
596
+ } catch (err) {
597
+ if (isAbortError(err)) {
598
+ throw new PortalError(
599
+ 0,
600
+ `\uD3EC\uD138 \uC751\uB2F5\uC774 ${Math.round(this.timeoutMs / 1e3)}\uCD08 \uC548\uC5D0 \uC624\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 (${method} ${path}). \uB124\uD2B8\uC6CC\uD06C\xB7\uD3EC\uD138 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
601
+ );
602
+ }
603
+ throw err;
604
+ }
605
+ }
606
+ async json(method, path, body) {
607
+ return (await this.request(method, path, body)).json();
608
+ }
609
+ stage(org, space) {
610
+ return `/organizations/${enc(org)}/stages/${enc(space)}`;
611
+ }
612
+ me() {
613
+ return this.json("GET", "/auth/me");
614
+ }
615
+ async listSpaces(org) {
616
+ const body = await this.json(
617
+ "GET",
618
+ `/organizations/${enc(org)}/spaces`
619
+ );
620
+ return unwrap(body, "spaces");
621
+ }
622
+ async listRepos(org, space) {
623
+ const body = await this.json(
624
+ "GET",
625
+ `${this.stage(org, space)}/repos`
626
+ );
627
+ return unwrap(body, "repos").map((r) => ({ ...r, spaceId: r.spaceId ?? space }));
628
+ }
629
+ async listBuilds(org, space, limit = 20) {
630
+ const body = await this.json(
631
+ "GET",
632
+ `${this.stage(org, space)}/builds?limit=${limit}`
633
+ );
634
+ return unwrap(body, "builds").map((b) => ({ ...b, spaceId: b.spaceId ?? space }));
635
+ }
636
+ async getBuild(org, space, buildId) {
637
+ const b = await this.json("GET", `${this.stage(org, space)}/builds/${enc(buildId)}`);
638
+ return { ...b, spaceId: b.spaceId ?? space };
639
+ }
640
+ async getBuildLog(org, space, buildId) {
641
+ return (await this.request("GET", `${this.stage(org, space)}/builds/${enc(buildId)}/log`)).text();
642
+ }
643
+ createBuild(org, space, repoId) {
644
+ return this.json("POST", `${this.stage(org, space)}/builds`, { repoId });
645
+ }
646
+ async listPlacements(org, space, surface) {
647
+ const body = await this.json(
648
+ "GET",
649
+ `${this.stage(org, space)}/placements?surface=${surface}`
650
+ );
651
+ return unwrap(body, "placements");
652
+ }
653
+ /**
654
+ * `expectedLiveBuildId`는 CAS(compare-and-swap) — 서버 측 live 빌드와 다르면 409.
655
+ * 응답은 배치 전체가 아니라 `{id, liveBuildId}`만 온다(포털 핸들러 그대로).
656
+ */
657
+ deployPlacement(org, space, placementId, buildId, expectedLiveBuildId) {
658
+ return this.json(
659
+ "POST",
660
+ `${this.stage(org, space)}/placements/${enc(placementId)}/deploy`,
661
+ { buildId, expectedLiveBuildId }
662
+ );
663
+ }
664
+ async setPlacementEnabled(org, space, placementId, enabled) {
665
+ const r = await this.json(
666
+ "PATCH",
667
+ `${this.stage(org, space)}/placements/${enc(placementId)}`,
668
+ { enabled }
669
+ );
670
+ return r.placement;
671
+ }
672
+ // ── 디바이스 코드 흐름(RFC 8628) — 토큰 없이 호출된다 ─────────────────────
673
+ startDeviceAuth(deviceName, cliVersion) {
674
+ return this.json("POST", "/cli/device/code", { deviceName, cliVersion });
675
+ }
676
+ /**
677
+ * `authorization_pending`·`slow_down`은 정상 흐름의 결과로 돌려준다(폴링 계속).
678
+ * `expired_token`·`access_denied`는 더 폴링해도 회복되지 않으므로 `PortalError(400, 코드)`로 던진다.
679
+ */
680
+ async pollDeviceToken(deviceCode) {
681
+ try {
682
+ const issued = await this.json("POST", "/cli/device/token", { deviceCode });
683
+ return { status: "ok", ...issued };
684
+ } catch (err) {
685
+ if (err instanceof PortalError && err.status === 400) {
686
+ if (err.message === "authorization_pending") return { status: "pending" };
687
+ if (err.message === "slow_down") {
688
+ return err.retryAfter === void 0 ? { status: "slow_down" } : { status: "slow_down", retryAfter: err.retryAfter };
689
+ }
690
+ }
691
+ throw err;
692
+ }
693
+ }
694
+ };
695
+ function enc(s) {
696
+ return encodeURIComponent(s);
697
+ }
698
+ function backoffMs(retryAfterHeader, attempt) {
699
+ const seconds = Number(retryAfterHeader);
700
+ if (Number.isFinite(seconds) && seconds > 0) {
701
+ return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS);
702
+ }
703
+ return DEFAULT_BACKOFF_MS[attempt] ?? 2e4;
704
+ }
705
+ var NETWORK_RETRY_DELAY_MS = 500;
706
+ function isNetworkError(err) {
707
+ return err instanceof TypeError && !isAbortError(err);
708
+ }
709
+ function toNetworkError(err, method, path) {
710
+ const code = err.cause?.code;
711
+ return new PortalError(
712
+ 0,
713
+ `\uD3EC\uD138\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4 (${method} ${path}${code ? `, ${code}` : ""}). \uB124\uD2B8\uC6CC\uD06C\xB7\uD3EC\uD138 \uC8FC\uC18C\uB97C \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
714
+ );
715
+ }
716
+ function isSafeToRetry(method, path) {
717
+ return method === "GET" || path === "/cli/device/token" || path === "/cli/device/code";
718
+ }
719
+ function isAbortError(err) {
720
+ return err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError");
721
+ }
722
+ function unwrap(body, key) {
723
+ if (Array.isArray(body)) return body;
724
+ return body[key] ?? [];
725
+ }
726
+ async function toError(res) {
727
+ const text2 = await res.text().catch(() => "");
728
+ let message = `HTTP ${res.status}`;
729
+ try {
730
+ const parsed = JSON.parse(text2);
731
+ message = parsed.error ?? parsed.message ?? message;
732
+ } catch {
733
+ if (text2.trim()) message = text2.trim().slice(0, 300);
734
+ }
735
+ const retryAfter = Number(res.headers.get("Retry-After"));
736
+ return new PortalError(res.status, message, retryAfter > 0 ? retryAfter : void 0);
737
+ }
738
+
739
+ // src/portal/output.ts
740
+ import pc2 from "picocolors";
741
+ var ExitCode = {
742
+ OK: 0,
743
+ FAILURE: 1,
744
+ PRECONDITION: 2,
745
+ AUTH: 3,
746
+ CONFLICT: 4,
747
+ RATE_LIMIT: 5
748
+ };
749
+ var CliExit = class extends Error {
750
+ constructor(code, message) {
751
+ super(message);
752
+ this.code = code;
753
+ this.name = "CliExit";
754
+ }
755
+ };
756
+ function fail(code, message) {
757
+ throw new CliExit(code, message);
758
+ }
759
+ function exitCodeFor(err) {
760
+ if (err instanceof CliExit) return err.code;
761
+ if (err instanceof PortalError) {
762
+ if (err.status === 401 || err.status === 403) return ExitCode.AUTH;
763
+ if (err.status === 409) return ExitCode.CONFLICT;
764
+ if (err.status === 429) return ExitCode.RATE_LIMIT;
765
+ }
766
+ return ExitCode.FAILURE;
767
+ }
768
+ function printJson(v) {
769
+ process.stdout.write(JSON.stringify(v, null, 2) + "\n");
770
+ }
771
+ function toEntry(i) {
772
+ return { id: i.id, label: i.label, from: i.from, to: i.to, message: i.message };
773
+ }
774
+ function jsonSummary(items, extra = {}) {
775
+ const pick2 = (status) => items.filter((i) => i.status === status).map(toEntry);
776
+ return {
777
+ ...extra,
778
+ changed: pick2("changed"),
779
+ unchanged: pick2("unchanged"),
780
+ conflicts: pick2("conflict"),
781
+ failed: pick2("failed")
782
+ };
783
+ }
784
+ function table(rows) {
785
+ const widths = [];
786
+ for (const row of rows) {
787
+ row.forEach((cell, i) => {
788
+ widths[i] = Math.max(widths[i] ?? 0, cell.length);
789
+ });
790
+ }
791
+ return rows.map(
792
+ (row) => row.map((cell, i) => i === row.length - 1 ? cell : cell.padEnd(widths[i])).join(" ")
793
+ ).join("\n");
794
+ }
795
+ function shortSha(sha) {
796
+ return sha ? sha.slice(0, 7) : "-";
797
+ }
798
+ function scrub(message) {
799
+ return message.replace(/bsc_[0-9a-f]{6,}/g, "bsc_****");
800
+ }
801
+ async function runCommand(fn) {
802
+ try {
803
+ await fn();
804
+ } catch (err) {
805
+ const code = exitCodeFor(err);
806
+ const message = err instanceof Error ? err.message : String(err);
807
+ console.error(pc2.red(`[bstage] ${scrub(message)}`));
808
+ if (code === ExitCode.AUTH) {
809
+ console.error(
810
+ pc2.dim(
811
+ " bstage login \uC73C\uB85C \uD1A0\uD070\uC744 \uB2E4\uC2DC \uB4F1\uB85D\uD558\uAC70\uB098, \uD1A0\uD070 \uB4F1\uAE09\xB7\uC2A4\uD14C\uC774\uC9C0 \uC2A4\uCF54\uD504\uB97C \uD655\uC778\uD558\uC138\uC694."
812
+ )
813
+ );
814
+ }
815
+ process.exit(code);
816
+ }
817
+ }
818
+
819
+ // src/portal/applyPlacements.ts
820
+ function publishHint(placement) {
821
+ return placement.enabled === false ? "\uAC8C\uC2DC \uAEBC\uC9D0 \u2014 bstage publish \uB85C \uAC8C\uC2DC\uD574\uC57C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBCF4\uC785\uB2C8\uB2E4." : void 0;
822
+ }
823
+ async function applyPlacements(ctx, plan) {
824
+ const { organizationId: org, spaceId: space } = ctx.link;
825
+ const results = [];
826
+ for (const row of plan) {
827
+ const base = { id: row.placement.id, label: row.label, from: row.from, to: row.to };
828
+ if (row.unchanged) {
829
+ results.push({ ...base, status: "unchanged", message: "\uBCC0\uACBD \uC5C6\uC74C" });
830
+ continue;
831
+ }
832
+ try {
833
+ await ctx.client.deployPlacement(org, space, row.placement.id, row.to, row.from ?? "");
834
+ results.push({ ...base, status: "changed", message: publishHint(row.placement) });
835
+ } catch (err) {
836
+ results.push({ ...base, ...failureOf(err) });
837
+ }
838
+ }
839
+ return results;
840
+ }
841
+ function failureOf(err) {
842
+ if (err instanceof PortalError && err.status === 409) {
843
+ return {
844
+ status: "conflict",
845
+ message: `\uB2E4\uB978 \uBC30\uD3EC\uAC00 \uBA3C\uC800 \uC801\uC6A9\uB410\uC2B5\uB2C8\uB2E4(${err.message}) \u2014 \uB77C\uC774\uBE0C \uC0C1\uD0DC\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4.`
846
+ };
847
+ }
848
+ return { status: "failed", message: err instanceof Error ? err.message : String(err) };
849
+ }
850
+ var MARKS = {
851
+ changed: pc3.green("\u2713"),
852
+ unchanged: pc3.dim("="),
853
+ conflict: pc3.yellow("!"),
854
+ failed: pc3.red("\u2717")
855
+ };
856
+ function printResults(results, out) {
857
+ for (const r of results) {
858
+ const suffix = r.message ? pc3.dim(` ${r.message}`) : "";
859
+ out(`${MARKS[r.status]} ${r.label} ${shortSha(r.from)} \u2192 ${shortSha(r.to)}${suffix}`);
860
+ }
861
+ }
862
+ function reportOutcome(results, options, out, extra = {}) {
863
+ if (options.json) printJson(jsonSummary(results, extra));
864
+ else printResults(results, out);
865
+ const conflicts = results.filter((r) => r.status === "conflict");
866
+ const errors = results.filter((r) => r.status === "failed");
867
+ if (conflicts.length > 0) {
868
+ const names = conflicts.map((r) => r.label).join(", ");
869
+ const errSuffix = errors.length > 0 ? ` (\uADF8 \uC678 \uC2E4\uD328: ${errors.map((r) => r.label).join(", ")})` : "";
870
+ fail(
871
+ ExitCode.CONFLICT,
872
+ `\uB2E4\uB978 \uBC30\uD3EC\uAC00 \uBA3C\uC800 \uC801\uC6A9\uB41C \uBC30\uCE58\uAC00 \uC788\uC2B5\uB2C8\uB2E4: ${names}.${errSuffix} bstage list \uB85C \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`
873
+ );
874
+ }
875
+ if (errors.length > 0) {
876
+ fail(
877
+ ExitCode.FAILURE,
878
+ `\uC77C\uBD80 \uBC30\uCE58 \uC801\uC6A9\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${errors.map((r) => r.label).join(", ")}`
879
+ );
880
+ }
881
+ }
882
+
883
+ // src/portal/confirm.ts
884
+ import * as p from "@clack/prompts";
885
+ var clackConfirm = async (message) => {
886
+ const v = await p.confirm({ message });
887
+ return !p.isCancel(v) && v === true;
888
+ };
889
+ async function askConfirm(deps, message) {
890
+ if (deps.confirm) return deps.confirm(message);
891
+ assertCanConfirm(deps);
892
+ return clackConfirm(message);
893
+ }
894
+ function canConfirm(deps) {
895
+ if (deps.confirm) return true;
896
+ return (deps.isTTY ?? (() => Boolean(process.stdin.isTTY)))();
897
+ }
898
+ function assertCanConfirm(deps) {
899
+ if (canConfirm(deps)) return;
900
+ fail(
901
+ ExitCode.PRECONDITION,
902
+ "\uD130\uBBF8\uB110\uC774 \uC544\uB2C8\uB77C \uD655\uC778\uC744 \uBB3C\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --yes \uB85C \uD655\uC778\uC744 \uAC74\uB108\uB6F0\uC138\uC694."
903
+ );
904
+ }
905
+
906
+ // src/portal/git.ts
907
+ import { execFile } from "child_process";
908
+ import { promisify } from "util";
909
+ var execFileP = promisify(execFile);
910
+ function gitExec(cwd) {
911
+ return async (args) => (await execFileP("git", args, { cwd })).stdout;
912
+ }
913
+ async function tryExec(exec, args) {
914
+ try {
915
+ return (await exec(args)).trim();
916
+ } catch {
917
+ return null;
918
+ }
919
+ }
920
+ async function readGitState(exec) {
921
+ const branch = (await exec(["rev-parse", "--abbrev-ref", "HEAD"])).trim();
922
+ const head = (await exec(["rev-parse", "HEAD"])).trim();
923
+ const dirty = (await exec(["status", "--porcelain"])).trim().length > 0;
924
+ const upstream = await tryExec(exec, [
925
+ "rev-parse",
926
+ "--abbrev-ref",
927
+ "--symbolic-full-name",
928
+ "@{u}"
929
+ ]);
930
+ let ahead = 0;
931
+ let behind = 0;
932
+ if (upstream) {
933
+ const counts = await tryExec(exec, ["rev-list", "--left-right", "--count", "HEAD...@{u}"]);
934
+ const [a, b] = (counts ?? "0 0").split(/\s+/).map((n) => parseInt(n, 10) || 0);
935
+ ahead = a;
936
+ behind = b;
937
+ }
938
+ return { branch, head, dirty, upstream, ahead, behind };
939
+ }
940
+ function assertDeployable(state, defaultBranch) {
941
+ if (state.dirty) {
942
+ fail(
943
+ ExitCode.PRECONDITION,
944
+ "\uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uBCC0\uACBD\uC774 \uC788\uC2B5\uB2C8\uB2E4. \uD3EC\uD138\uC740 push\uB41C \uCEE4\uBC0B\uB9CC \uBE4C\uB4DC\uD569\uB2C8\uB2E4 \u2014 \uCEE4\uBC0B\xB7push \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694. (--skip-git-check \uB85C \uBB34\uC2DC \uAC00\uB2A5)"
945
+ );
946
+ }
947
+ if (state.branch !== defaultBranch) {
948
+ fail(
949
+ ExitCode.PRECONDITION,
950
+ `\uD3EC\uD138\uC740 ${defaultBranch} \uBE0C\uB79C\uCE58 HEAD\uB97C \uBE4C\uB4DC\uD569\uB2C8\uB2E4. \uD604\uC7AC \uBE0C\uB79C\uCE58\uB294 ${state.branch} \uC785\uB2C8\uB2E4 \u2014 ${defaultBranch} \uC5D0 \uBA38\uC9C0\xB7push \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`
951
+ );
952
+ }
953
+ if (!state.upstream || state.ahead > 0) {
954
+ fail(
955
+ ExitCode.PRECONDITION,
956
+ "push\uB418\uC9C0 \uC54A\uC740 \uCEE4\uBC0B\uC774 \uC788\uC2B5\uB2C8\uB2E4. git push \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694. (--skip-git-check \uB85C \uBB34\uC2DC \uAC00\uB2A5)"
957
+ );
958
+ }
959
+ }
960
+
961
+ // src/portal/config.ts
962
+ import { mkdir as mkdir2, readFile as readFile2, writeFile } from "fs/promises";
963
+ import { dirname, join as join2 } from "path";
964
+ import { loadEnv as loadEnv2 } from "vite";
965
+ var LOCAL_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
966
+ function normalizePortalUrl(url) {
967
+ const u = new URL(url);
968
+ if (u.protocol !== "https:" && !(u.protocol === "http:" && LOCAL_HOSTS.has(u.hostname))) {
969
+ fail(
970
+ ExitCode.PRECONDITION,
971
+ `\uD3EC\uD138 \uC8FC\uC18C\uB294 https \uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uBC1B\uC740 \uAC12: ${url}). http\uB294 localhost\xB7127.0.0.1 \uC5D0\uC11C\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4.`
972
+ );
973
+ }
974
+ const path = u.pathname.replace(/\/+$/, "");
975
+ return u.origin + path;
976
+ }
977
+ function linkPath(cwd) {
978
+ return join2(cwd, ".bstage", "project.json");
979
+ }
980
+ async function saveLink(cwd, link) {
981
+ await mkdir2(dirname(linkPath(cwd)), { recursive: true });
982
+ await writeFile(linkPath(cwd), JSON.stringify(link, null, 2) + "\n");
983
+ }
984
+ function linkFromEnv(env2) {
985
+ const { BSTAGE_PORTAL_URL, BSTAGE_ORG, BSTAGE_SPACE, BSTAGE_REPO, BSTAGE_SURFACE } = env2;
986
+ if (!BSTAGE_PORTAL_URL || !BSTAGE_ORG || !BSTAGE_SPACE || !BSTAGE_REPO || !BSTAGE_SURFACE) {
987
+ return null;
988
+ }
989
+ if (BSTAGE_SURFACE !== "USER" && BSTAGE_SURFACE !== "ADMIN") return null;
990
+ return {
991
+ portalUrl: BSTAGE_PORTAL_URL,
992
+ organizationId: BSTAGE_ORG,
993
+ spaceId: BSTAGE_SPACE,
994
+ repoId: BSTAGE_REPO,
995
+ surface: BSTAGE_SURFACE
996
+ };
997
+ }
998
+ async function loadLink(cwd, env2 = process.env) {
999
+ const fromEnv = linkFromEnv(env2);
1000
+ if (fromEnv) return fromEnv;
1001
+ try {
1002
+ return JSON.parse(await readFile2(linkPath(cwd), "utf-8"));
1003
+ } catch {
1004
+ return null;
1005
+ }
1006
+ }
1007
+ function portalUrlForPhase(phase, cwd, env2 = process.env) {
1008
+ if (isPublicPhase(phase) && PORTAL_HOSTS[phase]) return `https://${PORTAL_HOSTS[phase]}`;
1009
+ const host = internalPortalHost(cwd, env2);
1010
+ if (!host) {
1011
+ throw new Error(
1012
+ `${phase}\uB294 \uC0AC\uB0B4 \uC804\uC6A9 \uD658\uACBD\uC774\uB77C \uD3EC\uD138 \uC8FC\uC18C\uAC00 SDK\uC5D0 \uB4E4\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
1013
+ \uC124\uC815\uB418\uC9C0 \uC54A\uC740 \uAC12: ${PORTAL_HOST_ENV_KEY}
1014
+ \xB7 \uC0AC\uB0B4: \uC704 \uAC12\uC744 .env\uC5D0 \uC124\uC815\uD558\uC138\uC694 (\uD638\uC2A4\uD2B8 \uAC12\uC740 \uC0AC\uB0B4 \uBB38\uC11C\uB97C \uCC38\uACE0\uD558\uC138\uC694)
1015
+ \xB7 \uADF8 \uC678: --phase sandbox \uB610\uB294 --portal <url> \uC744 \uC9C0\uC815\uD558\uC138\uC694`
1016
+ );
1017
+ }
1018
+ return normalizePortalUrl(host);
1019
+ }
1020
+ function allowedPortalOrigins(cwd, env2 = process.env, explicit) {
1021
+ const origins = new Set(Object.values(PORTAL_HOSTS).map((host) => `https://${host}`));
1022
+ if (explicit) origins.add(originOf(explicit));
1023
+ if (env2.BSTAGE_PORTAL_URL) origins.add(originOf(env2.BSTAGE_PORTAL_URL));
1024
+ if (!env2.BSTAGE_TOKEN) {
1025
+ const internal = internalPortalHost(cwd, env2);
1026
+ if (internal) origins.add(originOf(internal));
1027
+ }
1028
+ return origins;
1029
+ }
1030
+ function internalPortalHost(cwd, env2) {
1031
+ const fileEnv = loadEnv2("development", cwd, "");
1032
+ const host = (env2[PORTAL_HOST_ENV_KEY] || fileEnv[PORTAL_HOST_ENV_KEY] || "").trim();
1033
+ if (!host) return null;
1034
+ return host.startsWith("http") ? host : `https://${host}`;
1035
+ }
1036
+ function originOf(url) {
1037
+ return new URL(normalizePortalUrl(url)).origin;
1038
+ }
1039
+ var TOKEN_ENV_HINT = "BSTAGE_TOKEN \uC774 \uC124\uC815\uB41C \uD658\uACBD\uC5D0\uC11C\uB294 \uD3EC\uD138 \uC8FC\uC18C\uB97C --portal, BSTAGE_PORTAL_URL, \uB610\uB294 \uACF5\uAC1C phase \uB9E4\uD551\uC73C\uB85C\uB9CC \uC815\uD569\uB2C8\uB2E4.";
1040
+ function portalUrlFromLink(raw, cwd, env2) {
1041
+ const url = normalizePortalUrl(raw);
1042
+ if (env2.BSTAGE_TOKEN) {
1043
+ fail(
1044
+ ExitCode.PRECONDITION,
1045
+ `${TOKEN_ENV_HINT} .bstage/project.json \uC758 \uC8FC\uC18C\uB294 \uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 --portal \uB610\uB294 BSTAGE_PORTAL_URL \uB85C \uBA85\uC2DC\uD558\uC138\uC694.`
1046
+ );
1047
+ }
1048
+ const origin = new URL(url).origin;
1049
+ if (!allowedPortalOrigins(cwd, env2).has(origin)) {
1050
+ fail(
1051
+ ExitCode.PRECONDITION,
1052
+ `.bstage/project.json \uC758 \uD3EC\uD138 \uC8FC\uC18C(${origin})\uAC00 \uD5C8\uC6A9 \uBAA9\uB85D\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4. --portal \uB610\uB294 BSTAGE_PORTAL_URL \uB85C \uBA85\uC2DC\uD558\uAC70\uB098 bstage link \uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694`
1053
+ );
1054
+ }
1055
+ return url;
1056
+ }
1057
+ function portalUrlFromPhase(phaseFlag, cwd, env2) {
1058
+ const { phase } = resolvePhase(phaseFlag, cwd, env2);
1059
+ const url = portalUrlForPhase(phase, cwd, env2);
1060
+ if (env2.BSTAGE_TOKEN && !allowedPortalOrigins(cwd, env2).has(new URL(url).origin)) {
1061
+ fail(
1062
+ ExitCode.PRECONDITION,
1063
+ `${TOKEN_ENV_HINT} .env \uC758 ${PORTAL_HOST_ENV_KEY}(${phase})\uB294 \uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 --portal \uB610\uB294 BSTAGE_PORTAL_URL \uB85C \uBA85\uC2DC\uD558\uC138\uC694.`
1064
+ );
1065
+ }
1066
+ return url;
1067
+ }
1068
+ async function resolvePortalUrl(explicit, cwd, env2 = process.env, phaseFlag, options = {}) {
1069
+ if (explicit) return normalizePortalUrl(explicit);
1070
+ if (env2.BSTAGE_PORTAL_URL) return normalizePortalUrl(env2.BSTAGE_PORTAL_URL);
1071
+ const link = options.allowLink === false ? null : await loadLink(cwd, env2);
1072
+ if (link) return portalUrlFromLink(link.portalUrl, cwd, env2);
1073
+ return portalUrlFromPhase(phaseFlag, cwd, env2);
1074
+ }
1075
+
1076
+ // src/portal/credentials.ts
1077
+ import { chmod, mkdir as mkdir3, readFile as readFile3, rename as rename2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
1078
+ import { homedir } from "os";
1079
+ import { dirname as dirname2, join as join3 } from "path";
1080
+ var UNKNOWN_ORG = "*";
1081
+ function credentialsPath(env2 = process.env) {
1082
+ const base = env2.XDG_CONFIG_HOME || join3(homedir(), ".config");
1083
+ return join3(base, "bstage", "credentials.json");
1084
+ }
1085
+ function hasEntry(bucket, org) {
1086
+ return Object.prototype.hasOwnProperty.call(bucket, org);
1087
+ }
1088
+ function isEntry(v) {
1089
+ if (!v || typeof v !== "object") return false;
1090
+ const token = v.token;
1091
+ return typeof token === "string" && token.length > 0;
1092
+ }
1093
+ function sanitize(tokens) {
1094
+ const buckets = Object.entries(tokens).map(([portal, bucket]) => {
1095
+ if (!bucket || typeof bucket !== "object") return [portal, {}];
1096
+ const kept = Object.entries(bucket).filter(([, v]) => isEntry(v));
1097
+ return [portal, Object.fromEntries(kept)];
1098
+ });
1099
+ return Object.fromEntries(buckets.filter(([, bucket]) => Object.keys(bucket).length > 0));
1100
+ }
1101
+ function migrate(raw) {
1102
+ if (!raw || typeof raw !== "object") return { version: 2, tokens: {} };
1103
+ const obj = raw;
1104
+ if (obj.version === 2 && obj.tokens && typeof obj.tokens === "object") {
1105
+ return { version: 2, tokens: sanitize(obj.tokens) };
1106
+ }
1107
+ const tokens = Object.entries(obj).filter(([, v]) => isEntry(v)).map(([portal, v]) => [portal, { [UNKNOWN_ORG]: v }]);
1108
+ return { version: 2, tokens: Object.fromEntries(tokens) };
1109
+ }
1110
+ async function readCredentialFile(env2) {
1111
+ try {
1112
+ return migrate(JSON.parse(await readFile3(credentialsPath(env2), "utf-8")));
1113
+ } catch {
1114
+ return { version: 2, tokens: {} };
1115
+ }
1116
+ }
1117
+ async function writeCredentialFile(env2, data) {
1118
+ const p4 = credentialsPath(env2);
1119
+ await mkdir3(dirname2(p4), { recursive: true, mode: 448 });
1120
+ await chmod(dirname2(p4), 448);
1121
+ const tmp = `${p4}.${process.pid}.${Date.now()}.tmp`;
1122
+ await writeFile2(tmp, JSON.stringify(data, null, 2) + "\n", { mode: 384 });
1123
+ await chmod(tmp, 384);
1124
+ await rename2(tmp, p4);
1125
+ }
1126
+ function entryOf(c) {
1127
+ return {
1128
+ token: c.token,
1129
+ savedAt: c.savedAt,
1130
+ permission: c.permission,
1131
+ allSpaces: c.allSpaces,
1132
+ spaceIds: c.spaceIds,
1133
+ expiresAt: c.expiresAt
1134
+ };
1135
+ }
1136
+ async function saveCredential(c, env2 = process.env) {
1137
+ const key = normalizePortalUrl(c.portalUrl);
1138
+ const org = c.organizationId || UNKNOWN_ORG;
1139
+ const data = await readCredentialFile(env2);
1140
+ const kept = Object.entries(data.tokens[key] ?? {}).filter(
1141
+ ([o]) => org === UNKNOWN_ORG || o !== UNKNOWN_ORG
1142
+ );
1143
+ const bucket = { ...Object.fromEntries(kept), [org]: entryOf(c) };
1144
+ await writeCredentialFile(env2, { version: 2, tokens: { ...data.tokens, [key]: bucket } });
1145
+ }
1146
+ async function bucketOf(portalUrl, env2) {
1147
+ return (await readCredentialFile(env2)).tokens[normalizePortalUrl(portalUrl)] ?? {};
1148
+ }
1149
+ function pickOrg(bucket, organizationId) {
1150
+ const orgs = Object.keys(bucket);
1151
+ if (organizationId) {
1152
+ if (hasEntry(bucket, organizationId)) return organizationId;
1153
+ return orgs.length === 1 && orgs[0] === UNKNOWN_ORG ? UNKNOWN_ORG : null;
1154
+ }
1155
+ if (hasEntry(bucket, UNKNOWN_ORG)) return UNKNOWN_ORG;
1156
+ return orgs.length === 1 ? orgs[0] : null;
1157
+ }
1158
+ async function loadCredential(portalUrl, organizationId, env2 = process.env) {
1159
+ const bucket = await bucketOf(portalUrl, env2);
1160
+ const picked = pickOrg(bucket, organizationId);
1161
+ if (!picked) return null;
1162
+ return { portalUrl: normalizePortalUrl(portalUrl), organizationId: picked, ...bucket[picked] };
1163
+ }
1164
+ async function listCredentialOrgs(portalUrl, env2 = process.env) {
1165
+ return Object.keys(await bucketOf(portalUrl, env2)).sort();
1166
+ }
1167
+ async function listCredentials(portalUrl, env2 = process.env) {
1168
+ const bucket = await bucketOf(portalUrl, env2);
1169
+ const key = normalizePortalUrl(portalUrl);
1170
+ return Object.keys(bucket).sort().map((organizationId) => ({ portalUrl: key, organizationId, ...bucket[organizationId] }));
1171
+ }
1172
+ async function deleteCredential(portalUrl, organizationId, env2 = process.env) {
1173
+ const data = await readCredentialFile(env2);
1174
+ const key = normalizePortalUrl(portalUrl);
1175
+ const bucket = data.tokens[key];
1176
+ if (!bucket) return [];
1177
+ const resolved = organizationId ? pickOrg(bucket, organizationId) : null;
1178
+ const legacy = resolved && hasEntry(bucket, UNKNOWN_ORG) ? [UNKNOWN_ORG] : [];
1179
+ const removed = organizationId ? resolved ? [.../* @__PURE__ */ new Set([resolved, ...legacy])] : [] : Object.keys(bucket).sort();
1180
+ if (removed.length === 0) return [];
1181
+ const rest = Object.entries(bucket).filter(([org]) => !removed.includes(org));
1182
+ const tokens = Object.fromEntries(
1183
+ Object.entries(data.tokens).filter(([portal]) => portal !== key).concat(rest.length > 0 ? [[key, Object.fromEntries(rest)]] : [])
1184
+ );
1185
+ if (Object.keys(tokens).length === 0) {
1186
+ await rm2(credentialsPath(env2), { force: true });
1187
+ } else {
1188
+ await writeCredentialFile(env2, { version: 2, tokens });
1189
+ }
1190
+ return removed;
1191
+ }
1192
+ async function resolveToken(portalUrl, env2 = process.env, organizationId) {
1193
+ if (env2.BSTAGE_TOKEN) return env2.BSTAGE_TOKEN;
1194
+ return (await loadCredential(portalUrl, organizationId, env2))?.token ?? null;
1195
+ }
1196
+ function scopeLabel(allSpaces, spaceIds = []) {
1197
+ if (allSpaces) return "\uC774 \uC870\uC9C1\uC758 \uBAA8\uB4E0 \uC2A4\uD14C\uC774\uC9C0";
1198
+ if (spaceIds.length === 0) return "\uC2A4\uD14C\uC774\uC9C0 \uC5C6\uC74C";
1199
+ return `\uC2A4\uD14C\uC774\uC9C0 ${spaceIds.length}\uAC1C (${spaceIds.join(", ")})`;
1200
+ }
1201
+ function orgLabel(organizationId) {
1202
+ return organizationId === UNKNOWN_ORG ? "\uC870\uC9C1 \uBBF8\uC0C1" : organizationId;
1203
+ }
1204
+
1205
+ // src/portal/resolveContext.ts
1206
+ function failNoToken(portalUrl, organizationId, storedOrgs) {
1207
+ const named = storedOrgs.filter((o) => o !== UNKNOWN_ORG);
1208
+ const listed = named.length > 0 ? `
1209
+ \uC800\uC7A5\uB41C \uC870\uC9C1: ${named.join(", ")}` : "";
1210
+ if (organizationId) {
1211
+ fail(
1212
+ ExitCode.AUTH,
1213
+ `${portalUrl} \uC758 \uC870\uC9C1 ${organizationId} \uD1A0\uD070\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774 \uC870\uC9C1\uC73C\uB85C bstage login \uD558\uC138\uC694.${listed}`
1214
+ );
1215
+ }
1216
+ if (named.length > 1) {
1217
+ fail(
1218
+ ExitCode.AUTH,
1219
+ `${portalUrl} \uC5D0 \uC5EC\uB7EC \uC870\uC9C1\uC758 \uD1A0\uD070\uC774 \uC800\uC7A5\uB3FC \uC788\uC5B4 \uD558\uB098\uB97C \uACE0\uB97C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --org <id> \uB610\uB294 BSTAGE_ORG \uB85C \uC870\uC9C1\uC744 \uC9C0\uC815\uD558\uAC70\uB098, \uC4F0\uB824\uB294 \uC870\uC9C1\uC73C\uB85C bstage login \uD558\uC138\uC694.${listed}`
1220
+ );
1221
+ }
1222
+ fail(
1223
+ ExitCode.AUTH,
1224
+ `${portalUrl} \uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. bstage login --portal ${portalUrl} \uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.`
1225
+ );
1226
+ }
1227
+ async function resolveClientOnly(opts = {}) {
1228
+ const cwd = opts.cwd ?? process.cwd();
1229
+ const env2 = opts.env ?? process.env;
1230
+ const portalUrl = await resolvePortalUrl(opts.portal, cwd, env2, opts.phase);
1231
+ if (!portalUrl) {
1232
+ fail(
1233
+ ExitCode.PRECONDITION,
1234
+ "\uD3EC\uD138 \uC8FC\uC18C\uB97C \uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --portal <url> \uB610\uB294 --phase \uB97C \uC9C0\uC815\uD558\uC138\uC694."
1235
+ );
1236
+ }
1237
+ const organizationId = opts.organizationId ?? env2.BSTAGE_ORG ?? (await loadLink(cwd, env2))?.organizationId;
1238
+ const token = await resolveToken(portalUrl, env2, organizationId);
1239
+ if (!token) failNoToken(portalUrl, organizationId, await listCredentialOrgs(portalUrl, env2));
1240
+ return {
1241
+ client: new PortalClient({ portalUrl, token, fetch: opts.fetch }),
1242
+ portalUrl,
1243
+ organizationId
1244
+ };
1245
+ }
1246
+ async function resolveContext(opts = {}) {
1247
+ const cwd = opts.cwd ?? process.cwd();
1248
+ const env2 = opts.env ?? process.env;
1249
+ const link = await loadLink(cwd, env2);
1250
+ if (!link) {
1251
+ fail(
1252
+ ExitCode.PRECONDITION,
1253
+ "\uC774 \uB514\uB809\uD130\uB9AC\uB294 \uD3EC\uD138 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5F0\uACB0\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. bstage link \uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694."
1254
+ );
1255
+ }
1256
+ const portalUrl = await resolvePortalUrl(opts.portal, cwd, env2);
1257
+ if (!portalUrl) {
1258
+ fail(
1259
+ ExitCode.PRECONDITION,
1260
+ "\uD3EC\uD138 \uC8FC\uC18C\uB97C \uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --portal <url> \uB610\uB294 BSTAGE_PORTAL_URL \uC744 \uC9C0\uC815\uD558\uC138\uC694."
1261
+ );
1262
+ }
1263
+ const organizationId = opts.organizationId ?? link.organizationId;
1264
+ const token = await resolveToken(portalUrl, env2, organizationId);
1265
+ if (!token) failNoToken(portalUrl, organizationId, await listCredentialOrgs(portalUrl, env2));
1266
+ return {
1267
+ client: new PortalClient({ portalUrl, token, fetch: opts.fetch }),
1268
+ link: { ...link, portalUrl },
1269
+ organizationId
1270
+ };
1271
+ }
1272
+
1273
+ // src/portal/types.ts
1274
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
1275
+ "SUCCEEDED",
1276
+ "FAILED",
1277
+ "TIMED_OUT",
1278
+ "CANCELED"
1279
+ ]);
1280
+ function placementLabel(p4) {
1281
+ return p4.kind === "SLOT" ? p4.slotId ?? p4.artifact ?? p4.id : p4.path ?? p4.artifact ?? p4.id;
1282
+ }
1283
+
1284
+ // src/portal/selectPlacements.ts
1285
+ function selectPlacements(all, repoId, filter) {
1286
+ const mine = all.filter((p4) => p4.stageRepoId === repoId);
1287
+ if (!filter) return mine;
1288
+ return mine.filter((p4) => p4.id === filter || p4.path === filter || p4.slotId === filter);
1289
+ }
1290
+ function applyPlan(placements, buildId) {
1291
+ return placements.map((p4) => ({
1292
+ placement: p4,
1293
+ label: placementLabel(p4),
1294
+ from: p4.liveBuildId || void 0,
1295
+ to: buildId,
1296
+ unchanged: p4.liveBuildId === buildId
1297
+ }));
1298
+ }
1299
+ function requireTargets(all, repoId, filter, buildIdHint) {
1300
+ const mine = selectPlacements(all, repoId, filter);
1301
+ if (mine.length > 0) return mine;
1302
+ const base = filter ? `\uBC30\uCE58 '${filter}' \uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. bstage list \uB85C \uD655\uC778\uD558\uC138\uC694.` : "\uC774 \uB808\uD3EC\uC5D0 \uC5F0\uACB0\uB41C \uBC30\uCE58\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD3EC\uD138 > \uC2A4\uD14C\uC774\uC9C0 > \uD398\uC774\uC9C0\uC5D0\uC11C \uBC30\uCE58\uB97C \uBA3C\uC800 \uB9CC\uB4DC\uC138\uC694.";
1303
+ const hint = buildIdHint ? ` \uBE4C\uB4DC ${buildIdHint} \uB294 \uB0A8\uC544 \uC788\uC73C\uB2C8 \uBC30\uCE58\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC801\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.` : "";
1304
+ return fail(ExitCode.PRECONDITION, base + hint);
1305
+ }
1306
+ async function fetchTargets(ctx, filter, buildIdHint) {
1307
+ const all = await ctx.client.listPlacements(
1308
+ ctx.link.organizationId,
1309
+ ctx.link.spaceId,
1310
+ ctx.link.surface
1311
+ );
1312
+ return requireTargets(all, ctx.link.repoId, filter, buildIdHint);
1313
+ }
1314
+
1315
+ // src/portal/waitBuild.ts
1316
+ import pc4 from "picocolors";
1317
+ var DEFAULT_INTERVAL = 3e3;
1318
+ var DEFAULT_TIMEOUT = 20 * 60 * 1e3;
1319
+ async function waitForBuild(client, org, space, buildId, o = {}) {
1320
+ const interval = o.intervalMs ?? DEFAULT_INTERVAL;
1321
+ const timeout = o.timeoutMs ?? DEFAULT_TIMEOUT;
1322
+ const sleep = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
1323
+ const started = Date.now();
1324
+ let seen = 0;
1325
+ let logFailing = false;
1326
+ const flushLog = async () => {
1327
+ if (!o.onLog) return;
1328
+ let log2;
1329
+ try {
1330
+ log2 = await client.getBuildLog(org, space, buildId);
1331
+ } catch {
1332
+ if (!logFailing) {
1333
+ console.error(pc4.dim("[bstage] \uB85C\uADF8 \uC870\uD68C \uC2E4\uD328, \uB2E4\uC74C \uD3F4\uB9C1\uC5D0\uC11C \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4."));
1334
+ logFailing = true;
1335
+ }
1336
+ return;
1337
+ }
1338
+ logFailing = false;
1339
+ if (log2.length < seen) {
1340
+ o.onLog("\n[bstage] \uB85C\uADF8\uAC00 \uC7AC\uC2DC\uC791\uB41C \uAC83\uC73C\uB85C \uBCF4\uC785\uB2C8\uB2E4 \u2014 \uCC98\uC74C\uBD80\uD130 \uB2E4\uC2DC \uCD9C\uB825\uD569\uB2C8\uB2E4.\n");
1341
+ seen = 0;
1342
+ }
1343
+ if (log2.length > seen) {
1344
+ o.onLog(log2.slice(seen));
1345
+ seen = log2.length;
1346
+ }
1347
+ };
1348
+ for (; ; ) {
1349
+ const build = await client.getBuild(org, space, buildId);
1350
+ await flushLog();
1351
+ if (TERMINAL_STATUSES.has(build.status)) return build;
1352
+ if (Date.now() - started > timeout) {
1353
+ fail(
1354
+ ExitCode.FAILURE,
1355
+ `\uBE4C\uB4DC ${buildId} \uAC00 ${Math.round(timeout / 6e4)}\uBD84 \uC548\uC5D0 \uB05D\uB098\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uD3EC\uD138\uC5D0\uC11C \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694.`
1356
+ );
1357
+ }
1358
+ await sleep(interval);
1359
+ }
1360
+ }
1361
+
1362
+ // src/commands/deploy.ts
1363
+ async function repoDefaultBranch(ctx) {
1364
+ const repos = await ctx.client.listRepos(ctx.link.organizationId, ctx.link.spaceId);
1365
+ const repo = repos.find((r) => r.id === ctx.link.repoId);
1366
+ if (!repo) {
1367
+ fail(
1368
+ ExitCode.PRECONDITION,
1369
+ `\uC5F0\uACB0\uB41C \uB808\uD3EC ${ctx.link.repoId} \uB97C \uD3EC\uD138\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. bstage link \uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`
1370
+ );
1371
+ }
1372
+ return repo.defaultBranch;
1373
+ }
1374
+ async function precheckGit(deps, defaultBranch) {
1375
+ const exec = deps.exec ?? gitExec(deps.cwd ?? process.cwd());
1376
+ let state;
1377
+ try {
1378
+ state = await readGitState(exec);
1379
+ } catch (err) {
1380
+ fail(
1381
+ ExitCode.PRECONDITION,
1382
+ `git \uC0C1\uD0DC\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${err instanceof Error ? err.message : String(err)}. git\uC774 \uC124\uCE58\uB3FC \uC788\uACE0 \uC774 \uB514\uB809\uD130\uB9AC\uAC00 git \uC800\uC7A5\uC18C\uC778\uC9C0 \uD655\uC778\uD558\uAC70\uB098 --skip-git-check \uB85C \uAC74\uB108\uB6F0\uC138\uC694.`
1383
+ );
1384
+ }
1385
+ assertDeployable(state, defaultBranch);
1386
+ }
1387
+ async function createAndAnnounceBuild(ctx, options) {
1388
+ const created = await ctx.client.createBuild(
1389
+ ctx.link.organizationId,
1390
+ ctx.link.spaceId,
1391
+ ctx.link.repoId
1392
+ );
1393
+ if (!options.json) {
1394
+ console.error(pc5.dim(`[bstage] \uBE4C\uB4DC ${created.id} \uC2DC\uC791 (${shortSha(created.commitSha)})`));
1395
+ }
1396
+ return created;
1397
+ }
1398
+ function reportNoWait(build, options, out) {
1399
+ if (options.json) {
1400
+ printJson(jsonSummary([], { build }));
1401
+ return;
1402
+ }
1403
+ out(
1404
+ `\uBE4C\uB4DC ${build.id} \uB97C \uC2DC\uC791\uD588\uC2B5\uB2C8\uB2E4. bstage logs ${build.id} --follow \uB85C \uC9C4\uD589 \uC0C1\uD669\uC744 \uD655\uC778\uD558\uC138\uC694.`
1405
+ );
1406
+ }
1407
+ async function waitForCompletedBuild(ctx, deps, build, json) {
1408
+ const { organizationId: org, spaceId: space } = ctx.link;
1409
+ const done = await waitForBuild(ctx.client, org, space, build.id, {
1410
+ sleep: deps.sleep,
1411
+ onLog: json ? void 0 : (delta) => process.stderr.write(pc5.dim(delta))
1412
+ });
1413
+ if (done.status !== "SUCCEEDED") {
1414
+ fail(
1415
+ ExitCode.FAILURE,
1416
+ `\uBE4C\uB4DC ${done.id} \uC2E4\uD328: ${done.status}${done.failureReason ? ` (${done.failureReason})` : ""}. bstage logs ${done.id} \uB85C \uB85C\uADF8\uB97C \uD655\uC778\uD558\uC138\uC694.`
1417
+ );
1418
+ }
1419
+ return done;
1420
+ }
1421
+ function renderPlanTable(plan, build) {
1422
+ const header = `\uBE4C\uB4DC ${build.id} (\uCEE4\uBC0B ${shortSha(build.commitSha)})`;
1423
+ const rows = table([
1424
+ ["\uC790\uB9AC", "\uD604\uC7AC", "\u2192 \uC801\uC6A9"],
1425
+ ...plan.map((row) => {
1426
+ const to = shortSha(build.commitSha) + (row.unchanged ? " (\uBCC0\uACBD \uC5C6\uC74C)" : "");
1427
+ return [row.label, shortSha(row.placement.liveCommit), to];
1428
+ })
1429
+ ]);
1430
+ return `${header}
1431
+ ${rows}`;
1432
+ }
1433
+ async function confirmPlan(plan, build, options, deps) {
1434
+ if (options.yes) return;
1435
+ const out = deps.out ?? console.log;
1436
+ out(renderPlanTable(plan, build));
1437
+ const ok = await askConfirm(
1438
+ deps,
1439
+ `${plan.length}\uAC1C \uBC30\uCE58\uB97C \uBE4C\uB4DC ${build.id} \uB85C \uB77C\uC774\uBE0C \uC804\uD658\uD569\uB2C8\uB2E4. \uC9C4\uD589\uD560\uAE4C\uC694?`
1440
+ );
1441
+ if (!ok) {
1442
+ fail(ExitCode.PRECONDITION, "\uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4. \uBE4C\uB4DC\uB294 \uB0A8\uC544 \uC788\uC73C\uB2C8 \uD544\uC694\uD558\uBA74 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC801\uC6A9\uD558\uC138\uC694.");
483
1443
  }
484
1444
  }
485
- function formatSize(bytes) {
486
- if (bytes < 1024) return `${bytes} B`;
487
- const kb = bytes / 1024;
488
- if (kb < 1024) return `${kb.toFixed(1)} KB`;
489
- const mb = kb / 1024;
490
- return `${mb.toFixed(2)} MB`;
1445
+ async function deployCommand(options, deps = {}) {
1446
+ if (options.json && !options.yes) {
1447
+ fail(ExitCode.PRECONDITION, "--json \uC740 --yes \uC640 \uD568\uAED8 \uC4F0\uC138\uC694.");
1448
+ }
1449
+ if (!options.yes) assertCanConfirm(deps);
1450
+ const out = deps.out ?? console.log;
1451
+ const ctx = await resolveContext({ cwd: deps.cwd, env: deps.env, fetch: deps.fetch });
1452
+ const defaultBranch = await repoDefaultBranch(ctx);
1453
+ if (!options.skipGitCheck) await precheckGit(deps, defaultBranch);
1454
+ await fetchTargets(ctx, options.placement);
1455
+ const created = await createAndAnnounceBuild(ctx, options);
1456
+ if (options.noWait) {
1457
+ reportNoWait(created, options, out);
1458
+ return;
1459
+ }
1460
+ const build = await waitForCompletedBuild(ctx, deps, created, options.json);
1461
+ const fresh = await fetchTargets(ctx, options.placement, build.id);
1462
+ const plan = applyPlan(fresh, build.id);
1463
+ await confirmPlan(plan, build, options, deps);
1464
+ const results = await applyPlacements(ctx, plan);
1465
+ reportOutcome(results, options, out, { build });
491
1466
  }
492
1467
 
493
1468
  // src/commands/dev.ts
@@ -535,11 +1510,11 @@ async function devCommand(options) {
535
1510
  }
536
1511
 
537
1512
  // src/commands/init.ts
538
- import * as p from "@clack/prompts";
539
- import pc2 from "picocolors";
1513
+ import * as p2 from "@clack/prompts";
1514
+ import pc6 from "picocolors";
540
1515
  import { existsSync, mkdirSync, writeFileSync } from "fs";
541
1516
  import { execSync } from "child_process";
542
- import { resolve as resolve2, join as join2 } from "path";
1517
+ import { resolve as resolve2, join as join4 } from "path";
543
1518
 
544
1519
  // src/templates/agentsMdRegion.ts
545
1520
  var AGENTS_MANAGED_VERSION = 10;
@@ -790,7 +1765,7 @@ ${commandsSection}
790
1765
  - \uC804\uC5ED CSS \uD30C\uC77C(\`<link>\`, \uC678\uBD80 \uC2A4\uD0C0\uC77C\uC2DC\uD2B8)\uC744 \uC0AC\uC6A9\uD558\uC9C0 \uC54A\uB294\uB2E4 \u2014 Shadow DOM \uB0B4\uBD80\uC5D0 \uC801\uC6A9\uB418\uC9C0 \uC54A\uB294\uB2E4.
791
1766
  - **\`import './style.css'\`\uB85C \uC2A4\uD0C0\uC77C\uC744 \uB123\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uBC30\uD3EC \uC0B0\uCD9C\uBB3C\uC5D0\uC11C \uC0AC\uB77C\uC9C4\uB2E4.** \uB85C\uCEEC \uAC1C\uBC1C \uD654\uBA74\uC5D0\uC11C\uB294 \uC801\uC6A9\uB418\uC5B4 \uB208\uCE58\uCC44\uAE30 \uC5B4\uB835\uB2E4. CSS \uD30C\uC77C\uC744 \uC4F0\uB824\uBA74 \`import css from './style.css?inline'\`\uC73C\uB85C \uBB38\uC790\uC5F4\uC744 \uAC00\uC838\uC640 \`createTemplate(..., { styles: css })\`\uC5D0 \uB118\uAE30\uAC70\uB098 \uCEF4\uD3EC\uB10C\uD2B8 \uC548 \`<style>{css}</style>\`\uB85C \uB80C\uB354\uD55C\uB2E4. inline \`style={{}}\`\uB3C4 \uB41C\uB2E4
792
1767
  - UI \uC0C9\xB7\uD0C0\uC774\uD3EC\xB7\uADF8\uB9BC\uC790\uB97C hex/rgb\uB85C \uD558\uB4DC\uCF54\uB529\uD558\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uC704 '\uB514\uC790\uC778' \uC139\uC158\uC758 \uB514\uC790\uC778 \uD1A0\uD070\uC744 \uC0AC\uC6A9
793
- - \uC571\uD0A4(APP-ID \`bsa_\u2026\`\xB7Secret \`bsp_\u2026\`, \uC5B4\uB4DC\uBBFC \uAC8C\uC774\uD2B8\uC6E8\uC774 \`bsm_\u2026\`)\uB97C \uC18C\uC2A4 \uCF54\uB4DC\uC5D0 \uB9AC\uD130\uB7F4\uB85C \uB123\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uC778\uC99D \uAC12\uC740 \`.env\`(\uCEE4\uBC0B \uC548 \uB428)\uC5D0\uC11C \`import.meta.env.VITE_BSTAGE_*\`\uB85C\uB9CC \uC8FC\uC785\uD55C\uB2E4. pre-commit \uD6C5\uC774 \uCEE4\uBC0B \uC804 \uAC80\uCD9C\xB7\uCC28\uB2E8\uD558\uBA70 \`--no-verify\`\uB85C \uC6B0\uD68C\uD558\uC9C0 \uC54A\uB294\uB2E4
1768
+ - \uC571\uD0A4(APP-ID \`bsa_\u2026\`\xB7APP KEY \`bsp_\u2026\`, \uC5B4\uB4DC\uBBFC \uAC8C\uC774\uD2B8\uC6E8\uC774 \`bsm_\u2026\`)\uB97C \uC18C\uC2A4 \uCF54\uB4DC\uC5D0 \uB9AC\uD130\uB7F4\uB85C \uB123\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uC778\uC99D \uAC12\uC740 \`.env\`(\uCEE4\uBC0B \uC548 \uB428)\uC5D0\uC11C \`import.meta.env.VITE_BSTAGE_*\`\uB85C\uB9CC \uC8FC\uC785\uD55C\uB2E4. pre-commit \uD6C5\uC774 \uCEE4\uBC0B \uC804 \uAC80\uCD9C\xB7\uCC28\uB2E8\uD558\uBA70 \`--no-verify\`\uB85C \uC6B0\uD68C\uD558\uC9C0 \uC54A\uB294\uB2E4
794
1769
  ${apiForbidBullet}
795
1770
  `;
796
1771
  }
@@ -812,14 +1787,15 @@ function claudeMd() {
812
1787
  }
813
1788
 
814
1789
  // src/templates/env.ts
815
- function env({ phase, appId, appSecret, tenantId }) {
1790
+ function env({ phase, appId, appKey, tenantId }) {
816
1791
  return `VITE_BSTAGE_PHASE=${phase}
817
1792
 
818
1793
  # \uC778\uC99D \uC815\uBCF4(\uC120\uD0DD) \u2014 BstageClient\uB85C API\uB97C \uD638\uCD9C\uD560 \uB54C\uB9CC \uD544\uC694\uD569\uB2C8\uB2E4.
819
1794
  # API \uC5C6\uC774 UI\uB9CC \uB80C\uB354\uD558\uB294 \uD15C\uD50C\uB9BF\uC740 \uAC12\uC744 \uCC44\uC6B0\uC9C0 \uC54A\uC544\uB3C4 \uB429\uB2C8\uB2E4.
820
1795
  # .env\uB294 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(.gitignore). \uAC12\uC740 b.stage \uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09.
1796
+ # APP_KEY\uB294 \uD3EC\uD138\uC758 "APP KEY"\uC640 \uAC19\uC740 \uAC12\uC785\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800\uC5D0 \uB178\uCD9C\uB418\uB294 \uAC12\uC774\uB77C \uBE44\uBC00\uAC12\uC774 \uC544\uB2D9\uB2C8\uB2E4.
821
1797
  VITE_BSTAGE_APP_ID=${appId}
822
- VITE_BSTAGE_APP_SECRET=${appSecret}
1798
+ VITE_BSTAGE_APP_KEY=${appKey}
823
1799
  VITE_BSTAGE_TENANT_ID=${tenantId}
824
1800
 
825
1801
  # CF Access \uC790\uACA9\uC99D\uBA85 \u2014 dev\xB7qa phase\uB85C \uAC1C\uBC1C\uD560 \uB54C\uB9CC \uD544\uC694\uD569\uB2C8\uB2E4.
@@ -943,9 +1919,9 @@ createTemplate(MyWidget, {
943
1919
  VITE_BSTAGE_PHASE=dev # dev | qa | real | sandbox
944
1920
  \`\`\`
945
1921
 
946
- API \uD0A4(APP-ID/Secret)\uB294 \`.env\`\uC5D0\uC11C \uC124\uC815\uD569\uB2C8\uB2E4. \`.env\`\uB294 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC73C\uB2C8, clone \uD6C4 \`.env.example\`\uC744 \`.env\`\uB85C \uBCF5\uC0AC\uD574 \uAC12\uC744 \uCC44\uC6B0\uC138\uC694.
1922
+ API \uD0A4(APP-ID/APP KEY)\uB294 \`.env\`\uC5D0\uC11C \uC124\uC815\uD569\uB2C8\uB2E4. \`.env\`\uB294 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC73C\uB2C8, clone \uD6C4 \`.env.example\`\uC744 \`.env\`\uB85C \uBCF5\uC0AC\uD574 \uAC12\uC744 \uCC44\uC6B0\uC138\uC694.
947
1923
 
948
- > **\uBCF4\uC548**: \uC571\uD0A4 \uAC12\uC744 \uC18C\uC2A4 \uCF54\uB4DC(\uC608: \`client.ts\`)\uC5D0 \uC9C1\uC811 \uB123\uC9C0 \uB9C8\uC138\uC694 \u2014 \uBC18\uB4DC\uC2DC \`.env\`\uB85C\uB9CC \uC8FC\uC785\uD569\uB2C8\uB2E4. \`bstage init\`\uC774 \uC124\uCE58\uD55C pre-commit \uD6C5\uC774 \uCEE4\uBC0B \uC804 \uD0A4 \uB9AC\uD130\uB7F4\uC744 \uCC28\uB2E8\uD558\uBA70, \`--no-verify\`\uB85C \uC6B0\uD68C\uD558\uC9C0 \uB9C8\uC138\uC694.
1924
+ > **\uADDC\uCE59**: \uC571\uD0A4 \uAC12\uC744 \uC18C\uC2A4 \uCF54\uB4DC(\uC608: \`client.ts\`)\uC5D0 \uC9C1\uC811 \uB123\uC9C0 \uB9C8\uC138\uC694 \u2014 \uBC18\uB4DC\uC2DC \`.env\`\uB85C\uB9CC \uC8FC\uC785\uD569\uB2C8\uB2E4. APP KEY\uB294 \uBE0C\uB77C\uC6B0\uC800\uC5D0 \uB178\uCD9C\uB418\uB294 \uAC12\uC774\uB77C \uBE44\uBC00\uC740 \uC544\uB2C8\uC9C0\uB9CC, \uD658\uACBD\uB9C8\uB2E4 \uAC12\uC774 \uB2E4\uB974\uACE0 \uC800\uC7A5\uC18C\uC5D0 \uBC15\uD788\uBA74 \uC7AC\uBC1C\uAE09 \uB54C \uCF54\uB4DC \uC218\uC815\uC774 \uB530\uB77C\uC635\uB2C8\uB2E4. \`bstage init\`\uC774 \uC124\uCE58\uD55C pre-commit \uD6C5\uC774 \uCEE4\uBC0B \uC804 \uD0A4 \uB9AC\uD130\uB7F4\uC744 \uCC28\uB2E8\uD558\uBA70, \`--no-verify\`\uB85C \uC6B0\uD68C\uD558\uC9C0 \uB9C8\uC138\uC694.
949
1925
 
950
1926
  ## SDK \uC5C5\uB370\uC774\uD2B8
951
1927
 
@@ -1592,7 +2568,7 @@ function viteEnvDts() {
1592
2568
  interface ImportMetaEnv {
1593
2569
  readonly VITE_BSTAGE_PHASE: string
1594
2570
  readonly VITE_BSTAGE_APP_ID: string
1595
- readonly VITE_BSTAGE_APP_SECRET: string
2571
+ readonly VITE_BSTAGE_APP_KEY: string
1596
2572
  readonly VITE_BSTAGE_TENANT_ID: string
1597
2573
  }
1598
2574
 
@@ -1610,7 +2586,7 @@ function clientTs() {
1610
2586
  // clone \uD6C4 .env.example\uC744 .env\uB85C \uBCF5\uC0AC\uD574 \uAC12\uC744 \uCC44\uC6B0\uC138\uC694.
1611
2587
  export const client = new BstageClient({
1612
2588
  appId: import.meta.env.VITE_BSTAGE_APP_ID,
1613
- appSecret: import.meta.env.VITE_BSTAGE_APP_SECRET,
2589
+ appKey: import.meta.env.VITE_BSTAGE_APP_KEY,
1614
2590
  tenantId: import.meta.env.VITE_BSTAGE_TENANT_ID,
1615
2591
  })
1616
2592
  `;
@@ -1709,56 +2685,56 @@ npx bstage docs # SLOT_CATALOG_V2.md
1709
2685
  var PHASES = ["dev", "qa", "real", "sandbox"];
1710
2686
  var TARGETS = ["user", "admin"];
1711
2687
  async function initCommand(options) {
1712
- p.intro(`${pc2.cyan(pc2.bold("bstage init"))} ${pc2.dim("\u2014 b.stage Template Project Setup")}`);
2688
+ p2.intro(`${pc6.cyan(pc6.bold("bstage init"))} ${pc6.dim("\u2014 b.stage Template Project Setup")}`);
1713
2689
  let space;
1714
2690
  let templateName;
1715
2691
  let packageManager;
1716
2692
  let phase;
1717
2693
  let target;
1718
2694
  let appId = "";
1719
- let appSecret = "";
2695
+ let appKey = "";
1720
2696
  let tenantId = "";
1721
2697
  if (options.yes) {
1722
2698
  const targetOpt = options.target ?? "user";
1723
2699
  if (!TARGETS.includes(targetOpt)) {
1724
- p.log.error(`--target \uAC12\uC740 ${TARGETS.join(" | ")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
1725
- p.outro(pc2.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2700
+ p2.log.error(`--target \uAC12\uC740 ${TARGETS.join(" | ")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2701
+ p2.outro(pc6.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1726
2702
  process.exit(1);
1727
2703
  }
1728
2704
  target = targetOpt;
1729
2705
  if (!options.space) {
1730
- p.log.error("--yes \uBAA8\uB4DC\uC5D0\uC11C\uB294 --space\uAC00 \uD544\uC218\uC785\uB2C8\uB2E4.");
1731
- p.outro(pc2.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2706
+ p2.log.error("--yes \uBAA8\uB4DC\uC5D0\uC11C\uB294 --space\uAC00 \uD544\uC218\uC785\uB2C8\uB2E4.");
2707
+ p2.outro(pc6.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1732
2708
  process.exit(1);
1733
2709
  }
1734
2710
  const slugPattern = /^[a-zA-Z][a-zA-Z0-9-]*$/;
1735
2711
  if (!slugPattern.test(options.space)) {
1736
- p.log.error(
1737
- `${pc2.red("--space")} \uAC12\uC740 \uC601\uBB38\uC790\uB85C \uC2DC\uC791\uD574\uC57C \uD558\uBA70, \uC601\uBB38, \uC22B\uC790, \uD558\uC774\uD508\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`
2712
+ p2.log.error(
2713
+ `${pc6.red("--space")} \uAC12\uC740 \uC601\uBB38\uC790\uB85C \uC2DC\uC791\uD574\uC57C \uD558\uBA70, \uC601\uBB38, \uC22B\uC790, \uD558\uC774\uD508\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`
1738
2714
  );
1739
- p.outro(pc2.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2715
+ p2.outro(pc6.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1740
2716
  process.exit(1);
1741
2717
  }
1742
2718
  space = options.space;
1743
2719
  const templateOpt = resolveTemplateName(space, options.template);
1744
2720
  if (!ELEMENT_NAME_RE2.test(templateOpt)) {
1745
- p.log.error(
1746
- `${pc2.red("--template")} \uAC12\uC740 Custom Element \uC2A4\uD399\uC744 \uB530\uB77C\uC57C \uD569\uB2C8\uB2E4: \uC18C\uBB38\uC790 \uC2DC\uC791, \uD558\uC774\uD508 1\uAC1C \uC774\uC0C1, \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uB9CC.`
2721
+ p2.log.error(
2722
+ `${pc6.red("--template")} \uAC12\uC740 Custom Element \uC2A4\uD399\uC744 \uB530\uB77C\uC57C \uD569\uB2C8\uB2E4: \uC18C\uBB38\uC790 \uC2DC\uC791, \uD558\uC774\uD508 1\uAC1C \uC774\uC0C1, \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uB9CC.`
1747
2723
  );
1748
- p.outro(pc2.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2724
+ p2.outro(pc6.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1749
2725
  process.exit(1);
1750
2726
  }
1751
2727
  templateName = templateOpt;
1752
2728
  packageManager = options.pm ?? "npm";
1753
2729
  const phaseOpt = options.phase ?? "sandbox";
1754
2730
  if (!PHASES.includes(phaseOpt)) {
1755
- p.log.error(`--phase \uAC12\uC740 ${PHASES.join(" | ")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
1756
- p.outro(pc2.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2731
+ p2.log.error(`--phase \uAC12\uC740 ${PHASES.join(" | ")} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2732
+ p2.outro(pc6.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1757
2733
  process.exit(1);
1758
2734
  }
1759
2735
  phase = phaseOpt;
1760
2736
  } else {
1761
- target = await p.select({
2737
+ target = await p2.select({
1762
2738
  message: "\uCCAB \uBC88\uC9F8 \uD15C\uD50C\uB9BF\uC740 \uC5B4\uB290 \uD50C\uB7AB\uD3FC\uC6A9\uC778\uAC00\uC694? (\uB808\uD3EC\uC5D0 \uB450 \uC885\uB958\uB97C \uC11E\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4)",
1763
2739
  options: [
1764
2740
  { value: "user", label: "user (b.stage \uC720\uC800 \uD50C\uB7AB\uD3FC)" },
@@ -1766,13 +2742,13 @@ async function initCommand(options) {
1766
2742
  ],
1767
2743
  initialValue: options.target === "admin" ? "admin" : "user"
1768
2744
  });
1769
- if (p.isCancel(target)) return cancelled();
1770
- space = await p.text({
2745
+ if (p2.isCancel(target)) return cancelled();
2746
+ space = await p2.text({
1771
2747
  message: "\uD15C\uD50C\uB9BF\uC744 \uC801\uC6A9\uD560 Space ID\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694.",
1772
2748
  validate: validateSlug("Space ID\uB294 \uD544\uC218\uC785\uB2C8\uB2E4.")
1773
2749
  });
1774
- if (p.isCancel(space)) return cancelled();
1775
- phase = await p.select({
2750
+ if (p2.isCancel(space)) return cancelled();
2751
+ phase = await p2.select({
1776
2752
  message: "\uD15C\uD50C\uB9BF\uC744 \uBC30\uD3EC\uD560 \uD658\uACBD\uC744 \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.",
1777
2753
  options: [
1778
2754
  { value: "sandbox", label: "sandbox", hint: "\uAE30\uBCF8" },
@@ -1782,14 +2758,14 @@ async function initCommand(options) {
1782
2758
  ],
1783
2759
  initialValue: "sandbox"
1784
2760
  });
1785
- if (p.isCancel(phase)) return cancelled();
1786
- templateName = await p.text({
1787
- message: `\uCCAB \uBC88\uC9F8 \uD15C\uD50C\uB9BF \uC774\uB984\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694. ${pc2.dim(`(\uC608: ${space}-hello \u2014 \uD558\uC774\uD508 \uD544\uC218)`)}`,
2761
+ if (p2.isCancel(phase)) return cancelled();
2762
+ templateName = await p2.text({
2763
+ message: `\uCCAB \uBC88\uC9F8 \uD15C\uD50C\uB9BF \uC774\uB984\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694. ${pc6.dim(`(\uC608: ${space}-hello \u2014 \uD558\uC774\uD508 \uD544\uC218)`)}`,
1788
2764
  placeholder: resolveTemplateName(space, void 0),
1789
2765
  validate: validateElementName
1790
2766
  });
1791
- if (p.isCancel(templateName)) return cancelled();
1792
- packageManager = await p.select({
2767
+ if (p2.isCancel(templateName)) return cancelled();
2768
+ packageManager = await p2.select({
1793
2769
  message: "\uC0AC\uC6A9\uD560 \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800\uB97C \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.",
1794
2770
  options: [
1795
2771
  { value: "npm", label: "npm" },
@@ -1797,36 +2773,36 @@ async function initCommand(options) {
1797
2773
  ],
1798
2774
  initialValue: "npm"
1799
2775
  });
1800
- if (p.isCancel(packageManager)) return cancelled();
1801
- const configureAuth = await p.confirm({
2776
+ if (p2.isCancel(packageManager)) return cancelled();
2777
+ const configureAuth = await p2.confirm({
1802
2778
  message: "b.stage \uD30C\uD2B8\uB108\uC2A4\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 API \uD0A4\uAC00 \uC788\uC73C\uC2E0\uAC00\uC694? (\uC5C6\uC5B4\uB3C4 \uC2DC\uC791\uD560 \uC218 \uC788\uC5B4\uC694)",
1803
2779
  initialValue: false
1804
2780
  });
1805
- if (p.isCancel(configureAuth)) return cancelled();
2781
+ if (p2.isCancel(configureAuth)) return cancelled();
1806
2782
  if (configureAuth) {
1807
- p.log.info(pc2.dim("API \uC778\uC99D \uC124\uC815 (\uC120\uD0DD)"));
1808
- appId = await p.text({
2783
+ p2.log.info(pc6.dim("API \uC778\uC99D \uC124\uC815 (\uC120\uD0DD)"));
2784
+ appId = await p2.text({
1809
2785
  message: "\uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 APP-ID\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694."
1810
2786
  }) ?? "";
1811
- if (p.isCancel(appId)) return cancelled();
1812
- appSecret = await p.text({
1813
- message: "\uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 Secret \uAC12\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694."
2787
+ if (p2.isCancel(appId)) return cancelled();
2788
+ appKey = await p2.text({
2789
+ message: "\uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 APP KEY\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694."
1814
2790
  }) ?? "";
1815
- if (p.isCancel(appSecret)) return cancelled();
1816
- warnIfCredentialsSwapped(appId, appSecret);
2791
+ if (p2.isCancel(appKey)) return cancelled();
2792
+ warnIfCredentialsSwapped(appId, appKey);
1817
2793
  tenantId = space;
1818
2794
  }
1819
2795
  }
1820
2796
  const projectName = `${space}-custom-templates-${phase}`;
1821
2797
  const projectDir = resolve2(process.cwd(), projectName);
1822
2798
  if (existsSync(projectDir)) {
1823
- p.log.error(
1824
- `${pc2.red(projectDir)} \uB514\uB809\uD1A0\uB9AC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uD574\uB2F9 \uB514\uB809\uD1A0\uB9AC\uB97C \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.`
2799
+ p2.log.error(
2800
+ `${pc6.red(projectDir)} \uB514\uB809\uD1A0\uB9AC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uD574\uB2F9 \uB514\uB809\uD1A0\uB9AC\uB97C \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.`
1825
2801
  );
1826
- p.outro(pc2.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2802
+ p2.outro(pc6.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1827
2803
  process.exit(1);
1828
2804
  }
1829
- const s = p.spinner();
2805
+ const s = p2.spinner();
1830
2806
  s.start("SDK \uCD5C\uC2E0 \uBC84\uC804 \uC870\uD68C \uC911...");
1831
2807
  let sdkVersions;
1832
2808
  try {
@@ -1839,12 +2815,12 @@ async function initCommand(options) {
1839
2815
  sdkVersions = { cli, core, react: react2, design };
1840
2816
  s.stop(`SDK \uBC84\uC804 \uD655\uC815 \u2014 cli@${cli}, core@${core}, react@${react2}, design@${design}`);
1841
2817
  } catch (err) {
1842
- s.stop(pc2.red("SDK \uCD5C\uC2E0 \uBC84\uC804 \uC870\uD68C \uC2E4\uD328"));
1843
- p.log.error(
2818
+ s.stop(pc6.red("SDK \uCD5C\uC2E0 \uBC84\uC804 \uC870\uD68C \uC2E4\uD328"));
2819
+ p2.log.error(
1844
2820
  (err instanceof Error ? err.message : String(err)) + `
1845
- ${pc2.dim("\uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0\uACFC registry.npmjs.org \uC811\uADFC\uC774 \uAC00\uB2A5\uD55C\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.")}`
2821
+ ${pc6.dim("\uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0\uACFC registry.npmjs.org \uC811\uADFC\uC774 \uAC00\uB2A5\uD55C\uC9C0 \uD655\uC778\uD574 \uC8FC\uC138\uC694.")}`
1846
2822
  );
1847
- p.outro(pc2.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2823
+ p2.outro(pc6.red("\uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1848
2824
  process.exit(1);
1849
2825
  }
1850
2826
  s.start("\uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC911...");
@@ -1858,7 +2834,7 @@ async function initCommand(options) {
1858
2834
  ...BSTAGE_SKILLS.map((s2) => `.claude/skills/${s2.name}`)
1859
2835
  ];
1860
2836
  for (const dir of dirs) {
1861
- mkdirSync(join2(projectDir, dir), { recursive: true });
2837
+ mkdirSync(join4(projectDir, dir), { recursive: true });
1862
2838
  }
1863
2839
  const envFiles = [
1864
2840
  // .env(실제 값)는 커밋되지 않음(.gitignore). .env.example은 커밋되어 팀원 세팅 안내.
@@ -1867,7 +2843,7 @@ async function initCommand(options) {
1867
2843
  env({
1868
2844
  phase,
1869
2845
  appId: appId || "YOUR_APP_ID",
1870
- appSecret: appSecret || "YOUR_APP_SECRET",
2846
+ appKey: appKey || "YOUR_APP_KEY",
1871
2847
  tenantId: tenantId || "YOUR_TENANT_ID"
1872
2848
  })
1873
2849
  ],
@@ -1876,7 +2852,7 @@ async function initCommand(options) {
1876
2852
  env({
1877
2853
  phase,
1878
2854
  appId: "YOUR_APP_ID",
1879
- appSecret: "YOUR_APP_SECRET",
2855
+ appKey: "YOUR_APP_KEY",
1880
2856
  tenantId: "YOUR_TENANT_ID"
1881
2857
  })
1882
2858
  ]
@@ -1912,7 +2888,7 @@ async function initCommand(options) {
1912
2888
  )
1913
2889
  ];
1914
2890
  for (const [filePath, content] of files) {
1915
- writeFileSync(join2(projectDir, filePath), content, "utf-8");
2891
+ writeFileSync(join4(projectDir, filePath), content, "utf-8");
1916
2892
  }
1917
2893
  s.stop("\uD504\uB85C\uC81D\uD2B8 \uD30C\uC77C \uC0DD\uC131 \uC644\uB8CC");
1918
2894
  s.start("Git \uCD08\uAE30\uD654 \uC911...");
@@ -1925,7 +2901,7 @@ async function initCommand(options) {
1925
2901
  });
1926
2902
  s.stop("Git \uCD08\uAE30\uD654 \uC644\uB8CC");
1927
2903
  } catch {
1928
- s.stop(pc2.yellow("Git \uCD08\uAE30\uD654 \uAC74\uB108\uB700 (git\uC774 \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4)"));
2904
+ s.stop(pc6.yellow("Git \uCD08\uAE30\uD654 \uAC74\uB108\uB700 (git\uC774 \uC124\uCE58\uB418\uC9C0 \uC54A\uC558\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4)"));
1929
2905
  }
1930
2906
  s.start(`\uC758\uC874\uC131 \uC124\uCE58 \uC911... (${packageManager} install)`);
1931
2907
  try {
@@ -1933,16 +2909,16 @@ async function initCommand(options) {
1933
2909
  execSync(installCmd, { cwd: projectDir, stdio: "ignore", env: { ...process.env } });
1934
2910
  s.stop("\uC758\uC874\uC131 \uC124\uCE58 \uC644\uB8CC");
1935
2911
  } catch {
1936
- s.stop(pc2.yellow("\uC758\uC874\uC131 \uC124\uCE58 \uC2E4\uD328 \u2014 \uC9C1\uC811 \uC124\uCE58\uD574 \uC8FC\uC138\uC694"));
2912
+ s.stop(pc6.yellow("\uC758\uC874\uC131 \uC124\uCE58 \uC2E4\uD328 \u2014 \uC9C1\uC811 \uC124\uCE58\uD574 \uC8FC\uC138\uC694"));
1937
2913
  }
1938
2914
  const runCmd = packageManager === "pnpm" ? "pnpm dev" : "npm run dev";
1939
2915
  const label = target === "admin" ? "\uC5B4\uB4DC\uBBFC \uD15C\uD50C\uB9BF \uD504\uB85C\uC81D\uD2B8" : "\uD504\uB85C\uC81D\uD2B8";
1940
- p.outro(`${pc2.green(`${label}\uAC00 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4!`)}
2916
+ p2.outro(`${pc6.green(`${label}\uAC00 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4!`)}
1941
2917
 
1942
2918
  \uB2E4\uC74C \uB2E8\uACC4:
1943
- ${pc2.cyan(`cd ${projectName}`)}
1944
- ${pc2.cyan(runCmd)}
1945
- ${pc2.dim("# \uBE0C\uB77C\uC6B0\uC800: http://localhost:5173")}`);
2919
+ ${pc6.cyan(`cd ${projectName}`)}
2920
+ ${pc6.cyan(runCmd)}
2921
+ ${pc6.dim("# \uBE0C\uB77C\uC6B0\uC800: http://localhost:5173")}`);
1946
2922
  }
1947
2923
  async function fetchLatestVersion(packageName) {
1948
2924
  const res = await fetch(`https://registry.npmjs.org/${packageName}`, {
@@ -1976,24 +2952,24 @@ function validateElementName(v) {
1976
2952
  }
1977
2953
  }
1978
2954
  var APP_ID_PREFIX2 = "bsa_";
1979
- var APP_SECRET_PREFIX2 = "bsp_";
1980
- function warnIfCredentialsSwapped(appId, appSecret) {
1981
- if (!appId.startsWith(APP_SECRET_PREFIX2) && !appSecret.startsWith(APP_ID_PREFIX2)) return;
1982
- p.log.warn(
1983
- `APP-ID\uC640 Secret\uC774 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4.
1984
- APP-ID\uB294 "${APP_ID_PREFIX2}", Secret\uC740 "${APP_SECRET_PREFIX2}"\uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4.
2955
+ var APP_KEY_PREFIX2 = "bsp_";
2956
+ function warnIfCredentialsSwapped(appId, appKey) {
2957
+ if (!appId.startsWith(APP_KEY_PREFIX2) && !appKey.startsWith(APP_ID_PREFIX2)) return;
2958
+ p2.log.warn(
2959
+ `APP-ID\uC640 APP KEY\uAC00 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4.
2960
+ APP-ID\uB294 "${APP_ID_PREFIX2}", APP KEY\uB294 "${APP_KEY_PREFIX2}"\uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4.
1985
2961
  \uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 \uAC12\uC744 \uB2E4\uC2DC \uD655\uC778\uD574 \uC8FC\uC138\uC694.`
1986
2962
  );
1987
2963
  }
1988
2964
  function cancelled() {
1989
- p.outro(pc2.red("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2965
+ p2.outro(pc6.red("\uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1990
2966
  process.exit(0);
1991
2967
  }
1992
2968
 
1993
2969
  // src/commands/i18n.ts
1994
2970
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1995
- import { dirname, join as join3, resolve as resolve3 } from "path";
1996
- import pc3 from "picocolors";
2971
+ import { dirname as dirname3, join as join5, resolve as resolve3 } from "path";
2972
+ import pc7 from "picocolors";
1997
2973
  function latestKey(phase, tier, target) {
1998
2974
  const segments = tier === DEFAULT_TRANSLATION_TIER ? [TRANSLATION_CDN_BASE_KEY, phase, target] : [TRANSLATION_CDN_BASE_KEY, phase, target, tier];
1999
2975
  return [...segments, "latest.json"].join("/");
@@ -2040,87 +3016,87 @@ async function i18nPullCommand(options) {
2040
3016
  const tier = options.tier || DEFAULT_TRANSLATION_TIER;
2041
3017
  const target = options.target || "user";
2042
3018
  const latestUrl = `${TRANSLATION_CDN_ORIGIN}/${latestKey(options.phase, tier, target)}?date=${cacheStamp(/* @__PURE__ */ new Date())}`;
2043
- console.log(pc3.cyan(`[bstage] \uBC88\uC5ED pull: ${options.phase}/${target} (tier=${tier})`));
3019
+ console.log(pc7.cyan(`[bstage] \uBC88\uC5ED pull: ${options.phase}/${target} (tier=${tier})`));
2044
3020
  let latest;
2045
3021
  try {
2046
3022
  latest = await fetchJson(latestUrl);
2047
3023
  } catch (err) {
2048
3024
  const msg = err instanceof Error ? err.message : String(err);
2049
- console.error(pc3.red(`[bstage] latest.json \uC870\uD68C \uC2E4\uD328: ${msg}
3025
+ console.error(pc7.red(`[bstage] latest.json \uC870\uD68C \uC2E4\uD328: ${msg}
2050
3026
  ${latestUrl}`));
2051
3027
  process.exit(1);
2052
3028
  }
2053
3029
  const versionBase = `${TRANSLATION_CDN_ORIGIN}/${latest.cdnPath.replace(TRANSLATION_CDN_BUCKET_PREFIX, "")}`;
2054
- console.log(pc3.dim(` version: ${latest.version}`));
2055
- const cacheDir = join3(root, ".bstage", "i18n", target);
3030
+ console.log(pc7.dim(` version: ${latest.version}`));
3031
+ const cacheDir = join5(root, ".bstage", "i18n", target);
2056
3032
  mkdirSync2(cacheDir, { recursive: true });
2057
3033
  let refDict = null;
2058
3034
  const fetched = [];
2059
3035
  for (const locale of TRANSLATION_LANGUAGES) {
2060
3036
  try {
2061
3037
  const dict = await fetchJson(`${versionBase}/${locale}/translation.json`);
2062
- writeFileSync2(join3(cacheDir, `${locale}.json`), JSON.stringify(dict, null, 2));
3038
+ writeFileSync2(join5(cacheDir, `${locale}.json`), JSON.stringify(dict, null, 2));
2063
3039
  fetched.push(locale);
2064
3040
  if (locale === options.ref) refDict = dict;
2065
- console.log(pc3.green(` \u2713 ${locale} (${Object.keys(dict).length})`));
3041
+ console.log(pc7.green(` \u2713 ${locale} (${Object.keys(dict).length})`));
2066
3042
  } catch (err) {
2067
3043
  const msg = err instanceof Error ? err.message : String(err);
2068
- console.warn(pc3.yellow(` - ${locale}: ${msg} \u2014 \uAC74\uB108\uB700`));
3044
+ console.warn(pc7.yellow(` - ${locale}: ${msg} \u2014 \uAC74\uB108\uB700`));
2069
3045
  }
2070
3046
  }
2071
3047
  if (fetched.length === 0) {
2072
- console.error(pc3.red("[bstage] \uBC1B\uC740 \uBC88\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
3048
+ console.error(pc7.red("[bstage] \uBC1B\uC740 \uBC88\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2073
3049
  process.exit(1);
2074
3050
  }
2075
3051
  if (!refDict) {
2076
3052
  console.warn(
2077
- pc3.yellow(`[bstage] ref \uB85C\uCF00\uC77C(${options.ref}) \uC0AC\uC804\uC774 \uC5C6\uC5B4 \uD0C0\uC785 \uC0DD\uC131\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.`)
3053
+ pc7.yellow(`[bstage] ref \uB85C\uCF00\uC77C(${options.ref}) \uC0AC\uC804\uC774 \uC5C6\uC5B4 \uD0C0\uC785 \uC0DD\uC131\uC744 \uAC74\uB108\uB701\uB2C8\uB2E4.`)
2078
3054
  );
2079
3055
  return;
2080
3056
  }
2081
3057
  const outPath = resolve3(root, options.out);
2082
- mkdirSync2(dirname(outPath), { recursive: true });
3058
+ mkdirSync2(dirname3(outPath), { recursive: true });
2083
3059
  writeFileSync2(outPath, generateTypeModule(refDict, { version: latest.version, ref: options.ref }));
2084
- console.log(pc3.green(`[bstage] \uD0C0\uC785 \uC0DD\uC131: ${options.out} (${Object.keys(refDict).length} \uD0A4)`));
3060
+ console.log(pc7.green(`[bstage] \uD0C0\uC785 \uC0DD\uC131: ${options.out} (${Object.keys(refDict).length} \uD0A4)`));
2085
3061
  console.log(
2086
- pc3.dim(
3062
+ pc7.dim(
2087
3063
  ` \uCE90\uC2DC: .bstage/i18n/${target}/*.json \u2014 \uD0C0\uC785 \uC0DD\uC131 \uC785\uB825 + \uC6D0\uBB38 \uAC80\uC0C9\uC6A9(\uB7F0\uD0C0\uC784\uC740 CDN\uC5D0\uC11C \uC9C1\uC811 \uBC1B\uB294\uB2E4)`
2088
3064
  )
2089
3065
  );
2090
- console.log(pc3.dim(" \uC0DD\uC131 \uD30C\uC77C\uC744 \uD55C \uBC88 import\uD558\uBA74 useBstageTranslations().t() \uD0A4\uAC00 \uC881\uD600\uC9D1\uB2C8\uB2E4."));
3066
+ console.log(pc7.dim(" \uC0DD\uC131 \uD30C\uC77C\uC744 \uD55C \uBC88 import\uD558\uBA74 useBstageTranslations().t() \uD0A4\uAC00 \uC881\uD600\uC9D1\uB2C8\uB2E4."));
2091
3067
  }
2092
3068
 
2093
3069
  // src/commands/doctor.ts
2094
3070
  import { existsSync as existsSync3 } from "fs";
2095
- import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
2096
- import { dirname as dirname2, join as join5 } from "path";
3071
+ import { readFile as readFile4, readdir as readdir2 } from "fs/promises";
3072
+ import { dirname as dirname4, join as join7 } from "path";
2097
3073
  import { fileURLToPath } from "url";
2098
- import pc5 from "picocolors";
3074
+ import pc9 from "picocolors";
2099
3075
 
2100
3076
  // src/commands/skills.ts
2101
3077
  import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync3 } from "fs";
2102
- import { join as join4 } from "path";
2103
- import pc4 from "picocolors";
3078
+ import { join as join6 } from "path";
3079
+ import pc8 from "picocolors";
2104
3080
  async function skillsInstallCommand(options) {
2105
3081
  const root = process.cwd();
2106
3082
  const baseDir = options.dir || ".claude/skills";
2107
3083
  const target = resolveProjectTarget(root);
2108
- console.log(pc4.cyan(`[bstage] \uC2A4\uD0AC \uC124\uCE58: ${baseDir}/`) + pc4.dim(` (\uB514\uC790\uC778 \uD0C0\uAE43: ${target})`));
3084
+ console.log(pc8.cyan(`[bstage] \uC2A4\uD0AC \uC124\uCE58: ${baseDir}/`) + pc8.dim(` (\uB514\uC790\uC778 \uD0C0\uAE43: ${target})`));
2109
3085
  console.log(
2110
- pc4.dim(
3086
+ pc8.dim(
2111
3087
  " SDK\uAC00 \uC18C\uC720\uD558\uB294 \uC790\uC0B0\uC785\uB2C8\uB2E4. \uAC19\uC740 \uC774\uB984\uC758 \uAE30\uC874 SKILL.md\uB294 \uB36E\uC5B4\uC4F0\uC5EC \uCD5C\uC2E0\uC73C\uB85C \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4."
2112
3088
  )
2113
3089
  );
2114
3090
  for (const skill of BSTAGE_SKILLS) {
2115
- const dir = join4(root, baseDir, skill.name);
3091
+ const dir = join6(root, baseDir, skill.name);
2116
3092
  mkdirSync3(dir, { recursive: true });
2117
- const file = join4(dir, "SKILL.md");
3093
+ const file = join6(dir, "SKILL.md");
2118
3094
  const action = existsSync2(file) ? "\uAC31\uC2E0" : "\uC0DD\uC131";
2119
3095
  writeFileSync3(file, skill.content(target), "utf-8");
2120
- console.log(pc4.green(` \u2713 ${skill.name} (${action})`));
3096
+ console.log(pc8.green(` \u2713 ${skill.name} (${action})`));
2121
3097
  }
2122
3098
  console.log(
2123
- pc4.dim(
3099
+ pc8.dim(
2124
3100
  ` ${BSTAGE_SKILLS.length}\uAC1C \uC2A4\uD0AC \uC124\uCE58 \uC644\uB8CC. Claude Code\uAC00 ${baseDir}/ \uC758 \uC2A4\uD0AC\uC744 \uC790\uB3D9 \uC778\uC2DD\uD569\uB2C8\uB2E4.`
2125
3101
  )
2126
3102
  );
@@ -2128,16 +3104,16 @@ async function skillsInstallCommand(options) {
2128
3104
  installGuardHook(root);
2129
3105
  }
2130
3106
  function syncAgentsMd(root, target) {
2131
- const file = join4(root, "AGENTS.md");
3107
+ const file = join6(root, "AGENTS.md");
2132
3108
  if (!existsSync2(file)) {
2133
- console.log(pc4.dim(" \xB7 AGENTS.md \uC5C6\uC74C \u2014 `bstage init` \uB610\uB294 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694."));
3109
+ console.log(pc8.dim(" \xB7 AGENTS.md \uC5C6\uC74C \u2014 `bstage init` \uB610\uB294 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694."));
2134
3110
  return;
2135
3111
  }
2136
3112
  const content = readFileSync(file, "utf-8");
2137
3113
  const status = agentsMdStatus(content);
2138
3114
  if (status === "legacy") {
2139
3115
  console.log(
2140
- pc4.yellow(" ! AGENTS.md\uAC00 \uB808\uAC70\uC2DC \uD3EC\uB9F7\uC785\uB2C8\uB2E4(\uAD00\uB9AC \uC601\uC5ED \uB9C8\uCEE4 \uC5C6\uC74C).") + pc4.dim(" `bstage-migrate` \uC2A4\uD0AC\uB85C reconcile\uD558\uC138\uC694 \u2014 skills install\uC740 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.")
3116
+ pc8.yellow(" ! AGENTS.md\uAC00 \uB808\uAC70\uC2DC \uD3EC\uB9F7\uC785\uB2C8\uB2E4(\uAD00\uB9AC \uC601\uC5ED \uB9C8\uCEE4 \uC5C6\uC74C).") + pc8.dim(" `bstage-migrate` \uC2A4\uD0AC\uB85C reconcile\uD558\uC138\uC694 \u2014 skills install\uC740 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.")
2141
3117
  );
2142
3118
  return;
2143
3119
  }
@@ -2146,20 +3122,20 @@ function syncAgentsMd(root, target) {
2146
3122
  const space = parsed.space ?? projectName.split("-custom-templates-")[0] ?? projectName;
2147
3123
  const swapped = swapManagedBlock(content, renderManagedBlock({ space, projectName, target }));
2148
3124
  if (swapped === null) {
2149
- console.log(pc4.dim(" \xB7 AGENTS.md \uAD00\uB9AC \uC601\uC5ED\uC744 \uCC3E\uC9C0 \uBABB\uD574 \uAC74\uB108\uB701\uB2C8\uB2E4."));
3125
+ console.log(pc8.dim(" \xB7 AGENTS.md \uAD00\uB9AC \uC601\uC5ED\uC744 \uCC3E\uC9C0 \uBABB\uD574 \uAC74\uB108\uB701\uB2C8\uB2E4."));
2150
3126
  return;
2151
3127
  }
2152
3128
  if (swapped === content) {
2153
- console.log(pc4.green(" \u2713 AGENTS.md (\uAD00\uB9AC \uC601\uC5ED \uCD5C\uC2E0)"));
3129
+ console.log(pc8.green(" \u2713 AGENTS.md (\uAD00\uB9AC \uC601\uC5ED \uCD5C\uC2E0)"));
2154
3130
  return;
2155
3131
  }
2156
3132
  writeFileSync3(file, swapped, "utf-8");
2157
- console.log(pc4.green(" \u2713 AGENTS.md (\uAD00\uB9AC \uC601\uC5ED \uAC31\uC2E0, \uC790\uC720 \uC601\uC5ED \uBCF4\uC874)"));
3133
+ console.log(pc8.green(" \u2713 AGENTS.md (\uAD00\uB9AC \uC601\uC5ED \uAC31\uC2E0, \uC790\uC720 \uC601\uC5ED \uBCF4\uC874)"));
2158
3134
  }
2159
3135
  function installGuardHook(root) {
2160
- const pkgPath = join4(root, "package.json");
3136
+ const pkgPath = join6(root, "package.json");
2161
3137
  if (!existsSync2(pkgPath)) {
2162
- console.log(pc4.dim(" \xB7 package.json \uC5C6\uC74C \u2014 pre-commit \uAC00\uB4DC \uC124\uCE58\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4."));
3138
+ console.log(pc8.dim(" \xB7 package.json \uC5C6\uC74C \u2014 pre-commit \uAC00\uB4DC \uC124\uCE58\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4."));
2163
3139
  return;
2164
3140
  }
2165
3141
  let pkgChanged = false;
@@ -2182,39 +3158,39 @@ function installGuardHook(root) {
2182
3158
  writeFileSync3(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
2183
3159
  }
2184
3160
  } catch {
2185
- console.log(pc4.yellow(" ! package.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 pre-commit \uAC00\uB4DC \uC124\uCE58\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4."));
3161
+ console.log(pc8.yellow(" ! package.json \uD30C\uC2F1 \uC2E4\uD328 \u2014 pre-commit \uAC00\uB4DC \uC124\uCE58\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4."));
2186
3162
  return;
2187
3163
  }
2188
- const huskyDir = join4(root, ".husky");
3164
+ const huskyDir = join6(root, ".husky");
2189
3165
  mkdirSync3(huskyDir, { recursive: true });
2190
- const scriptFile = join4(huskyDir, "check-secrets.mjs");
3166
+ const scriptFile = join6(huskyDir, "check-secrets.mjs");
2191
3167
  const scriptExisted = existsSync2(scriptFile);
2192
3168
  writeFileSync3(scriptFile, checkSecretsScript(), "utf-8");
2193
- console.log(pc4.green(` \u2713 .husky/check-secrets.mjs (${scriptExisted ? "\uAC31\uC2E0" : "\uC0DD\uC131"})`));
2194
- const hookFile = join4(huskyDir, "pre-commit");
3169
+ console.log(pc8.green(` \u2713 .husky/check-secrets.mjs (${scriptExisted ? "\uAC31\uC2E0" : "\uC0DD\uC131"})`));
3170
+ const hookFile = join6(huskyDir, "pre-commit");
2195
3171
  const body = huskyPreCommit();
2196
3172
  if (!existsSync2(hookFile)) {
2197
3173
  writeFileSync3(hookFile, body, "utf-8");
2198
- console.log(pc4.green(" \u2713 .husky/pre-commit (\uC0DD\uC131)"));
3174
+ console.log(pc8.green(" \u2713 .husky/pre-commit (\uC0DD\uC131)"));
2199
3175
  } else {
2200
3176
  const cur = readFileSync(hookFile, "utf-8");
2201
3177
  if (cur.includes("check-secrets.mjs")) {
2202
- console.log(pc4.green(" \u2713 .husky/pre-commit (\uAC00\uB4DC \uC874\uC7AC)"));
3178
+ console.log(pc8.green(" \u2713 .husky/pre-commit (\uAC00\uB4DC \uC874\uC7AC)"));
2203
3179
  } else {
2204
3180
  const sep = cur.endsWith("\n") ? "" : "\n";
2205
3181
  writeFileSync3(hookFile, cur + sep + body, "utf-8");
2206
- console.log(pc4.green(" \u2713 .husky/pre-commit (\uAC00\uB4DC \uB77C\uC778 \uCD94\uAC00)"));
3182
+ console.log(pc8.green(" \u2713 .husky/pre-commit (\uAC00\uB4DC \uB77C\uC778 \uCD94\uAC00)"));
2207
3183
  }
2208
3184
  }
2209
3185
  if (pkgChanged) {
2210
3186
  console.log(
2211
- pc4.dim(" \xB7 husky\uB97C package.json\uC5D0 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4 \u2014 install \uD6C4 pre-commit \uD6C5\uC774 \uD65C\uC131\uD654\uB429\uB2C8\uB2E4.")
3187
+ pc8.dim(" \xB7 husky\uB97C package.json\uC5D0 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4 \u2014 install \uD6C4 pre-commit \uD6C5\uC774 \uD65C\uC131\uD654\uB429\uB2C8\uB2E4.")
2212
3188
  );
2213
3189
  }
2214
3190
  }
2215
3191
  function readPkgName(root) {
2216
3192
  try {
2217
- return JSON.parse(readFileSync(join4(root, "package.json"), "utf-8")).name ?? null;
3193
+ return JSON.parse(readFileSync(join6(root, "package.json"), "utf-8")).name ?? null;
2218
3194
  } catch {
2219
3195
  return null;
2220
3196
  }
@@ -2255,23 +3231,23 @@ var EXPECTED_FILES = [
2255
3231
  ];
2256
3232
  async function doctorCommand(options) {
2257
3233
  const root = process.cwd();
2258
- const report = await diagnose(root);
3234
+ const report2 = await diagnose(root);
2259
3235
  if (options.json) {
2260
- console.log(JSON.stringify(report, null, 2));
3236
+ console.log(JSON.stringify(report2, null, 2));
2261
3237
  return;
2262
3238
  }
2263
- printReport(report);
2264
- await autoSyncSkills(report);
3239
+ printReport(report2);
3240
+ await autoSyncSkills(report2);
2265
3241
  }
2266
- async function autoSyncSkills(report) {
2267
- if (!report.isBstageProject) return;
2268
- if (!report.skills.some((s) => s.status !== "ok")) return;
3242
+ async function autoSyncSkills(report2) {
3243
+ if (!report2.isBstageProject) return;
3244
+ if (!report2.skills.some((s) => s.status !== "ok")) return;
2269
3245
  await skillsInstallCommand({ dir: ".claude/skills" });
2270
3246
  }
2271
3247
  async function diagnose(root) {
2272
3248
  let pkgJson = null;
2273
3249
  try {
2274
- pkgJson = JSON.parse(await readFile2(join5(root, "package.json"), "utf-8"));
3250
+ pkgJson = JSON.parse(await readFile4(join7(root, "package.json"), "utf-8"));
2275
3251
  } catch {
2276
3252
  }
2277
3253
  const deps = {
@@ -2280,7 +3256,7 @@ async function diagnose(root) {
2280
3256
  ...pkgJson?.peerDependencies,
2281
3257
  ...pkgJson?.optionalDependencies
2282
3258
  };
2283
- const isBstageProject = SDK_PACKAGES.some((p2) => p2 in deps);
3259
+ const isBstageProject = SDK_PACKAGES.some((p4) => p4 in deps);
2284
3260
  const installed = /* @__PURE__ */ new Map();
2285
3261
  for (const pkg of SDK_PACKAGES) {
2286
3262
  installed.set(pkg, await readInstalledVersion(root, pkg) ?? cleanRange(deps[pkg]));
@@ -2309,9 +3285,9 @@ async function diagnose(root) {
2309
3285
  const files = EXPECTED_FILES.map((f) => ({
2310
3286
  path: f.path,
2311
3287
  note: f.note,
2312
- present: existsSync3(join5(root, f.path))
3288
+ present: existsSync3(join7(root, f.path))
2313
3289
  }));
2314
- const isPnpmProject = existsSync3(join5(root, "pnpm-lock.yaml"));
3290
+ const isPnpmProject = existsSync3(join7(root, "pnpm-lock.yaml"));
2315
3291
  let pnpmWorkspace2 = {
2316
3292
  relevant: false,
2317
3293
  allowBuildsDeclared: false,
@@ -2321,7 +3297,7 @@ async function diagnose(root) {
2321
3297
  if (isPnpmProject) {
2322
3298
  let yaml = null;
2323
3299
  try {
2324
- yaml = await readFile2(join5(root, "pnpm-workspace.yaml"), "utf-8");
3300
+ yaml = await readFile4(join7(root, "pnpm-workspace.yaml"), "utf-8");
2325
3301
  } catch {
2326
3302
  }
2327
3303
  pnpmWorkspace2 = {
@@ -2367,7 +3343,7 @@ async function diagnose(root) {
2367
3343
  };
2368
3344
  }
2369
3345
  async function diagnoseLayout(root) {
2370
- const legacyRoot = join5(root, "src", "templates");
3346
+ const legacyRoot = join7(root, "src", "templates");
2371
3347
  let legacyTemplates = [];
2372
3348
  let legacyPresent = false;
2373
3349
  try {
@@ -2379,8 +3355,8 @@ async function diagnoseLayout(root) {
2379
3355
  return {
2380
3356
  legacyPresent,
2381
3357
  legacyTemplates,
2382
- pagesPresent: existsSync3(join5(root, "src", "pages")),
2383
- slotsPresent: existsSync3(join5(root, "src", "slots"))
3358
+ pagesPresent: existsSync3(join7(root, "src", "pages")),
3359
+ slotsPresent: existsSync3(join7(root, "src", "slots"))
2384
3360
  };
2385
3361
  }
2386
3362
  async function diagnoseSkills(root) {
@@ -2389,7 +3365,7 @@ async function diagnoseSkills(root) {
2389
3365
  BSTAGE_SKILLS.map(async (skill) => {
2390
3366
  let installed;
2391
3367
  try {
2392
- installed = await readFile2(join5(root, ".claude", "skills", skill.name, "SKILL.md"), "utf-8");
3368
+ installed = await readFile4(join7(root, ".claude", "skills", skill.name, "SKILL.md"), "utf-8");
2393
3369
  } catch {
2394
3370
  return { name: skill.name, status: "missing" };
2395
3371
  }
@@ -2400,7 +3376,7 @@ async function diagnoseSkills(root) {
2400
3376
  async function diagnoseAgentsMd(root, pkgName) {
2401
3377
  let content;
2402
3378
  try {
2403
- content = await readFile2(join5(root, "AGENTS.md"), "utf-8");
3379
+ content = await readFile4(join7(root, "AGENTS.md"), "utf-8");
2404
3380
  } catch {
2405
3381
  return { status: "missing", managedBlock: null };
2406
3382
  }
@@ -2414,7 +3390,7 @@ async function diagnoseAgentsMd(root, pkgName) {
2414
3390
  }
2415
3391
  async function readInstalledVersion(root, pkg) {
2416
3392
  try {
2417
- const raw = await readFile2(join5(root, "node_modules", pkg, "package.json"), "utf-8");
3393
+ const raw = await readFile4(join7(root, "node_modules", pkg, "package.json"), "utf-8");
2418
3394
  const parsed = JSON.parse(raw);
2419
3395
  return parsed.version ?? null;
2420
3396
  } catch {
@@ -2489,18 +3465,18 @@ function normalizeMigrationPkg(short) {
2489
3465
  return short.startsWith("@") ? short : `@bstage-sdk/${short}`;
2490
3466
  }
2491
3467
  async function readBundledMigrationDoc() {
2492
- const here = dirname2(fileURLToPath(import.meta.url));
3468
+ const here = dirname4(fileURLToPath(import.meta.url));
2493
3469
  const candidates = [
2494
- join5(here, "MIGRATION.md"),
3470
+ join7(here, "MIGRATION.md"),
2495
3471
  // 배포물: dist/ 옆 (cli/dist/MIGRATION.md)
2496
- join5(here, "..", "..", "..", "docs", "MIGRATION.md"),
3472
+ join7(here, "..", "..", "..", "docs", "MIGRATION.md"),
2497
3473
  // dev(dist): cli/dist → repo/docs
2498
- join5(here, "..", "..", "..", "..", "docs", "MIGRATION.md")
3474
+ join7(here, "..", "..", "..", "..", "docs", "MIGRATION.md")
2499
3475
  // dev(src): cli/src/commands → repo/docs
2500
3476
  ];
2501
3477
  for (const path of candidates) {
2502
3478
  try {
2503
- return await readFile2(path, "utf-8");
3479
+ return await readFile4(path, "utf-8");
2504
3480
  } catch {
2505
3481
  }
2506
3482
  }
@@ -2522,172 +3498,172 @@ function compareSemver(a, b) {
2522
3498
  }
2523
3499
  function printReport(r) {
2524
3500
  console.log("");
2525
- console.log(`${pc5.cyan(pc5.bold("bstage doctor"))} ${pc5.dim(`\u2014 ${r.project ?? "(\uC774\uB984 \uC5C6\uC74C)"}`)}`);
3501
+ console.log(`${pc9.cyan(pc9.bold("bstage doctor"))} ${pc9.dim(`\u2014 ${r.project ?? "(\uC774\uB984 \uC5C6\uC74C)"}`)}`);
2526
3502
  console.log("");
2527
3503
  if (!r.isBstageProject) {
2528
- console.log(pc5.yellow("\uC774 \uB514\uB809\uD1A0\uB9AC\uB294 bstage \uD15C\uD50C\uB9BF \uD504\uB85C\uC81D\uD2B8\uAC00 \uC544\uB2CC \uAC83 \uAC19\uC2B5\uB2C8\uB2E4."));
2529
- console.log(pc5.dim(" package.json\uC5D0 @bstage-sdk/* \uC758\uC874\uC131\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
3504
+ console.log(pc9.yellow("\uC774 \uB514\uB809\uD1A0\uB9AC\uB294 bstage \uD15C\uD50C\uB9BF \uD504\uB85C\uC81D\uD2B8\uAC00 \uC544\uB2CC \uAC83 \uAC19\uC2B5\uB2C8\uB2E4."));
3505
+ console.log(pc9.dim(" package.json\uC5D0 @bstage-sdk/* \uC758\uC874\uC131\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2530
3506
  return;
2531
3507
  }
2532
- console.log(pc5.bold("\u{1F4E6} SDK \uBC84\uC804"));
3508
+ console.log(pc9.bold("\u{1F4E6} SDK \uBC84\uC804"));
2533
3509
  if (!r.latestChecked) {
2534
- console.log(pc5.dim(" \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC870\uD68C\uB97C \uAC74\uB108\uB6F0\uC5B4 \uC124\uCE58 \uBC84\uC804\uB9CC \uD45C\uC2DC\uD569\uB2C8\uB2E4."));
3510
+ console.log(pc9.dim(" \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC870\uD68C\uB97C \uAC74\uB108\uB6F0\uC5B4 \uC124\uCE58 \uBC84\uC804\uB9CC \uD45C\uC2DC\uD569\uB2C8\uB2E4."));
2535
3511
  }
2536
3512
  const nameWidth = Math.max(12, ...r.versions.map((v) => v.pkg.replace("@bstage-sdk/", "").length));
2537
3513
  for (const v of r.versions) {
2538
3514
  const name = v.pkg.replace("@bstage-sdk/", "").padEnd(nameWidth);
2539
3515
  const inst = v.installed ?? "?";
2540
3516
  if (v.latest && v.installed) {
2541
- const arrow = v.outdated ? pc5.yellow(`\u2192 ${v.latest} (\uB4A4\uCC98\uC9D0)`) : pc5.green("(\uCD5C\uC2E0)");
3517
+ const arrow = v.outdated ? pc9.yellow(`\u2192 ${v.latest} (\uB4A4\uCC98\uC9D0)`) : pc9.green("(\uCD5C\uC2E0)");
2542
3518
  console.log(` ${name} ${inst.padEnd(8)} ${arrow}`);
2543
3519
  } else {
2544
3520
  console.log(` ${name} ${inst}`);
2545
3521
  }
2546
3522
  }
2547
3523
  console.log("");
2548
- console.log(pc5.bold("\u{1F4C1} init \uAE30\uBCF8 \uD30C\uC77C (\uCC38\uACE0)"));
3524
+ console.log(pc9.bold("\u{1F4C1} init \uAE30\uBCF8 \uD30C\uC77C (\uCC38\uACE0)"));
2549
3525
  const absent = r.files.filter((f) => !f.present);
2550
3526
  if (absent.length === 0) {
2551
- console.log(pc5.green(" \u2713 init \uAE30\uBCF8 \uAD6C\uC131\uACFC \uB3D9\uC77C"));
3527
+ console.log(pc9.green(" \u2713 init \uAE30\uBCF8 \uAD6C\uC131\uACFC \uB3D9\uC77C"));
2552
3528
  } else {
2553
- console.log(pc5.dim(" init \uAE30\uBCF8\uAC12\uACFC\uC758 \uCC28\uC774\uC77C \uBFD0 \u2014 \uD504\uB85C\uC81D\uD2B8\uAC00 \uB2E4\uB974\uAC8C \uCC98\uB9AC \uC911\uC774\uBA74 \uBB34\uC2DC\uD558\uC138\uC694."));
3529
+ console.log(pc9.dim(" init \uAE30\uBCF8\uAC12\uACFC\uC758 \uCC28\uC774\uC77C \uBFD0 \u2014 \uD504\uB85C\uC81D\uD2B8\uAC00 \uB2E4\uB974\uAC8C \uCC98\uB9AC \uC911\uC774\uBA74 \uBB34\uC2DC\uD558\uC138\uC694."));
2554
3530
  for (const f of r.files) {
2555
- if (f.present) console.log(` ${pc5.green("\u2713")} ${f.path}`);
2556
- else console.log(` ${pc5.dim("\xB7")} ${f.path.padEnd(20)} ${pc5.dim(`\uC5C6\uC74C (${f.note})`)}`);
3531
+ if (f.present) console.log(` ${pc9.green("\u2713")} ${f.path}`);
3532
+ else console.log(` ${pc9.dim("\xB7")} ${f.path.padEnd(20)} ${pc9.dim(`\uC5C6\uC74C (${f.note})`)}`);
2557
3533
  }
2558
3534
  }
2559
3535
  console.log("");
2560
- console.log(pc5.bold("\u{1F5C2} \uD15C\uD50C\uB9BF \uB514\uB809\uD1A0\uB9AC"));
3536
+ console.log(pc9.bold("\u{1F5C2} \uD15C\uD50C\uB9BF \uB514\uB809\uD1A0\uB9AC"));
2561
3537
  if (r.layout.legacyPresent) {
2562
- console.log(` ${pc5.yellow("\u2192")} ${pc5.yellow("src/templates/ \uAC00 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4 (\uC61B \uAD6C\uC870)")}`);
2563
- console.log(pc5.dim(" \uBE4C\uB4DC \uC0B0\uCD9C\uBB3C \uACBD\uB85C\uAC00 \uD3F4\uB354 \uAD6C\uC870\uB97C \uB530\uB77C\uAC00\uB3C4\uB85D \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4."));
2564
- console.log(pc5.dim(" \uD398\uC774\uC9C0 \u2192 src/pages/{\uACBD\uB85C}/ \xB7 \uC704\uC82F \u2192 src/slots/{\uC544\uBB34\uC774\uB984}/"));
3538
+ console.log(` ${pc9.yellow("\u2192")} ${pc9.yellow("src/templates/ \uAC00 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4 (\uC61B \uAD6C\uC870)")}`);
3539
+ console.log(pc9.dim(" \uBE4C\uB4DC \uC0B0\uCD9C\uBB3C \uACBD\uB85C\uAC00 \uD3F4\uB354 \uAD6C\uC870\uB97C \uB530\uB77C\uAC00\uB3C4\uB85D \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4."));
3540
+ console.log(pc9.dim(" \uD398\uC774\uC9C0 \u2192 src/pages/{\uACBD\uB85C}/ \xB7 \uC704\uC82F \u2192 src/slots/{\uC544\uBB34\uC774\uB984}/"));
2565
3541
  if (r.layout.legacyTemplates.length > 0) {
2566
- console.log(pc5.dim(` \uC62E\uACA8\uC57C \uD560 \uD15C\uD50C\uB9BF: ${r.layout.legacyTemplates.join(", ")}`));
3542
+ console.log(pc9.dim(` \uC62E\uACA8\uC57C \uD560 \uD15C\uD50C\uB9BF: ${r.layout.legacyTemplates.join(", ")}`));
2567
3543
  }
2568
- console.log(pc5.dim(' \uBCC0\uD658: \uC5D0\uC774\uC804\uD2B8\uC5D0\uAC8C "bstage \uB9C8\uC774\uADF8\uB808\uC774\uC158" \uC694\uCCAD'));
3544
+ console.log(pc9.dim(' \uBCC0\uD658: \uC5D0\uC774\uC804\uD2B8\uC5D0\uAC8C "bstage \uB9C8\uC774\uADF8\uB808\uC774\uC158" \uC694\uCCAD'));
2569
3545
  } else if (!r.layout.pagesPresent && !r.layout.slotsPresent) {
2570
- console.log(` ${pc5.dim("\xB7")} src/pages/ \xB7 src/slots/ \uB458 \uB2E4 \uC5C6\uC74C`);
3546
+ console.log(` ${pc9.dim("\xB7")} src/pages/ \xB7 src/slots/ \uB458 \uB2E4 \uC5C6\uC74C`);
2571
3547
  } else {
2572
- console.log(pc5.green(" \u2713 src/pages/ \xB7 src/slots/ \uAD6C\uC870"));
3548
+ console.log(pc9.green(" \u2713 src/pages/ \xB7 src/slots/ \uAD6C\uC870"));
2573
3549
  }
2574
3550
  console.log("");
2575
3551
  if (r.pnpmWorkspace.relevant) {
2576
- console.log(pc5.bold("\u{1F4E6} pnpm workspace \uC124\uC815"));
3552
+ console.log(pc9.bold("\u{1F4E6} pnpm workspace \uC124\uC815"));
2577
3553
  if (r.pnpmWorkspace.allowBuildsDeclared) {
2578
- console.log(pc5.green(" \u2713 pnpm-workspace.yaml\uC5D0 allowBuilds \uC120\uC5B8\uB428"));
3554
+ console.log(pc9.green(" \u2713 pnpm-workspace.yaml\uC5D0 allowBuilds \uC120\uC5B8\uB428"));
2579
3555
  } else {
2580
- console.log(` ${pc5.yellow("\u2192")} ${pc5.yellow("pnpm-workspace.yaml\uC5D0 allowBuilds \uC5C6\uC74C")}`);
3556
+ console.log(` ${pc9.yellow("\u2192")} ${pc9.yellow("pnpm-workspace.yaml\uC5D0 allowBuilds \uC5C6\uC74C")}`);
2581
3557
  console.log(
2582
- pc5.dim(" pnpm\uC740 \uC758\uC874\uC131 build script\uB97C \uAE30\uBCF8 \uCC28\uB2E8\uD558\uACE0, pnpm 11\uC740 \uADF8\uAC83\uC744 \uC5D0\uB7EC\uB85C \uB0B8\uB2E4")
3558
+ pc9.dim(" pnpm\uC740 \uC758\uC874\uC131 build script\uB97C \uAE30\uBCF8 \uCC28\uB2E8\uD558\uACE0, pnpm 11\uC740 \uADF8\uAC83\uC744 \uC5D0\uB7EC\uB85C \uB0B8\uB2E4")
2583
3559
  );
2584
3560
  console.log(
2585
- pc5.dim(" \u2192 `pnpm install`\uC774 ERR_PNPM_IGNORED_BUILDS\uB85C \uC2E4\uD328\uD558\uACE0 `pnpm dev`\uB3C4 \uC8FD\uB294\uB2E4")
3561
+ pc9.dim(" \u2192 `pnpm install`\uC774 ERR_PNPM_IGNORED_BUILDS\uB85C \uC2E4\uD328\uD558\uACE0 `pnpm dev`\uB3C4 \uC8FD\uB294\uB2E4")
2586
3562
  );
2587
- console.log(pc5.dim(" \uCD94\uAC00: pnpm-workspace.yaml\uC5D0 `allowBuilds:` + `esbuild: true`"));
2588
- console.log(pc5.dim(" \uB610\uB294 `pnpm approve-builds`\uB85C \uB300\uD654\uC2DD \uC120\uD0DD"));
3563
+ console.log(pc9.dim(" \uCD94\uAC00: pnpm-workspace.yaml\uC5D0 `allowBuilds:` + `esbuild: true`"));
3564
+ console.log(pc9.dim(" \uB610\uB294 `pnpm approve-builds`\uB85C \uB300\uD654\uC2DD \uC120\uD0DD"));
2589
3565
  }
2590
3566
  if (r.pnpmWorkspace.filePresent && !r.pnpmWorkspace.packagesDeclared) {
2591
- console.log(` ${pc5.yellow("\u2192")} ${pc5.yellow("pnpm-workspace.yaml\uC5D0 packages \uC5C6\uC74C")}`);
3567
+ console.log(` ${pc9.yellow("\u2192")} ${pc9.yellow("pnpm-workspace.yaml\uC5D0 packages \uC5C6\uC74C")}`);
2592
3568
  console.log(
2593
- pc5.dim(" pnpm 9\uB294 \uC774 \uD30C\uC77C\uC774 \uC788\uC73C\uBA74 workspace \uB8E8\uD2B8\uB85C \uBCF4\uACE0 packages\uB97C \uC694\uAD6C\uD55C\uB2E4")
3569
+ pc9.dim(" pnpm 9\uB294 \uC774 \uD30C\uC77C\uC774 \uC788\uC73C\uBA74 workspace \uB8E8\uD2B8\uB85C \uBCF4\uACE0 packages\uB97C \uC694\uAD6C\uD55C\uB2E4")
2594
3570
  );
2595
- console.log(pc5.dim(" \u2192 pnpm 9\uC5D0\uC11C `ERROR packages field missing or empty`\uB85C \uC8FD\uB294\uB2E4"));
2596
- console.log(pc5.dim(" \uCD94\uAC00: pnpm-workspace.yaml \uB9E8 \uC704\uC5D0 `packages:` + `- '.'`"));
3571
+ console.log(pc9.dim(" \u2192 pnpm 9\uC5D0\uC11C `ERROR packages field missing or empty`\uB85C \uC8FD\uB294\uB2E4"));
3572
+ console.log(pc9.dim(" \uCD94\uAC00: pnpm-workspace.yaml \uB9E8 \uC704\uC5D0 `packages:` + `- '.'`"));
2597
3573
  } else if (r.pnpmWorkspace.packagesDeclared) {
2598
- console.log(pc5.green(" \u2713 pnpm-workspace.yaml\uC5D0 packages \uC120\uC5B8\uB428"));
3574
+ console.log(pc9.green(" \u2713 pnpm-workspace.yaml\uC5D0 packages \uC120\uC5B8\uB428"));
2599
3575
  }
2600
3576
  console.log("");
2601
3577
  }
2602
- console.log(pc5.bold("\u{1F6E0} package.json \uC2A4\uD06C\uB9BD\uD2B8"));
3578
+ console.log(pc9.bold("\u{1F6E0} package.json \uC2A4\uD06C\uB9BD\uD2B8"));
2603
3579
  const scriptIssues = r.scripts.filter((sc) => sc.actual !== sc.expected);
2604
3580
  if (scriptIssues.length === 0) {
2605
- console.log(pc5.green(" \u2713 init \uAE30\uBCF8 \uAD6C\uC131\uACFC \uB3D9\uC77C"));
3581
+ console.log(pc9.green(" \u2713 init \uAE30\uBCF8 \uAD6C\uC131\uACFC \uB3D9\uC77C"));
2606
3582
  } else {
2607
3583
  for (const sc of r.scripts) {
2608
3584
  if (sc.actual === sc.expected) {
2609
- console.log(` ${pc5.green("\u2713")} ${sc.name}`);
3585
+ console.log(` ${pc9.green("\u2713")} ${sc.name}`);
2610
3586
  continue;
2611
3587
  }
2612
3588
  if (sc.actual === null) {
2613
- console.log(` ${pc5.yellow("\u2192")} ${sc.name} ${pc5.yellow("\uC5C6\uC74C")}`);
3589
+ console.log(` ${pc9.yellow("\u2192")} ${sc.name} ${pc9.yellow("\uC5C6\uC74C")}`);
2614
3590
  } else {
2615
3591
  console.log(
2616
- ` ${pc5.yellow("\u2192")} ${sc.name} ${pc5.yellow("\uB2E4\uB984")} ${pc5.dim(`\u2014 \uD604\uC7AC \`${sc.actual}\``)}`
3592
+ ` ${pc9.yellow("\u2192")} ${sc.name} ${pc9.yellow("\uB2E4\uB984")} ${pc9.dim(`\u2014 \uD604\uC7AC \`${sc.actual}\``)}`
2617
3593
  );
2618
3594
  if (sc.actual.includes("--noEmit") && !sc.actual.includes("-p ")) {
2619
3595
  console.log(
2620
- pc5.dim(
3596
+ pc9.dim(
2621
3597
  " \uC8FC\uC758: solution-style tsconfig\uC5D0\uC11C `tsc --noEmit`\uC740 \uC544\uBB34 \uD30C\uC77C\uB3C4 \uAC80\uC0AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
2622
3598
  )
2623
3599
  );
2624
3600
  }
2625
3601
  }
2626
- console.log(pc5.dim(` ${sc.why}`));
2627
- console.log(pc5.dim(` \uAD8C\uC7A5: "${sc.name}": "${sc.expected}"`));
3602
+ console.log(pc9.dim(` ${sc.why}`));
3603
+ console.log(pc9.dim(` \uAD8C\uC7A5: "${sc.name}": "${sc.expected}"`));
2628
3604
  }
2629
3605
  }
2630
3606
  console.log("");
2631
- console.log(pc5.bold("\u{1F9F1} \uAD8C\uC7A5 \uC758\uC874\uC131"));
3607
+ console.log(pc9.bold("\u{1F9F1} \uAD8C\uC7A5 \uC758\uC874\uC131"));
2632
3608
  if (r.recommendedDeps.every((d) => d.declared)) {
2633
- console.log(pc5.green(" \u2713 \uBAA8\uB450 \uC120\uC5B8\uB428"));
3609
+ console.log(pc9.green(" \u2713 \uBAA8\uB450 \uC120\uC5B8\uB428"));
2634
3610
  } else {
2635
3611
  for (const d of r.recommendedDeps) {
2636
3612
  if (d.declared) {
2637
- console.log(` ${pc5.green("\u2713")} ${d.pkg}`);
3613
+ console.log(` ${pc9.green("\u2713")} ${d.pkg}`);
2638
3614
  continue;
2639
3615
  }
2640
- console.log(` ${pc5.yellow("\u2192")} ${d.pkg} ${pc5.yellow("\uBBF8\uC120\uC5B8")} ${pc5.dim(`\u2014 ${d.note}`)}`);
2641
- console.log(pc5.dim(` ${d.why}`));
2642
- console.log(pc5.dim(` \uCD94\uAC00: npm i ${d.pkg} (\uD504\uB85C\uC81D\uD2B8 \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800\uB85C)`));
3616
+ console.log(` ${pc9.yellow("\u2192")} ${d.pkg} ${pc9.yellow("\uBBF8\uC120\uC5B8")} ${pc9.dim(`\u2014 ${d.note}`)}`);
3617
+ console.log(pc9.dim(` ${d.why}`));
3618
+ console.log(pc9.dim(` \uCD94\uAC00: npm i ${d.pkg} (\uD504\uB85C\uC81D\uD2B8 \uD328\uD0A4\uC9C0 \uB9E4\uB2C8\uC800\uB85C)`));
2643
3619
  }
2644
3620
  }
2645
3621
  console.log("");
2646
- console.log(pc5.bold("\u{1F4D6} \uC801\uC6A9 \uAC00\uB2A5\uD55C \uB9C8\uC774\uADF8\uB808\uC774\uC158"));
3622
+ console.log(pc9.bold("\u{1F4D6} \uC801\uC6A9 \uAC00\uB2A5\uD55C \uB9C8\uC774\uADF8\uB808\uC774\uC158"));
2647
3623
  if (r.migrations.length === 0) {
2648
- console.log(pc5.dim(" \uD574\uB2F9 \uD56D\uBAA9 \uC5C6\uC74C."));
3624
+ console.log(pc9.dim(" \uD574\uB2F9 \uD56D\uBAA9 \uC5C6\uC74C."));
2649
3625
  } else {
2650
3626
  for (const m of r.migrations) {
2651
- console.log(` ${pc5.cyan(`\u2192 ${m.label}`)}`);
3627
+ console.log(` ${pc9.cyan(`\u2192 ${m.label}`)}`);
2652
3628
  for (const item of m.items) console.log(` \u2022 ${item}`);
2653
3629
  }
2654
3630
  }
2655
3631
  console.log("");
2656
- console.log(pc5.bold("\u{1F4DD} AGENTS.md"));
3632
+ console.log(pc9.bold("\u{1F4DD} AGENTS.md"));
2657
3633
  switch (r.agentsMd.status) {
2658
3634
  case "ok":
2659
- console.log(pc5.green(" \u2713 \uAD00\uB9AC \uC601\uC5ED \uCD5C\uC2E0"));
3635
+ console.log(pc9.green(" \u2713 \uAD00\uB9AC \uC601\uC5ED \uCD5C\uC2E0"));
2660
3636
  break;
2661
3637
  case "missing":
2662
- console.log(pc5.dim(" \xB7 \uD30C\uC77C \uC5C6\uC74C \u2014 bstage \uD504\uB85C\uC81D\uD2B8\uB77C\uBA74 init/\uB9C8\uC774\uADF8\uB808\uC774\uC158\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694."));
3638
+ console.log(pc9.dim(" \xB7 \uD30C\uC77C \uC5C6\uC74C \u2014 bstage \uD504\uB85C\uC81D\uD2B8\uB77C\uBA74 init/\uB9C8\uC774\uADF8\uB808\uC774\uC158\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694."));
2663
3639
  break;
2664
3640
  case "stale":
2665
3641
  console.log(
2666
- pc5.yellow(" \u2192 \uAD00\uB9AC \uC601\uC5ED\uC774 \uC624\uB798\uB428") + pc5.dim(" \u2014 `bstage skills install`\uB85C \uAC31\uC2E0(\uC790\uC720 \uC601\uC5ED\uC740 \uBCF4\uC874).")
3642
+ pc9.yellow(" \u2192 \uAD00\uB9AC \uC601\uC5ED\uC774 \uC624\uB798\uB428") + pc9.dim(" \u2014 `bstage skills install`\uB85C \uAC31\uC2E0(\uC790\uC720 \uC601\uC5ED\uC740 \uBCF4\uC874).")
2667
3643
  );
2668
3644
  break;
2669
3645
  case "legacy":
2670
3646
  console.log(
2671
- pc5.yellow(" \u2192 \uB808\uAC70\uC2DC \uD3EC\uB9F7(\uAD00\uB9AC \uC601\uC5ED \uB9C8\uCEE4 \uC5C6\uC74C)") + pc5.dim(
3647
+ pc9.yellow(" \u2192 \uB808\uAC70\uC2DC \uD3EC\uB9F7(\uAD00\uB9AC \uC601\uC5ED \uB9C8\uCEE4 \uC5C6\uC74C)") + pc9.dim(
2672
3648
  " \u2014 `bstage skills install`\uB85C \uC2A4\uD0AC\uC744 \uBA3C\uC800 \uC124\uCE58\uD55C \uB4A4, `bstage-migrate` \uC2A4\uD0AC\uB85C reconcile(SDK \uC601\uC5ED \uAD50\uCCB4 + \uC0AC\uC6A9\uC790 \uB0B4\uC6A9 \uBCF4\uC874)."
2673
3649
  )
2674
3650
  );
2675
3651
  break;
2676
3652
  }
2677
3653
  console.log("");
2678
- console.log(pc5.bold("\u{1F9E9} \uC5D0\uC774\uC804\uD2B8 \uC2A4\uD0AC (.claude/skills/)"));
3654
+ console.log(pc9.bold("\u{1F9E9} \uC5D0\uC774\uC804\uD2B8 \uC2A4\uD0AC (.claude/skills/)"));
2679
3655
  const skillsNeedWork = r.skills.filter((s) => s.status !== "ok");
2680
3656
  if (skillsNeedWork.length === 0) {
2681
- console.log(pc5.green(" \u2713 \uBAA8\uB4E0 \uC2A4\uD0AC \uCD5C\uC2E0"));
3657
+ console.log(pc9.green(" \u2713 \uBAA8\uB4E0 \uC2A4\uD0AC \uCD5C\uC2E0"));
2682
3658
  } else {
2683
3659
  for (const s of r.skills) {
2684
- if (s.status === "ok") console.log(` ${pc5.green("\u2713")} ${s.name}`);
3660
+ if (s.status === "ok") console.log(` ${pc9.green("\u2713")} ${s.name}`);
2685
3661
  else if (s.status === "missing")
2686
- console.log(` ${pc5.yellow("\u2192")} ${s.name.padEnd(16)} ${pc5.yellow("\uBBF8\uC124\uCE58")}`);
2687
- else console.log(` ${pc5.yellow("\u2192")} ${s.name.padEnd(16)} ${pc5.yellow("\uAD6C\uBC84\uC804/\uC218\uC815\uB428")}`);
3662
+ console.log(` ${pc9.yellow("\u2192")} ${s.name.padEnd(16)} ${pc9.yellow("\uBBF8\uC124\uCE58")}`);
3663
+ else console.log(` ${pc9.yellow("\u2192")} ${s.name.padEnd(16)} ${pc9.yellow("\uAD6C\uBC84\uC804/\uC218\uC815\uB428")}`);
2688
3664
  }
2689
3665
  console.log(
2690
- pc5.dim(
3666
+ pc9.dim(
2691
3667
  " \uC544\uB798\uC5D0\uC11C \uC790\uB3D9 \uB3D9\uAE30\uD654\uD569\uB2C8\uB2E4 \u2014 bstage-* \uC2A4\uD0AC\uB9CC \uB36E\uC5B4\uC4F0\uBA70, \uADF8 \uC678 \uC0AC\uC6A9\uC790 \uC2A4\uD0AC\uC740 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
2692
3668
  )
2693
3669
  );
@@ -2697,26 +3673,741 @@ function printReport(r) {
2697
3673
  const hasWork = r.versions.some((v) => v.outdated) || r.migrations.length > 0 || agentsNeedsWork;
2698
3674
  if (hasWork) {
2699
3675
  console.log(
2700
- pc5.dim(
3676
+ pc9.dim(
2701
3677
  "\uB2E4\uC74C \uB2E8\uACC4: \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC2A4\uD0AC\uC744 \uC2E4\uD589\uD574 \uC704 \uD56D\uBAA9\uC744 \uD504\uB85C\uC81D\uD2B8\uC5D0 \uB9DE\uCDB0 \uC801\uC6A9\uD558\uC138\uC694.\n \uC0C1\uC138 \uAC00\uC774\uB4DC: SDK \uBB38\uC11C\uC758 MIGRATION.md (\uC704 \uD56D\uBAA9\uBCC4 \uC801\uC6A9 \uC808\uCC28)"
2702
3678
  )
2703
3679
  );
2704
3680
  } else {
2705
- console.log(pc5.green("\u2713 \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uB9C8\uC774\uADF8\uB808\uC774\uC158\uC774 \uD544\uC694 \uC5C6\uC2B5\uB2C8\uB2E4."));
3681
+ console.log(pc9.green("\u2713 \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4. \uB9C8\uC774\uADF8\uB808\uC774\uC158\uC774 \uD544\uC694 \uC5C6\uC2B5\uB2C8\uB2E4."));
2706
3682
  if (absent.length > 0) {
2707
- console.log(pc5.dim(" (\uC704 init \uAE30\uBCF8 \uD30C\uC77C \uCC28\uC774\uB294 \uC120\uD0DD \uC0AC\uD56D\uC785\uB2C8\uB2E4.)"));
3683
+ console.log(pc9.dim(" (\uC704 init \uAE30\uBCF8 \uD30C\uC77C \uCC28\uC774\uB294 \uC120\uD0DD \uC0AC\uD56D\uC785\uB2C8\uB2E4.)"));
2708
3684
  }
2709
3685
  if (r.recommendedDeps.some((d) => !d.declared)) {
2710
- console.log(pc5.dim(" (\uC704 \uAD8C\uC7A5 \uC758\uC874\uC131\uC740 \uBC84\uC804\uACFC \uBB34\uAD00\uD569\uB2C8\uB2E4 \u2014 \uC4F8 \uAC70\uBA74 \uB530\uB85C \uCD94\uAC00\uD558\uC138\uC694.)"));
3686
+ console.log(pc9.dim(" (\uC704 \uAD8C\uC7A5 \uC758\uC874\uC131\uC740 \uBC84\uC804\uACFC \uBB34\uAD00\uD569\uB2C8\uB2E4 \u2014 \uC4F8 \uAC70\uBA74 \uB530\uB85C \uCD94\uAC00\uD558\uC138\uC694.)"));
3687
+ }
3688
+ }
3689
+ console.log("");
3690
+ }
3691
+
3692
+ // src/commands/link.ts
3693
+ import * as p3 from "@clack/prompts";
3694
+ import pc10 from "picocolors";
3695
+ var clackSelect = async (message, items) => {
3696
+ const options = items.map((i) => ({
3697
+ value: i.value,
3698
+ label: i.label
3699
+ }));
3700
+ const v = await p3.select({ message, options });
3701
+ if (p3.isCancel(v)) fail(ExitCode.PRECONDITION, "\uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.");
3702
+ return v;
3703
+ };
3704
+ async function originOwnerRepo(exec) {
3705
+ try {
3706
+ const stdout = await exec(["remote", "get-url", "origin"]);
3707
+ const m = stdout.trim().match(/[/:]([^/:]+)\/([^/]+?)(?:\.git)?$/);
3708
+ return m ? `${m[1]}/${m[2]}` : null;
3709
+ } catch {
3710
+ return null;
3711
+ }
3712
+ }
3713
+ function findUniqueOriginMatch(repos, origin) {
3714
+ const lower = origin.toLowerCase();
3715
+ const matches = repos.filter((r) => `${r.owner}/${r.repo}`.toLowerCase() === lower);
3716
+ return matches.length === 1 ? matches[0] : null;
3717
+ }
3718
+ async function pick(label, flag, items, explicit, yes, interactive, select3) {
3719
+ if (items.length === 0)
3720
+ fail(ExitCode.PRECONDITION, `\uC120\uD0DD\uD560 ${label} \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uD3EC\uD138\uC5D0\uC11C \uBA3C\uC800 \uC5F0\uACB0\uD558\uC138\uC694.`);
3721
+ if (explicit) {
3722
+ const hit = items.find((i) => i.match.includes(explicit));
3723
+ if (!hit) {
3724
+ fail(
3725
+ ExitCode.PRECONDITION,
3726
+ `${label} '${explicit}' \uC744(\uB97C) \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uD6C4\uBCF4: ${items.map((i) => i.label).join(", ")}`
3727
+ );
3728
+ }
3729
+ return hit.value;
3730
+ }
3731
+ if (items.length === 1) return items[0].value;
3732
+ if (yes || !interactive) {
3733
+ fail(
3734
+ ExitCode.PRECONDITION,
3735
+ `${label} \uD6C4\uBCF4\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4. --${flag} \uB85C \uC9C0\uC815\uD558\uC138\uC694: ${items.map((i) => i.label).join(", ")}`
3736
+ );
3737
+ }
3738
+ return select3(`${label}\uC744(\uB97C) \uC120\uD0DD\uD558\uC138\uC694`, items);
3739
+ }
3740
+ async function resolveRepoSelection(repos, ctx) {
3741
+ const explicit = ctx.options.repo ?? ctx.env.BSTAGE_REPO;
3742
+ if (!explicit && repos.length > 1) {
3743
+ const origin = await originOwnerRepo(ctx.exec);
3744
+ const match = origin ? findUniqueOriginMatch(repos, origin) : null;
3745
+ if (match) return { repo: match, fromOrigin: true };
3746
+ }
3747
+ const items = repos.map((r) => ({
3748
+ value: r,
3749
+ label: `${r.owner}/${r.repo} [${r.surface}] (${r.defaultBranch})`,
3750
+ match: [r.id, `${r.owner}/${r.repo}`]
3751
+ }));
3752
+ const repo = await pick(
3753
+ "\uB808\uD3EC",
3754
+ "repo",
3755
+ items,
3756
+ explicit,
3757
+ ctx.options.yes,
3758
+ ctx.interactive,
3759
+ ctx.select
3760
+ );
3761
+ return { repo, fromOrigin: false };
3762
+ }
3763
+ async function resolveSelections(ctx) {
3764
+ const { client, options, env: env2, interactive, select: select3 } = ctx;
3765
+ const me = await client.me();
3766
+ const organizationId = await pick(
3767
+ "\uC870\uC9C1",
3768
+ "org",
3769
+ me.memberships.map((m) => ({
3770
+ value: m.organizationId,
3771
+ label: m.organizationId,
3772
+ match: [m.organizationId]
3773
+ })),
3774
+ options.org ?? env2.BSTAGE_ORG,
3775
+ options.yes,
3776
+ interactive,
3777
+ select3
3778
+ );
3779
+ const spaces = await client.listSpaces(organizationId);
3780
+ const spaceId = await pick(
3781
+ "\uC2A4\uD14C\uC774\uC9C0",
3782
+ "space",
3783
+ spaces.map((s) => ({
3784
+ value: s.spaceId,
3785
+ label: `${s.spaceId} (${s.tier})`,
3786
+ match: [s.spaceId]
3787
+ })),
3788
+ options.space ?? env2.BSTAGE_SPACE,
3789
+ options.yes,
3790
+ interactive,
3791
+ select3
3792
+ );
3793
+ const repos = await client.listRepos(organizationId, spaceId);
3794
+ const { repo, fromOrigin } = await resolveRepoSelection(repos, ctx);
3795
+ return { organizationId, spaceId, repo, repoFromOrigin: fromOrigin };
3796
+ }
3797
+ function printLinked(link, repoLabel, repoFromOrigin, json) {
3798
+ if (json) {
3799
+ printJson(link);
3800
+ return;
3801
+ }
3802
+ if (repoFromOrigin) {
3803
+ console.log(pc10.dim(` git origin(${repoLabel})\uACFC \uC77C\uCE58\uD558\uB294 \uB808\uD3EC\uB97C \uAE30\uBCF8\uAC12\uC73C\uB85C \uC120\uD0DD\uD588\uC2B5\uB2C8\uB2E4.`));
3804
+ }
3805
+ console.log(
3806
+ pc10.green(
3807
+ `\u2713 \uC5F0\uACB0\uD588\uC2B5\uB2C8\uB2E4: ${link.organizationId} / ${link.spaceId} / ${repoLabel} [${link.surface}]`
3808
+ )
3809
+ );
3810
+ console.log(
3811
+ pc10.dim(
3812
+ " .bstage/project.json \uC5D0 \uC800\uC7A5 (gitignore \uB300\uC0C1). CI\uC5D0\uC11C\uB294 BSTAGE_ORG\xB7BSTAGE_SPACE\xB7BSTAGE_REPO\xB7BSTAGE_SURFACE\xB7BSTAGE_PORTAL_URL \uD658\uACBD\uBCC0\uC218\uB97C \uC4F0\uC138\uC694."
3813
+ )
3814
+ );
3815
+ }
3816
+ async function linkCommand(options, deps = {}) {
3817
+ if (options.json && !options.yes) {
3818
+ fail(ExitCode.PRECONDITION, "--json \uC740 --yes \uC640 \uD568\uAED8 \uC4F0\uC138\uC694.");
3819
+ }
3820
+ const cwd = deps.cwd ?? process.cwd();
3821
+ const env2 = deps.env ?? process.env;
3822
+ const interactive = deps.select !== void 0 || p3.isTTY(process.stdout);
3823
+ const select3 = deps.select ?? clackSelect;
3824
+ const exec = deps.exec ?? gitExec(cwd);
3825
+ const { client, portalUrl } = await resolveClientOnly({
3826
+ portal: options.portal,
3827
+ phase: options.phase,
3828
+ // 링크 파일이 아직 없을 수 있으니 조직은 사람이 준 값으로 정한다 — 여러 조직 토큰이
3829
+ // 저장돼 있을 때 --org 안내가 실제로 통하게 하는 자리다.
3830
+ organizationId: options.org ?? env2.BSTAGE_ORG,
3831
+ cwd,
3832
+ env: env2,
3833
+ fetch: deps.fetch
3834
+ });
3835
+ const { organizationId, spaceId, repo, repoFromOrigin } = await resolveSelections({
3836
+ client,
3837
+ exec,
3838
+ env: env2,
3839
+ options,
3840
+ interactive,
3841
+ select: select3
3842
+ });
3843
+ const link = {
3844
+ portalUrl,
3845
+ organizationId,
3846
+ spaceId,
3847
+ repoId: repo.id,
3848
+ surface: repo.surface
3849
+ };
3850
+ await saveLink(cwd, link);
3851
+ printLinked(link, `${repo.owner}/${repo.repo}`, repoFromOrigin, options.json);
3852
+ }
3853
+
3854
+ // src/commands/list.ts
3855
+ import pc11 from "picocolors";
3856
+
3857
+ // src/portal/buildRepo.ts
3858
+ function buildMatchesRepo(b, repo) {
3859
+ if (b.stageRepoId) return b.stageRepoId === repo.id;
3860
+ if (!b.owner || !b.repo) return false;
3861
+ if (b.owner.toLowerCase() !== repo.owner.toLowerCase()) return false;
3862
+ if (b.repo.toLowerCase() !== repo.repo.toLowerCase()) return false;
3863
+ if (!b.surface || b.surface !== repo.surface) return false;
3864
+ if (!b.spaceId || b.spaceId !== repo.spaceId) return false;
3865
+ return true;
3866
+ }
3867
+ async function resolveLinkedRepo(ctx) {
3868
+ const repos = await ctx.client.listRepos(ctx.link.organizationId, ctx.link.spaceId);
3869
+ const repo = repos.find((r) => r.id === ctx.link.repoId);
3870
+ if (!repo) {
3871
+ fail(
3872
+ ExitCode.PRECONDITION,
3873
+ "\uC5F0\uACB0\uB41C \uB808\uD3EC\uB97C \uC2A4\uD14C\uC774\uC9C0\uC5D0\uC11C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. bstage link \uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694."
3874
+ );
3875
+ }
3876
+ return repo;
3877
+ }
3878
+
3879
+ // src/commands/list.ts
3880
+ function renderList(placements, builds, repo) {
3881
+ const mine = placements.filter((p4) => p4.stageRepoId === repo.id);
3882
+ const lines = [];
3883
+ lines.push(pc11.bold("\uBC30\uCE58"));
3884
+ if (mine.length === 0) {
3885
+ lines.push(
3886
+ pc11.yellow(
3887
+ " \uC774 \uB808\uD3EC\uC5D0 \uC5F0\uACB0\uB41C \uBC30\uCE58\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uD3EC\uD138 > \uC2A4\uD14C\uC774\uC9C0 > \uD398\uC774\uC9C0\uC5D0\uC11C \uBC30\uCE58\uB97C \uBA3C\uC800 \uB9CC\uB4DC\uC138\uC694."
3888
+ )
3889
+ );
3890
+ } else {
3891
+ lines.push(
3892
+ table([
3893
+ ["\uC885\uB958", "\uC790\uB9AC", "\uC0C1\uD0DC", "\uB77C\uC774\uBE0C \uCEE4\uBC0B", "\uAC8C\uC2DC"],
3894
+ ...mine.map((p4) => [
3895
+ p4.kind,
3896
+ placementLabel(p4),
3897
+ p4.status,
3898
+ shortSha(p4.liveCommit),
3899
+ p4.enabled === false ? "off" : "on"
3900
+ ])
3901
+ ])
3902
+ );
3903
+ }
3904
+ lines.push("");
3905
+ lines.push(pc11.bold("\uCD5C\uADFC \uBE4C\uB4DC"));
3906
+ const recent = builds.filter((b) => buildMatchesRepo(b, repo));
3907
+ lines.push(
3908
+ recent.length === 0 ? pc11.dim(" \uBE4C\uB4DC \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.") : table([
3909
+ ["\uBE4C\uB4DC", "\uC0C1\uD0DC", "\uCEE4\uBC0B", "\uC694\uCCAD \uC2DC\uAC01"],
3910
+ ...recent.map((b) => [b.id, b.status, shortSha(b.commitSha), b.requestedAt])
3911
+ ])
3912
+ );
3913
+ return lines.join("\n");
3914
+ }
3915
+ async function listCommand(options, deps = {}) {
3916
+ const out = deps.out ?? console.log;
3917
+ const { client, link } = await resolveContext({
3918
+ cwd: deps.cwd,
3919
+ env: deps.env,
3920
+ fetch: deps.fetch
3921
+ });
3922
+ const limit = parseInt(options.builds ?? "5", 10) || 5;
3923
+ const [placements, builds, repo] = await Promise.all([
3924
+ client.listPlacements(link.organizationId, link.spaceId, link.surface),
3925
+ client.listBuilds(link.organizationId, link.spaceId, limit),
3926
+ resolveLinkedRepo({ client, link })
3927
+ ]);
3928
+ const mine = placements.filter((p4) => p4.stageRepoId === repo.id);
3929
+ const myBuilds = builds.filter((b) => buildMatchesRepo(b, repo));
3930
+ if (options.json) {
3931
+ printJson({ link, placements: mine, builds: myBuilds });
3932
+ return;
3933
+ }
3934
+ out(pc11.dim(`${link.organizationId} / ${link.spaceId} [${link.surface}]`));
3935
+ out(renderList(placements, builds, repo));
3936
+ }
3937
+
3938
+ // src/commands/logs.ts
3939
+ import pc12 from "picocolors";
3940
+ function statusLine(build) {
3941
+ return `[bstage] \uBE4C\uB4DC ${build.id} \xB7 ${build.status}${build.failureReason ? ` \xB7 ${build.failureReason}` : ""}`;
3942
+ }
3943
+ async function latestBuildId(ctx) {
3944
+ const { organizationId: org, spaceId: space } = ctx.link;
3945
+ const repo = await resolveLinkedRepo(ctx);
3946
+ const latest = (await ctx.client.listBuilds(org, space, 20)).find(
3947
+ (b) => buildMatchesRepo(b, repo)
3948
+ );
3949
+ if (!latest) fail(ExitCode.PRECONDITION, "\uC774 \uB808\uD3EC\uC758 \uBE4C\uB4DC \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
3950
+ return latest.id;
3951
+ }
3952
+ function logSink(json) {
3953
+ return json ? (delta) => void process.stderr.write(pc12.dim(delta)) : (delta) => void process.stdout.write(delta);
3954
+ }
3955
+ async function logsCommand(buildId, options, deps = {}) {
3956
+ const out = deps.out ?? ((s) => void process.stdout.write(s));
3957
+ const ctx = await resolveContext({ cwd: deps.cwd, env: deps.env, fetch: deps.fetch });
3958
+ const { organizationId: org, spaceId: space } = ctx.link;
3959
+ const id = buildId ?? await latestBuildId(ctx);
3960
+ const initial = await ctx.client.getBuild(org, space, id);
3961
+ const following = Boolean(options.follow) && !TERMINAL_STATUSES.has(initial.status);
3962
+ const build = following ? await waitForBuild(ctx.client, org, space, id, {
3963
+ sleep: deps.sleep,
3964
+ onLog: logSink(options.json)
3965
+ }) : initial;
3966
+ await report(ctx.client, org, space, id, build, options, out, following);
3967
+ if (TERMINAL_STATUSES.has(build.status) && build.status !== "SUCCEEDED") {
3968
+ process.exitCode = ExitCode.FAILURE;
3969
+ }
3970
+ }
3971
+ async function report(client, org, space, id, build, options, out, following) {
3972
+ if (options.json) {
3973
+ printJson({ build, log: await client.getBuildLog(org, space, id) });
3974
+ return;
3975
+ }
3976
+ if (following) {
3977
+ console.error(pc12.dim(`
3978
+ ${statusLine(build)}`));
3979
+ return;
3980
+ }
3981
+ const log2 = await client.getBuildLog(org, space, id);
3982
+ out(log2.endsWith("\n") ? log2 : log2 + "\n");
3983
+ console.error(pc12.dim(statusLine(build)));
3984
+ }
3985
+
3986
+ // src/commands/login.ts
3987
+ import { hostname as osHostname } from "os";
3988
+ import pc13 from "picocolors";
3989
+
3990
+ // src/portal/openBrowser.ts
3991
+ import { execFile as execFile2 } from "child_process";
3992
+ var SHELL_METACHARS = /[&|^<>%!"'`\\]/;
3993
+ function isWebUrl(url) {
3994
+ try {
3995
+ const u = new URL(url);
3996
+ return u.protocol === "http:" || u.protocol === "https:";
3997
+ } catch {
3998
+ return false;
3999
+ }
4000
+ }
4001
+ function openBrowser(url) {
4002
+ if (!isWebUrl(url) || SHELL_METACHARS.test(url)) return Promise.resolve(false);
4003
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]] : ["xdg-open", [url]];
4004
+ return new Promise((resolve4) => {
4005
+ try {
4006
+ execFile2(cmd, args, { timeout: 5e3 }, (err) => resolve4(!err));
4007
+ } catch {
4008
+ resolve4(false);
4009
+ }
4010
+ });
4011
+ }
4012
+
4013
+ // src/commands/login.ts
4014
+ var SLOW_DOWN_STEP_MS = 5e3;
4015
+ async function verifyToken(client) {
4016
+ try {
4017
+ return await client.me();
4018
+ } catch (err) {
4019
+ if (err instanceof PortalError && (err.status === 401 || err.status === 403)) {
4020
+ fail(
4021
+ ExitCode.AUTH,
4022
+ "\uD1A0\uD070\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD3EC\uD138 \uC124\uC815 \u203A CLI \uD1A0\uD070\uC5D0\uC11C \uC0C8 \uD1A0\uD070\uC744 \uBC1C\uAE09\uD558\uC138\uC694."
4023
+ );
2711
4024
  }
4025
+ throw err;
4026
+ }
4027
+ }
4028
+ async function loginWithToken(portalUrl, token, options, deps, env2) {
4029
+ if (!token.startsWith("bsc_")) {
4030
+ fail(
4031
+ ExitCode.PRECONDITION,
4032
+ "CLI \uD1A0\uD070\uC740 bsc_ \uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4. \uD3EC\uD138 \uC124\uC815 \u203A CLI \uD1A0\uD070\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC12\uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694."
4033
+ );
2712
4034
  }
4035
+ const organizationId = options.org ?? env2.BSTAGE_ORG;
4036
+ if (!organizationId) {
4037
+ fail(
4038
+ ExitCode.PRECONDITION,
4039
+ "\uBD99\uC5EC \uB123\uC740 \uD1A0\uD070\uC774 \uC5B4\uB290 \uC870\uC9C1 \uAC83\uC778\uC9C0 \uC54C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --org <id> \uB610\uB294 BSTAGE_ORG \uB85C \uC870\uC9C1\uC744 \uC9C0\uC815\uD558\uC138\uC694."
4040
+ );
4041
+ }
4042
+ const client = new PortalClient({ portalUrl, token, fetch: deps.fetch });
4043
+ const me = await verifyToken(client);
4044
+ await saveCredential({ portalUrl, organizationId, token, savedAt: (/* @__PURE__ */ new Date()).toISOString() }, env2);
4045
+ if (options.json) {
4046
+ printJson({
4047
+ portalUrl,
4048
+ method: "token",
4049
+ organizationId,
4050
+ email: me.user.email,
4051
+ organizations: me.memberships.map((m) => m.organizationId)
4052
+ });
4053
+ return;
4054
+ }
4055
+ console.log(
4056
+ pc13.green(`\u2713 ${portalUrl} \uC5D0 ${me.user.email} \uB85C \uB85C\uADF8\uC778\uD588\uC2B5\uB2C8\uB2E4 (\uC870\uC9C1 ${organizationId}).`)
4057
+ );
4058
+ }
4059
+ function nextIntervalOnSlowDown(interval, retryAfterSec) {
4060
+ const bumped = interval + SLOW_DOWN_STEP_MS;
4061
+ return retryAfterSec !== void 0 ? Math.max(bumped, retryAfterSec * 1e3) : bumped;
4062
+ }
4063
+ function printDeviceCodePrompt(userCode, verificationUriComplete, expiresIn) {
4064
+ console.log("");
4065
+ console.log(` \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC544\uB798 \uCF54\uB4DC\uB97C \uD655\uC778\uD558\uACE0 \uC2B9\uC778\uD558\uC138\uC694.`);
4066
+ console.log("");
4067
+ console.log(` \uCF54\uB4DC ${pc13.bold(userCode)}`);
4068
+ console.log(` \uC8FC\uC18C ${pc13.underline(verificationUriComplete)}`);
2713
4069
  console.log("");
4070
+ console.log(
4071
+ pc13.dim(
4072
+ ` \uD130\uBBF8\uB110\uC758 \uCF54\uB4DC\uC640 \uBE0C\uB77C\uC6B0\uC800\uC758 \uCF54\uB4DC\uAC00 \uAC19\uC740\uC9C0 \uAF2D \uD655\uC778\uD558\uC138\uC694. ${Math.round(expiresIn / 60)}\uBD84 \uC548\uC5D0 \uC2B9\uC778\uB418\uC9C0 \uC54A\uC73C\uBA74 \uB9CC\uB8CC\uB429\uB2C8\uB2E4.`
4073
+ )
4074
+ );
4075
+ }
4076
+ async function pollUntilIssued(client, deviceCode, startInterval, expiresIn, sleep, now) {
4077
+ let interval = startInterval;
4078
+ const deadline = now() + expiresIn * 1e3;
4079
+ for (; ; ) {
4080
+ const remaining = deadline - now();
4081
+ if (remaining <= 0) {
4082
+ fail(ExitCode.PRECONDITION, "\uC2B9\uC778 \uC2DC\uAC04\uC774 \uC9C0\uB0AC\uC2B5\uB2C8\uB2E4. bstage login \uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694");
4083
+ }
4084
+ await sleep(Math.min(interval, remaining));
4085
+ let result;
4086
+ try {
4087
+ result = await client.pollDeviceToken(deviceCode);
4088
+ } catch (err) {
4089
+ if (err instanceof PortalError && err.message === "access_denied") {
4090
+ fail(ExitCode.AUTH, "\uD3EC\uD138\uC5D0\uC11C \uC774 \uB85C\uADF8\uC778 \uC694\uCCAD\uC744 \uAC70\uBD80\uD588\uC2B5\uB2C8\uB2E4.");
4091
+ }
4092
+ if (err instanceof PortalError && err.message === "expired_token") {
4093
+ fail(
4094
+ ExitCode.PRECONDITION,
4095
+ "10\uBD84 \uC548\uC5D0 \uC2B9\uC778\uB418\uC9C0 \uC54A\uC544 \uCF54\uB4DC\uAC00 \uB9CC\uB8CC\uB410\uC2B5\uB2C8\uB2E4. bstage login \uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694."
4096
+ );
4097
+ }
4098
+ throw err;
4099
+ }
4100
+ if (result.status === "slow_down") {
4101
+ interval = nextIntervalOnSlowDown(interval, result.retryAfter);
4102
+ continue;
4103
+ }
4104
+ if (result.status === "pending") continue;
4105
+ return result;
4106
+ }
4107
+ }
4108
+ async function openVerificationUri(portalUrl, verificationUriComplete, options, deps) {
4109
+ if (!sameOrigin(portalUrl, verificationUriComplete)) {
4110
+ if (!options.json) {
4111
+ console.log(
4112
+ pc13.yellow(
4113
+ " \uC2B9\uC778 \uC8FC\uC18C\uAC00 \uD3EC\uD138\uACFC \uB2E4\uB978 \uC0AC\uC774\uD2B8\uC785\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 \uC8FC\uC18C\uB97C \uD655\uC778\uD55C \uB4A4 \uC9C1\uC811 \uC5F4\uC5B4 \uC8FC\uC138\uC694."
4114
+ )
4115
+ );
4116
+ }
4117
+ return;
4118
+ }
4119
+ const opened = await (deps.openBrowser ?? openBrowser)(verificationUriComplete);
4120
+ if (!opened && !options.json) {
4121
+ console.log(pc13.dim(" \uBE0C\uB77C\uC6B0\uC800\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC704 \uC8FC\uC18C\uB97C \uC9C1\uC811 \uC5F4\uC5B4 \uC8FC\uC138\uC694."));
4122
+ }
4123
+ }
4124
+ function sameOrigin(a, b) {
4125
+ try {
4126
+ return new URL(a).origin === new URL(b).origin;
4127
+ } catch {
4128
+ return false;
4129
+ }
4130
+ }
4131
+ async function loginWithDevice(portalUrl, options, deps, env2) {
4132
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
4133
+ const anon = new PortalClient({ portalUrl, token: "", fetch: deps.fetch });
4134
+ const code = await anon.startDeviceAuth(
4135
+ (deps.hostname ?? osHostname)(),
4136
+ deps.cliVersion ?? "unknown"
4137
+ );
4138
+ if (!options.json)
4139
+ printDeviceCodePrompt(code.userCode, code.verificationUriComplete, code.expiresIn);
4140
+ if (!options.noBrowser) {
4141
+ await openVerificationUri(portalUrl, code.verificationUriComplete, options, deps);
4142
+ }
4143
+ const now = deps.now ?? Date.now;
4144
+ const issued = await pollUntilIssued(
4145
+ anon,
4146
+ code.deviceCode,
4147
+ code.interval * 1e3,
4148
+ code.expiresIn,
4149
+ sleep,
4150
+ now
4151
+ );
4152
+ const organizationId = issued.organizationId ?? options.org ?? env2.BSTAGE_ORG;
4153
+ await storeIssued(portalUrl, issued, organizationId, env2);
4154
+ reportIssued(portalUrl, issued, organizationId, options.json);
4155
+ }
4156
+ async function storeIssued(portalUrl, issued, organizationId, env2) {
4157
+ await saveCredential(
4158
+ {
4159
+ portalUrl,
4160
+ organizationId,
4161
+ token: issued.token,
4162
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
4163
+ permission: issued.permission,
4164
+ allSpaces: issued.allSpaces,
4165
+ spaceIds: issued.spaceIds,
4166
+ expiresAt: issued.expiresAt
4167
+ },
4168
+ env2
4169
+ );
4170
+ }
4171
+ function reportIssued(portalUrl, issued, organizationId, json) {
4172
+ if (json) {
4173
+ printJson({
4174
+ portalUrl,
4175
+ method: "device",
4176
+ tokenId: issued.id,
4177
+ prefix: issued.prefix,
4178
+ organizationId: organizationId ?? null,
4179
+ permission: issued.permission,
4180
+ allSpaces: issued.allSpaces ?? false,
4181
+ spaceIds: issued.spaceIds,
4182
+ expiresAt: issued.expiresAt
4183
+ });
4184
+ return;
4185
+ }
4186
+ console.log(pc13.green(`\u2713 ${portalUrl} \uC5D0 \uB85C\uADF8\uC778\uD588\uC2B5\uB2C8\uB2E4.`));
4187
+ console.log(
4188
+ pc13.dim(
4189
+ ` \uC870\uC9C1 ${orgLabel(organizationId ?? UNKNOWN_ORG)} \xB7 \uD1A0\uD070 ${issued.prefix}\u2026 \xB7 \uB4F1\uAE09 ${issued.permission} \xB7 ${scopeLabel(issued.allSpaces, issued.spaceIds)} \xB7 \uB9CC\uB8CC ${issued.expiresAt.slice(0, 10)}`
4190
+ )
4191
+ );
4192
+ console.log(pc13.dim(" \uB2E4\uC74C: \uD504\uB85C\uC81D\uD2B8 \uB514\uB809\uD130\uB9AC\uC5D0\uC11C bstage link"));
4193
+ }
4194
+ async function loginCommand(options, deps = {}) {
4195
+ const env2 = deps.env ?? process.env;
4196
+ const portalUrl = await resolvePortalUrl(
4197
+ options.portal,
4198
+ deps.cwd ?? process.cwd(),
4199
+ env2,
4200
+ options.phase,
4201
+ { allowLink: false }
4202
+ );
4203
+ const pasted = (options.token ?? env2.BSTAGE_TOKEN ?? "").trim();
4204
+ if (pasted) return loginWithToken(portalUrl, pasted, options, deps, env2);
4205
+ return loginWithDevice(portalUrl, options, deps, env2);
4206
+ }
4207
+
4208
+ // src/commands/logout.ts
4209
+ import pc14 from "picocolors";
4210
+ async function logoutCommand(options, deps = {}) {
4211
+ const cwd = deps.cwd ?? process.cwd();
4212
+ const env2 = deps.env ?? process.env;
4213
+ const portalUrl = await resolvePortalUrl(options.portal, cwd, env2, options.phase);
4214
+ if (!portalUrl) {
4215
+ fail(
4216
+ ExitCode.PRECONDITION,
4217
+ "\uD3EC\uD138 \uC8FC\uC18C\uB97C \uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: bstage logout --phase <phase> \uB610\uB294 --portal <url>"
4218
+ );
4219
+ }
4220
+ const removed = await deleteCredential(portalUrl, options.org, env2);
4221
+ if (removed.length === 0) {
4222
+ const scope = options.org ? `\uC870\uC9C1 ${options.org} \uC758 ` : "";
4223
+ console.log(pc14.yellow(`${portalUrl} \uC5D0 \uC800\uC7A5\uB41C ${scope}\uC790\uACA9\uC99D\uBA85\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`));
4224
+ } else {
4225
+ const orgs = removed.map(orgLabel).join(", ");
4226
+ console.log(pc14.green(`\u2713 ${portalUrl} \uC790\uACA9\uC99D\uBA85\uC744 \uC0AD\uC81C\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uC870\uC9C1 ${orgs}`));
4227
+ }
4228
+ console.log(pc14.dim(" \uD3EC\uD138 \uD654\uBA74\uC5D0\uC11C \uD1A0\uD070 \uC790\uCCB4\uB97C \uD3D0\uAE30\uD558\uBA74 \uB2E4\uB978 \uAE30\uAE30\uC5D0\uC11C\uB3C4 \uBB34\uD6A8\uAC00 \uB429\uB2C8\uB2E4."));
4229
+ }
4230
+
4231
+ // src/commands/publish.ts
4232
+ import pc15 from "picocolors";
4233
+ function parseOnOff(v) {
4234
+ const s = v.toLowerCase();
4235
+ if (s === "on") return true;
4236
+ if (s === "off") return false;
4237
+ return fail(ExitCode.PRECONDITION, `publish \uC778\uC790\uB294 on \uB610\uB294 off \uC5EC\uC57C \uD569\uB2C8\uB2E4 (\uBC1B\uC740 \uAC12: ${v})`);
4238
+ }
4239
+ function isPublished(p4) {
4240
+ return p4.enabled !== false;
4241
+ }
4242
+ async function confirmToggle(toChange, enabled, options, deps) {
4243
+ if (options.yes) return;
4244
+ const names = toChange.map(placementLabel).join(", ");
4245
+ const ok = await askConfirm(deps, `${names} \uAC8C\uC2DC\uB97C ${enabled ? "\uCF2D\uB2C8\uB2E4" : "\uB055\uB2C8\uB2E4"}. \uC9C4\uD589\uD560\uAE4C\uC694?`);
4246
+ if (!ok) fail(ExitCode.PRECONDITION, "\uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.");
4247
+ }
4248
+ async function togglePlacement(ctx, p4, enabled) {
4249
+ const base = { id: p4.id, label: placementLabel(p4) };
4250
+ if (isPublished(p4) === enabled) {
4251
+ return { ...base, enabled, status: "unchanged", message: "\uBCC0\uACBD \uC5C6\uC74C" };
4252
+ }
4253
+ try {
4254
+ const { organizationId: org, spaceId: space } = ctx.link;
4255
+ const updated = await ctx.client.setPlacementEnabled(org, space, p4.id, enabled);
4256
+ return { ...base, enabled: updated.enabled !== false, status: "changed" };
4257
+ } catch (err) {
4258
+ if (err instanceof PortalError && err.status === 409) {
4259
+ return { ...base, enabled: isPublished(p4), status: "conflict", message: err.message };
4260
+ }
4261
+ return {
4262
+ ...base,
4263
+ enabled: isPublished(p4),
4264
+ status: "failed",
4265
+ message: err instanceof Error ? err.message : String(err)
4266
+ };
4267
+ }
4268
+ }
4269
+ function printResults2(results, enabled, out) {
4270
+ for (const r of results) {
4271
+ const mark = r.status === "failed" ? pc15.red("\u2717") : r.status === "conflict" ? pc15.yellow("!") : pc15.green("\u2713");
4272
+ const stateLabel = r.status === "unchanged" ? "\uBCC0\uACBD \uC5C6\uC74C" : `\uAC8C\uC2DC ${r.enabled ? "on" : "off"}`;
4273
+ const suffix = r.message && r.status !== "unchanged" ? pc15.dim(` ${r.message}`) : "";
4274
+ out(`${mark} ${r.label} ${stateLabel}${suffix}`);
4275
+ }
4276
+ if (!enabled) {
4277
+ out(pc15.dim(" \uB77C\uC774\uBE0C \uBC84\uC804 \uD3EC\uC778\uD130\uB294 \uC720\uC9C0\uB429\uB2C8\uB2E4 \u2014 \uB2E4\uC2DC \uCF1C\uBA74 \uAC19\uC740 \uBE4C\uB4DC\uAC00 \uC989\uC2DC \uC11C\uBE59\uB429\uB2C8\uB2E4."));
4278
+ }
4279
+ }
4280
+ function reportOutcome2(results, options, enabled, out) {
4281
+ if (options.json) printJson(jsonSummary(results));
4282
+ else printResults2(results, enabled, out);
4283
+ const conflicts = results.filter((r) => r.status === "conflict");
4284
+ const errors = results.filter((r) => r.status === "failed");
4285
+ if (conflicts.length > 0) {
4286
+ fail(
4287
+ ExitCode.CONFLICT,
4288
+ `\uB2E4\uB978 \uBCC0\uACBD\uC774 \uBA3C\uC800 \uC801\uC6A9\uB41C \uBC30\uCE58\uAC00 \uC788\uC2B5\uB2C8\uB2E4: ${conflicts.map((r) => r.label).join(", ")}. bstage list \uB85C \uD655\uC778\uD558\uACE0 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`
4289
+ );
4290
+ }
4291
+ if (errors.length > 0) {
4292
+ fail(
4293
+ ExitCode.FAILURE,
4294
+ `\uC77C\uBD80 \uBC30\uCE58 \uC801\uC6A9\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${errors.map((r) => r.label).join(", ")}`
4295
+ );
4296
+ }
4297
+ }
4298
+ async function publishCommand(state, options, deps = {}) {
4299
+ if (options.json && !options.yes) {
4300
+ fail(ExitCode.PRECONDITION, "--json \uC740 --yes \uC640 \uD568\uAED8 \uC4F0\uC138\uC694.");
4301
+ }
4302
+ const enabled = parseOnOff(state);
4303
+ if (!options.yes) assertCanConfirm(deps);
4304
+ const out = deps.out ?? console.log;
4305
+ const ctx = await resolveContext({ cwd: deps.cwd, env: deps.env, fetch: deps.fetch });
4306
+ const targets = await fetchTargets(ctx, options.placement);
4307
+ const toChange = targets.filter((p4) => isPublished(p4) !== enabled);
4308
+ if (toChange.length > 0) await confirmToggle(toChange, enabled, options, deps);
4309
+ const results = [];
4310
+ for (const p4 of targets) {
4311
+ results.push(await togglePlacement(ctx, p4, enabled));
4312
+ }
4313
+ reportOutcome2(results, options, enabled, out);
4314
+ }
4315
+
4316
+ // src/commands/rollback.ts
4317
+ import pc16 from "picocolors";
4318
+ function pickRollbackTarget(builds, repo, currentLive) {
4319
+ const ok = builds.filter((b) => buildMatchesRepo(b, repo) && b.status === "SUCCEEDED").sort((a, b) => a.requestedAt < b.requestedAt ? 1 : -1);
4320
+ if (!currentLive) return ok[0] ?? null;
4321
+ const idx = ok.findIndex((b) => b.id === currentLive);
4322
+ if (idx === -1) return ok[0] ?? null;
4323
+ return ok[idx + 1] ?? null;
4324
+ }
4325
+ async function resolveExplicitTarget(ctx, builds, repo, buildId) {
4326
+ const { organizationId: org, spaceId: space } = ctx.link;
4327
+ let target = builds.find((b) => b.id === buildId);
4328
+ if (!target) {
4329
+ try {
4330
+ target = await ctx.client.getBuild(org, space, buildId);
4331
+ } catch (err) {
4332
+ if (err instanceof PortalError && err.status === 404) {
4333
+ fail(
4334
+ ExitCode.PRECONDITION,
4335
+ `\uBE4C\uB4DC ${buildId} \uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. bstage list \uB85C \uCD5C\uADFC \uBE4C\uB4DC\uB97C \uD655\uC778\uD558\uC138\uC694.`
4336
+ );
4337
+ }
4338
+ throw err;
4339
+ }
4340
+ }
4341
+ if (target.status !== "SUCCEEDED" || !buildMatchesRepo(target, repo)) {
4342
+ fail(ExitCode.PRECONDITION, `\uBE4C\uB4DC ${buildId} \uB294 \uC774 \uB808\uD3EC\uC758 \uC131\uACF5\uD55C \uBE4C\uB4DC\uAC00 \uC544\uB2D9\uB2C8\uB2E4.`);
4343
+ }
4344
+ return target;
4345
+ }
4346
+ function pickOrFail(builds, repo, currentLive, label) {
4347
+ const target = pickRollbackTarget(builds, repo, currentLive);
4348
+ if (!target) fail(ExitCode.PRECONDITION, `${label}: \uB418\uB3CC\uB9B4 \uC774\uC804 \uC131\uACF5 \uBE4C\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`);
4349
+ return target;
4350
+ }
4351
+ async function buildRollbackPlan(ctx, placements, builds, repo, buildId) {
4352
+ const sharedTarget = buildId ? await resolveExplicitTarget(ctx, builds, repo, buildId) : void 0;
4353
+ return placements.map((pl) => {
4354
+ const target = sharedTarget ?? pickOrFail(builds, repo, pl.liveBuildId, placementLabel(pl));
4355
+ return {
4356
+ placement: pl,
4357
+ label: placementLabel(pl),
4358
+ from: pl.liveBuildId || void 0,
4359
+ to: target.id,
4360
+ unchanged: pl.liveBuildId === target.id,
4361
+ targetCommit: target.commitSha
4362
+ };
4363
+ });
4364
+ }
4365
+ function renderConfirmTable(changing) {
4366
+ const header = `\uBE4C\uB4DC ${changing[0].to} (\uCEE4\uBC0B ${shortSha(changing[0].targetCommit)})`;
4367
+ const rows = table([
4368
+ ["\uC790\uB9AC", "\uD604\uC7AC", "\u2192 \uB418\uB3CC\uB9B4 \uBE4C\uB4DC"],
4369
+ ...changing.map((r) => [r.label, shortSha(r.placement.liveCommit), shortSha(r.targetCommit)])
4370
+ ]);
4371
+ return `${header}
4372
+ ${rows}`;
4373
+ }
4374
+ async function confirmRollback(changing, out, deps) {
4375
+ out(renderConfirmTable(changing));
4376
+ const ok = await askConfirm(deps, `${changing.length}\uAC1C \uBC30\uCE58\uB97C \uB418\uB3CC\uB9BD\uB2C8\uB2E4. \uC9C4\uD589\uD560\uAE4C\uC694?`);
4377
+ if (!ok) fail(ExitCode.PRECONDITION, "\uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.");
4378
+ }
4379
+ async function rollbackCommand(buildId, options, deps = {}) {
4380
+ if (options.json && !options.yes) {
4381
+ fail(ExitCode.PRECONDITION, "--json \uC740 --yes \uC640 \uD568\uAED8 \uC4F0\uC138\uC694.");
4382
+ }
4383
+ if (!options.yes) assertCanConfirm(deps);
4384
+ const out = deps.out ?? console.log;
4385
+ const ctx = await resolveContext({ cwd: deps.cwd, env: deps.env, fetch: deps.fetch });
4386
+ const { organizationId: org, spaceId: space } = ctx.link;
4387
+ const [placements, builds, repo] = await Promise.all([
4388
+ fetchTargets(ctx, options.placement),
4389
+ ctx.client.listBuilds(org, space, 50),
4390
+ resolveLinkedRepo(ctx)
4391
+ ]);
4392
+ const plan = await buildRollbackPlan(ctx, placements, builds, repo, buildId);
4393
+ const changing = plan.filter((r) => !r.unchanged);
4394
+ if (changing.length === 0) {
4395
+ fail(ExitCode.PRECONDITION, "\uC774\uBBF8 \uD574\uB2F9 \uBE4C\uB4DC\uAC00 \uB77C\uC774\uBE0C\uC785\uB2C8\uB2E4.");
4396
+ }
4397
+ if (!options.yes) await confirmRollback(changing, out, deps);
4398
+ const results = await applyPlacements(ctx, plan);
4399
+ reportOutcome(results, options, out);
4400
+ if (!options.json) {
4401
+ out(
4402
+ pc16.dim(" \uB418\uB3CC\uB9B0 \uB4A4\uC5D0\uB3C4 \uCD5C\uC2E0 \uBE4C\uB4DC\uB294 \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4 \u2014 bstage deploy \uB85C \uB2E4\uC2DC \uC62C\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4.")
4403
+ );
4404
+ }
2714
4405
  }
2715
4406
 
2716
4407
  // src/commands/docs.ts
2717
4408
  import { existsSync as existsSync4, readdirSync } from "fs";
2718
- import { join as join6 } from "path";
2719
- import pc6 from "picocolors";
4409
+ import { join as join8 } from "path";
4410
+ import pc17 from "picocolors";
2720
4411
  var DOC_ORDER = [
2721
4412
  ["GETTING_STARTED.md", "\uBE60\uB978 \uC2DC\uC791 \uAC00\uC774\uB4DC"],
2722
4413
  ["API_REFERENCE.md", "createTemplate, hooks, BstageClient, PlatformBridge API"],
@@ -2733,10 +4424,10 @@ var DOC_ORDER = [
2733
4424
  ["SDK_ARCHITECTURE.md", "\uD328\uD0A4\uC9C0 \uAD6C\uC870, \uC124\uACC4 \uACB0\uC815"]
2734
4425
  ];
2735
4426
  async function docsCommand() {
2736
- const docsDir = join6(process.cwd(), "node_modules", "@bstage-sdk", "core", "docs");
4427
+ const docsDir = join8(process.cwd(), "node_modules", "@bstage-sdk", "core", "docs");
2737
4428
  if (!existsSync4(docsDir)) {
2738
- console.log(pc6.yellow("SDK \uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2739
- console.log(pc6.dim(" @bstage-sdk/core\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694 (npm install \uD6C4 \uC7AC\uC2DC\uB3C4)."));
4429
+ console.log(pc17.yellow("SDK \uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
4430
+ console.log(pc17.dim(" @bstage-sdk/core\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694 (npm install \uD6C4 \uC7AC\uC2DC\uB3C4)."));
2740
4431
  return;
2741
4432
  }
2742
4433
  const present = new Set(readdirSync(docsDir).filter((f) => f.endsWith(".md")));
@@ -2744,27 +4435,119 @@ async function docsCommand() {
2744
4435
  const known = new Set(DOC_ORDER.map(([f]) => f));
2745
4436
  const extras = [...present].filter((f) => !known.has(f)).sort();
2746
4437
  console.log("");
2747
- console.log(pc6.bold("bstage SDK \uBB38\uC11C"));
2748
- console.log(pc6.dim(` ${docsDir}`));
4438
+ console.log(pc17.bold("bstage SDK \uBB38\uC11C"));
4439
+ console.log(pc17.dim(` ${docsDir}`));
2749
4440
  console.log("");
2750
4441
  for (const [file, desc] of ordered) {
2751
- console.log(` ${pc6.cyan(file.padEnd(22))} ${pc6.dim(desc)}`);
4442
+ console.log(` ${pc17.cyan(file.padEnd(22))} ${pc17.dim(desc)}`);
2752
4443
  }
2753
4444
  for (const file of extras) {
2754
- console.log(` ${pc6.cyan(file.padEnd(22))}`);
4445
+ console.log(` ${pc17.cyan(file.padEnd(22))}`);
2755
4446
  }
2756
4447
  console.log("");
2757
4448
  console.log(
2758
- pc6.dim("\uD30C\uC77C\uC744 \uC5F4\uC5B4 \uC0C1\uC138 \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694. \uCEE4\uB9E8\uB4DC \uB3C4\uC6C0\uB9D0\uC740 `bstage <command> --help`.")
4449
+ pc17.dim("\uD30C\uC77C\uC744 \uC5F4\uC5B4 \uC0C1\uC138 \uB0B4\uC6A9\uC744 \uD655\uC778\uD558\uC138\uC694. \uCEE4\uB9E8\uB4DC \uB3C4\uC6C0\uB9D0\uC740 `bstage <command> --help`.")
2759
4450
  );
2760
4451
  console.log("");
2761
4452
  }
2762
4453
 
4454
+ // src/commands/whoami.ts
4455
+ import pc18 from "picocolors";
4456
+ function jsonEntry(c, active) {
4457
+ return {
4458
+ organizationId: c.organizationId,
4459
+ permission: c.permission ?? null,
4460
+ allSpaces: c.allSpaces ?? false,
4461
+ spaceIds: c.spaceIds ?? [],
4462
+ expiresAt: c.expiresAt ?? null,
4463
+ savedAt: c.savedAt,
4464
+ active
4465
+ };
4466
+ }
4467
+ function printCredentials(stored, activeOrg) {
4468
+ if (stored.length === 0) return;
4469
+ console.log(pc18.dim("\uC800\uC7A5\uB41C \uD1A0\uD070"));
4470
+ const rows = stored.map((c) => [
4471
+ ` \uC870\uC9C1 ${orgLabel(c.organizationId)}`,
4472
+ `\uB4F1\uAE09 ${c.permission ?? "-"}`,
4473
+ scopeLabel(c.allSpaces, c.spaceIds),
4474
+ `\uB9CC\uB8CC ${c.expiresAt ? c.expiresAt.slice(0, 10) : "-"}`,
4475
+ c.organizationId === activeOrg ? pc18.dim("(\uC0AC\uC6A9 \uC911)") : ""
4476
+ ]);
4477
+ console.log(table(rows));
4478
+ }
4479
+ function printMe(me, portalUrl) {
4480
+ console.log(`${pc18.bold(me.user.email)} ${pc18.dim(portalUrl)}`);
4481
+ for (const m of me.memberships) {
4482
+ const stages = Object.entries(m.stageRoles ?? {}).map(([s, r]) => `${s}:${r}`).join(", ");
4483
+ console.log(` \uC870\uC9C1 ${m.organizationId} ${m.organizationRole ?? ""} ${pc18.dim(stages)}`);
4484
+ }
4485
+ }
4486
+ async function whoamiCommand(options, deps = {}) {
4487
+ const cwd = deps.cwd ?? process.cwd();
4488
+ const env2 = deps.env ?? process.env;
4489
+ const portalUrl = await resolvePortalUrl(options.portal, cwd, env2, options.phase);
4490
+ if (!portalUrl) {
4491
+ fail(
4492
+ ExitCode.PRECONDITION,
4493
+ "\uD3EC\uD138 \uC8FC\uC18C\uB97C \uC815\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. --portal <url> \uB610\uB294 --phase \uB97C \uC9C0\uC815\uD558\uC138\uC694."
4494
+ );
4495
+ }
4496
+ const stored = await listCredentials(portalUrl, env2);
4497
+ const organizationId = options.org ?? env2.BSTAGE_ORG;
4498
+ const picked = await loadCredential(portalUrl, organizationId, env2);
4499
+ const envToken = env2.BSTAGE_TOKEN || void 0;
4500
+ const viaEnv = Boolean(envToken);
4501
+ const token = envToken ?? picked?.token;
4502
+ const activeOrg = viaEnv ? void 0 : picked?.organizationId;
4503
+ if (!token) {
4504
+ if (stored.length === 0) {
4505
+ fail(
4506
+ ExitCode.AUTH,
4507
+ `${portalUrl} \uC5D0 \uB85C\uADF8\uC778\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. bstage login --portal ${portalUrl} \uC744 \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694.`
4508
+ );
4509
+ }
4510
+ return reportStoredOnly(portalUrl, stored, options.json);
4511
+ }
4512
+ const me = await new PortalClient({ portalUrl, token, fetch: deps.fetch }).me();
4513
+ if (options.json) {
4514
+ printJson({
4515
+ portalUrl,
4516
+ tokenSource: viaEnv ? "env" : "stored",
4517
+ organizationId: activeOrg ?? null,
4518
+ user: me.user,
4519
+ memberships: me.memberships,
4520
+ credentials: stored.map((c) => jsonEntry(c, !viaEnv && c.organizationId === activeOrg))
4521
+ });
4522
+ return;
4523
+ }
4524
+ printMe(me, portalUrl);
4525
+ if (viaEnv) console.error(" \uD1A0\uD070 \uCD9C\uCC98: BSTAGE_TOKEN \uD658\uACBD\uBCC0\uC218 (\uC800\uC7A5\uB41C \uC790\uACA9\uC99D\uBA85\uC740 \uC0AC\uC6A9\uD558\uC9C0 \uC54A\uC74C)");
4526
+ printCredentials(stored, activeOrg);
4527
+ }
4528
+ function reportStoredOnly(portalUrl, stored, json) {
4529
+ if (json) {
4530
+ printJson({
4531
+ portalUrl,
4532
+ organizationId: null,
4533
+ user: null,
4534
+ memberships: [],
4535
+ credentials: stored.map((c) => jsonEntry(c, false))
4536
+ });
4537
+ return;
4538
+ }
4539
+ console.log(pc18.dim(portalUrl));
4540
+ printCredentials(stored);
4541
+ console.log(
4542
+ pc18.yellow(" \uC5EC\uB7EC \uC870\uC9C1\uC758 \uD1A0\uD070\uC774 \uC788\uC2B5\uB2C8\uB2E4 \u2014 --org <id> \uB85C \uC870\uC9C1\uC744 \uC9C0\uC815\uD558\uBA74 \uACC4\uC815 \uC815\uBCF4\uB3C4 \uBD05\uB2C8\uB2E4.")
4543
+ );
4544
+ }
4545
+
2763
4546
  // src/index.ts
2764
4547
  function readVersion() {
2765
4548
  try {
2766
- const here = dirname3(fileURLToPath2(import.meta.url));
2767
- return JSON.parse(readFileSync2(join7(here, "..", "package.json"), "utf-8")).version || "0.0.0";
4549
+ const here = dirname5(fileURLToPath2(import.meta.url));
4550
+ return JSON.parse(readFileSync2(join9(here, "..", "package.json"), "utf-8")).version || "0.0.0";
2768
4551
  } catch {
2769
4552
  return "0.0.0";
2770
4553
  }
@@ -2782,6 +4565,28 @@ var skills = program.command("skills").description("\uC5D0\uC774\uC804\uD2B8\uC6
2782
4565
  skills.command("install").description("\uC5D0\uC774\uC804\uD2B8\uC6A9 \uC2A4\uD0AC\uC744 .claude/skills/\uC5D0 \uC124\uCE58\xB7\uB3D9\uAE30\uD654 (\uAE30\uC874 \uD504\uB85C\uC81D\uD2B8\uC6A9)").option("--dir <dir>", "\uC124\uCE58 \uC704\uCE58", ".claude/skills").action(skillsInstallCommand);
2783
4566
  var i18n = program.command("i18n").description("\uB2E4\uAD6D\uC5B4(\uBC88\uC5ED) \uC720\uD2F8");
2784
4567
  i18n.command("pull").description("\uD50C\uB7AB\uD3FC \uBC88\uC5ED \uC0AC\uC804\uC744 CDN\uC5D0\uC11C \uBC1B\uC544 \uD0C0\uC785 \uC0DD\uC131 + \uB85C\uCEEC \uCE90\uC2DC").option("--phase <phase>", "\uB300\uC0C1 phase (real, qa, dev)", "real").option("--target <target>", "\uBC88\uC5ED \uC2DC\uC2A4\uD15C (user, admin)", "user").option("--tier <tier>", "\uBC88\uC5ED tier (inhouse, mnetplus, hiand)", "inhouse").option("--ref <locale>", "\uD0C0\uC785 \uC6D0\uBB38 \uAE30\uC900 \uB85C\uCF00\uC77C", "ko").option("--out <file>", "\uC0DD\uC131 \uD0C0\uC785 \uD30C\uC77C \uACBD\uB85C", "src/bstage-i18n.ts").action(i18nPullCommand);
4568
+ program.command("login").description("\uD3EC\uD138 CLI \uD1A0\uD070 \uB4F1\uB85D (\uD3EC\uD138 \uC124\uC815 > CLI \uD1A0\uD070\uC5D0\uC11C \uBC1C\uAE09)").option(
4569
+ "--phase <phase>",
4570
+ "\uD3EC\uD138 \uD658\uACBD (real, sandbox, dev, qa). \uC0DD\uB7B5 \uC2DC .env\uC758 VITE_BSTAGE_PHASE, \uADF8\uAC83\uB3C4 \uC5C6\uC73C\uBA74 sandbox"
4571
+ ).option(
4572
+ "--portal <url>",
4573
+ "\uD3EC\uD138 \uC8FC\uC18C \uC9C1\uC811 \uC9C0\uC815 (phase \uB9E4\uD551\uBCF4\uB2E4 \uC6B0\uC120, BSTAGE_PORTAL_URL). \uB9C1\uD06C \uD30C\uC77C\xB7.env \uC758 \uC8FC\uC18C\uB294 \uD5C8\uC6A9 \uBAA9\uB85D \uC548\uC77C \uB54C\uB9CC \uC4F0\uC774\uBBC0\uB85C \uBAA9\uB85D \uBC16 \uD3EC\uD138\uC740 \uC774 \uC635\uC158\uC73C\uB85C \uC9C0\uC815\uD55C\uB2E4"
4574
+ ).option(
4575
+ "--token <token>",
4576
+ "\uBD99\uC5EC \uB123\uAE30 \uACBD\uB85C \u2014 CI\uC6A9 (BSTAGE_TOKEN). \uC0DD\uB7B5 \uC2DC \uBE0C\uB77C\uC6B0\uC800 \uC2B9\uC778(\uB514\uBC14\uC774\uC2A4 \uCF54\uB4DC)"
4577
+ ).option("--org <id>", "\uBD99\uC5EC \uB123\uC740 \uD1A0\uD070\uC758 \uC870\uC9C1 (BSTAGE_ORG). \uB514\uBC14\uC774\uC2A4 \uC2B9\uC778 \uACBD\uB85C\uC5D0\uC11C\uB294 \uBD88\uD544\uC694").option("--no-browser", "\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4\uC9C0 \uC54A\uACE0 \uCF54\uB4DC\xB7\uC8FC\uC18C\uB9CC \uCD9C\uB825").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825").action(
4578
+ (o) => runCommand(
4579
+ () => loginCommand({ ...o, noBrowser: o.browser === false }, { cliVersion: readVersion() })
4580
+ )
4581
+ );
4582
+ program.command("logout").description("\uC800\uC7A5\uB41C \uD3EC\uD138 \uC790\uACA9\uC99D\uBA85 \uC0AD\uC81C (\uAE30\uBCF8: \uADF8 \uD3EC\uD138\uC758 \uBAA8\uB4E0 \uC870\uC9C1)").option("--phase <phase>", "\uD3EC\uD138 \uD658\uACBD").option("--portal <url>", "\uD3EC\uD138 \uC8FC\uC18C \uC9C1\uC811 \uC9C0\uC815 (\uD5C8\uC6A9 \uBAA9\uB85D \uBC16 \uD3EC\uD138\uC740 \uC774 \uC635\uC158\uC73C\uB85C)").option("--org <id>", "\uC774 \uC870\uC9C1 \uD1A0\uD070\uB9CC \uC0AD\uC81C").action((o) => runCommand(() => logoutCommand(o)));
4583
+ program.command("link").description("\uC774 \uB514\uB809\uD130\uB9AC\uB97C \uD3EC\uD138 \uC870\uC9C1\xB7\uC2A4\uD14C\uC774\uC9C0\xB7\uB808\uD3EC\uC5D0 \uC5F0\uACB0 (.bstage/project.json)").option("--phase <phase>", "\uD3EC\uD138 \uD658\uACBD (\uC0DD\uB7B5 \uC2DC .env\uC758 VITE_BSTAGE_PHASE \u2192 sandbox)").option("--portal <url>", "\uD3EC\uD138 \uC8FC\uC18C \uC9C1\uC811 \uC9C0\uC815 (\uD5C8\uC6A9 \uBAA9\uB85D \uBC16 \uD3EC\uD138\uC740 \uC774 \uC635\uC158\uC73C\uB85C)").option("--org <id>", "\uC870\uC9C1 ID").option("--space <id>", "\uC2A4\uD14C\uC774\uC9C0(\uC2A4\uD398\uC774\uC2A4) ID").option("--repo <id|owner/repo>", "\uC5F0\uACB0 \uB808\uD3EC").option("-y, --yes", "\uD504\uB86C\uD504\uD2B8 \uC5C6\uC774 (\uD6C4\uBCF4\uAC00 \uC720\uC77C\uD558\uAC70\uB098 \uC635\uC158\uC73C\uB85C \uC9C0\uC815\uB3FC\uC57C \uD568)").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825 (--yes \uC640 \uD568\uAED8)").action((o) => runCommand(() => linkCommand(o)));
4584
+ program.command("list").alias("ls").description("\uC774 \uB808\uD3EC\uC758 \uBC30\uCE58(\uB77C\uC774\uBE0C \uBC84\uC804\xB7\uAC8C\uC2DC \uC0C1\uD0DC)\uC640 \uCD5C\uADFC \uBE4C\uB4DC").option("--builds <n>", "\uD45C\uC2DC\uD560 \uBE4C\uB4DC \uC218", "5").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825").action((o) => runCommand(() => listCommand(o)));
4585
+ program.command("deploy").description("push\uB41C \uCEE4\uBC0B\uC744 \uD3EC\uD138\uC5D0\uC11C \uBE4C\uB4DC\uD558\uACE0 \uC774 \uB808\uD3EC\uC758 \uBC30\uCE58\uC5D0 \uB77C\uC774\uBE0C \uC801\uC6A9").option("-y, --yes", "\uD655\uC778 \uC5C6\uC774 \uC801\uC6A9").option("--placement <id|path|slotId>", "\uD2B9\uC815 \uBC30\uCE58\uB9CC").option("--skip-git-check", "\uB354\uD2F0 \uD2B8\uB9AC\xB7\uBE0C\uB79C\uCE58\xB7push \uC810\uAC80 \uC0DD\uB7B5").option("--no-wait", "\uBE4C\uB4DC\uB9CC \uC2DC\uC791\uD558\uACE0 \uC885\uB8CC").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825 (--yes \uC640 \uD568\uAED8)").action((o) => runCommand(() => deployCommand({ ...o, noWait: o.wait === false })));
4586
+ program.command("rollback [buildId]").description("\uC774\uC804 \uC131\uACF5 \uBE4C\uB4DC(\uB610\uB294 \uC9C0\uC815\uD55C \uBE4C\uB4DC)\uB97C \uB77C\uC774\uBE0C\uB85C \uC7AC\uC801\uC6A9").option("-y, --yes", "\uD655\uC778 \uC5C6\uC774 \uC801\uC6A9").option("--placement <id|path|slotId>", "\uD2B9\uC815 \uBC30\uCE58\uB9CC").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825 (--yes \uC640 \uD568\uAED8)").action((id, o) => runCommand(() => rollbackCommand(id, o)));
4587
+ program.command("publish <on|off>").description("\uBC30\uCE58 \uAC8C\uC2DC \uCF1C\uAE30/\uB044\uAE30 (\uB77C\uC774\uBE0C \uBC84\uC804\uC740 \uC720\uC9C0)").option("--placement <id|path|slotId>", "\uD2B9\uC815 \uBC30\uCE58\uB9CC").option("-y, --yes", "\uD655\uC778 \uC5C6\uC774").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825 (--yes \uC640 \uD568\uAED8)").action((state, o) => runCommand(() => publishCommand(state, o)));
4588
+ program.command("logs [buildId]").description("\uBE4C\uB4DC \uB85C\uADF8 (\uC0DD\uB7B5 \uC2DC \uCD5C\uADFC \uBE4C\uB4DC)").option("-f, --follow", "\uC9C4\uD589 \uC911\uC774\uBA74 \uB05D\uB0A0 \uB54C\uAE4C\uC9C0 \uC774\uC5B4\uC11C \uCD9C\uB825").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825").action((id, o) => runCommand(() => logsCommand(id, o)));
4589
+ program.command("whoami").description("\uD604\uC7AC \uB85C\uADF8\uC778 \uC0AC\uC6A9\uC790\xB7\uC870\uC9C1\xB7\uC2A4\uD14C\uC774\uC9C0 \uC5ED\uD560\uACFC \uC800\uC7A5\uB41C \uC870\uC9C1\uBCC4 \uD1A0\uD070").option("--phase <phase>", "\uD3EC\uD138 \uD658\uACBD").option("--portal <url>", "\uD3EC\uD138 \uC8FC\uC18C \uC9C1\uC811 \uC9C0\uC815 (\uD5C8\uC6A9 \uBAA9\uB85D \uBC16 \uD3EC\uD138\uC740 \uC774 \uC635\uC158\uC73C\uB85C)").option("--org <id>", "\uC774 \uC870\uC9C1 \uD1A0\uD070\uC73C\uB85C \uC870\uD68C (BSTAGE_ORG)").option("--json", "\uAD6C\uC870\uD654 \uCD9C\uB825").action((o) => runCommand(() => whoamiCommand(o)));
2785
4590
  program.addHelpText(
2786
4591
  "after",
2787
4592
  `
@@ -2792,6 +4597,17 @@ program.addHelpText(
2792
4597
  $ bstage doctor SDK \uBC84\uC804\xB7\uB4DC\uB9AC\uD504\uD2B8 \uC9C4\uB2E8
2793
4598
  $ bstage skills install \uC5D0\uC774\uC804\uD2B8 \uC2A4\uD0AC \uC124\uCE58/\uB3D9\uAE30\uD654
2794
4599
  $ bstage i18n pull \uD50C\uB7AB\uD3FC \uBC88\uC5ED \uBC1B\uAE30 + \uD0C0\uC785 \uC0DD\uC131
4600
+ $ bstage login \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778\uD574 \uB85C\uADF8\uC778 (CI: --token \uB610\uB294 BSTAGE_TOKEN)
4601
+ $ bstage link \uB514\uB809\uD130\uB9AC\uB97C \uD3EC\uD138 \uC870\uC9C1\xB7\uC2A4\uD14C\uC774\uC9C0\xB7\uB808\uD3EC\uC5D0 \uC5F0\uACB0
4602
+ $ bstage list \uC774 \uB808\uD3EC\uC758 \uBC30\uCE58\xB7\uCD5C\uADFC \uBE4C\uB4DC \uD655\uC778
4603
+ $ bstage deploy push\uB41C \uCEE4\uBC0B\uC744 \uBE4C\uB4DC\uD574 \uC774 \uB808\uD3EC\uC758 \uBC30\uCE58\uC5D0 \uB77C\uC774\uBE0C \uC801\uC6A9
4604
+ $ bstage rollback \uC774\uC804 \uC131\uACF5 \uBE4C\uB4DC\uB85C \uB77C\uC774\uBE0C\uB97C \uB418\uB3CC\uB9BC
4605
+ $ bstage publish off \uAC8C\uC2DC \uB044\uAE30 (\uB77C\uC774\uBE0C \uBC84\uC804 \uC720\uC9C0)
4606
+ $ bstage logs -f \uCD5C\uADFC \uBE4C\uB4DC \uB85C\uADF8\uB97C \uB05D\uB0A0 \uB54C\uAE4C\uC9C0 \uC774\uC5B4\uC11C \uCD9C\uB825
4607
+ $ bstage whoami \uD604\uC7AC \uB85C\uADF8\uC778 \uC0AC\uC6A9\uC790 \uD655\uC778
4608
+ $ bstage logout \uC800\uC7A5\uB41C \uC790\uACA9\uC99D\uBA85 \uC0AD\uC81C
4609
+
4610
+ \uC885\uB8CC \uCF54\uB4DC: 0 \uC131\uACF5 \xB7 1 \uC2E4\uD328 \xB7 2 \uC0AC\uC804\uC870\uAC74 \xB7 3 \uC778\uC99D \xB7 4 \uCDA9\uB3CC \xB7 5 \uC694\uCCAD \uC81C\uD55C
2795
4611
 
2796
4612
  \uBB38\uC11C:
2797
4613
  $ bstage docs \uC124\uCE58\uB41C SDK \uBB38\uC11C \uBAA9\uB85D\xB7\uACBD\uB85C