@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/MIGRATION.md
CHANGED
|
@@ -45,6 +45,35 @@
|
|
|
45
45
|
|
|
46
46
|
> **`적용` 단계는 옛 프로젝트에서 실제로 따라가 본 뒤 적는다.** 새 `bstage init` 프로젝트에서 되는 것과, 기존 프로젝트가 이 절차만 보고 도달하는 것은 다르다. 따라 했을 때 깨지는 선행 조건이 있으면 **그 조건까지 적는다** — `core 0.44.0` 항목이 그 사례다(스크립트만 넣으면 `@types/node` 부재로 `TS2688`이 난다. 스캐폴드 템플릿을 고쳐 새 프로젝트는 해결됐지만, 먼저 써둔 적용 절차를 되짚지 않아 기존 프로젝트용 지침이 깨진 채 남았다).
|
|
47
47
|
|
|
48
|
-
> **헤더 규칙**: `
|
|
48
|
+
> **헤더 규칙**: `## → {패키지} {버전}` 형식으로, 그 변경을 실제로 낸 패키지와 버전을 명시한다(예: `→ cli 0.3.0`, `→ core 0.3.0`). 한 변경이 여러 패키지를 함께 범프시켰으면 소비자가 올려야 할 패키지를 `·`로 나열한다(예: `→ core 0.3.0 · cli 0.3.0`). 소비자는 이 헤더의 패키지를 해당 버전 **이상**으로 올려야 한다. 단순 의존성 재핀(dependency bump)만 된 패키지는 헤더에 적지 않는다.
|
|
49
|
+
>
|
|
50
|
+
> **정렬**: 릴리즈 **시점 최신이 위**. 패키지가 독립 버전이라 헤더 숫자만으로는 시간순 정렬이 되지 않으므로 릴리즈 순서를 기준으로 한다.
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
---
|
|
53
|
+
## → core 0.3.0 · cli 0.3.0
|
|
54
|
+
|
|
55
|
+
### 앱 키 이름 정리(`appSecret` → `appKey`, `VITE_BSTAGE_APP_SECRET` → `VITE_BSTAGE_APP_KEY`) — `판단` · `선택`
|
|
56
|
+
|
|
57
|
+
**영향**
|
|
58
|
+
|
|
59
|
+
- **기존 프로젝트가 반드시 할 일은 없다.** `appSecret`·`VITE_BSTAGE_APP_SECRET`은 deprecated 별칭으로 그대로 동작한다. 별칭은 **0.4.0**에서 제거되므로 그 전에 옮긴다.
|
|
60
|
+
- 감지: `src/shared/client.ts`에 `appSecret:`이 있거나, `.env`·`.env.example`에 `VITE_BSTAGE_APP_SECRET`이 있는 경우.
|
|
61
|
+
|
|
62
|
+
**변경 내용**
|
|
63
|
+
|
|
64
|
+
- 같은 값을 포털 화면(APP KEY)·포털 API(`appKey`)·게이트웨이 헤더(`X-BSTAGE-APP-KEY`)는 모두 KEY라고 부르는데 SDK만 `secret`이라고 불렀다. 이 값은 `VITE_` 환경변수라 **빌드 결과물에 평문으로 실려 브라우저에서 보이는 값**이고 비밀값이 아니다 — 이름이 사실과 반대여서 "숨겨야 하는 값"으로 오해하게 했다. SDK 이름을 나머지와 맞췄다.
|
|
65
|
+
- `BstageConfig.appKey`가 정본이고 `appSecret`은 deprecated 별칭이다(둘 다 주면 `appKey` 우선). 타입 주석도 "앱 시크릿"에서 "앱 키(APP KEY) — 브라우저에 노출되는 값"으로 바꿨다.
|
|
66
|
+
- 새 `bstage init` 스캐폴드는 `.env`·`.env.example`·`client.ts`·`vite-env.d.ts`에 새 이름을 쓴다.
|
|
67
|
+
- **소스와 `.env`가 다른 이름을 써도 빌드가 이어 붙인다.** `bstage build`(프리셋)와 `bstageDevPlugin`이 `VITE_BSTAGE_APP_KEY`·`VITE_BSTAGE_APP_SECRET` 중 한쪽만 있으면 다른 이름으로도 같은 값이 읽히게 `define`한다. 그래서 옛 `client.ts`(`appSecret: import.meta.env.VITE_BSTAGE_APP_SECRET`)를 둔 채 `.env`만 새 이름으로 바꿔도, 포털의 환경변수 입력이 새 이름으로 들어와도 번들에 값이 실린다. 둘 다 있으면 각자 실제 값이 그대로 쓰인다.
|
|
68
|
+
- `bstage build`의 인증 값 점검도 같은 순서로 본다 — 소스가 읽는 이름의 값이 우선, 다른 이름은 폴백. 보고에는 소스가 실제로 읽는 이름을 쓴다.
|
|
69
|
+
- 앱 키가 아예 없으면 `X-BSTAGE-APP-KEY` 헤더에 `"undefined"` 문자열을 실던 것을 헤더 생략 + 생성 시 콘솔 경고로 바꿨다. 게이트웨이 응답(401)은 같다.
|
|
70
|
+
|
|
71
|
+
**적용**
|
|
72
|
+
|
|
73
|
+
1. (자동) `@bstage-sdk/core`·`@bstage-sdk/cli`를 `0.3.0` 이상으로 올린다.
|
|
74
|
+
2. (판단) `src/shared/client.ts`의 `appSecret: import.meta.env.VITE_BSTAGE_APP_SECRET`을 `appKey: import.meta.env.VITE_BSTAGE_APP_KEY`로 바꾼다. 리터럴 키를 박아 둔 옛 프로젝트는 키 이름만 `appSecret` → `appKey`로 바꾼다.
|
|
75
|
+
3. (판단) `.env`·`.env.example`의 `VITE_BSTAGE_APP_SECRET=`을 `VITE_BSTAGE_APP_KEY=`로 바꾼다. 값은 그대로다 — 재발급이 아니다.
|
|
76
|
+
4. (판단) `src/vite-env.d.ts`의 `readonly VITE_BSTAGE_APP_SECRET: string`을 `readonly VITE_BSTAGE_APP_KEY: string`으로 바꾼다.
|
|
77
|
+
5. CI·배포 환경에 `VITE_BSTAGE_APP_SECRET`을 주입하고 있었다면 같은 값을 `VITE_BSTAGE_APP_KEY`로 주입한다. 2~4를 한 번에 못 옮기는 기간에는 위 별칭 덕에 어느 한쪽 이름만 있어도 빌드가 통과한다.
|
|
78
|
+
|
|
79
|
+
---
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import { defineConfig } from "vite";
|
|
3
3
|
import react from "@vitejs/plugin-react";
|
|
4
4
|
|
|
5
|
-
// src/
|
|
6
|
-
import {
|
|
7
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
8
|
-
import { join, dirname } from "path";
|
|
5
|
+
// src/vite/appKeyEnvAlias.ts
|
|
6
|
+
import { loadEnv } from "vite";
|
|
9
7
|
|
|
10
8
|
// src/constants.ts
|
|
11
9
|
import { readFileSync } from "fs";
|
|
12
10
|
import { resolve } from "path";
|
|
11
|
+
var APP_KEY_VAR = "VITE_BSTAGE_APP_KEY";
|
|
12
|
+
var LEGACY_APP_KEY_VAR = "VITE_BSTAGE_APP_SECRET";
|
|
13
13
|
var BSTAGE_INTERNAL_PREFIX = "/__bstage__";
|
|
14
14
|
var LOGIN_PATH = `${BSTAGE_INTERNAL_PREFIX}/login`;
|
|
15
15
|
var PROXY_PATH_PREFIX = "/__proxy__";
|
|
@@ -65,6 +65,11 @@ var DEFAULT_TRANSLATION_TIER = "inhouse";
|
|
|
65
65
|
var TRANSLATION_CDN_ORIGIN = "https://cdn.static.bstage.in";
|
|
66
66
|
var TRANSLATION_CDN_BASE_KEY = "static/i18n";
|
|
67
67
|
var TRANSLATION_CDN_BUCKET_PREFIX = "cdn.bstage.in/";
|
|
68
|
+
var PORTAL_HOSTS = {
|
|
69
|
+
real: "portal.bstage.in",
|
|
70
|
+
sandbox: "bstage-portal.sandbox.bstage.systems"
|
|
71
|
+
};
|
|
72
|
+
var PORTAL_HOST_ENV_KEY = "VITE_BSTAGE_PORTAL_HOST";
|
|
68
73
|
function getAuthHost(mode, phase, tenantId) {
|
|
69
74
|
if (mode === "admin") {
|
|
70
75
|
const fn2 = ADMIN_AUTH_HOSTS[phase];
|
|
@@ -83,6 +88,31 @@ function resolveProjectTarget(root) {
|
|
|
83
88
|
return pkg.bstage?.target === "admin" ? "admin" : "user";
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
// src/vite/appKeyEnvAlias.ts
|
|
92
|
+
function appKeyEnvDefine(env) {
|
|
93
|
+
const key = env[APP_KEY_VAR]?.trim();
|
|
94
|
+
const legacy = env[LEGACY_APP_KEY_VAR]?.trim();
|
|
95
|
+
if (key && !legacy) return { [`import.meta.env.${LEGACY_APP_KEY_VAR}`]: JSON.stringify(key) };
|
|
96
|
+
if (legacy && !key) return { [`import.meta.env.${APP_KEY_VAR}`]: JSON.stringify(legacy) };
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
function loadProjectEnv(config, mode) {
|
|
100
|
+
return loadEnv(mode, config.envDir ?? config.root ?? process.cwd(), "VITE_");
|
|
101
|
+
}
|
|
102
|
+
function appKeyEnvAliasPlugin() {
|
|
103
|
+
return {
|
|
104
|
+
name: "bstage-app-key-env-alias",
|
|
105
|
+
config(config, { mode }) {
|
|
106
|
+
return { define: appKeyEnvDefine(loadProjectEnv(config, mode)) };
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/dev/devVitePlugin.ts
|
|
112
|
+
import { createRequire } from "module";
|
|
113
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
114
|
+
import { join, dirname } from "path";
|
|
115
|
+
|
|
86
116
|
// src/dev/loginPage.ts
|
|
87
117
|
function getLoginPageHtml(phase, mode = "user") {
|
|
88
118
|
const tokenPath = AUTH_TOKEN_PATH_BY_MODE[mode].replace(/^\/svc/, AUTH_PATH_PREFIX);
|
|
@@ -410,14 +440,14 @@ function getLoginPageHtml(phase, mode = "user") {
|
|
|
410
440
|
// src/dev/proxyUtils.ts
|
|
411
441
|
import { request as httpsRequest } from "https";
|
|
412
442
|
import { gunzipSync, brotliDecompressSync, inflateSync } from "zlib";
|
|
413
|
-
import { loadEnv } from "vite";
|
|
443
|
+
import { loadEnv as loadEnv2 } from "vite";
|
|
414
444
|
var CF_ACCESS_ENV_KEYS = ["VITE_CF_ACCESS_CLIENT_ID", "VITE_CF_ACCESS_CLIENT_SECRET"];
|
|
415
445
|
var cachedCreds;
|
|
416
446
|
var missingWarned = false;
|
|
417
447
|
function resolveCfAccessCreds() {
|
|
418
448
|
if (cachedCreds !== void 0) return cachedCreds;
|
|
419
449
|
const [idKey, secretKey] = CF_ACCESS_ENV_KEYS;
|
|
420
|
-
const fileEnv =
|
|
450
|
+
const fileEnv = loadEnv2("development", process.cwd(), "");
|
|
421
451
|
const id = process.env[idKey] || fileEnv[idKey] || "";
|
|
422
452
|
const secret = process.env[secretKey] || fileEnv[secretKey] || "";
|
|
423
453
|
cachedCreds = id && secret ? { id, secret } : null;
|
|
@@ -513,16 +543,31 @@ function stripResponseCookies(headers, extraCookies) {
|
|
|
513
543
|
headers["set-cookie"] = cookies;
|
|
514
544
|
}
|
|
515
545
|
}
|
|
516
|
-
|
|
517
|
-
|
|
546
|
+
var MAX_REQUEST_BODY_BYTES = 10 * 1024 * 1024;
|
|
547
|
+
function collectBody(req, maxBytes = MAX_REQUEST_BODY_BYTES) {
|
|
548
|
+
return new Promise((resolve2, reject) => {
|
|
518
549
|
const chunks = [];
|
|
519
|
-
|
|
520
|
-
|
|
550
|
+
let total = 0;
|
|
551
|
+
let overflowed = false;
|
|
552
|
+
req.on("data", (c) => {
|
|
553
|
+
if (overflowed) return;
|
|
554
|
+
total += c.length;
|
|
555
|
+
if (total > maxBytes) {
|
|
556
|
+
overflowed = true;
|
|
557
|
+
chunks.length = 0;
|
|
558
|
+
reject(new Error(`Request body exceeds ${maxBytes} bytes`));
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
chunks.push(c);
|
|
562
|
+
});
|
|
563
|
+
req.on("end", () => {
|
|
564
|
+
if (!overflowed) resolve2(Buffer.concat(chunks));
|
|
565
|
+
});
|
|
521
566
|
});
|
|
522
567
|
}
|
|
523
568
|
|
|
524
569
|
// src/dev/resolveHosts.ts
|
|
525
|
-
import { loadEnv as
|
|
570
|
+
import { loadEnv as loadEnv3 } from "vite";
|
|
526
571
|
var HOST_ENV_KEYS = {
|
|
527
572
|
gateway: "VITE_BSTAGE_GATEWAY_HOST",
|
|
528
573
|
auth: "VITE_BSTAGE_AUTH_HOST",
|
|
@@ -539,7 +584,7 @@ function resolveDevHosts(phase, mode, cwd = process.cwd()) {
|
|
|
539
584
|
auth: (tenantId) => getAuthHost(mode, phase, tenantId)
|
|
540
585
|
};
|
|
541
586
|
}
|
|
542
|
-
const fileEnv =
|
|
587
|
+
const fileEnv = loadEnv3("development", cwd, "");
|
|
543
588
|
const read = (key) => (process.env[key] || fileEnv[key] || "").trim();
|
|
544
589
|
const authKey = authKeyFor(mode);
|
|
545
590
|
const gatewayHost = read(HOST_ENV_KEYS.gateway);
|
|
@@ -562,13 +607,13 @@ function resolveDevHosts(phase, mode, cwd = process.cwd()) {
|
|
|
562
607
|
}
|
|
563
608
|
|
|
564
609
|
// src/dev/resolvePhase.ts
|
|
565
|
-
import { loadEnv as
|
|
610
|
+
import { loadEnv as loadEnv4 } from "vite";
|
|
566
611
|
var PHASES = ["dev", "qa", "real", "sandbox"];
|
|
567
612
|
var DEFAULT_PHASE = "sandbox";
|
|
568
613
|
function isPhase(value) {
|
|
569
614
|
return PHASES.includes(value);
|
|
570
615
|
}
|
|
571
|
-
function resolvePhase(explicit, cwd = process.cwd()) {
|
|
616
|
+
function resolvePhase(explicit, cwd = process.cwd(), env = process.env) {
|
|
572
617
|
if (explicit) {
|
|
573
618
|
if (!isPhase(explicit)) {
|
|
574
619
|
throw new Error(
|
|
@@ -577,8 +622,8 @@ function resolvePhase(explicit, cwd = process.cwd()) {
|
|
|
577
622
|
}
|
|
578
623
|
return { phase: explicit, source: "flag" };
|
|
579
624
|
}
|
|
580
|
-
const fileEnv =
|
|
581
|
-
const fromEnv =
|
|
625
|
+
const fileEnv = loadEnv4("development", cwd, "");
|
|
626
|
+
const fromEnv = env.VITE_BSTAGE_PHASE || fileEnv.VITE_BSTAGE_PHASE || "";
|
|
582
627
|
if (fromEnv) {
|
|
583
628
|
if (isPhase(fromEnv)) return { phase: fromEnv, source: "env" };
|
|
584
629
|
return { phase: DEFAULT_PHASE, source: "default", ignoredEnvValue: fromEnv };
|
|
@@ -1729,13 +1774,13 @@ async function refreshAccessToken(mode, phase, hosts, tenantId, accessToken, coo
|
|
|
1729
1774
|
headers,
|
|
1730
1775
|
body: Buffer.from(postBody)
|
|
1731
1776
|
});
|
|
1732
|
-
const
|
|
1733
|
-
const
|
|
1734
|
-
if (result.statusCode === 200 &&
|
|
1777
|
+
const json = JSON.parse(result.body.toString("utf-8"));
|
|
1778
|
+
const newToken = json.accessToken ?? json.data?.accessToken;
|
|
1779
|
+
if (result.statusCode === 200 && newToken) {
|
|
1735
1780
|
console.info(`[bstage] token refreshed (${mode})`);
|
|
1736
|
-
return
|
|
1781
|
+
return newToken;
|
|
1737
1782
|
}
|
|
1738
|
-
console.warn(
|
|
1783
|
+
console.warn(`[bstage] token refresh failed (mode=${mode}, status=${result.statusCode})`);
|
|
1739
1784
|
return null;
|
|
1740
1785
|
} catch {
|
|
1741
1786
|
return null;
|
|
@@ -1821,10 +1866,15 @@ function handleGatewayProxy(mode, phase, hosts, req, res) {
|
|
|
1821
1866
|
res.writeHead(result.statusCode, result.headers);
|
|
1822
1867
|
res.end(result.body);
|
|
1823
1868
|
} catch (err) {
|
|
1824
|
-
|
|
1869
|
+
console.error("[bstage] gateway proxy error:", err instanceof Error ? err.message : err);
|
|
1825
1870
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
1826
|
-
res.end(JSON.stringify({ message: "Gateway proxy error"
|
|
1871
|
+
res.end(JSON.stringify({ message: "Gateway proxy error" }));
|
|
1827
1872
|
}
|
|
1873
|
+
}).catch((err) => {
|
|
1874
|
+
console.error("[bstage] request body rejected:", err instanceof Error ? err.message : err);
|
|
1875
|
+
if (res.headersSent) return;
|
|
1876
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
1877
|
+
res.end(JSON.stringify({ message: "Request body too large" }));
|
|
1828
1878
|
});
|
|
1829
1879
|
}
|
|
1830
1880
|
function subpathOf(source) {
|
|
@@ -1895,18 +1945,22 @@ function handleAuthProxy(mode, phase, hosts, req, res) {
|
|
|
1895
1945
|
}
|
|
1896
1946
|
} else {
|
|
1897
1947
|
console.warn(
|
|
1898
|
-
`[bstage] auth proxy upstream non-2xx (mode=${mode}, status=${result.statusCode})
|
|
1899
|
-
textBody.slice(0, 500)
|
|
1948
|
+
`[bstage] auth proxy upstream non-2xx (mode=${mode}, status=${result.statusCode})`
|
|
1900
1949
|
);
|
|
1901
1950
|
}
|
|
1902
1951
|
stripResponseCookies(result.headers, extraCookies);
|
|
1903
1952
|
res.writeHead(result.statusCode, result.headers);
|
|
1904
1953
|
res.end(textBody);
|
|
1905
1954
|
} catch (err) {
|
|
1906
|
-
|
|
1955
|
+
console.error("[bstage] auth proxy error:", err instanceof Error ? err.message : err);
|
|
1907
1956
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
1908
|
-
res.end(JSON.stringify({ message: "Auth proxy error"
|
|
1957
|
+
res.end(JSON.stringify({ message: "Auth proxy error" }));
|
|
1909
1958
|
}
|
|
1959
|
+
}).catch((err) => {
|
|
1960
|
+
console.error("[bstage] request body rejected:", err instanceof Error ? err.message : err);
|
|
1961
|
+
if (res.headersSent) return;
|
|
1962
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
1963
|
+
res.end(JSON.stringify({ message: "Request body too large" }));
|
|
1910
1964
|
});
|
|
1911
1965
|
}
|
|
1912
1966
|
function bstageDevPlugin(options = {}) {
|
|
@@ -1920,10 +1974,13 @@ function bstageDevPlugin(options = {}) {
|
|
|
1920
1974
|
return {
|
|
1921
1975
|
name: "bstage-dev",
|
|
1922
1976
|
enforce: "pre",
|
|
1923
|
-
config() {
|
|
1977
|
+
config(config, { mode: mode2 }) {
|
|
1924
1978
|
const root = process.cwd();
|
|
1925
1979
|
projectRequire = createRequire(join(root, "package.json"));
|
|
1926
1980
|
return {
|
|
1981
|
+
// 앱 키 env 별칭 — 소비자 `vite.config.ts`는 이 플러그인만 얹으므로 여기서도 이어 붙인다
|
|
1982
|
+
// (`bstage build`는 프리셋이 맡는다). 상세는 appKeyEnvAlias.ts.
|
|
1983
|
+
define: appKeyEnvDefine(loadProjectEnv(config, mode2)),
|
|
1927
1984
|
optimizeDeps: {
|
|
1928
1985
|
exclude: ["@bstage-sdk/core", "@bstage-sdk/react"]
|
|
1929
1986
|
},
|
|
@@ -2035,7 +2092,7 @@ function bstageDevPlugin(options = {}) {
|
|
|
2035
2092
|
// src/vite/preset.ts
|
|
2036
2093
|
function createViteConfig(options = {}) {
|
|
2037
2094
|
return defineConfig({
|
|
2038
|
-
plugins: [react()],
|
|
2095
|
+
plugins: [react(), appKeyEnvAliasPlugin()],
|
|
2039
2096
|
define: {
|
|
2040
2097
|
"process.env.NODE_ENV": JSON.stringify("production")
|
|
2041
2098
|
},
|
|
@@ -2062,13 +2119,20 @@ function createViteConfig(options = {}) {
|
|
|
2062
2119
|
}
|
|
2063
2120
|
|
|
2064
2121
|
export {
|
|
2122
|
+
APP_KEY_VAR,
|
|
2123
|
+
LEGACY_APP_KEY_VAR,
|
|
2065
2124
|
LOGIN_PATH,
|
|
2125
|
+
isPublicPhase,
|
|
2066
2126
|
TRANSLATION_LANGUAGES,
|
|
2067
2127
|
DEFAULT_TRANSLATION_TIER,
|
|
2068
2128
|
TRANSLATION_CDN_ORIGIN,
|
|
2069
2129
|
TRANSLATION_CDN_BASE_KEY,
|
|
2070
2130
|
TRANSLATION_CDN_BUCKET_PREFIX,
|
|
2131
|
+
PORTAL_HOSTS,
|
|
2132
|
+
PORTAL_HOST_ENV_KEY,
|
|
2071
2133
|
resolveProjectTarget,
|
|
2134
|
+
appKeyEnvDefine,
|
|
2135
|
+
appKeyEnvAliasPlugin,
|
|
2072
2136
|
resolvePhase,
|
|
2073
2137
|
describePhase,
|
|
2074
2138
|
bstageDevPlugin,
|