@bstage-sdk/core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -192,20 +192,32 @@ var resolveBstageFetch = (input, init) => {
192
192
  return hook ? hook(input, init) : globalThis.fetch(input, init);
193
193
  };
194
194
  var APP_ID_PREFIX = "bsa_";
195
- var APP_SECRET_PREFIX = "bsp_";
196
- function warnOnSwappedCredentials(appId, appSecret) {
197
- const appIdLooksLikeSecret = typeof appId === "string" && appId.startsWith(APP_SECRET_PREFIX);
198
- const appSecretLooksLikeId = typeof appSecret === "string" && appSecret.startsWith(APP_ID_PREFIX);
199
- if (!appIdLooksLikeSecret && !appSecretLooksLikeId) return;
195
+ var APP_KEY_PREFIX = "bsp_";
196
+ function warnOnSwappedCredentials(appId, appKey) {
197
+ const appIdLooksLikeKey = typeof appId === "string" && appId.startsWith(APP_KEY_PREFIX);
198
+ const appKeyLooksLikeId = typeof appKey === "string" && appKey.startsWith(APP_ID_PREFIX);
199
+ if (!appIdLooksLikeKey && !appKeyLooksLikeId) return;
200
200
  console.warn(
201
- `[b.stage] appId\uC640 appSecret\uC774 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4.
202
- appId\uB294 "${APP_ID_PREFIX}", appSecret\uC740 "${APP_SECRET_PREFIX}"\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.
201
+ `[b.stage] appId\uC640 appKey\uAC00 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4.
202
+ appId\uB294 "${APP_ID_PREFIX}", appKey\uB294 "${APP_KEY_PREFIX}"\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.
203
203
  \uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 \uAC12\uC744 \uB2E4\uC2DC \uD655\uC778\uD574 \uC8FC\uC138\uC694.`
204
204
  );
205
205
  }
206
+ function resolveAppKey(config) {
207
+ return config.appKey || config.appSecret || void 0;
208
+ }
209
+ function warnOnMissingAppKey(appKey) {
210
+ if (appKey !== void 0) return;
211
+ console.warn(
212
+ `[b.stage] appKey\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 X-BSTAGE-APP-KEY \uD5E4\uB354 \uC5C6\uC774 \uD638\uCD9C\uB418\uC5B4 API\uAC00 401\uC744 \uB0C5\uB2C8\uB2E4.
213
+ .env\uC758 VITE_BSTAGE_APP_KEY(\uD30C\uD2B8\uB108 \uCF58\uC194\uC758 APP KEY)\uB97C \uD655\uC778\uD574 \uC8FC\uC138\uC694.`
214
+ );
215
+ }
206
216
  var BstageClient = class {
207
217
  constructor(config) {
208
- warnOnSwappedCredentials(config.appId, config.appSecret);
218
+ const appKey = resolveAppKey(config);
219
+ warnOnMissingAppKey(appKey);
220
+ warnOnSwappedCredentials(config.appId, appKey);
209
221
  const fetchFn = config.fetch ?? resolveBstageFetch;
210
222
  this.http = new HttpClient({
211
223
  baseUrl: config.baseUrl ?? resolveBaseUrl(),
@@ -214,7 +226,8 @@ var BstageClient = class {
214
226
  headers: {
215
227
  "Content-Type": "application/json",
216
228
  "X-BSTAGE-APP-ID": config.appId,
217
- "X-BSTAGE-APP-KEY": config.appSecret,
229
+ // 값이 없으면(UI 전용 템플릿의 빈 .env) 헤더 자체를 빼서 "undefined" 문자열이 실리지 않게 한다.
230
+ ...appKey !== void 0 ? { "X-BSTAGE-APP-KEY": appKey } : {},
218
231
  "X-BSTAGE-TENANT-ID": config.tenantId
219
232
  }
220
233
  });
package/dist/index.d.cts CHANGED
@@ -93,8 +93,21 @@ declare class HttpTimeoutError extends Error {
93
93
  interface BstageConfig {
94
94
  /** 파트너 콘솔에서 발급받은 앱 식별자. */
95
95
  appId: string;
96
- /** 파트너 콘솔에서 발급받은 앱 시크릿. */
97
- appSecret: string;
96
+ /**
97
+ * 파트너 콘솔에서 발급받은 앱 키(APP KEY). 게이트웨이 `X-BSTAGE-APP-KEY` 헤더로 나간다.
98
+ *
99
+ * **브라우저에 노출되는 값이다** — `VITE_` 환경변수로 번들에 평문으로 실리고 요청 헤더에서도
100
+ * 보인다. 비밀값이 아니며, 접근 범위는 게이트웨이가 앱 단위로 제어한다.
101
+ * `appKey`·`appSecret` 중 하나는 있어야 하고, 둘 다 있으면 `appKey`가 우선한다.
102
+ */
103
+ appKey?: string;
104
+ /**
105
+ * `appKey`의 옛 이름. 값은 같다(비밀값이 아니다).
106
+ *
107
+ * @deprecated `appKey`를 쓰세요. 기존 프로젝트 호환용 별칭이며 **0.4.0**(공개 배포판 `@bstage-sdk/core`
108
+ * 기준)에서 제거된다. 다른 배포 라인은 제거 전 MIGRATION 문서에 먼저 고지한다. 둘 다 주면 `appKey`가 우선한다.
109
+ */
110
+ appSecret?: string;
98
111
  /** 템플릿을 적용할 Space(테넌트) 식별자. */
99
112
  tenantId: string;
100
113
  /**
package/dist/index.d.ts CHANGED
@@ -93,8 +93,21 @@ declare class HttpTimeoutError extends Error {
93
93
  interface BstageConfig {
94
94
  /** 파트너 콘솔에서 발급받은 앱 식별자. */
95
95
  appId: string;
96
- /** 파트너 콘솔에서 발급받은 앱 시크릿. */
97
- appSecret: string;
96
+ /**
97
+ * 파트너 콘솔에서 발급받은 앱 키(APP KEY). 게이트웨이 `X-BSTAGE-APP-KEY` 헤더로 나간다.
98
+ *
99
+ * **브라우저에 노출되는 값이다** — `VITE_` 환경변수로 번들에 평문으로 실리고 요청 헤더에서도
100
+ * 보인다. 비밀값이 아니며, 접근 범위는 게이트웨이가 앱 단위로 제어한다.
101
+ * `appKey`·`appSecret` 중 하나는 있어야 하고, 둘 다 있으면 `appKey`가 우선한다.
102
+ */
103
+ appKey?: string;
104
+ /**
105
+ * `appKey`의 옛 이름. 값은 같다(비밀값이 아니다).
106
+ *
107
+ * @deprecated `appKey`를 쓰세요. 기존 프로젝트 호환용 별칭이며 **0.4.0**(공개 배포판 `@bstage-sdk/core`
108
+ * 기준)에서 제거된다. 다른 배포 라인은 제거 전 MIGRATION 문서에 먼저 고지한다. 둘 다 주면 `appKey`가 우선한다.
109
+ */
110
+ appSecret?: string;
98
111
  /** 템플릿을 적용할 Space(테넌트) 식별자. */
99
112
  tenantId: string;
100
113
  /**
package/dist/index.js CHANGED
@@ -138,20 +138,32 @@ var resolveBstageFetch = (input, init) => {
138
138
  return hook ? hook(input, init) : globalThis.fetch(input, init);
139
139
  };
140
140
  var APP_ID_PREFIX = "bsa_";
141
- var APP_SECRET_PREFIX = "bsp_";
142
- function warnOnSwappedCredentials(appId, appSecret) {
143
- const appIdLooksLikeSecret = typeof appId === "string" && appId.startsWith(APP_SECRET_PREFIX);
144
- const appSecretLooksLikeId = typeof appSecret === "string" && appSecret.startsWith(APP_ID_PREFIX);
145
- if (!appIdLooksLikeSecret && !appSecretLooksLikeId) return;
141
+ var APP_KEY_PREFIX = "bsp_";
142
+ function warnOnSwappedCredentials(appId, appKey) {
143
+ const appIdLooksLikeKey = typeof appId === "string" && appId.startsWith(APP_KEY_PREFIX);
144
+ const appKeyLooksLikeId = typeof appKey === "string" && appKey.startsWith(APP_ID_PREFIX);
145
+ if (!appIdLooksLikeKey && !appKeyLooksLikeId) return;
146
146
  console.warn(
147
- `[b.stage] appId\uC640 appSecret\uC774 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4.
148
- appId\uB294 "${APP_ID_PREFIX}", appSecret\uC740 "${APP_SECRET_PREFIX}"\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.
147
+ `[b.stage] appId\uC640 appKey\uAC00 \uC11C\uB85C \uBC14\uB010 \uAC83 \uAC19\uC2B5\uB2C8\uB2E4.
148
+ appId\uB294 "${APP_ID_PREFIX}", appKey\uB294 "${APP_KEY_PREFIX}"\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.
149
149
  \uD30C\uD2B8\uB108 \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uBC1B\uC740 \uAC12\uC744 \uB2E4\uC2DC \uD655\uC778\uD574 \uC8FC\uC138\uC694.`
150
150
  );
151
151
  }
152
+ function resolveAppKey(config) {
153
+ return config.appKey || config.appSecret || void 0;
154
+ }
155
+ function warnOnMissingAppKey(appKey) {
156
+ if (appKey !== void 0) return;
157
+ console.warn(
158
+ `[b.stage] appKey\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 X-BSTAGE-APP-KEY \uD5E4\uB354 \uC5C6\uC774 \uD638\uCD9C\uB418\uC5B4 API\uAC00 401\uC744 \uB0C5\uB2C8\uB2E4.
159
+ .env\uC758 VITE_BSTAGE_APP_KEY(\uD30C\uD2B8\uB108 \uCF58\uC194\uC758 APP KEY)\uB97C \uD655\uC778\uD574 \uC8FC\uC138\uC694.`
160
+ );
161
+ }
152
162
  var BstageClient = class {
153
163
  constructor(config) {
154
- warnOnSwappedCredentials(config.appId, config.appSecret);
164
+ const appKey = resolveAppKey(config);
165
+ warnOnMissingAppKey(appKey);
166
+ warnOnSwappedCredentials(config.appId, appKey);
155
167
  const fetchFn = config.fetch ?? resolveBstageFetch;
156
168
  this.http = new HttpClient({
157
169
  baseUrl: config.baseUrl ?? resolveBaseUrl(),
@@ -160,7 +172,8 @@ var BstageClient = class {
160
172
  headers: {
161
173
  "Content-Type": "application/json",
162
174
  "X-BSTAGE-APP-ID": config.appId,
163
- "X-BSTAGE-APP-KEY": config.appSecret,
175
+ // 값이 없으면(UI 전용 템플릿의 빈 .env) 헤더 자체를 빼서 "undefined" 문자열이 실리지 않게 한다.
176
+ ...appKey !== void 0 ? { "X-BSTAGE-APP-KEY": appKey } : {},
164
177
  "X-BSTAGE-TENANT-ID": config.tenantId
165
178
  }
166
179
  });
@@ -101,8 +101,8 @@ Platform → Template 이벤트는 `bubbles: false, composed: false`로 해당
101
101
 
102
102
  ```typescript
103
103
  const client = new BstageClient({
104
- appId: 'app-xxx',
105
- appSecret: 'secret-xxx',
104
+ appId: 'bsa_xxx',
105
+ appKey: 'bsp_xxx',
106
106
  tenantId: 'tenant-xxx',
107
107
  })
108
108
  ```
@@ -112,7 +112,8 @@ const client = new BstageClient({
112
112
  | 옵션 | 타입 | 설명 |
113
113
  | ----------- | --------------------------------------------- | ------------------------------------------------------------ |
114
114
  | `appId` | `string` | 앱 식별자 |
115
- | `appSecret` | `string` | 앱 시크릿 |
115
+ | `appKey` | `string` | 앱 키(APP KEY). `appKey`·`appSecret` 중 하나는 필요합니다. 포털이 발급하고 게이트웨이 `X-BSTAGE-APP-KEY` 헤더로 검증하는 값. **브라우저에 노출되는 값**으로 비밀값이 아닙니다 |
116
+ | `appSecret` | `string` (deprecated) | `appKey`의 옛 이름. 값은 같습니다. 둘 다 주면 `appKey`가 우선하며, 0.4.0(공개 배포판 `@bstage-sdk/core` 기준)에서 제거됩니다 |
116
117
  | `tenantId` | `string` | 테넌트 식별자 |
117
118
  | `phase` | `'dev' \| 'qa' \| 'real' \| 'sandbox'` (선택) | 환경. 현재 미사용 (향후 확장용) |
118
119
  | `baseUrl` | `string` (선택) | base URL 직접 지정. 미지정 시 `resolveBaseUrl()`로 자동 결정 |
@@ -173,7 +174,7 @@ const post = await client.get<PostResponse>('/content/v1/boards/{boardId}/posts/
173
174
 
174
175
  응답 타입(`MenuResponse` 등)은 게이트웨이 API Reference Doc을 보고 필요한 필드만 프로젝트 안에 선언해 쓰는 것을 권장합니다.
175
176
 
176
- 내부적으로 `HttpClient`(Fetch 기반, 인터셉터 지원)를 사용하며, 모든 요청에 `X-BSTAGE-APP-ID`/`X-BSTAGE-APP-KEY`/`X-BSTAGE-TENANT-ID` 헤더를 자동 포함합니다.
177
+ 내부적으로 `HttpClient`(Fetch 기반, 인터셉터 지원)를 사용하며, 모든 요청에 `X-BSTAGE-APP-ID`/`X-BSTAGE-APP-KEY`/`X-BSTAGE-TENANT-ID` 헤더를 자동 포함합니다(앱 키가 없으면 `X-BSTAGE-APP-KEY`는 생략되고 생성 시 콘솔 경고가 납니다).
177
178
 
178
179
  ---
179
180
 
@@ -58,7 +58,7 @@ Vite 빌드라서 `import.meta.env.VITE_BSTAGE_*`는 **빌드 시점에 문자
58
58
  - **대상 판정** — 두 조건을 모두 만족할 때만 점검합니다.
59
59
  1. 번들에 BstageClient의 앱 ID 헤더(`X-BSTAGE-APP-ID`)가 있을 것. API를 쓰지 않는 UI 전용 템플릿은 `client.ts`를 스캐폴드만 하고 import하지 않으면 트리셰이킹으로 사라지므로 대상이 아닙니다.
60
60
  2. 소스가 `import.meta.env.VITE_BSTAGE_*`를 참조할 것. cli 0.40.1 이전 스캐폴드처럼 `client.ts`에 키를 리터럴로 박은 프로젝트는 `.env`가 없는 게 정상이므로(그 전환은 마이그레이션에서 `선택`) 대상이 아닙니다. 참조한 변수만 검사합니다.
61
- - **검사 항목** — 값 누락, 자리표시자(`YOUR_APP_ID` 등) 잔존, appId·appSecret 뒤바뀜, 접두사(`bsa_`/`bsp_`) 불일치
61
+ - **검사 항목** — 값 누락, 자리표시자(`YOUR_APP_ID` 등) 잔존, appId·appKey 뒤바뀜, 접두사(`bsa_`/`bsp_`) 불일치. 앱 키는 `VITE_BSTAGE_APP_KEY`(정본)와 옛 이름 `VITE_BSTAGE_APP_SECRET`을 한 값으로 보며, 한쪽만 있으면 빌드가 다른 이름으로도 읽히게 이어 붙인다
62
62
  - **env 해석** — Vite의 `loadEnv`를 그대로 씁니다. `.env` 파일뿐 아니라 CI가 `process.env`로 주입한 값도 번들에 인라인되므로 파일만 읽으면 오탐이 납니다.
63
63
  - **빌드를 실패시키지 않습니다** — 값이 비어도 번들 자체는 정상적으로 나오고, 여기서 실패시키면 기존 소비자 CI가 깨집니다. 경고는 빌드 로그 맨 끝에 출력됩니다.
64
64
  - 어느 환경에서 발급한 키인지(QA 키를 리얼에 썼는지)는 **감지할 수 없습니다** — 키 형식에 발급 환경이 담기지 않습니다. 그 계열은 [GETTING_STARTED.md](./GETTING_STARTED.md) "9.2 배포 전 점검"의 체크리스트가 담당합니다.
@@ -6,7 +6,7 @@ b.stage 3rd-party 템플릿을 개발하고 배포하는 전체 흐름을 안내
6
6
 
7
7
  - **Node.js** v20 이상
8
8
  - **npm** 또는 **pnpm**
9
- - **BstageClient 인증 정보** (선택) — API 호출에 필요한 `appId`, `appSecret`을 파트너 콘솔에서 발급받아 두면 프로젝트 생성 시 자동으로 설정됩니다.
9
+ - **BstageClient 인증 정보** (선택) — API 호출에 필요한 `appId`, `appKey`(포털의 APP KEY)를 파트너 콘솔에서 발급받아 두면 프로젝트 생성 시 자동으로 설정됩니다.
10
10
 
11
11
  ---
12
12
 
@@ -259,11 +259,13 @@ import { BstageClient } from '@bstage-sdk/core'
259
259
 
260
260
  export const client = new BstageClient({
261
261
  appId: 'bsa_xxxxx',
262
- appSecret: 'bsp_xxxxx',
262
+ appKey: 'bsp_xxxxx',
263
263
  tenantId: 'myspace',
264
264
  })
265
265
  ```
266
266
 
267
+ - `appKey`는 포털 화면의 **APP KEY**, 게이트웨이 헤더 `X-BSTAGE-APP-KEY`와 같은 값입니다. `VITE_` 환경변수로 번들에 평문으로 실려 브라우저에서 보이는 값이라 **비밀값이 아닙니다** — 접근 범위는 게이트웨이가 앱 단위로 제어합니다. 옛 이름 `appSecret`(`.env`의 `VITE_BSTAGE_APP_SECRET`)은 별칭으로 당분간 동작합니다([마이그레이션 가이드](./MIGRATION.md) 참고).
268
+
267
269
  - 게이트웨이 base URL은 클라이언트가 임베드된 **페이지의 origin**(`location.origin + /gw`)으로 자동 결정됩니다. `{tenant}.sandstage.in`에 배포되면 그 환경의 게이트웨이로 가므로 환경별로 다시 빌드·설정할 필요가 없습니다.
268
270
  - 로컬 개발에서는 **dev 서버**가 `.env`의 `VITE_BSTAGE_PHASE`(→ `vite.config.ts`의 `bstageDevPlugin`)를 보고 해당 phase 게이트웨이로 프록시합니다. 이 phase는 **dev 서버 설정**이지 `BstageClient`의 옵션이 아닙니다.
269
271
  - 특정 게이트웨이를 직접 지정하려면 `baseUrl` 옵션을 사용합니다(예: 플랫폼 임베드가 아닌 standalone 앱).
@@ -629,7 +631,7 @@ CI에서는 `BSTAGE_TOKEN`·`BSTAGE_PORTAL_URL`(또는 `VITE_BSTAGE_PHASE`)·`BS
629
631
 
630
632
  | 증상 | 원인 | 조치 | 소관 |
631
633
  | ----------------------- | ---------------------------------------- | ----------------------------------------------- | ------------ |
632
- | `401 Unauthorized` | 다른 환경(QA 등)의 키로 배포 / 키 미발급 | 배포할 환경에서 발급한 appId·appSecret인지 확인 | 파트너 콘솔 |
634
+ | `401 Unauthorized` | 다른 환경(QA 등)의 키로 배포 / 키 미발급 | 배포할 환경에서 발급한 appId·appKey인지 확인 | 파트너 콘솔 |
633
635
  | `403 Forbidden` | Space ID가 프로젝트에 미등록 | BE에 Space ID 등록 요청 | BE |
634
636
  | `400 MISSING_ORIGIN` | 키의 Allowed Origins에 배포 도메인 없음 | 배포 도메인을 키 설정에 추가 요청 | BE (키 설정) |
635
637
  | 템플릿이 화면에 안 뜸 | 콘솔 "커스텀 템플릿 사용" 토글이 꺼짐 | 콘솔에서 토글 확인 | 콘솔 |
package/docs/INIT.md CHANGED
@@ -31,7 +31,7 @@ Node.js v20 이상과 npm(또는 pnpm)만 있으면 됩니다. SDK 패키지는
31
31
  | 4 | 패키지 매니저 | O | `npm` 또는 `pnpm` |
32
32
  | 5 | API 키 설정 여부 | - | "없어도 시작할 수 있어요" |
33
33
  | 6 | APP-ID | - | API 키가 있는 경우만 |
34
- | 7 | Secret | - | API 키가 있는 경우만 |
34
+ | 7 | APP KEY | - | API 키가 있는 경우만 |
35
35
 
36
36
  ### 레포명 결정 로직
37
37
 
package/docs/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
+ ---
@@ -35,7 +35,7 @@ Shadow DOM으로 서드파티 CSS가 플랫폼 UI에 영향을 주거나 플랫
35
35
 
36
36
  ### 토큰 비노출
37
37
 
38
- 서드파티 코드는 플랫폼 사용자의 인증 토큰에 직접 접근하지 않습니다. API 호출은 BstageClient가 `appId`/`appSecret`으로 처리합니다.
38
+ 서드파티 코드는 플랫폼 사용자의 인증 토큰에 직접 접근하지 않습니다. API 호출은 BstageClient가 `appId`/`appKey`로 처리합니다.
39
39
 
40
40
  ---
41
41
 
@@ -145,7 +145,7 @@ Custom Element 등록(`customElements.define`)은 BstageElement 자체가 하지
145
145
 
146
146
  파트너에게 공개된 API에 접근하는 HTTP 클라이언트입니다.
147
147
 
148
- **설계 의도:** 서드파티가 플랫폼 API를 호출할 때, 플랫폼 사용자의 인증 토큰 대신 `appId`/`appSecret`/`tenantId`를 사용합니다. 이로써 서드파티 코드가 사용자 토큰에 접근할 수 없으며 API 접근 범위를 앱 단위로 제어할 수 있습니다.
148
+ **설계 의도:** 서드파티가 플랫폼 API를 호출할 때, 플랫폼 사용자의 인증 토큰 대신 `appId`/`appKey`/`tenantId`를 사용합니다. 이로써 서드파티 코드가 사용자 토큰에 접근할 수 없으며 API 접근 범위를 앱 단위로 제어할 수 있습니다.
149
149
 
150
150
  **base URL 결정:** `resolveBaseUrl()` 함수가 `location.origin/gw`를 반환하여 현재 호스트 기반으로 요청합니다. 로컬 개발 시에는 devVitePlugin이 이 함수를 치환해 localhost 프록시로 라우팅합니다.
151
151
 
@@ -242,7 +242,7 @@ Shadow DOM은 **스타일 격리**이지 **보안 격리**가 아닙니다. Java
242
242
  | 토큰/쿠키 탈취 | 민감 정보는 HttpOnly 쿠키로만 관리 |
243
243
  | API 남용 | API Gateway에서 rate limiting |
244
244
 
245
- 서드파티는 BstageClient를 통해 `appId`/`appSecret`으로 API에 접근하고 플랫폼 사용자의 인증 토큰에는 직접 접근하지 않습니다.
245
+ 서드파티는 BstageClient를 통해 `appId`/`appKey`로 API에 접근하고 플랫폼 사용자의 인증 토큰에는 직접 접근하지 않습니다.
246
246
 
247
247
  ---
248
248
 
@@ -132,7 +132,7 @@ import { createTemplate, usePlatformEvent } from '@bstage-sdk/react'
132
132
  import { BstageClient } from '@bstage-sdk/core'
133
133
  import { useState } from 'react'
134
134
 
135
- const client = new BstageClient({ appId: '...', appSecret: '...', tenantId: '...' })
135
+ const client = new BstageClient({ appId: '...', appKey: '...', tenantId: '...' })
136
136
 
137
137
  function TicketCustomField() {
138
138
  const [resourceId, setResourceId] = useState<string>()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bstage-sdk/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "bstage SDK core — HTTP client for b.stage API",
5
5
  "type": "module",
6
6
  "publishConfig": {