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