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