connectbase-client 6.10.0 → 6.11.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/CHANGELOG.md +47 -0
- package/README.md +2 -0
- package/dist/cli.js +230 -35
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +118 -40
- package/dist/index.d.ts +118 -40
- package/dist/index.js +537 -118
- package/dist/index.mjs +537 -118
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,53 @@
|
|
|
3
3
|
본 SDK 의 모든 주요 변경사항을 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/) 형식으로 기록합니다.
|
|
4
4
|
버전은 [Semantic Versioning](https://semver.org/lang/ko/) 을 따릅니다.
|
|
5
5
|
|
|
6
|
+
## [6.11.0] - 2026-11-11
|
|
7
|
+
|
|
8
|
+
### Security — CLI 브라우저 로그인은 터미널의 사용자 코드로 승인한다
|
|
9
|
+
|
|
10
|
+
- `connectbase login` 이 터미널에 사용자 코드(XXXX-XXXX)를 보여 주고, 브라우저에서는 그 코드를 직접 입력해 승인한다
|
|
11
|
+
(RFC 8628 방식). 예전에는 승인 링크가 곧 폴링 자격이어서, 남이 만든 링크를 승인하면 그 사람이 내 계정의 Secret Key 를
|
|
12
|
+
받았다. 승인 화면은 요청한 기기, OS, IP, 시각을 보여 주고, 거부하면 CLI 가 바로 끝난다.
|
|
13
|
+
- 구버전 CLI 는 서버가 `426 cli_upgrade_required` 로 거절한다. 이 버전 이상으로 올려야 로그인할 수 있다.
|
|
14
|
+
|
|
15
|
+
### Changed — 로그인한 적 없는 방문자의 세션 복구가 브라우저 콘솔 오류를 남기지 않는다
|
|
16
|
+
|
|
17
|
+
- 쿠키만으로 세션을 복구할 때 `/v1/auth/re-issue?quiet=1` 로 묻고, 세션이 없으면 서버가 401 대신 204 로 답한다. 앱이 받는
|
|
18
|
+
결과(세션 없음, `onTokenExpired`)는 그대로이고, 매 페이지 「Failed to load resource 401」 오류(Lighthouse 콘솔 오류)만 사라진다.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Security — 소셜 로그인 콜백은 이 브라우저에서 시작한 로그인만 받는다
|
|
22
|
+
|
|
23
|
+
- `cb.oauth.signIn()` / `signUp()` 이 일회용 `state` 를 이 브라우저(localStorage)에 남기고 서버로 보낸다. 콜백의 토큰이나
|
|
24
|
+
`code` 는 그 값이 돌아올 때만 받는다. 확인되지 않으면 토큰을 쓰지 않고 `getCallbackResult()` 가
|
|
25
|
+
`{ error: 'oauth_state_mismatch' }` 를, `exchangeCodeFromCallback()` 이 같은 코드의 오류를 던진다. 남이 보낸 콜백
|
|
26
|
+
링크로 다른 계정에 로그인되던 문제(로그인 CSRF)를 막는다. 기존 세션은 끊지 않는다.
|
|
27
|
+
- 앱이 넘긴 `state` 는 서버로 보내지 않고 `result.state` 로 그대로 돌려준다.
|
|
28
|
+
- URL 의 토큰은 `new ConnectBase()` 때 `history.replaceState` 로 바로 지운다(`autoRestoreSession` 과 무관). `location.search`
|
|
29
|
+
로 토큰을 직접 읽던 코드는 `getCallbackResult()` 결과를 써야 한다.
|
|
30
|
+
- 인가 URL 을 직접 만드는 앱을 위한 `cb.oauth.createCallbackState(appState?)` 추가. 그 값을 `state` 쿼리로 넘긴다.
|
|
31
|
+
- 콜백 주소는 로그인을 시작한 페이지와 같은 출처여야 한다(다르면 시작할 때 콘솔 경고).
|
|
32
|
+
- 팝업: 우리가 연 팝업(`event.source`)에서 우리가 아는 출처(`event.origin`)로 온 메시지만 받는다. 서버 제공 콜백을 쓰면
|
|
33
|
+
`opener_origin` 을 보내 1회용 `code` 를 받아 교환한다. 앱이 지정한 팝업 콜백 페이지는 같은 출처 창에만 결과를 보낸다.
|
|
34
|
+
서버의 `popup_opener_not_allowed` 는 해결 방법을 담은 `ApiError`(같은 `code`)로 던진다.
|
|
35
|
+
- 분석(`page_url`, `referrer`)과 에러 리포트(`url`, `source`, `message`, `stack`)에서 토큰, `code` 같은 자격증명을 지운다.
|
|
36
|
+
|
|
37
|
+
### Security — 그 밖
|
|
38
|
+
|
|
39
|
+
- 게임 상태 델타의 `__proto__`, `constructor`, `prototype` 경로를 버리고 자기 속성으로만 따라간다(프로토타입 오염).
|
|
40
|
+
- 브라우저에서 `secretKey`(또는 `cb_sk_` 를 publicKey 로)를 쓰면 경고한다.
|
|
41
|
+
- CLI: 서버 주소는 `--base-url` > `CONNECTBASE_BASE_URL` > `.connectbaserc` 순이다. 저장소의 `.connectbaserc` 가 공식 서버가
|
|
42
|
+
아닌 주소를 정하면 터미널에서는 확인을 묻고 비대화형에서는 중단한다(`--allow-repo-base-url` 로 허용). `.connectbaserc` 의
|
|
43
|
+
`deployDir` 이 프로젝트 밖이면 배포하지 않는다. 브라우저 열기는 셸을 거치지 않는다. `.connectbaserc`, `.mcp.json` 은
|
|
44
|
+
0600 으로 쓴다.
|
|
45
|
+
|
|
46
|
+
### Fixed — 큰 웹 배포가 「요청 본문이 상한을 초과했습니다」로 실패하던 문제
|
|
47
|
+
|
|
48
|
+
서버는 배포 요청 하나를 128 MiB 로 자릅니다. 변경 파일이 많으면(예: 영상 120개, 148MB) 한 요청에 다 실어 실패했고, 나눠
|
|
49
|
+
올리면 release 가 여러 개로 쪼개져 중간 판이 잠깐 사이트에 나갔습니다. 이제 요청 하나가 약 96 MiB 를 넘으면 바이너리를 큰
|
|
50
|
+
것부터 Object Storage 로 직접 올려(presign, 이미 있던 경로) 요청 하나, release 하나로 반영합니다. 텍스트만으로 넘치면 이유를
|
|
51
|
+
알리고 멈춥니다.
|
|
52
|
+
|
|
6
53
|
## [6.10.0] - 2026-10-30
|
|
7
54
|
|
|
8
55
|
### Added — 실시간 메시지의 `fromConn`(보낸 연결의 서버 ID)
|
package/README.md
CHANGED
|
@@ -907,6 +907,8 @@ await cb.storage.deletePageMeta('web-storage-id', '/products/123')
|
|
|
907
907
|
|
|
908
908
|
문서를 등록하고 BM25 풀텍스트 검색으로 관련 청크를 찾는 RAG 인프라. 한국어는 nori 형태소 분석기 적용. AI 채팅에 컨텍스트로 연결하려면 `cb.ai.chatStream({ knowledgeBaseId })` 를 사용한다.
|
|
909
909
|
|
|
910
|
+
> 문서 추가, 수정, 삭제는 로그인한 회원 토큰(본인 자료)이나 `secretKey`(서버)가 필요합니다. Public Key 만으로는 401 `KB_WRITE_CREDENTIAL_REQUIRED` 입니다.
|
|
911
|
+
|
|
910
912
|
```typescript
|
|
911
913
|
// 텍스트 문서 추가 (즉시 처리)
|
|
912
914
|
const doc = await cb.knowledge.addDocument('kb-id', {
|
package/dist/cli.js
CHANGED
|
@@ -31,12 +31,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/cli.ts
|
|
32
32
|
var cli_exports = {};
|
|
33
33
|
__export(cli_exports, {
|
|
34
|
+
DEPLOY_REQUEST_BUDGET_BYTES: () => DEPLOY_REQUEST_BUDGET_BYTES,
|
|
34
35
|
TunnelConnectSelector: () => TunnelConnectSelector,
|
|
35
36
|
ancestorCommandLines: () => ancestorCommandLines,
|
|
36
37
|
apiErrorMessage: () => apiErrorMessage,
|
|
38
|
+
browserAuthStartBody: () => browserAuthStartBody,
|
|
39
|
+
browserAuthUrl: () => browserAuthUrl,
|
|
40
|
+
browserOpenCommand: () => browserOpenCommand,
|
|
37
41
|
buildFirstHint: () => buildFirstHint,
|
|
38
42
|
collectDeployFiles: () => collectDeployFiles,
|
|
39
43
|
computeDeployDiff: () => computeDeployDiff,
|
|
44
|
+
decideBaseUrl: () => decideBaseUrl,
|
|
45
|
+
describeBaseUrl: () => describeBaseUrl,
|
|
40
46
|
detectPackageManager: () => detectPackageManager,
|
|
41
47
|
formatSkippedFilesReport: () => formatSkippedFilesReport,
|
|
42
48
|
getTunnelConnectCandidates: () => getTunnelConnectCandidates,
|
|
@@ -44,21 +50,28 @@ __export(cli_exports, {
|
|
|
44
50
|
isAllowedDeployExt: () => isAllowedDeployExt,
|
|
45
51
|
isBinaryDeployExt: () => isBinaryDeployExt,
|
|
46
52
|
isExtensionlessDataFile: () => isExtensionlessDataFile,
|
|
53
|
+
isOutsideProject: () => isOutsideProject,
|
|
54
|
+
isPlatformBaseUrl: () => isPlatformBaseUrl,
|
|
55
|
+
loadConfig: () => loadConfig,
|
|
47
56
|
makeRequest: () => makeRequest,
|
|
48
57
|
normalizeRelativePath: () => normalizeRelativePath,
|
|
58
|
+
openBrowser: () => openBrowser,
|
|
49
59
|
packageManagerCommands: () => packageManagerCommands,
|
|
50
60
|
packageScriptInvocationPattern: () => packageScriptInvocationPattern,
|
|
51
61
|
parseArgs: () => parseArgs,
|
|
52
62
|
registerEndpointBinding: () => registerEndpointBinding,
|
|
53
63
|
runningInsidePackageScript: () => runningInsidePackageScript,
|
|
64
|
+
selectBinariesToOffload: () => selectBinariesToOffload,
|
|
54
65
|
sha256Hex: () => sha256Hex,
|
|
55
|
-
storageAdminHeaders: () => storageAdminHeaders
|
|
66
|
+
storageAdminHeaders: () => storageAdminHeaders,
|
|
67
|
+
writeSecretFile: () => writeSecretFile
|
|
56
68
|
});
|
|
57
69
|
module.exports = __toCommonJS(cli_exports);
|
|
58
70
|
var crypto = __toESM(require("crypto"));
|
|
59
71
|
var fs2 = __toESM(require("fs"));
|
|
60
72
|
var http = __toESM(require("http"));
|
|
61
73
|
var https = __toESM(require("https"));
|
|
74
|
+
var os2 = __toESM(require("os"));
|
|
62
75
|
var path2 = __toESM(require("path"));
|
|
63
76
|
var readline = __toESM(require("readline"));
|
|
64
77
|
|
|
@@ -157,7 +170,7 @@ function handleTunnelError(msg, appId, localPort) {
|
|
|
157
170
|
}
|
|
158
171
|
|
|
159
172
|
// src/cli.ts
|
|
160
|
-
var VERSION = "6.
|
|
173
|
+
var VERSION = "6.11.0" ? "6.11.0" : getPackageVersion();
|
|
161
174
|
var DEFAULT_BASE_URL = "https://api.connectbase.world";
|
|
162
175
|
var colors = {
|
|
163
176
|
reset: "\x1B[0m",
|
|
@@ -315,6 +328,32 @@ function isExtensionlessDataFile(filePath) {
|
|
|
315
328
|
return path2.extname(base) === "" && !ALLOWED_EXTENSIONLESS_FILES.has(base);
|
|
316
329
|
}
|
|
317
330
|
var WEB_FILE_MAX_STORED_BYTES = 6 * 1024 * 1024 - 64 * 1024;
|
|
331
|
+
var DEPLOY_REQUEST_BUDGET_BYTES = 96 * 1024 * 1024;
|
|
332
|
+
function deployEntryBytes(f) {
|
|
333
|
+
return Buffer.byteLength(f.content, "utf8") + Buffer.byteLength(f.path, "utf8") + 256;
|
|
334
|
+
}
|
|
335
|
+
function selectBinariesToOffload(upsert, perFileLimit = WEB_FILE_MAX_STORED_BYTES, budget = DEPLOY_REQUEST_BUDGET_BYTES) {
|
|
336
|
+
const offload = new Set(
|
|
337
|
+
upsert.filter(
|
|
338
|
+
(f) => f.is_binary && Buffer.byteLength(f.content, "utf8") > perFileLimit
|
|
339
|
+
)
|
|
340
|
+
);
|
|
341
|
+
let inlineBytes = upsert.reduce(
|
|
342
|
+
(sum, f) => offload.has(f) ? sum : sum + deployEntryBytes(f),
|
|
343
|
+
0
|
|
344
|
+
);
|
|
345
|
+
if (inlineBytes > budget) {
|
|
346
|
+
const rest = upsert.filter((f) => f.is_binary && !offload.has(f)).sort(
|
|
347
|
+
(a, b) => Buffer.byteLength(b.content, "utf8") - Buffer.byteLength(a.content, "utf8")
|
|
348
|
+
);
|
|
349
|
+
for (const f of rest) {
|
|
350
|
+
if (inlineBytes <= budget) break;
|
|
351
|
+
offload.add(f);
|
|
352
|
+
inlineBytes -= deployEntryBytes(f);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return { offload: upsert.filter((f) => offload.has(f)), inlineBytes };
|
|
356
|
+
}
|
|
318
357
|
function sha256Hex(value) {
|
|
319
358
|
return crypto.createHash("sha256").update(value, "utf8").digest("hex");
|
|
320
359
|
}
|
|
@@ -359,11 +398,13 @@ function reportDeployAuthError(status, data) {
|
|
|
359
398
|
return true;
|
|
360
399
|
}
|
|
361
400
|
function loadConfig() {
|
|
401
|
+
const envBaseUrl = process.env.CONNECTBASE_BASE_URL;
|
|
362
402
|
const config = {
|
|
363
403
|
publicKey: process.env.CONNECTBASE_PUBLIC_KEY,
|
|
364
404
|
secretKey: process.env.CONNECTBASE_SECRET_KEY,
|
|
365
405
|
storageId: process.env.CONNECTBASE_STORAGE_ID,
|
|
366
|
-
baseUrl:
|
|
406
|
+
baseUrl: envBaseUrl || DEFAULT_BASE_URL,
|
|
407
|
+
baseUrlFromRepo: false
|
|
367
408
|
};
|
|
368
409
|
const rcPath = path2.join(process.cwd(), ".connectbaserc");
|
|
369
410
|
if (fs2.existsSync(rcPath)) {
|
|
@@ -372,7 +413,10 @@ function loadConfig() {
|
|
|
372
413
|
if (rcContent.publicKey) config.publicKey = rcContent.publicKey;
|
|
373
414
|
if (rcContent.secretKey) config.secretKey = rcContent.secretKey;
|
|
374
415
|
if (rcContent.storageId) config.storageId = rcContent.storageId;
|
|
375
|
-
if (rcContent.baseUrl
|
|
416
|
+
if (!envBaseUrl && rcContent.baseUrl && typeof rcContent.baseUrl === "string") {
|
|
417
|
+
config.baseUrl = rcContent.baseUrl;
|
|
418
|
+
config.baseUrlFromRepo = true;
|
|
419
|
+
}
|
|
376
420
|
if (rcContent.deployDir) config.deployDir = rcContent.deployDir;
|
|
377
421
|
if (rcContent.includeExtensionless === true)
|
|
378
422
|
config.includeExtensionless = true;
|
|
@@ -573,12 +617,9 @@ function putToPresignedUrl(uploadUrl, body, contentType, timeoutMs) {
|
|
|
573
617
|
});
|
|
574
618
|
}
|
|
575
619
|
async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, timeoutMs) {
|
|
576
|
-
const
|
|
577
|
-
(f) => Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES
|
|
620
|
+
const largeText = diff.upsert.filter(
|
|
621
|
+
(f) => !f.is_binary && Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES
|
|
578
622
|
);
|
|
579
|
-
if (oversized.length === 0) return;
|
|
580
|
-
const largeText = oversized.filter((f) => !f.is_binary);
|
|
581
|
-
const largeBinaries = oversized.filter((f) => f.is_binary);
|
|
582
623
|
if (largeText.length > 0) {
|
|
583
624
|
error(
|
|
584
625
|
`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uD30C\uC77C\uB2F9 \uD06C\uAE30 \uD55C\uB3C4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)})\uB97C \uCD08\uACFC\uD55C \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC774 \uC788\uC5B4 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:`
|
|
@@ -593,6 +634,18 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
|
|
|
593
634
|
);
|
|
594
635
|
process.exit(1);
|
|
595
636
|
}
|
|
637
|
+
const { offload: largeBinaries, inlineBytes } = selectBinariesToOffload(
|
|
638
|
+
diff.upsert
|
|
639
|
+
);
|
|
640
|
+
if (inlineBytes > DEPLOY_REQUEST_BUDGET_BYTES) {
|
|
641
|
+
error(
|
|
642
|
+
`\uD14D\uC2A4\uD2B8 \uD30C\uC77C\uB9CC\uC73C\uB85C \uBC30\uD3EC \uC694\uCCAD \uD55C\uB3C4(\uC57D ${formatSize(DEPLOY_REQUEST_BUDGET_BYTES)})\uB97C \uB118\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB294 \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB85C \uB3CC\uB9B4 \uC218 \uC5C6\uC5B4\uC694.`
|
|
643
|
+
);
|
|
644
|
+
warn(
|
|
645
|
+
"\uBCC0\uACBD\uD55C \uD14D\uC2A4\uD2B8 \uD30C\uC77C\uC744 \uB098\uB220 \uC5EC\uB7EC \uBC88 \uBC30\uD3EC\uD558\uAC70\uB098, \uD070 \uB370\uC774\uD130 \uD30C\uC77C\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(cb.storage)\uB85C \uC62E\uAE30\uC138\uC694."
|
|
646
|
+
);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
596
649
|
if (largeBinaries.length === 0) return;
|
|
597
650
|
const presignFiles = largeBinaries.map((f) => ({
|
|
598
651
|
path: f.path,
|
|
@@ -632,7 +685,7 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
|
|
|
632
685
|
}
|
|
633
686
|
const entryByPath = new Map(presigned.map((p) => [p.path, p]));
|
|
634
687
|
info(
|
|
635
|
-
`\
|
|
688
|
+
`\uBC14\uC774\uB108\uB9AC ${largeBinaries.length}\uAC1C\uB97C Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4(\uD30C\uC77C\uB2F9 \uD55C\uB3C4 \uCD08\uACFC\uC774\uAC70\uB098 \uC694\uCCAD \uD558\uB098\uAC00 \uC57D ${formatSize(DEPLOY_REQUEST_BUDGET_BYTES)} \uB97C \uB118\uC5B4\uC11C)...`
|
|
636
689
|
);
|
|
637
690
|
for (const f of largeBinaries) {
|
|
638
691
|
const entry = entryByPath.get(f.path);
|
|
@@ -1197,26 +1250,59 @@ function getProjectRoot() {
|
|
|
1197
1250
|
function consoleUrlFor(baseUrl) {
|
|
1198
1251
|
return baseUrl.replace("api.", "");
|
|
1199
1252
|
}
|
|
1200
|
-
function
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1253
|
+
function browserOpenCommand(url, platform2 = process.platform) {
|
|
1254
|
+
let target;
|
|
1255
|
+
try {
|
|
1256
|
+
const parsed = new URL(url);
|
|
1257
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
target = parsed.toString();
|
|
1261
|
+
} catch {
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
if (platform2 === "darwin") return { command: "open", args: [target] };
|
|
1265
|
+
if (platform2 === "win32") {
|
|
1266
|
+
return {
|
|
1267
|
+
command: "rundll32",
|
|
1268
|
+
args: ["url.dll,FileProtocolHandler", target]
|
|
1269
|
+
};
|
|
1210
1270
|
}
|
|
1211
|
-
|
|
1271
|
+
return { command: "xdg-open", args: [target] };
|
|
1272
|
+
}
|
|
1273
|
+
function openBrowser(url) {
|
|
1274
|
+
const opener = browserOpenCommand(url);
|
|
1275
|
+
if (!opener) return;
|
|
1276
|
+
const { spawn } = require("child_process");
|
|
1277
|
+
const child = spawn(opener.command, opener.args, {
|
|
1278
|
+
stdio: "ignore",
|
|
1279
|
+
detached: true
|
|
1280
|
+
});
|
|
1281
|
+
child.on("error", () => {
|
|
1212
1282
|
});
|
|
1283
|
+
child.unref();
|
|
1284
|
+
}
|
|
1285
|
+
function browserAuthStartBody() {
|
|
1286
|
+
return {
|
|
1287
|
+
flow: "user_code",
|
|
1288
|
+
hostname: os2.hostname(),
|
|
1289
|
+
os: `${process.platform} ${process.arch}`,
|
|
1290
|
+
cli_version: VERSION
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function browserAuthUrl(baseUrl, start) {
|
|
1294
|
+
if (!start.user_code) {
|
|
1295
|
+
return `${consoleUrlFor(baseUrl)}/auth/cli-auth?session=${start.session_id}`;
|
|
1296
|
+
}
|
|
1297
|
+
return `${consoleUrlFor(baseUrl)}/auth/cli-auth`;
|
|
1213
1298
|
}
|
|
1214
1299
|
async function browserAuthFlow(baseUrl = DEFAULT_BASE_URL) {
|
|
1215
1300
|
info("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4...");
|
|
1216
1301
|
const startRes = await makeRequest(
|
|
1217
1302
|
`${baseUrl}/v1/public/cli-auth/start`,
|
|
1218
1303
|
"POST",
|
|
1219
|
-
{}
|
|
1304
|
+
{},
|
|
1305
|
+
JSON.stringify(browserAuthStartBody())
|
|
1220
1306
|
);
|
|
1221
1307
|
if (startRes.status !== 200) {
|
|
1222
1308
|
error(
|
|
@@ -1224,15 +1310,28 @@ async function browserAuthFlow(baseUrl = DEFAULT_BASE_URL) {
|
|
|
1224
1310
|
);
|
|
1225
1311
|
process.exit(1);
|
|
1226
1312
|
}
|
|
1227
|
-
const
|
|
1228
|
-
const
|
|
1313
|
+
const start = startRes.data;
|
|
1314
|
+
const { session_id } = start;
|
|
1315
|
+
const authUrl = browserAuthUrl(baseUrl, start);
|
|
1229
1316
|
log(`
|
|
1230
1317
|
${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC138\uC694:${colors.reset}`);
|
|
1231
1318
|
log(`${colors.dim}${authUrl}${colors.reset}
|
|
1232
1319
|
`);
|
|
1320
|
+
if (start.user_code) {
|
|
1321
|
+
log(`${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0 \uC774 \uCF54\uB4DC\uB97C \uC785\uB825\uD558\uC138\uC694:${colors.reset}`);
|
|
1322
|
+
log(`
|
|
1323
|
+
${colors.green}${start.user_code}${colors.reset}
|
|
1324
|
+
`);
|
|
1325
|
+
log(
|
|
1326
|
+
`${colors.dim}\uC774 \uBA85\uB839\uC744 \uC9C1\uC811 \uC2E4\uD589\uD558\uC9C0 \uC54A\uC558\uB2E4\uBA74 \uC2B9\uC778\uD558\uC9C0 \uB9C8\uC138\uC694.${colors.reset}
|
|
1327
|
+
`
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1233
1330
|
openBrowser(authUrl);
|
|
1234
|
-
const pollInterval =
|
|
1235
|
-
const maxAttempts =
|
|
1331
|
+
const pollInterval = (start.interval ?? 3) * 1e3;
|
|
1332
|
+
const maxAttempts = Math.ceil(
|
|
1333
|
+
(start.expires_in ?? 300) * 1e3 / pollInterval
|
|
1334
|
+
);
|
|
1236
1335
|
let attempts = 0;
|
|
1237
1336
|
let consecutive404 = 0;
|
|
1238
1337
|
const max404Retries = 5;
|
|
@@ -1346,7 +1445,7 @@ function saveSecretKeyToRc(secretKey, dir = process.cwd()) {
|
|
|
1346
1445
|
}
|
|
1347
1446
|
}
|
|
1348
1447
|
rcData.secretKey = secretKey;
|
|
1349
|
-
|
|
1448
|
+
writeSecretFile(rcPath, `${JSON.stringify(rcData, null, 2)}
|
|
1350
1449
|
`);
|
|
1351
1450
|
}
|
|
1352
1451
|
async function fetchAppsWithSecretKey(baseUrl, secretKey) {
|
|
@@ -1679,7 +1778,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
|
|
|
1679
1778
|
if (secretKey) {
|
|
1680
1779
|
rcData.secretKey = secretKey;
|
|
1681
1780
|
}
|
|
1682
|
-
|
|
1781
|
+
writeSecretFile(rcPath, `${JSON.stringify(rcData, null, 2)}
|
|
1683
1782
|
`);
|
|
1684
1783
|
success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
|
|
1685
1784
|
addToGitignore(".connectbaserc");
|
|
@@ -1723,6 +1822,13 @@ function detectBuildDir() {
|
|
|
1723
1822
|
return "./dist";
|
|
1724
1823
|
return "./dist";
|
|
1725
1824
|
}
|
|
1825
|
+
function writeSecretFile(filePath, content) {
|
|
1826
|
+
try {
|
|
1827
|
+
if (fs2.existsSync(filePath)) fs2.chmodSync(filePath, 384);
|
|
1828
|
+
} catch {
|
|
1829
|
+
}
|
|
1830
|
+
fs2.writeFileSync(filePath, content, { mode: 384 });
|
|
1831
|
+
}
|
|
1726
1832
|
function addToGitignore(entry, basePath) {
|
|
1727
1833
|
const dir = basePath || process.cwd();
|
|
1728
1834
|
const gitignorePath = path2.join(dir, ".gitignore");
|
|
@@ -2248,12 +2354,16 @@ async function setupMcp(secretKey, baseUrl = DEFAULT_BASE_URL) {
|
|
|
2248
2354
|
} catch {
|
|
2249
2355
|
const backupPath = `${mcpConfigPath}.backup`;
|
|
2250
2356
|
fs2.copyFileSync(mcpConfigPath, backupPath);
|
|
2357
|
+
try {
|
|
2358
|
+
fs2.chmodSync(backupPath, 384);
|
|
2359
|
+
} catch {
|
|
2360
|
+
}
|
|
2251
2361
|
warn(`.mcp.json \uD30C\uC2F1 \uC2E4\uD328, \uBC31\uC5C5 \uC0DD\uC131: ${backupPath}`);
|
|
2252
2362
|
mcpConfig = { mcpServers: {} };
|
|
2253
2363
|
}
|
|
2254
2364
|
}
|
|
2255
2365
|
mcpConfig.mcpServers["connect-base"] = mcpEntry;
|
|
2256
|
-
|
|
2366
|
+
writeSecretFile(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}
|
|
2257
2367
|
`);
|
|
2258
2368
|
success(`${mcpConfigPath} \uC0DD\uC131 \uC644\uB8CC`);
|
|
2259
2369
|
addToGitignore(".mcp.json", root);
|
|
@@ -2422,7 +2532,7 @@ ${colors.cyan}ConnectBase Update${colors.reset}
|
|
|
2422
2532
|
headers: { Authorization: `Bearer ${secretKey}` }
|
|
2423
2533
|
};
|
|
2424
2534
|
mcpConfig.mcpServers = servers;
|
|
2425
|
-
|
|
2535
|
+
writeSecretFile(
|
|
2426
2536
|
mcpConfigPath,
|
|
2427
2537
|
`${JSON.stringify(mcpConfig, null, 2)}
|
|
2428
2538
|
`
|
|
@@ -2973,7 +3083,7 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
2973
3083
|
}
|
|
2974
3084
|
}
|
|
2975
3085
|
rcData.secretKey = sk;
|
|
2976
|
-
|
|
3086
|
+
writeSecretFile(rcPath2, `${JSON.stringify(rcData, null, 2)}
|
|
2977
3087
|
`);
|
|
2978
3088
|
addToGitignore(".connectbaserc");
|
|
2979
3089
|
success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
|
|
@@ -3007,7 +3117,7 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
3007
3117
|
}
|
|
3008
3118
|
if (rcData.tunnelAppId !== appId) {
|
|
3009
3119
|
rcData.tunnelAppId = appId;
|
|
3010
|
-
|
|
3120
|
+
writeSecretFile(rcPath, `${JSON.stringify(rcData, null, 2)}
|
|
3011
3121
|
`);
|
|
3012
3122
|
}
|
|
3013
3123
|
} catch {
|
|
@@ -3688,6 +3798,7 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
|
|
|
3688
3798
|
-s, --storage <id> \uC2A4\uD1A0\uB9AC\uC9C0 ID
|
|
3689
3799
|
-k, --public-key <key> API Key
|
|
3690
3800
|
-u, --base-url <url> \uC11C\uBC84 URL (\uAE30\uBCF8: ${DEFAULT_BASE_URL})
|
|
3801
|
+
--allow-repo-base-url .connectbaserc \uC758 \uBE44\uACF5\uC2DD \uC11C\uBC84 URL \uC744 \uD655\uC778 \uC5C6\uC774 \uD5C8\uC6A9 (Secret Key \uAC00 \uADF8 \uC11C\uBC84\uB85C \uC804\uC1A1\uB428)
|
|
3691
3802
|
-t, --timeout <sec> \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel/deploy \uACF5\uD1B5. deploy \uBBF8\uC9C0\uC815 \uC2DC \uD30C\uC77C \uD06C\uAE30\uC5D0 \uB530\uB77C \uC790\uB3D9 \uC0B0\uC815)
|
|
3692
3803
|
--max-body <MB> \uD130\uB110 \uCD5C\uB300 \uBC14\uB514 \uD06C\uAE30 (MB, tunnel \uC804\uC6A9)
|
|
3693
3804
|
--force \uD130\uB110 lockfile \uBB34\uC2DC (\uC911\uBCF5 \uC2E4\uD589 \uAC15\uC81C, tunnel \uC804\uC6A9)
|
|
@@ -3748,7 +3859,7 @@ ${colors.yellow}\uD658\uACBD\uBCC0\uC218:${colors.reset}
|
|
|
3748
3859
|
CONNECTBASE_SECRET_KEY Secret Key (cb_sk_*) \u2014 \uBC30\uD3EC/\uC2B9\uACA9/\uD130\uB110\uC5D0 \uD544\uC218
|
|
3749
3860
|
CONNECTBASE_PUBLIC_KEY (deprecated) \uB808\uAC70\uC2DC \u2014 publicKey \uB610\uB294 secretKey \uB85C \uC0AC\uC6A9
|
|
3750
3861
|
CONNECTBASE_STORAGE_ID \uC2A4\uD1A0\uB9AC\uC9C0 ID
|
|
3751
|
-
CONNECTBASE_BASE_URL \uC11C\uBC84 URL
|
|
3862
|
+
CONNECTBASE_BASE_URL \uC11C\uBC84 URL (.connectbaserc \uBCF4\uB2E4 \uC6B0\uC120)
|
|
3752
3863
|
|
|
3753
3864
|
${colors.yellow}\uC124\uC815 \uD30C\uC77C (.connectbaserc):${colors.reset}
|
|
3754
3865
|
{
|
|
@@ -3760,6 +3871,65 @@ ${colors.yellow}\uC124\uC815 \uD30C\uC77C (.connectbaserc):${colors.reset}
|
|
|
3760
3871
|
}
|
|
3761
3872
|
`);
|
|
3762
3873
|
}
|
|
3874
|
+
var PLATFORM_API_HOST = new URL(DEFAULT_BASE_URL).host;
|
|
3875
|
+
var LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
3876
|
+
function isPlatformBaseUrl(baseUrl) {
|
|
3877
|
+
try {
|
|
3878
|
+
const url = new URL(baseUrl);
|
|
3879
|
+
if (url.protocol === "https:" && url.host === PLATFORM_API_HOST) {
|
|
3880
|
+
return true;
|
|
3881
|
+
}
|
|
3882
|
+
return (url.protocol === "http:" || url.protocol === "https:") && LOCAL_HOSTNAMES.has(url.hostname);
|
|
3883
|
+
} catch {
|
|
3884
|
+
return false;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
function describeBaseUrl(baseUrl) {
|
|
3888
|
+
try {
|
|
3889
|
+
return new URL(baseUrl).origin;
|
|
3890
|
+
} catch {
|
|
3891
|
+
return JSON.stringify(baseUrl);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
function decideBaseUrl(opts) {
|
|
3895
|
+
if (isPlatformBaseUrl(opts.baseUrl)) return "proceed";
|
|
3896
|
+
if (!opts.fromRepoConfig || opts.allowRepoBaseUrl) return "notice";
|
|
3897
|
+
return opts.interactive ? "confirm" : "refuse";
|
|
3898
|
+
}
|
|
3899
|
+
async function enforceBaseUrlPolicy(baseUrl, opts) {
|
|
3900
|
+
const decision = decideBaseUrl({
|
|
3901
|
+
baseUrl,
|
|
3902
|
+
...opts,
|
|
3903
|
+
interactive: process.stdin.isTTY === true
|
|
3904
|
+
});
|
|
3905
|
+
if (decision === "proceed") return;
|
|
3906
|
+
const target = describeBaseUrl(baseUrl);
|
|
3907
|
+
if (decision === "notice") {
|
|
3908
|
+
warn(
|
|
3909
|
+
`\uC11C\uBC84 URL \uC774 ConnectBase \uACF5\uC2DD \uC8FC\uC18C\uAC00 \uC544\uB2D9\uB2C8\uB2E4: ${target}. Secret Key \uB97C \uD3EC\uD568\uD55C \uC790\uACA9\uC99D\uBA85\uC774 \uC774 \uC11C\uBC84\uB85C \uC804\uC1A1\uB429\uB2C8\uB2E4.`
|
|
3910
|
+
);
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
warn(`\uC774 \uC800\uC7A5\uC18C\uC758 .connectbaserc \uAC00 \uC11C\uBC84 URL \uC744 ${target} \uB85C \uC9C0\uC815\uD569\uB2C8\uB2E4.`);
|
|
3914
|
+
log(
|
|
3915
|
+
" \uACC4\uC18D\uD558\uBA74 Secret Key(CONNECTBASE_SECRET_KEY \uD3EC\uD568)\uAC00 \uC774 \uC11C\uBC84\uB85C \uC804\uC1A1\uB429\uB2C8\uB2E4. \uBC1B\uC740 \uC800\uC7A5\uC18C\uB77C\uBA74 \uC911\uB2E8\uD558\uC138\uC694."
|
|
3916
|
+
);
|
|
3917
|
+
if (decision === "confirm") {
|
|
3918
|
+
const answer = await prompt(` ${target} \uB85C \uACC4\uC18D\uD560\uAE4C\uC694? (y/N): `);
|
|
3919
|
+
if (answer.toLowerCase() === "y") return;
|
|
3920
|
+
error("\uC911\uB2E8\uD588\uC2B5\uB2C8\uB2E4.");
|
|
3921
|
+
process.exit(1);
|
|
3922
|
+
}
|
|
3923
|
+
error("\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC774\uB77C \uD655\uC778\uD560 \uC218 \uC5C6\uC5B4 \uC911\uB2E8\uD569\uB2C8\uB2E4.");
|
|
3924
|
+
log(
|
|
3925
|
+
` \uC774 \uC11C\uBC84\uAC00 \uB9DE\uB2E4\uBA74 ${colors.cyan}--allow-repo-base-url${colors.reset} \uB97C \uBD99\uC774\uAC70\uB098 ${colors.cyan}--base-url${colors.reset}, ${colors.cyan}CONNECTBASE_BASE_URL${colors.reset} \uB85C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694.`
|
|
3926
|
+
);
|
|
3927
|
+
process.exit(1);
|
|
3928
|
+
}
|
|
3929
|
+
function isOutsideProject(dir, root) {
|
|
3930
|
+
const relative2 = path2.relative(path2.resolve(root), path2.resolve(root, dir));
|
|
3931
|
+
return relative2 === ".." || relative2.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative2);
|
|
3932
|
+
}
|
|
3763
3933
|
function parseArgs(args) {
|
|
3764
3934
|
const result = {
|
|
3765
3935
|
options: {},
|
|
@@ -3776,6 +3946,8 @@ function parseArgs(args) {
|
|
|
3776
3946
|
result.options.secretKey = args[++i];
|
|
3777
3947
|
} else if (arg === "-u" || arg === "--base-url") {
|
|
3778
3948
|
result.options.baseUrl = args[++i];
|
|
3949
|
+
} else if (arg === "--allow-repo-base-url") {
|
|
3950
|
+
result.options.allowRepoBaseUrl = "true";
|
|
3779
3951
|
} else if (arg === "-t" || arg === "--timeout") {
|
|
3780
3952
|
result.options.timeout = args[++i];
|
|
3781
3953
|
} else if (arg === "--max-body") {
|
|
@@ -3840,8 +4012,12 @@ async function main() {
|
|
|
3840
4012
|
publicKey: parsed.options.publicKey || fileConfig.publicKey,
|
|
3841
4013
|
secretKey: parsed.options.secretKey || fileConfig.secretKey,
|
|
3842
4014
|
storageId: parsed.options.storageId || fileConfig.storageId,
|
|
3843
|
-
baseUrl: parsed.options.baseUrl || fileConfig.baseUrl
|
|
4015
|
+
baseUrl: parsed.options.baseUrl || fileConfig.baseUrl
|
|
3844
4016
|
};
|
|
4017
|
+
await enforceBaseUrlPolicy(config.baseUrl, {
|
|
4018
|
+
fromRepoConfig: !parsed.options.baseUrl && fileConfig.baseUrlFromRepo,
|
|
4019
|
+
allowRepoBaseUrl: parsed.options.allowRepoBaseUrl === "true"
|
|
4020
|
+
});
|
|
3845
4021
|
if (parsed.command === "init") {
|
|
3846
4022
|
await init(config, {
|
|
3847
4023
|
yes: parsed.options.yes === "true",
|
|
@@ -3863,6 +4039,13 @@ async function main() {
|
|
|
3863
4039
|
await setupMcp(config.secretKey, config.baseUrl);
|
|
3864
4040
|
} else if (parsed.command === "deploy") {
|
|
3865
4041
|
const directory = parsed.args[0] || fileConfig.deployDir || ".";
|
|
4042
|
+
if (!parsed.args[0] && fileConfig.deployDir && isOutsideProject(fileConfig.deployDir, getProjectRoot())) {
|
|
4043
|
+
error(
|
|
4044
|
+
`.connectbaserc \uC758 deployDir \uC774 \uD504\uB85C\uC81D\uD2B8 \uBC16\uC744 \uAC00\uB9AC\uCF1C \uBC30\uD3EC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${JSON.stringify(fileConfig.deployDir)}`
|
|
4045
|
+
);
|
|
4046
|
+
log(" \uC774 \uD3F4\uB354\uAC00 \uB9DE\uB2E4\uBA74 \uBA85\uB839\uC904\uC5D0\uC11C \uC9C1\uC811 \uC9C0\uC815\uD558\uC138\uC694: deploy <\uD3F4\uB354>");
|
|
4047
|
+
process.exit(1);
|
|
4048
|
+
}
|
|
3866
4049
|
if (!config.publicKey) {
|
|
3867
4050
|
error(
|
|
3868
4051
|
'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase-client init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
|
|
@@ -3981,12 +4164,18 @@ main().catch((err) => {
|
|
|
3981
4164
|
});
|
|
3982
4165
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3983
4166
|
0 && (module.exports = {
|
|
4167
|
+
DEPLOY_REQUEST_BUDGET_BYTES,
|
|
3984
4168
|
TunnelConnectSelector,
|
|
3985
4169
|
ancestorCommandLines,
|
|
3986
4170
|
apiErrorMessage,
|
|
4171
|
+
browserAuthStartBody,
|
|
4172
|
+
browserAuthUrl,
|
|
4173
|
+
browserOpenCommand,
|
|
3987
4174
|
buildFirstHint,
|
|
3988
4175
|
collectDeployFiles,
|
|
3989
4176
|
computeDeployDiff,
|
|
4177
|
+
decideBaseUrl,
|
|
4178
|
+
describeBaseUrl,
|
|
3990
4179
|
detectPackageManager,
|
|
3991
4180
|
formatSkippedFilesReport,
|
|
3992
4181
|
getTunnelConnectCandidates,
|
|
@@ -3994,13 +4183,19 @@ main().catch((err) => {
|
|
|
3994
4183
|
isAllowedDeployExt,
|
|
3995
4184
|
isBinaryDeployExt,
|
|
3996
4185
|
isExtensionlessDataFile,
|
|
4186
|
+
isOutsideProject,
|
|
4187
|
+
isPlatformBaseUrl,
|
|
4188
|
+
loadConfig,
|
|
3997
4189
|
makeRequest,
|
|
3998
4190
|
normalizeRelativePath,
|
|
4191
|
+
openBrowser,
|
|
3999
4192
|
packageManagerCommands,
|
|
4000
4193
|
packageScriptInvocationPattern,
|
|
4001
4194
|
parseArgs,
|
|
4002
4195
|
registerEndpointBinding,
|
|
4003
4196
|
runningInsidePackageScript,
|
|
4197
|
+
selectBinariesToOffload,
|
|
4004
4198
|
sha256Hex,
|
|
4005
|
-
storageAdminHeaders
|
|
4199
|
+
storageAdminHeaders,
|
|
4200
|
+
writeSecretFile
|
|
4006
4201
|
});
|