connectbase-client 3.49.0 → 3.51.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 +12 -0
- package/README.md +7 -0
- package/dist/cli.js +761 -230
- package/dist/connect-base.umd.js +5 -5
- package/dist/index.d.mts +7467 -7395
- package/dist/index.d.ts +7467 -7395
- package/dist/index.js +9347 -8721
- package/dist/index.mjs +9347 -8721
- package/package.json +75 -72
package/dist/cli.js
CHANGED
|
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/cli.ts
|
|
32
32
|
var cli_exports = {};
|
|
33
33
|
__export(cli_exports, {
|
|
34
|
+
TunnelConnectSelector: () => TunnelConnectSelector,
|
|
34
35
|
computeDeployDiff: () => computeDeployDiff,
|
|
36
|
+
getTunnelConnectCandidates: () => getTunnelConnectCandidates,
|
|
35
37
|
guessMimeType: () => guessMimeType,
|
|
36
38
|
isAllowedDeployExt: () => isAllowedDeployExt,
|
|
37
39
|
isBinaryDeployExt: () => isBinaryDeployExt,
|
|
@@ -42,23 +44,33 @@ __export(cli_exports, {
|
|
|
42
44
|
sha256Hex: () => sha256Hex
|
|
43
45
|
});
|
|
44
46
|
module.exports = __toCommonJS(cli_exports);
|
|
45
|
-
var fs2 = __toESM(require("fs"));
|
|
46
|
-
var path2 = __toESM(require("path"));
|
|
47
47
|
var crypto = __toESM(require("crypto"));
|
|
48
|
-
var
|
|
48
|
+
var fs2 = __toESM(require("fs"));
|
|
49
49
|
var http = __toESM(require("http"));
|
|
50
|
+
var https = __toESM(require("https"));
|
|
51
|
+
var path2 = __toESM(require("path"));
|
|
50
52
|
var readline = __toESM(require("readline"));
|
|
51
53
|
|
|
52
54
|
// src/tunnel-utils.ts
|
|
53
55
|
var fs = __toESM(require("fs"));
|
|
54
|
-
var path = __toESM(require("path"));
|
|
55
56
|
var os = __toESM(require("os"));
|
|
57
|
+
var path = __toESM(require("path"));
|
|
56
58
|
function getTunnelLockDir() {
|
|
57
59
|
const platform2 = os.platform();
|
|
58
60
|
if (platform2 === "darwin") {
|
|
59
|
-
return path.join(
|
|
61
|
+
return path.join(
|
|
62
|
+
os.homedir(),
|
|
63
|
+
"Library",
|
|
64
|
+
"Application Support",
|
|
65
|
+
"connectbase",
|
|
66
|
+
"tunnel-locks"
|
|
67
|
+
);
|
|
60
68
|
} else if (platform2 === "win32") {
|
|
61
|
-
return path.join(
|
|
69
|
+
return path.join(
|
|
70
|
+
process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"),
|
|
71
|
+
"connectbase",
|
|
72
|
+
"tunnel-locks"
|
|
73
|
+
);
|
|
62
74
|
}
|
|
63
75
|
const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
64
76
|
return path.join(stateHome, "connectbase", "tunnel-locks");
|
|
@@ -95,7 +107,9 @@ function acquireTunnelLock(appID, port, force, version) {
|
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
try {
|
|
98
|
-
const existing = JSON.parse(
|
|
110
|
+
const existing = JSON.parse(
|
|
111
|
+
fs.readFileSync(lockPath, "utf-8")
|
|
112
|
+
);
|
|
99
113
|
if (isProcessAlive(existing.pid)) {
|
|
100
114
|
return `LOCKED:${existing.pid}:${existing.startedAt}:${existing.host}`;
|
|
101
115
|
}
|
|
@@ -343,21 +357,35 @@ function validateFileSizes(files) {
|
|
|
343
357
|
return { path: f.path, isBinary: f.isBinary, storedBytes, originalBytes };
|
|
344
358
|
}).filter((f) => f.storedBytes > WEB_FILE_MAX_STORED_BYTES);
|
|
345
359
|
if (oversized.length === 0) return;
|
|
346
|
-
error(
|
|
360
|
+
error(
|
|
361
|
+
`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uD30C\uC77C\uB2F9 \uD06C\uAE30 \uD55C\uB3C4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)})\uB97C \uCD08\uACFC\uD55C \uD30C\uC77C\uC774 \uC788\uC5B4 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4:`
|
|
362
|
+
);
|
|
347
363
|
for (const f of oversized) {
|
|
348
364
|
const detail = f.isBinary ? `${formatSize(f.originalBytes)} \u2192 base64 ${formatSize(f.storedBytes)}` : formatSize(f.storedBytes);
|
|
349
365
|
error(` \u2022 ${f.path} (${detail})`);
|
|
350
366
|
}
|
|
351
367
|
log("");
|
|
352
|
-
warn(
|
|
368
|
+
warn(
|
|
369
|
+
"\uC815\uC801 \uD638\uC2A4\uD305 \uD30C\uC77C\uC740 DB \uB2E8\uC77C \uC5D4\uD2B8\uB9AC(6 MiB)\uC5D0 \uC800\uC7A5\uB418\uBA70, \uBC14\uC774\uB108\uB9AC\uB294 base64(\uC57D 1.33x)\uB85C"
|
|
370
|
+
);
|
|
353
371
|
warn("\uBD80\uD480\uC5B4 \uC6D0\uBCF8 \uAE30\uC900 \uC57D 4.5 MB \uAC00 \uC2E4\uC9C8 \uC0C1\uD55C\uC785\uB2C8\uB2E4.");
|
|
354
|
-
warn(
|
|
355
|
-
|
|
372
|
+
warn(
|
|
373
|
+
`\uB300\uC6A9\uB7C9 \uC5D0\uC14B(3D \uBAA8\uB378\xB7\uB3D9\uC601\uC0C1\xB7\uD070 \uD3F0\uD2B8 \uB4F1)\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(${colors.cyan}cb.storage${colors.reset}, presigned URL)\uC5D0`
|
|
374
|
+
);
|
|
375
|
+
warn(
|
|
376
|
+
"\uC62C\uB9AC\uACE0 URL \uB85C \uCC38\uC870\uD558\uC138\uC694. \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0\uB294 \uD30C\uC77C\uB2F9 \uACE0\uC815 \uC0C1\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."
|
|
377
|
+
);
|
|
356
378
|
process.exit(1);
|
|
357
379
|
}
|
|
358
380
|
async function requestDeployPresign(baseStorageUrl, headers, files, timeoutMs, dev = false) {
|
|
359
381
|
const body = JSON.stringify(dev ? { files, dev: true } : { files });
|
|
360
|
-
const res = await makeRequest(
|
|
382
|
+
const res = await makeRequest(
|
|
383
|
+
`${baseStorageUrl}/deploy/presign`,
|
|
384
|
+
"POST",
|
|
385
|
+
headers,
|
|
386
|
+
body,
|
|
387
|
+
{ timeoutMs }
|
|
388
|
+
);
|
|
361
389
|
if (res.status === 404) return null;
|
|
362
390
|
if (res.status < 200 || res.status >= 300) {
|
|
363
391
|
const data = res.data;
|
|
@@ -413,22 +441,33 @@ function putToPresignedUrl(uploadUrl, body, contentType, timeoutMs) {
|
|
|
413
441
|
socket.setKeepAlive(true, 3e4);
|
|
414
442
|
});
|
|
415
443
|
req.setTimeout(timeoutMs, () => {
|
|
416
|
-
req.destroy(
|
|
444
|
+
req.destroy(
|
|
445
|
+
new Error(`\uC5C5\uB85C\uB4DC \uC2DC\uAC04 \uCD08\uACFC (${Math.round(timeoutMs / 1e3)}\uCD08)`)
|
|
446
|
+
);
|
|
417
447
|
});
|
|
418
448
|
req.on("error", reject);
|
|
419
449
|
req.end(body);
|
|
420
450
|
});
|
|
421
451
|
}
|
|
422
452
|
async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, timeoutMs) {
|
|
423
|
-
const oversized = diff.upsert.filter(
|
|
453
|
+
const oversized = diff.upsert.filter(
|
|
454
|
+
(f) => Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES
|
|
455
|
+
);
|
|
424
456
|
if (oversized.length === 0) return;
|
|
425
457
|
const largeText = oversized.filter((f) => !f.is_binary);
|
|
426
458
|
const largeBinaries = oversized.filter((f) => f.is_binary);
|
|
427
459
|
if (largeText.length > 0) {
|
|
428
|
-
error(
|
|
429
|
-
|
|
460
|
+
error(
|
|
461
|
+
`\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:`
|
|
462
|
+
);
|
|
463
|
+
for (const f of largeText)
|
|
464
|
+
error(
|
|
465
|
+
` \u2022 ${f.path} (${formatSize(Buffer.byteLength(f.content, "utf8"))})`
|
|
466
|
+
);
|
|
430
467
|
log("");
|
|
431
|
-
warn(
|
|
468
|
+
warn(
|
|
469
|
+
"\uD070 JS/CSS \uB294 \uCF54\uB4DC \uC2A4\uD50C\uB9AC\uD305\uC73C\uB85C \uBD84\uD560\uD558\uC138\uC694. (\uBC14\uC774\uB108\uB9AC\uC640 \uB2EC\uB9AC \uD14D\uC2A4\uD2B8\uB294 \uC624\uBE0C\uC81D\uD2B8 \uC2A4\uD1A0\uB9AC\uC9C0\uB85C \uC624\uD504\uB85C\uB4DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.)"
|
|
470
|
+
);
|
|
432
471
|
process.exit(1);
|
|
433
472
|
}
|
|
434
473
|
if (largeBinaries.length === 0) return;
|
|
@@ -442,20 +481,36 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
|
|
|
442
481
|
}));
|
|
443
482
|
let presigned;
|
|
444
483
|
try {
|
|
445
|
-
presigned = await requestDeployPresign(
|
|
484
|
+
presigned = await requestDeployPresign(
|
|
485
|
+
baseStorageUrl,
|
|
486
|
+
headers,
|
|
487
|
+
presignFiles,
|
|
488
|
+
timeoutMs
|
|
489
|
+
);
|
|
446
490
|
} catch (err) {
|
|
447
|
-
error(
|
|
491
|
+
error(
|
|
492
|
+
`\uB300\uC6A9\uB7C9 \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC900\uBE44 \uC2E4\uD328: ${err instanceof Error ? err.message : err}`
|
|
493
|
+
);
|
|
448
494
|
process.exit(1);
|
|
449
495
|
}
|
|
450
496
|
if (presigned === null) {
|
|
451
|
-
error(
|
|
452
|
-
|
|
497
|
+
error(
|
|
498
|
+
`\uC11C\uBC84\uAC00 \uB300\uC6A9\uB7C9 \uD30C\uC77C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC544 \uB2E4\uC74C \uD30C\uC77C\uC744 \uBC30\uD3EC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4(\uC57D ${formatSize(WEB_FILE_MAX_STORED_BYTES)} \uCD08\uACFC):`
|
|
499
|
+
);
|
|
500
|
+
for (const f of largeBinaries)
|
|
501
|
+
error(
|
|
502
|
+
` \u2022 ${f.path} (${formatSize(Buffer.from(f.content, "base64").length)})`
|
|
503
|
+
);
|
|
453
504
|
log("");
|
|
454
|
-
warn(
|
|
505
|
+
warn(
|
|
506
|
+
`\uB300\uC6A9\uB7C9 \uC5D0\uC14B\uC740 \uD30C\uC77C \uC2A4\uD1A0\uB9AC\uC9C0(${colors.cyan}cb.storage${colors.reset})\uC5D0 \uC62C\uB9AC\uACE0 URL \uB85C \uCC38\uC870\uD558\uAC70\uB098, \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
|
|
507
|
+
);
|
|
455
508
|
process.exit(1);
|
|
456
509
|
}
|
|
457
510
|
const entryByPath = new Map(presigned.map((p) => [p.path, p]));
|
|
458
|
-
info(
|
|
511
|
+
info(
|
|
512
|
+
`\uB300\uC6A9\uB7C9 \uBC14\uC774\uB108\uB9AC ${largeBinaries.length}\uAC1C\uB97C Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4...`
|
|
513
|
+
);
|
|
459
514
|
for (const f of largeBinaries) {
|
|
460
515
|
const entry = entryByPath.get(f.path);
|
|
461
516
|
if (!entry) {
|
|
@@ -464,16 +519,25 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
|
|
|
464
519
|
}
|
|
465
520
|
const bytes = Buffer.from(f.content, "base64");
|
|
466
521
|
if (entry.already_exists) {
|
|
467
|
-
success(
|
|
522
|
+
success(
|
|
523
|
+
`\uC5C5\uB85C\uB4DC \uC0DD\uB7B5(\uAE30\uC874 blob \uC7AC\uC0AC\uC6A9): ${f.path} (${formatSize(bytes.length)})`
|
|
524
|
+
);
|
|
468
525
|
} else {
|
|
469
526
|
if (!entry.upload_url) {
|
|
470
527
|
error(`presign \uC751\uB2F5\uC5D0 \uC5C5\uB85C\uB4DC URL \uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${f.path}`);
|
|
471
528
|
process.exit(1);
|
|
472
529
|
}
|
|
473
530
|
try {
|
|
474
|
-
await putToPresignedUrl(
|
|
531
|
+
await putToPresignedUrl(
|
|
532
|
+
entry.upload_url,
|
|
533
|
+
bytes,
|
|
534
|
+
guessMimeType(f.path),
|
|
535
|
+
timeoutMs
|
|
536
|
+
);
|
|
475
537
|
} catch (err) {
|
|
476
|
-
error(
|
|
538
|
+
error(
|
|
539
|
+
`\uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`
|
|
540
|
+
);
|
|
477
541
|
process.exit(1);
|
|
478
542
|
}
|
|
479
543
|
success(`\uC9C1\uC811 \uC5C5\uB85C\uB4DC: ${f.path} (${formatSize(bytes.length)})`);
|
|
@@ -484,7 +548,9 @@ async function offloadLargeBinaries(baseStorageUrl, headers, diff, _envLabel, ti
|
|
|
484
548
|
}
|
|
485
549
|
}
|
|
486
550
|
async function offloadDevLargeBinaries(baseStorageUrl, headers, files, timeoutMs) {
|
|
487
|
-
const large = files.filter(
|
|
551
|
+
const large = files.filter(
|
|
552
|
+
(f) => f.isBinary && Buffer.byteLength(f.content, "utf8") > WEB_FILE_MAX_STORED_BYTES
|
|
553
|
+
);
|
|
488
554
|
if (large.length === 0) return files;
|
|
489
555
|
const presignFiles = large.map((f) => ({
|
|
490
556
|
path: f.path,
|
|
@@ -493,21 +559,35 @@ async function offloadDevLargeBinaries(baseStorageUrl, headers, files, timeoutMs
|
|
|
493
559
|
}));
|
|
494
560
|
let presigned;
|
|
495
561
|
try {
|
|
496
|
-
presigned = await requestDeployPresign(
|
|
562
|
+
presigned = await requestDeployPresign(
|
|
563
|
+
baseStorageUrl,
|
|
564
|
+
headers,
|
|
565
|
+
presignFiles,
|
|
566
|
+
timeoutMs,
|
|
567
|
+
true
|
|
568
|
+
);
|
|
497
569
|
} catch (err) {
|
|
498
|
-
error(
|
|
570
|
+
error(
|
|
571
|
+
`dev \uB300\uC6A9\uB7C9 \uD30C\uC77C \uC5C5\uB85C\uB4DC \uC900\uBE44 \uC2E4\uD328: ${err instanceof Error ? err.message : err}`
|
|
572
|
+
);
|
|
499
573
|
process.exit(1);
|
|
500
574
|
}
|
|
501
575
|
if (presigned === null) {
|
|
502
|
-
info(
|
|
576
|
+
info(
|
|
577
|
+
"\uC11C\uBC84\uAC00 \uB300\uC6A9\uB7C9 \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uBBF8\uC9C0\uC6D0 \u2014 dev \uB294 \uC804\uB7C9 \uC5C5\uB85C\uB4DC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4."
|
|
578
|
+
);
|
|
503
579
|
return files;
|
|
504
580
|
}
|
|
505
581
|
if (!presigned.every((p) => p.object_key.includes("/dev/"))) {
|
|
506
|
-
info(
|
|
582
|
+
info(
|
|
583
|
+
"\uC11C\uBC84\uAC00 dev \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uB97C \uBBF8\uC9C0\uC6D0(\uC751\uB2F5 \uD0A4\uAC00 dev \uACBD\uB85C \uC544\uB2D8) \u2014 dev \uB294 \uC804\uB7C9 \uC5C5\uB85C\uB4DC\uB85C \uC9C4\uD589\uD569\uB2C8\uB2E4."
|
|
584
|
+
);
|
|
507
585
|
return files;
|
|
508
586
|
}
|
|
509
587
|
const urlByPath = new Map(presigned.map((p) => [p.path, p.upload_url]));
|
|
510
|
-
info(
|
|
588
|
+
info(
|
|
589
|
+
`\uB300\uC6A9\uB7C9 \uBC14\uC774\uB108\uB9AC ${large.length}\uAC1C\uB97C dev Object Storage \uB85C \uC9C1\uC811 \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4...`
|
|
590
|
+
);
|
|
511
591
|
const offloaded = /* @__PURE__ */ new Set();
|
|
512
592
|
for (const f of large) {
|
|
513
593
|
const uploadUrl = urlByPath.get(f.path);
|
|
@@ -517,9 +597,16 @@ async function offloadDevLargeBinaries(baseStorageUrl, headers, files, timeoutMs
|
|
|
517
597
|
}
|
|
518
598
|
const bytes = Buffer.from(f.content, "base64");
|
|
519
599
|
try {
|
|
520
|
-
await putToPresignedUrl(
|
|
600
|
+
await putToPresignedUrl(
|
|
601
|
+
uploadUrl,
|
|
602
|
+
bytes,
|
|
603
|
+
guessMimeType(f.path),
|
|
604
|
+
timeoutMs
|
|
605
|
+
);
|
|
521
606
|
} catch (err) {
|
|
522
|
-
error(
|
|
607
|
+
error(
|
|
608
|
+
`dev \uC5C5\uB85C\uB4DC \uC2E4\uD328 (${f.path}): ${err instanceof Error ? err.message : err}`
|
|
609
|
+
);
|
|
523
610
|
process.exit(1);
|
|
524
611
|
}
|
|
525
612
|
success(`\uC9C1\uC811 \uC5C5\uB85C\uB4DC(dev): ${f.path} (${formatSize(bytes.length)})`);
|
|
@@ -603,8 +690,12 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
|
|
|
603
690
|
try {
|
|
604
691
|
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
605
692
|
if (pkg.scripts?.build) {
|
|
606
|
-
warn(
|
|
607
|
-
|
|
693
|
+
warn(
|
|
694
|
+
`\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase deploy${colors.reset}`
|
|
695
|
+
);
|
|
696
|
+
warn(
|
|
697
|
+
`\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase init${colors.reset}`
|
|
698
|
+
);
|
|
608
699
|
}
|
|
609
700
|
} catch {
|
|
610
701
|
}
|
|
@@ -628,37 +719,77 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
|
|
|
628
719
|
}
|
|
629
720
|
const totalSize = files.reduce((sum, f) => sum + f.content.length, 0);
|
|
630
721
|
const sizeKB = (totalSize / 1024).toFixed(1);
|
|
631
|
-
log(
|
|
722
|
+
log(
|
|
723
|
+
`${colors.green}\u2713${colors.reset} ${files.length}\uAC1C \uD30C\uC77C \uBC1C\uACAC (${sizeKB} KB)`
|
|
724
|
+
);
|
|
632
725
|
const envLabel = isDev ? "Dev" : "Prod";
|
|
633
726
|
const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
|
|
634
727
|
const headers = { "X-Public-Key": config.publicKey };
|
|
635
728
|
const timeoutMs = computeDeployTimeout(totalSize, deployOpts.timeoutMs);
|
|
636
|
-
log(
|
|
729
|
+
log(
|
|
730
|
+
`${colors.dim}\uD0C0\uC784\uC544\uC6C3: ${Math.round(timeoutMs / 1e3)}\uCD08${colors.reset}`
|
|
731
|
+
);
|
|
637
732
|
if (isDev) {
|
|
638
|
-
const toSend = await offloadDevLargeBinaries(
|
|
639
|
-
|
|
733
|
+
const toSend = await offloadDevLargeBinaries(
|
|
734
|
+
baseStorageUrl,
|
|
735
|
+
headers,
|
|
736
|
+
files,
|
|
737
|
+
timeoutMs
|
|
738
|
+
);
|
|
739
|
+
await fullDeploy(
|
|
740
|
+
baseStorageUrl,
|
|
741
|
+
headers,
|
|
742
|
+
toSend,
|
|
743
|
+
envLabel,
|
|
744
|
+
"deploy/dev",
|
|
745
|
+
timeoutMs
|
|
746
|
+
);
|
|
640
747
|
return;
|
|
641
748
|
}
|
|
642
749
|
try {
|
|
643
750
|
const manifest = await tryFetchManifest(baseStorageUrl, headers);
|
|
644
751
|
if (!manifest) {
|
|
645
752
|
validateFileSizes(files);
|
|
646
|
-
await fullDeploy(
|
|
753
|
+
await fullDeploy(
|
|
754
|
+
baseStorageUrl,
|
|
755
|
+
headers,
|
|
756
|
+
files,
|
|
757
|
+
envLabel,
|
|
758
|
+
"deploy",
|
|
759
|
+
timeoutMs
|
|
760
|
+
);
|
|
647
761
|
return;
|
|
648
762
|
}
|
|
649
763
|
const diff = computeDeployDiff(files, manifest);
|
|
650
764
|
if (diff.upsert.length === 0 && diff.delete.length === 0) {
|
|
651
765
|
success(`\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C (${files.length}\uAC1C \uD30C\uC77C \uC77C\uCE58)`);
|
|
652
|
-
log(
|
|
766
|
+
log(
|
|
767
|
+
`
|
|
653
768
|
${colors.cyan}URL: https://${config.storageId}.web.connectbase.world${colors.reset}
|
|
654
|
-
`
|
|
769
|
+
`
|
|
770
|
+
);
|
|
655
771
|
return;
|
|
656
772
|
}
|
|
657
|
-
await offloadLargeBinaries(
|
|
658
|
-
|
|
773
|
+
await offloadLargeBinaries(
|
|
774
|
+
baseStorageUrl,
|
|
775
|
+
headers,
|
|
776
|
+
diff,
|
|
777
|
+
envLabel,
|
|
778
|
+
timeoutMs
|
|
779
|
+
);
|
|
780
|
+
info(
|
|
781
|
+
`\uBCC0\uACBD: ${colors.green}+${diff.upsert.length}${colors.reset} / ${colors.red}-${diff.delete.length}${colors.reset} (\uC804\uCCB4 ${files.length}\uAC1C \uC911)`
|
|
782
|
+
);
|
|
659
783
|
const uploadSize = diff.upsert.reduce((s, f) => s + f.content.length, 0);
|
|
660
784
|
info(`\uC5C5\uB85C\uB4DC \uD06C\uAE30: ${(uploadSize / 1024).toFixed(1)} KB`);
|
|
661
|
-
await incrementalDeploy(
|
|
785
|
+
await incrementalDeploy(
|
|
786
|
+
baseStorageUrl,
|
|
787
|
+
headers,
|
|
788
|
+
diff,
|
|
789
|
+
manifest.revision,
|
|
790
|
+
envLabel,
|
|
791
|
+
timeoutMs
|
|
792
|
+
);
|
|
662
793
|
} catch (err) {
|
|
663
794
|
process.stdout.write("\r \r");
|
|
664
795
|
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
@@ -666,7 +797,11 @@ ${colors.cyan}URL: https://${config.storageId}.web.connectbase.world${colors.res
|
|
|
666
797
|
}
|
|
667
798
|
}
|
|
668
799
|
async function tryFetchManifest(baseStorageUrl, headers) {
|
|
669
|
-
const res = await makeRequest(
|
|
800
|
+
const res = await makeRequest(
|
|
801
|
+
`${baseStorageUrl}/deploy/manifest`,
|
|
802
|
+
"GET",
|
|
803
|
+
headers
|
|
804
|
+
);
|
|
670
805
|
if (res.status === 404) return null;
|
|
671
806
|
if (res.status < 200 || res.status >= 300) {
|
|
672
807
|
const data = res.data;
|
|
@@ -707,18 +842,28 @@ function startUploadHeartbeat(envLabel, suffix = "\uBC30\uD3EC \uC911") {
|
|
|
707
842
|
const startedAt = Date.now();
|
|
708
843
|
return setInterval(() => {
|
|
709
844
|
const elapsed = Math.floor((Date.now() - startedAt) / 1e3);
|
|
710
|
-
process.stdout.write(
|
|
845
|
+
process.stdout.write(
|
|
846
|
+
`\r${colors.blue}\u27F3${colors.reset} ${envLabel} ${suffix}... ${colors.dim}(${elapsed}s)${colors.reset}`
|
|
847
|
+
);
|
|
711
848
|
}, 1e3);
|
|
712
849
|
}
|
|
713
850
|
async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, envLabel, timeoutMs) {
|
|
714
|
-
process.stdout.write(
|
|
851
|
+
process.stdout.write(
|
|
852
|
+
`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC911...`
|
|
853
|
+
);
|
|
715
854
|
let heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC911");
|
|
716
855
|
const body = JSON.stringify({
|
|
717
856
|
upsert: diff.upsert,
|
|
718
857
|
delete: diff.delete,
|
|
719
858
|
base_revision: baseRevision
|
|
720
859
|
});
|
|
721
|
-
const res = await makeRequest(
|
|
860
|
+
const res = await makeRequest(
|
|
861
|
+
`${baseStorageUrl}/deploy/incremental`,
|
|
862
|
+
"POST",
|
|
863
|
+
headers,
|
|
864
|
+
body,
|
|
865
|
+
{ timeoutMs }
|
|
866
|
+
);
|
|
722
867
|
if (heartbeat) clearInterval(heartbeat);
|
|
723
868
|
process.stdout.write("\r \r");
|
|
724
869
|
if (res.status === 409) {
|
|
@@ -733,9 +878,17 @@ async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, en
|
|
|
733
878
|
delete: diff.delete,
|
|
734
879
|
base_revision: manifest.revision
|
|
735
880
|
});
|
|
736
|
-
process.stdout.write(
|
|
881
|
+
process.stdout.write(
|
|
882
|
+
`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4...`
|
|
883
|
+
);
|
|
737
884
|
heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4");
|
|
738
|
-
const res2 = await makeRequest(
|
|
885
|
+
const res2 = await makeRequest(
|
|
886
|
+
`${baseStorageUrl}/deploy/incremental`,
|
|
887
|
+
"POST",
|
|
888
|
+
headers,
|
|
889
|
+
body2,
|
|
890
|
+
{ timeoutMs }
|
|
891
|
+
);
|
|
739
892
|
if (heartbeat) clearInterval(heartbeat);
|
|
740
893
|
process.stdout.write("\r \r");
|
|
741
894
|
handleDeployResponse(res2, envLabel);
|
|
@@ -753,7 +906,13 @@ async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint, ti
|
|
|
753
906
|
is_binary: f.isBinary
|
|
754
907
|
}))
|
|
755
908
|
});
|
|
756
|
-
const res = await makeRequest(
|
|
909
|
+
const res = await makeRequest(
|
|
910
|
+
`${baseStorageUrl}/${endpoint}`,
|
|
911
|
+
"POST",
|
|
912
|
+
headers,
|
|
913
|
+
body,
|
|
914
|
+
{ timeoutMs }
|
|
915
|
+
);
|
|
757
916
|
if (heartbeat) clearInterval(heartbeat);
|
|
758
917
|
process.stdout.write("\r \r");
|
|
759
918
|
handleDeployResponse(res, envLabel);
|
|
@@ -765,16 +924,25 @@ function handleDeployResponse(response, envLabel) {
|
|
|
765
924
|
if (dep?.deployment_id) {
|
|
766
925
|
if (dep.promoted) {
|
|
767
926
|
success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC! (release #${dep.seq})`);
|
|
768
|
-
if (dep.production_url)
|
|
769
|
-
|
|
770
|
-
|
|
927
|
+
if (dep.production_url)
|
|
928
|
+
log(
|
|
929
|
+
`
|
|
930
|
+
${colors.cyan}Production: ${dep.production_url}${colors.reset}`
|
|
931
|
+
);
|
|
932
|
+
if (dep.qa_url)
|
|
933
|
+
log(`${colors.dim}QA: ${dep.qa_url}${colors.reset}
|
|
771
934
|
`);
|
|
772
935
|
} else {
|
|
773
|
-
success(
|
|
774
|
-
|
|
936
|
+
success(
|
|
937
|
+
`QA \uD658\uACBD\uC5D0 \uBC30\uD3EC\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (release #${dep.seq}) \u2014 production \uC740 \uC544\uC9C1 \uC774\uC804 \uBC84\uC804\uC785\uB2C8\uB2E4`
|
|
938
|
+
);
|
|
939
|
+
if (dep.qa_url)
|
|
940
|
+
log(`
|
|
775
941
|
${colors.yellow}QA URL: ${dep.qa_url}${colors.reset}`);
|
|
776
|
-
log(
|
|
777
|
-
|
|
942
|
+
log(
|
|
943
|
+
`${colors.dim}\uAC80\uC99D \uD6C4 \uBC18\uC601: ${colors.cyan}npx connectbase promote${colors.reset}
|
|
944
|
+
`
|
|
945
|
+
);
|
|
778
946
|
}
|
|
779
947
|
return;
|
|
780
948
|
}
|
|
@@ -798,11 +966,22 @@ ${colors.cyan}URL: ${data.url}${colors.reset}
|
|
|
798
966
|
async function promote(config, deploymentId) {
|
|
799
967
|
const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
|
|
800
968
|
const headers = { "X-Public-Key": config.publicKey };
|
|
801
|
-
info(
|
|
802
|
-
|
|
803
|
-
|
|
969
|
+
info(
|
|
970
|
+
deploymentId ? `release ${deploymentId} \uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4...` : "\uCD5C\uC2E0 \uBC30\uD3EC\uB97C production \uC73C\uB85C \uC2B9\uACA9\uD569\uB2C8\uB2E4..."
|
|
971
|
+
);
|
|
972
|
+
const body = JSON.stringify(
|
|
973
|
+
deploymentId ? { deployment_id: deploymentId } : {}
|
|
974
|
+
);
|
|
975
|
+
const res = await makeRequest(
|
|
976
|
+
`${baseStorageUrl}/promote`,
|
|
977
|
+
"POST",
|
|
978
|
+
headers,
|
|
979
|
+
body
|
|
980
|
+
);
|
|
804
981
|
if (res.status === 404 && !res.data?.error) {
|
|
805
|
-
error(
|
|
982
|
+
error(
|
|
983
|
+
"\uC11C\uBC84\uAC00 promote \uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uAD6C\uBC84\uC804 \uC11C\uBC84) \u2014 \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694"
|
|
984
|
+
);
|
|
806
985
|
process.exit(1);
|
|
807
986
|
}
|
|
808
987
|
if (res.status < 200 || res.status >= 300) {
|
|
@@ -822,7 +1001,11 @@ ${colors.cyan}Production: ${data.production_url}${colors.reset}
|
|
|
822
1001
|
async function listReleases(config) {
|
|
823
1002
|
const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
|
|
824
1003
|
const headers = { "X-Public-Key": config.publicKey };
|
|
825
|
-
const res = await makeRequest(
|
|
1004
|
+
const res = await makeRequest(
|
|
1005
|
+
`${baseStorageUrl}/deployments`,
|
|
1006
|
+
"GET",
|
|
1007
|
+
headers
|
|
1008
|
+
);
|
|
826
1009
|
if (res.status < 200 || res.status >= 300) {
|
|
827
1010
|
const data2 = res.data;
|
|
828
1011
|
const msg = typeof data2 === "object" && data2 !== null ? data2.error || data2.message || JSON.stringify(data2) : `HTTP ${res.status}`;
|
|
@@ -834,12 +1017,15 @@ async function listReleases(config) {
|
|
|
834
1017
|
info("\uBC30\uD3EC \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 deploy \uD558\uC138\uC694.");
|
|
835
1018
|
return;
|
|
836
1019
|
}
|
|
837
|
-
log(
|
|
1020
|
+
log(
|
|
1021
|
+
`
|
|
838
1022
|
${colors.cyan}\uBC30\uD3EC \uC774\uB825${colors.reset} (promote_mode: ${data.promote_mode || "auto"}, \uCD1D ${data.total}\uAC1C)
|
|
839
|
-
`
|
|
1023
|
+
`
|
|
1024
|
+
);
|
|
840
1025
|
for (const d of data.deployments) {
|
|
841
1026
|
const markers = [];
|
|
842
|
-
if (d.is_production)
|
|
1027
|
+
if (d.is_production)
|
|
1028
|
+
markers.push(`${colors.green}production${colors.reset}`);
|
|
843
1029
|
if (d.is_qa) markers.push(`${colors.yellow}qa${colors.reset}`);
|
|
844
1030
|
const marker = markers.length > 0 ? ` \u2190 ${markers.join(", ")}` : "";
|
|
845
1031
|
const statusColor = d.status === "ready" ? colors.green : d.status === "failed" ? colors.red : colors.yellow;
|
|
@@ -848,10 +1034,14 @@ ${colors.cyan}\uBC30\uD3EC \uC774\uB825${colors.reset} (promote_mode: ${data.pro
|
|
|
848
1034
|
);
|
|
849
1035
|
}
|
|
850
1036
|
log("");
|
|
851
|
-
if (data.production_url)
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
`);
|
|
1037
|
+
if (data.production_url)
|
|
1038
|
+
log(`${colors.cyan}Production: ${data.production_url}${colors.reset}`);
|
|
1039
|
+
if (data.qa_url)
|
|
1040
|
+
log(`${colors.yellow}QA: ${data.qa_url}${colors.reset}`);
|
|
1041
|
+
log(
|
|
1042
|
+
`${colors.dim}\uB864\uBC31: npx connectbase promote <deployment_id>${colors.reset}
|
|
1043
|
+
`
|
|
1044
|
+
);
|
|
855
1045
|
}
|
|
856
1046
|
function prompt(question) {
|
|
857
1047
|
const rl = readline.createInterface({
|
|
@@ -870,7 +1060,10 @@ function promptSecret(question) {
|
|
|
870
1060
|
process.stdout.write(question);
|
|
871
1061
|
const input = [];
|
|
872
1062
|
if (!process.stdin.isTTY) {
|
|
873
|
-
const rl = readline.createInterface({
|
|
1063
|
+
const rl = readline.createInterface({
|
|
1064
|
+
input: process.stdin,
|
|
1065
|
+
output: process.stdout
|
|
1066
|
+
});
|
|
874
1067
|
rl.question("", (answer) => {
|
|
875
1068
|
rl.close();
|
|
876
1069
|
resolve2(answer.trim());
|
|
@@ -956,7 +1149,9 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
|
|
|
956
1149
|
const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
957
1150
|
while (attempts < maxAttempts) {
|
|
958
1151
|
const frame = spinnerFrames[attempts % spinnerFrames.length];
|
|
959
|
-
process.stdout.write(
|
|
1152
|
+
process.stdout.write(
|
|
1153
|
+
`\r${colors.blue}${frame}${colors.reset} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778 \uB300\uAE30 \uC911...`
|
|
1154
|
+
);
|
|
960
1155
|
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
961
1156
|
try {
|
|
962
1157
|
const pollRes = await makeRequest(
|
|
@@ -968,17 +1163,23 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
|
|
|
968
1163
|
consecutive404 = 0;
|
|
969
1164
|
const data = pollRes.data;
|
|
970
1165
|
if (data.status === "approved" && data.secret_key) {
|
|
971
|
-
process.stdout.write(
|
|
1166
|
+
process.stdout.write(
|
|
1167
|
+
"\r \r"
|
|
1168
|
+
);
|
|
972
1169
|
success("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!");
|
|
973
1170
|
return data.secret_key;
|
|
974
1171
|
}
|
|
975
1172
|
if (data.status === "expired") {
|
|
976
|
-
process.stdout.write(
|
|
1173
|
+
process.stdout.write(
|
|
1174
|
+
"\r \r"
|
|
1175
|
+
);
|
|
977
1176
|
error("\uC778\uC99D \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
|
|
978
1177
|
process.exit(1);
|
|
979
1178
|
}
|
|
980
1179
|
if (data.status === "denied") {
|
|
981
|
-
process.stdout.write(
|
|
1180
|
+
process.stdout.write(
|
|
1181
|
+
"\r \r"
|
|
1182
|
+
);
|
|
982
1183
|
error("\uC778\uC99D\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
983
1184
|
process.exit(1);
|
|
984
1185
|
}
|
|
@@ -986,7 +1187,9 @@ ${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC13
|
|
|
986
1187
|
if (pollRes.status === 404) {
|
|
987
1188
|
consecutive404++;
|
|
988
1189
|
if (consecutive404 >= max404Retries) {
|
|
989
|
-
process.stdout.write(
|
|
1190
|
+
process.stdout.write(
|
|
1191
|
+
"\r \r"
|
|
1192
|
+
);
|
|
990
1193
|
error("\uC778\uC99D \uC138\uC158\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
|
|
991
1194
|
process.exit(1);
|
|
992
1195
|
}
|
|
@@ -1007,14 +1210,20 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
|
|
|
1007
1210
|
const projectRoot = getProjectRoot();
|
|
1008
1211
|
if (cwd !== projectRoot) {
|
|
1009
1212
|
info(`\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uAC10\uC9C0: ${projectRoot}`);
|
|
1010
|
-
info(
|
|
1011
|
-
`
|
|
1213
|
+
info(
|
|
1214
|
+
`MCP/\uBB38\uC11C\uB294 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0, \uBC30\uD3EC \uC124\uC815\uC740 \uD604\uC7AC \uB514\uB809\uD1A0\uB9AC\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4
|
|
1215
|
+
`
|
|
1216
|
+
);
|
|
1012
1217
|
}
|
|
1013
1218
|
const nonInteractive = opts.yes === true;
|
|
1014
1219
|
if (!process.stdin.isTTY && !nonInteractive) {
|
|
1015
1220
|
error("\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC785\uB2C8\uB2E4. --yes \uD50C\uB798\uADF8\uC640 \uD568\uAED8 \uD544\uC694\uD55C \uAC12\uC744 \uC9C0\uC815\uD558\uC138\uC694");
|
|
1016
|
-
log(
|
|
1017
|
-
|
|
1221
|
+
log(
|
|
1222
|
+
`${colors.dim}\uC608: npx connectbase init --yes --secret-key cb_sk_... [--app <\uC571ID>] [--storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>] [--deploy-dir ./dist]${colors.reset}`
|
|
1223
|
+
);
|
|
1224
|
+
log(
|
|
1225
|
+
`${colors.dim}Secret Key\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218\uB85C\uB3C4 \uC804\uB2EC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4${colors.reset}`
|
|
1226
|
+
);
|
|
1018
1227
|
process.exit(1);
|
|
1019
1228
|
}
|
|
1020
1229
|
const rcPath = path2.join(cwd, ".connectbaserc");
|
|
@@ -1026,11 +1235,15 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
|
|
|
1026
1235
|
}
|
|
1027
1236
|
if (nonInteractive) {
|
|
1028
1237
|
if (!opts.force) {
|
|
1029
|
-
error(
|
|
1238
|
+
error(
|
|
1239
|
+
".connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uB824\uBA74 --force\uB97C \uD568\uAED8 \uC0AC\uC6A9\uD558\uC138\uC694"
|
|
1240
|
+
);
|
|
1030
1241
|
process.exit(1);
|
|
1031
1242
|
}
|
|
1032
1243
|
} else {
|
|
1033
|
-
const overwrite = await prompt(
|
|
1244
|
+
const overwrite = await prompt(
|
|
1245
|
+
`${colors.yellow}\u26A0${colors.reset} .connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/N): `
|
|
1246
|
+
);
|
|
1034
1247
|
if (overwrite.toLowerCase() !== "y") {
|
|
1035
1248
|
info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
|
|
1036
1249
|
return;
|
|
@@ -1040,12 +1253,16 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
|
|
|
1040
1253
|
let secretKey = config.secretKey || "";
|
|
1041
1254
|
if (secretKey) {
|
|
1042
1255
|
if (!secretKey.startsWith("cb_sk_")) {
|
|
1043
|
-
error(
|
|
1256
|
+
error(
|
|
1257
|
+
"\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD0A4 \uD615\uC2DD\uC785\uB2C8\uB2E4. cb_sk_\uB85C \uC2DC\uC791\uD558\uB294 Secret Key\uB97C \uC9C0\uC815\uD558\uC138\uC694"
|
|
1258
|
+
);
|
|
1044
1259
|
process.exit(1);
|
|
1045
1260
|
}
|
|
1046
1261
|
info("Secret Key \uD655\uC778\uB428 (\uD50C\uB798\uADF8/\uD658\uACBD\uBCC0\uC218/.connectbaserc)");
|
|
1047
1262
|
} else if (nonInteractive) {
|
|
1048
|
-
error(
|
|
1263
|
+
error(
|
|
1264
|
+
"Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: --secret-key cb_sk_... \uB610\uB294 CONNECTBASE_SECRET_KEY \uD658\uACBD\uBCC0\uC218"
|
|
1265
|
+
);
|
|
1049
1266
|
process.exit(1);
|
|
1050
1267
|
} else {
|
|
1051
1268
|
log(`
|
|
@@ -1053,30 +1270,42 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
1053
1270
|
log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
|
|
1054
1271
|
log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
|
|
1055
1272
|
`);
|
|
1056
|
-
const authChoice = await prompt(
|
|
1273
|
+
const authChoice = await prompt(
|
|
1274
|
+
`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `
|
|
1275
|
+
);
|
|
1057
1276
|
if (authChoice === "2") {
|
|
1058
1277
|
try {
|
|
1059
1278
|
secretKey = await browserAuthFlow();
|
|
1060
1279
|
} catch (err) {
|
|
1061
1280
|
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
1062
|
-
info(
|
|
1281
|
+
info(
|
|
1282
|
+
"\uC778\uD130\uB137 \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uAC70\uB098, Secret Key \uC9C1\uC811 \uC785\uB825(\uC635\uC158 1)\uC744 \uC2DC\uB3C4\uD558\uC138\uC694"
|
|
1283
|
+
);
|
|
1063
1284
|
process.exit(1);
|
|
1064
1285
|
}
|
|
1065
1286
|
} else {
|
|
1066
|
-
log(
|
|
1067
|
-
|
|
1068
|
-
|
|
1287
|
+
log(
|
|
1288
|
+
`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
|
|
1289
|
+
`
|
|
1290
|
+
);
|
|
1291
|
+
secretKey = await promptSecret(
|
|
1292
|
+
`${colors.blue}?${colors.reset} Secret Key: `
|
|
1293
|
+
);
|
|
1069
1294
|
if (!secretKey) {
|
|
1070
1295
|
error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
|
|
1071
1296
|
process.exit(1);
|
|
1072
1297
|
}
|
|
1073
1298
|
if (secretKey.startsWith("cb_pk_")) {
|
|
1074
1299
|
error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
|
|
1075
|
-
info(
|
|
1300
|
+
info(
|
|
1301
|
+
"Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4"
|
|
1302
|
+
);
|
|
1076
1303
|
process.exit(1);
|
|
1077
1304
|
}
|
|
1078
1305
|
if (!secretKey.startsWith("cb_sk_")) {
|
|
1079
|
-
error(
|
|
1306
|
+
error(
|
|
1307
|
+
"\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uD0A4 \uD615\uC2DD\uC785\uB2C8\uB2E4. cb_sk_\uB85C \uC2DC\uC791\uD558\uB294 Secret Key\uB97C \uC785\uB825\uD558\uC138\uC694"
|
|
1308
|
+
);
|
|
1080
1309
|
process.exit(1);
|
|
1081
1310
|
}
|
|
1082
1311
|
}
|
|
@@ -1145,11 +1374,15 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
1145
1374
|
${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
|
|
1146
1375
|
apps.forEach((a, i) => {
|
|
1147
1376
|
const date = a.created_at ? a.created_at.substring(0, 10) : "";
|
|
1148
|
-
log(
|
|
1377
|
+
log(
|
|
1378
|
+
` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`
|
|
1379
|
+
);
|
|
1149
1380
|
});
|
|
1150
1381
|
log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
|
|
1151
|
-
const choice = await prompt(
|
|
1152
|
-
|
|
1382
|
+
const choice = await prompt(
|
|
1383
|
+
`
|
|
1384
|
+
${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
|
|
1385
|
+
);
|
|
1153
1386
|
const num = parseInt(choice, 10);
|
|
1154
1387
|
if (num > 0 && num <= apps.length) {
|
|
1155
1388
|
appId = apps[num - 1].id;
|
|
@@ -1158,7 +1391,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1158
1391
|
}
|
|
1159
1392
|
}
|
|
1160
1393
|
if (!appId) {
|
|
1161
|
-
const appName = nonInteractive ? projectName : await prompt(
|
|
1394
|
+
const appName = nonInteractive ? projectName : await prompt(
|
|
1395
|
+
`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `
|
|
1396
|
+
) || projectName;
|
|
1162
1397
|
info("\uC571 \uC0DD\uC131 \uC911...");
|
|
1163
1398
|
const createRes = await makeRequest(
|
|
1164
1399
|
`${DEFAULT_BASE_URL}/v1/public/cli/apps`,
|
|
@@ -1186,19 +1421,25 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1186
1421
|
} catch (err) {
|
|
1187
1422
|
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
1188
1423
|
if (nonInteractive) {
|
|
1189
|
-
info(
|
|
1424
|
+
info(
|
|
1425
|
+
"\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, \uCF58\uC194\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC12\uC73C\uB85C .connectbaserc\uB97C \uC9C1\uC811 \uC791\uC131\uD558\uC138\uC694"
|
|
1426
|
+
);
|
|
1190
1427
|
process.exit(1);
|
|
1191
1428
|
}
|
|
1192
1429
|
log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
|
|
1193
1430
|
appId = await prompt(`${colors.blue}?${colors.reset} App ID: `);
|
|
1194
|
-
publicKey = await prompt(
|
|
1431
|
+
publicKey = await prompt(
|
|
1432
|
+
`${colors.blue}?${colors.reset} Public Key (cb_pk_): `
|
|
1433
|
+
);
|
|
1195
1434
|
if (!appId || !publicKey) {
|
|
1196
1435
|
error("App ID\uC640 Public Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
|
|
1197
1436
|
process.exit(1);
|
|
1198
1437
|
}
|
|
1199
1438
|
}
|
|
1200
1439
|
if (!publicKey) {
|
|
1201
|
-
error(
|
|
1440
|
+
error(
|
|
1441
|
+
"Public Key\uAC00 \uC5C6\uC5B4 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0\uB97C \uC870\uD68C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C Public Key\uB97C \uBC1C\uAE09\uBC1B\uC544 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694"
|
|
1442
|
+
);
|
|
1202
1443
|
process.exit(1);
|
|
1203
1444
|
}
|
|
1204
1445
|
const publicKeyForSdk = publicKey;
|
|
@@ -1227,11 +1468,15 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1227
1468
|
log(`
|
|
1228
1469
|
${colors.dim}\uAE30\uC874 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0:${colors.reset}`);
|
|
1229
1470
|
storages.forEach((s, i) => {
|
|
1230
|
-
log(
|
|
1471
|
+
log(
|
|
1472
|
+
` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`
|
|
1473
|
+
);
|
|
1231
1474
|
});
|
|
1232
1475
|
log(` ${colors.cyan}0${colors.reset}) \uC0C8\uB85C \uC0DD\uC131`);
|
|
1233
|
-
const choice = await prompt(
|
|
1234
|
-
|
|
1476
|
+
const choice = await prompt(
|
|
1477
|
+
`
|
|
1478
|
+
${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `
|
|
1479
|
+
);
|
|
1235
1480
|
const num = parseInt(choice, 10);
|
|
1236
1481
|
if (num > 0 && num <= storages.length) {
|
|
1237
1482
|
storageId = storages[num - 1].id;
|
|
@@ -1239,7 +1484,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1239
1484
|
}
|
|
1240
1485
|
}
|
|
1241
1486
|
if (!storageId) {
|
|
1242
|
-
const name = nonInteractive ? projectName : await prompt(
|
|
1487
|
+
const name = nonInteractive ? projectName : await prompt(
|
|
1488
|
+
`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `
|
|
1489
|
+
) || projectName;
|
|
1243
1490
|
info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
|
|
1244
1491
|
const createRes = await makeRequest(
|
|
1245
1492
|
`${DEFAULT_BASE_URL}/v1/public/storages/webs`,
|
|
@@ -1249,7 +1496,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1249
1496
|
);
|
|
1250
1497
|
if (createRes.status !== 200) {
|
|
1251
1498
|
const data = createRes.data;
|
|
1252
|
-
error(
|
|
1499
|
+
error(
|
|
1500
|
+
`\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`
|
|
1501
|
+
);
|
|
1253
1502
|
process.exit(1);
|
|
1254
1503
|
}
|
|
1255
1504
|
const createData = createRes.data;
|
|
@@ -1262,7 +1511,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1262
1511
|
} catch (err) {
|
|
1263
1512
|
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
1264
1513
|
if (nonInteractive) {
|
|
1265
|
-
info(
|
|
1514
|
+
info(
|
|
1515
|
+
"\uB124\uD2B8\uC6CC\uD06C \uBCF5\uAD6C \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uAC70\uB098, --storage <\uC2A4\uD1A0\uB9AC\uC9C0ID>\uC640 \uD568\uAED8 \uC2E4\uD589\uD558\uC138\uC694"
|
|
1516
|
+
);
|
|
1266
1517
|
process.exit(1);
|
|
1267
1518
|
}
|
|
1268
1519
|
log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C Storage ID\uB97C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
|
|
@@ -1274,7 +1525,9 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1274
1525
|
}
|
|
1275
1526
|
}
|
|
1276
1527
|
const defaultDir = opts.deployDir || detectBuildDir();
|
|
1277
|
-
const deployDir = nonInteractive ? defaultDir : await prompt(
|
|
1528
|
+
const deployDir = nonInteractive ? defaultDir : await prompt(
|
|
1529
|
+
`${colors.blue}?${colors.reset} \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC (${defaultDir}): `
|
|
1530
|
+
) || defaultDir;
|
|
1278
1531
|
const rcData = {
|
|
1279
1532
|
appId,
|
|
1280
1533
|
publicKey: publicKey || "",
|
|
@@ -1284,7 +1537,8 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1284
1537
|
if (secretKey) {
|
|
1285
1538
|
rcData.secretKey = secretKey;
|
|
1286
1539
|
}
|
|
1287
|
-
fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2)
|
|
1540
|
+
fs2.writeFileSync(rcPath, `${JSON.stringify(rcData, null, 2)}
|
|
1541
|
+
`);
|
|
1288
1542
|
success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
|
|
1289
1543
|
addToGitignore(".connectbaserc");
|
|
1290
1544
|
addDeployScript(deployDir);
|
|
@@ -1308,14 +1562,17 @@ function detectBuildDir() {
|
|
|
1308
1562
|
try {
|
|
1309
1563
|
const src = fs2.readFileSync(nextConfig, "utf-8");
|
|
1310
1564
|
if (!/output\s*:\s*['"]export['"]/.test(src)) {
|
|
1311
|
-
warn(
|
|
1565
|
+
warn(
|
|
1566
|
+
`Next.js \uAC10\uC9C0: \uC815\uC801 \uBC30\uD3EC\uD558\uB824\uBA74 ${path2.basename(nextConfig)}\uC5D0 output: 'export' \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4`
|
|
1567
|
+
);
|
|
1312
1568
|
}
|
|
1313
1569
|
} catch {
|
|
1314
1570
|
}
|
|
1315
1571
|
}
|
|
1316
1572
|
return "./out";
|
|
1317
1573
|
}
|
|
1318
|
-
if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js")))
|
|
1574
|
+
if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js")))
|
|
1575
|
+
return "./dist";
|
|
1319
1576
|
return "./dist";
|
|
1320
1577
|
}
|
|
1321
1578
|
function addToGitignore(entry, basePath) {
|
|
@@ -1350,12 +1607,15 @@ function addDeployScript(deployDir) {
|
|
|
1350
1607
|
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
1351
1608
|
if (!pkg.scripts) pkg.scripts = {};
|
|
1352
1609
|
if (pkg.scripts.deploy) {
|
|
1353
|
-
info(
|
|
1610
|
+
info(
|
|
1611
|
+
`package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`
|
|
1612
|
+
);
|
|
1354
1613
|
return;
|
|
1355
1614
|
}
|
|
1356
1615
|
const deployCmd = `connectbase deploy ${deployDir}`;
|
|
1357
1616
|
pkg.scripts.deploy = pkg.scripts.build ? `${pkg.scripts.build} && ${deployCmd}` : deployCmd;
|
|
1358
|
-
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)
|
|
1617
|
+
fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
1618
|
+
`);
|
|
1359
1619
|
success(`package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC`);
|
|
1360
1620
|
} catch {
|
|
1361
1621
|
warn("package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
|
|
@@ -1364,14 +1624,20 @@ function addDeployScript(deployDir) {
|
|
|
1364
1624
|
function getGitRoot() {
|
|
1365
1625
|
try {
|
|
1366
1626
|
const { execSync } = require("child_process");
|
|
1367
|
-
return execSync("git rev-parse --show-toplevel", {
|
|
1627
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
1628
|
+
encoding: "utf-8"
|
|
1629
|
+
}).trim();
|
|
1368
1630
|
} catch {
|
|
1369
1631
|
return null;
|
|
1370
1632
|
}
|
|
1371
1633
|
}
|
|
1372
1634
|
function detectMonorepo(gitRoot) {
|
|
1373
1635
|
const cwd = process.cwd();
|
|
1374
|
-
const result = {
|
|
1636
|
+
const result = {
|
|
1637
|
+
type: "none",
|
|
1638
|
+
root: gitRoot,
|
|
1639
|
+
isSubPackage: false
|
|
1640
|
+
};
|
|
1375
1641
|
if (fs2.existsSync(path2.join(gitRoot, "pnpm-workspace.yaml"))) {
|
|
1376
1642
|
result.type = "pnpm";
|
|
1377
1643
|
} else if (fs2.existsSync(path2.join(gitRoot, "turbo.json"))) {
|
|
@@ -1408,7 +1674,9 @@ function detectMonorepo(gitRoot) {
|
|
|
1408
1674
|
var DOCS_MANIFEST_FILE = ".connectbase-manifest.json";
|
|
1409
1675
|
function readDocsManifest(docsDir) {
|
|
1410
1676
|
try {
|
|
1411
|
-
const parsed = JSON.parse(
|
|
1677
|
+
const parsed = JSON.parse(
|
|
1678
|
+
fs2.readFileSync(path2.join(docsDir, DOCS_MANIFEST_FILE), "utf-8")
|
|
1679
|
+
);
|
|
1412
1680
|
if (parsed && Array.isArray(parsed.files)) return parsed;
|
|
1413
1681
|
} catch {
|
|
1414
1682
|
}
|
|
@@ -1467,7 +1735,9 @@ async function downloadDocs(baseDir, opts = {}) {
|
|
|
1467
1735
|
if (!quiet && monorepo.type !== "none") {
|
|
1468
1736
|
info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${gitRoot})`);
|
|
1469
1737
|
if (monorepo.isSubPackage) {
|
|
1470
|
-
info(
|
|
1738
|
+
info(
|
|
1739
|
+
`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`
|
|
1740
|
+
);
|
|
1471
1741
|
}
|
|
1472
1742
|
}
|
|
1473
1743
|
const docsDir = path2.join(gitRoot, ".claude", "docs");
|
|
@@ -1496,12 +1766,18 @@ async function downloadDocs(baseDir, opts = {}) {
|
|
|
1496
1766
|
}
|
|
1497
1767
|
const contentHash = hasher.digest("hex");
|
|
1498
1768
|
const prevManifest = readDocsManifest(docsDir);
|
|
1499
|
-
const allPresent = newFiles.every(
|
|
1769
|
+
const allPresent = newFiles.every(
|
|
1770
|
+
(f) => fs2.existsSync(path2.join(docsDir, f))
|
|
1771
|
+
);
|
|
1500
1772
|
if (prevManifest && prevManifest.contentHash === contentHash && allPresent) {
|
|
1501
1773
|
if (!quiet) success("SDK \uBB38\uC11C\uAC00 \uC774\uBBF8 \uCD5C\uC2E0\uC785\uB2C8\uB2E4");
|
|
1502
1774
|
updateRootClaudeMd(rootClaudeMd, quiet);
|
|
1503
1775
|
if (monorepo.isSubPackage) {
|
|
1504
|
-
createSubPackageClaudeMd(
|
|
1776
|
+
createSubPackageClaudeMd(
|
|
1777
|
+
path2.join(process.cwd(), "CLAUDE.md"),
|
|
1778
|
+
gitRoot,
|
|
1779
|
+
quiet
|
|
1780
|
+
);
|
|
1505
1781
|
}
|
|
1506
1782
|
return "unchanged";
|
|
1507
1783
|
}
|
|
@@ -1530,13 +1806,27 @@ async function downloadDocs(baseDir, opts = {}) {
|
|
|
1530
1806
|
contentHash,
|
|
1531
1807
|
files: newFiles
|
|
1532
1808
|
};
|
|
1533
|
-
fs2.writeFileSync(
|
|
1534
|
-
|
|
1809
|
+
fs2.writeFileSync(
|
|
1810
|
+
path2.join(docsDir, DOCS_MANIFEST_FILE),
|
|
1811
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
1812
|
+
`
|
|
1813
|
+
);
|
|
1814
|
+
if (!quiet)
|
|
1815
|
+
success(
|
|
1816
|
+
`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`
|
|
1817
|
+
);
|
|
1535
1818
|
updateRootClaudeMd(rootClaudeMd, quiet);
|
|
1536
1819
|
if (monorepo.isSubPackage) {
|
|
1537
|
-
createSubPackageClaudeMd(
|
|
1820
|
+
createSubPackageClaudeMd(
|
|
1821
|
+
path2.join(process.cwd(), "CLAUDE.md"),
|
|
1822
|
+
gitRoot,
|
|
1823
|
+
quiet
|
|
1824
|
+
);
|
|
1538
1825
|
}
|
|
1539
|
-
if (!quiet)
|
|
1826
|
+
if (!quiet)
|
|
1827
|
+
log(
|
|
1828
|
+
`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`
|
|
1829
|
+
);
|
|
1540
1830
|
return "updated";
|
|
1541
1831
|
} catch {
|
|
1542
1832
|
if (quiet) return "failed";
|
|
@@ -1580,7 +1870,9 @@ ${endMarker}`;
|
|
|
1580
1870
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
1581
1871
|
content = content.substring(0, startIdx) + sdkBlock + content.substring(endIdx + endMarker.length);
|
|
1582
1872
|
} else {
|
|
1583
|
-
content = sdkBlock
|
|
1873
|
+
content = `${sdkBlock}
|
|
1874
|
+
|
|
1875
|
+
${content.trimStart()}`;
|
|
1584
1876
|
}
|
|
1585
1877
|
fs2.writeFileSync(claudeMdPath, content);
|
|
1586
1878
|
if (!quiet) info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
|
|
@@ -1614,16 +1906,22 @@ ${endMarker}`;
|
|
|
1614
1906
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
1615
1907
|
content = content.substring(0, startIdx) + subBlock + content.substring(endIdx + endMarker.length);
|
|
1616
1908
|
} else if (content.indexOf("<!-- CONNECTBASE_START -->") === -1) {
|
|
1617
|
-
content = content.trimEnd()
|
|
1909
|
+
content = `${content.trimEnd()}
|
|
1910
|
+
|
|
1911
|
+
${subBlock}
|
|
1912
|
+
`;
|
|
1618
1913
|
} else {
|
|
1619
1914
|
return;
|
|
1620
1915
|
}
|
|
1621
1916
|
fs2.writeFileSync(subClaudeMdPath, content);
|
|
1622
1917
|
} else {
|
|
1623
|
-
fs2.writeFileSync(
|
|
1918
|
+
fs2.writeFileSync(
|
|
1919
|
+
subClaudeMdPath,
|
|
1920
|
+
`# ${path2.basename(path2.dirname(subClaudeMdPath))}
|
|
1624
1921
|
|
|
1625
1922
|
${subBlock}
|
|
1626
|
-
`
|
|
1923
|
+
`
|
|
1924
|
+
);
|
|
1627
1925
|
}
|
|
1628
1926
|
if (!quiet) success(`\uC11C\uBE0C \uD328\uD0A4\uC9C0 CLAUDE.md \uC0DD\uC131 \uC644\uB8CC: ${subClaudeMdPath}`);
|
|
1629
1927
|
}
|
|
@@ -1642,8 +1940,12 @@ async function setupMcp(secretKey) {
|
|
|
1642
1940
|
if (!process.stdin.isTTY) {
|
|
1643
1941
|
error("Secret Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (\uBE44\uB300\uD654\uD615 \uD658\uACBD)");
|
|
1644
1942
|
log(`${colors.dim}\uB2E4\uC74C \uC911 \uD558\uB098\uB85C \uD0A4\uB97C \uC804\uB2EC\uD558\uC138\uC694:${colors.reset}`);
|
|
1645
|
-
log(
|
|
1646
|
-
|
|
1943
|
+
log(
|
|
1944
|
+
` ${colors.cyan}npx connectbase mcp --secret-key cb_sk_...${colors.reset}`
|
|
1945
|
+
);
|
|
1946
|
+
log(
|
|
1947
|
+
` ${colors.cyan}CONNECTBASE_SECRET_KEY=cb_sk_... npx connectbase mcp${colors.reset}`
|
|
1948
|
+
);
|
|
1647
1949
|
process.exit(1);
|
|
1648
1950
|
}
|
|
1649
1951
|
log(`
|
|
@@ -1651,7 +1953,9 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
1651
1953
|
log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
|
|
1652
1954
|
log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
|
|
1653
1955
|
`);
|
|
1654
|
-
const authChoice = await prompt(
|
|
1956
|
+
const authChoice = await prompt(
|
|
1957
|
+
`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `
|
|
1958
|
+
);
|
|
1655
1959
|
if (authChoice === "2") {
|
|
1656
1960
|
try {
|
|
1657
1961
|
secretKey = await browserAuthFlow();
|
|
@@ -1664,7 +1968,8 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
1664
1968
|
}
|
|
1665
1969
|
}
|
|
1666
1970
|
rcData.secretKey = secretKey;
|
|
1667
|
-
fs2.writeFileSync(skRcPath, JSON.stringify(rcData, null, 2)
|
|
1971
|
+
fs2.writeFileSync(skRcPath, `${JSON.stringify(rcData, null, 2)}
|
|
1972
|
+
`);
|
|
1668
1973
|
addToGitignore(".connectbaserc", root);
|
|
1669
1974
|
success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
|
|
1670
1975
|
} catch (err) {
|
|
@@ -1672,7 +1977,9 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
1672
1977
|
process.exit(1);
|
|
1673
1978
|
}
|
|
1674
1979
|
} else {
|
|
1675
|
-
secretKey = await promptSecret(
|
|
1980
|
+
secretKey = await promptSecret(
|
|
1981
|
+
`${colors.blue}?${colors.reset} Secret Key: `
|
|
1982
|
+
);
|
|
1676
1983
|
}
|
|
1677
1984
|
}
|
|
1678
1985
|
const mcpEntry = {
|
|
@@ -1690,14 +1997,15 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
1690
1997
|
mcpConfig.mcpServers = {};
|
|
1691
1998
|
}
|
|
1692
1999
|
} catch {
|
|
1693
|
-
const backupPath = mcpConfigPath
|
|
2000
|
+
const backupPath = `${mcpConfigPath}.backup`;
|
|
1694
2001
|
fs2.copyFileSync(mcpConfigPath, backupPath);
|
|
1695
2002
|
warn(`.mcp.json \uD30C\uC2F1 \uC2E4\uD328, \uBC31\uC5C5 \uC0DD\uC131: ${backupPath}`);
|
|
1696
2003
|
mcpConfig = { mcpServers: {} };
|
|
1697
2004
|
}
|
|
1698
2005
|
}
|
|
1699
2006
|
mcpConfig.mcpServers["connect-base"] = mcpEntry;
|
|
1700
|
-
fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2)
|
|
2007
|
+
fs2.writeFileSync(mcpConfigPath, `${JSON.stringify(mcpConfig, null, 2)}
|
|
2008
|
+
`);
|
|
1701
2009
|
success(`${mcpConfigPath} \uC0DD\uC131 \uC644\uB8CC`);
|
|
1702
2010
|
addToGitignore(".mcp.json", root);
|
|
1703
2011
|
if (secretKey && secretKey !== "YOUR_SECRET_KEY_HERE") {
|
|
@@ -1706,8 +2014,10 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
1706
2014
|
warn("MCP \uC11C\uBC84\uB294 Secret Key (cb_sk_)\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4.");
|
|
1707
2015
|
info(".mcp.json \uD30C\uC77C\uC758 YOUR_SECRET_KEY_HERE\uB97C Secret Key\uB85C \uAD50\uCCB4\uD558\uC138\uC694.");
|
|
1708
2016
|
}
|
|
1709
|
-
log(
|
|
1710
|
-
|
|
2017
|
+
log(
|
|
2018
|
+
`
|
|
2019
|
+
${colors.dim}Claude Code\uC5D0\uC11C ConnectBase MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`
|
|
2020
|
+
);
|
|
1711
2021
|
}
|
|
1712
2022
|
async function setupClaudeCode(secretKey, projectRoot) {
|
|
1713
2023
|
const root = projectRoot || getProjectRoot();
|
|
@@ -1764,7 +2074,8 @@ function findSubPackagesWithConfig(gitRoot) {
|
|
|
1764
2074
|
try {
|
|
1765
2075
|
const entries = fs2.readdirSync(gitRoot, { withFileTypes: true });
|
|
1766
2076
|
for (const entry of entries) {
|
|
1767
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || candidates.includes(entry.name))
|
|
2077
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || candidates.includes(entry.name))
|
|
2078
|
+
continue;
|
|
1768
2079
|
if (entry.name === "node_modules") continue;
|
|
1769
2080
|
const subDir = path2.join(gitRoot, entry.name);
|
|
1770
2081
|
if (fs2.existsSync(path2.join(subDir, ".connectbaserc")) && !results.includes(subDir)) {
|
|
@@ -1789,8 +2100,12 @@ ${colors.cyan}ConnectBase Update${colors.reset}
|
|
|
1789
2100
|
if (cmp >= 0) {
|
|
1790
2101
|
success(`\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${VERSION})`);
|
|
1791
2102
|
} else {
|
|
1792
|
-
warn(
|
|
1793
|
-
|
|
2103
|
+
warn(
|
|
2104
|
+
`\uC0C8 \uBC84\uC804\uC774 \uC788\uC2B5\uB2C8\uB2E4: v${VERSION} \u2192 v${colors.green}${latestVersion}${colors.reset}`
|
|
2105
|
+
);
|
|
2106
|
+
log(
|
|
2107
|
+
` ${colors.cyan}npm install connectbase-client@latest${colors.reset}`
|
|
2108
|
+
);
|
|
1794
2109
|
log(` ${colors.cyan}pnpm add connectbase-client@latest${colors.reset}`);
|
|
1795
2110
|
log("");
|
|
1796
2111
|
}
|
|
@@ -1806,7 +2121,9 @@ ${colors.cyan}ConnectBase Update${colors.reset}
|
|
|
1806
2121
|
if (subPackages.length > 0) {
|
|
1807
2122
|
info(`ConnectBase\uB97C \uC0AC\uC6A9\uD558\uB294 \uC11C\uBE0C \uD328\uD0A4\uC9C0 ${subPackages.length}\uAC1C \uBC1C\uACAC:`);
|
|
1808
2123
|
for (const sp of subPackages) {
|
|
1809
|
-
log(
|
|
2124
|
+
log(
|
|
2125
|
+
` ${colors.dim}\u2022 ${path2.relative(projectRoot, sp)}${colors.reset}`
|
|
2126
|
+
);
|
|
1810
2127
|
}
|
|
1811
2128
|
}
|
|
1812
2129
|
}
|
|
@@ -1834,7 +2151,9 @@ ${colors.cyan}ConnectBase Update${colors.reset}
|
|
|
1834
2151
|
const subPackages = findSubPackagesWithConfig(projectRoot);
|
|
1835
2152
|
for (const sp of subPackages) {
|
|
1836
2153
|
try {
|
|
1837
|
-
const rc = JSON.parse(
|
|
2154
|
+
const rc = JSON.parse(
|
|
2155
|
+
fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8")
|
|
2156
|
+
);
|
|
1838
2157
|
if (rc.secretKey) {
|
|
1839
2158
|
secretKey = rc.secretKey;
|
|
1840
2159
|
break;
|
|
@@ -1854,10 +2173,16 @@ ${colors.cyan}ConnectBase Update${colors.reset}
|
|
|
1854
2173
|
headers: { Authorization: `Bearer ${secretKey}` }
|
|
1855
2174
|
};
|
|
1856
2175
|
mcpConfig.mcpServers = servers;
|
|
1857
|
-
fs2.writeFileSync(
|
|
2176
|
+
fs2.writeFileSync(
|
|
2177
|
+
mcpConfigPath,
|
|
2178
|
+
`${JSON.stringify(mcpConfig, null, 2)}
|
|
2179
|
+
`
|
|
2180
|
+
);
|
|
1858
2181
|
success("MCP \uC124\uC815 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
|
|
1859
2182
|
} else {
|
|
1860
|
-
info(
|
|
2183
|
+
info(
|
|
2184
|
+
'.mcp.json\uC5D0 connect-base \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. "connectbase mcp"\uB85C \uC124\uC815\uD558\uC138\uC694'
|
|
2185
|
+
);
|
|
1861
2186
|
}
|
|
1862
2187
|
} catch {
|
|
1863
2188
|
warn(".mcp.json \uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
|
|
@@ -1880,7 +2205,9 @@ ${colors.green}\uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!${colors.reset}
|
|
|
1880
2205
|
function setupMonorepoRoot(projectRoot, monorepo) {
|
|
1881
2206
|
const rootPkgPath = path2.join(projectRoot, "package.json");
|
|
1882
2207
|
if (!fs2.existsSync(rootPkgPath)) {
|
|
1883
|
-
warn(
|
|
2208
|
+
warn(
|
|
2209
|
+
"\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 package.json\uC774 \uC5C6\uC5B4 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4"
|
|
2210
|
+
);
|
|
1884
2211
|
return;
|
|
1885
2212
|
}
|
|
1886
2213
|
try {
|
|
@@ -1900,12 +2227,19 @@ function setupMonorepoRoot(projectRoot, monorepo) {
|
|
|
1900
2227
|
changed = true;
|
|
1901
2228
|
}
|
|
1902
2229
|
if (changed) {
|
|
1903
|
-
fs2.writeFileSync(rootPkgPath, JSON.stringify(pkg, null, 2)
|
|
2230
|
+
fs2.writeFileSync(rootPkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
2231
|
+
`);
|
|
1904
2232
|
success("\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC");
|
|
1905
2233
|
log(` ${colors.dim}\uC0AC\uC6A9\uBC95:${colors.reset}`);
|
|
1906
|
-
log(
|
|
1907
|
-
|
|
1908
|
-
|
|
2234
|
+
log(
|
|
2235
|
+
` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:update${colors.reset} \u2014 SDK \uC5C5\uB370\uC774\uD2B8 + \uBB38\uC11C \uAC31\uC2E0`
|
|
2236
|
+
);
|
|
2237
|
+
log(
|
|
2238
|
+
` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:docs${colors.reset} \u2014 \uBB38\uC11C\uB9CC \uAC31\uC2E0`
|
|
2239
|
+
);
|
|
2240
|
+
log(
|
|
2241
|
+
` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:mcp${colors.reset} \u2014 MCP \uC124\uC815`
|
|
2242
|
+
);
|
|
1909
2243
|
} else {
|
|
1910
2244
|
info("\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uC774\uBBF8 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC124\uC815\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4");
|
|
1911
2245
|
}
|
|
@@ -2030,7 +2364,9 @@ var UpstreamWsFrameParser = class {
|
|
|
2030
2364
|
}
|
|
2031
2365
|
if (this.buffer.length < offset + payloadLen) return;
|
|
2032
2366
|
if (rsv1 && (opcode === 1 || opcode === 2 || opcode === 0)) {
|
|
2033
|
-
throw new Error(
|
|
2367
|
+
throw new Error(
|
|
2368
|
+
"upstream sent compressed (RSV1) frame without negotiation"
|
|
2369
|
+
);
|
|
2034
2370
|
}
|
|
2035
2371
|
let payload = this.buffer.subarray(offset, offset + payloadLen);
|
|
2036
2372
|
if (maskKey) {
|
|
@@ -2045,9 +2381,14 @@ var UpstreamWsFrameParser = class {
|
|
|
2045
2381
|
switch (opcode) {
|
|
2046
2382
|
case 0:
|
|
2047
2383
|
if (this.fragmentBuffer == null) {
|
|
2048
|
-
throw new Error(
|
|
2384
|
+
throw new Error(
|
|
2385
|
+
"continuation frame without preceding non-final data frame"
|
|
2386
|
+
);
|
|
2049
2387
|
}
|
|
2050
|
-
this.fragmentBuffer = Buffer.concat([
|
|
2388
|
+
this.fragmentBuffer = Buffer.concat([
|
|
2389
|
+
this.fragmentBuffer,
|
|
2390
|
+
payloadCopy
|
|
2391
|
+
]);
|
|
2051
2392
|
if (fin) {
|
|
2052
2393
|
const out = this.fragmentBuffer;
|
|
2053
2394
|
this.fragmentBuffer = null;
|
|
@@ -2189,6 +2530,61 @@ function getTunnelServerUrl(baseUrl) {
|
|
|
2189
2530
|
}
|
|
2190
2531
|
return baseUrl.replace(/:\d+/, ":8090");
|
|
2191
2532
|
}
|
|
2533
|
+
function getTunnelConnectCandidates(baseUrl) {
|
|
2534
|
+
const override = process.env.CB_TUNNEL_CONNECT_URL;
|
|
2535
|
+
if (override) return [override];
|
|
2536
|
+
const proxyHost = getTunnelServerUrl(baseUrl);
|
|
2537
|
+
if (proxyHost.includes("tunnel.connectbase.world")) {
|
|
2538
|
+
return [
|
|
2539
|
+
proxyHost.replace(
|
|
2540
|
+
"tunnel.connectbase.world",
|
|
2541
|
+
"tunnel-direct.connectbase.world"
|
|
2542
|
+
),
|
|
2543
|
+
proxyHost
|
|
2544
|
+
];
|
|
2545
|
+
}
|
|
2546
|
+
return [proxyHost];
|
|
2547
|
+
}
|
|
2548
|
+
var TunnelConnectSelector = class {
|
|
2549
|
+
constructor(candidates) {
|
|
2550
|
+
this.candidates = candidates;
|
|
2551
|
+
this.idx = 0;
|
|
2552
|
+
this.failures = 0;
|
|
2553
|
+
if (candidates.length === 0) {
|
|
2554
|
+
throw new Error("connect candidate\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4");
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
current() {
|
|
2558
|
+
return this.candidates[this.idx];
|
|
2559
|
+
}
|
|
2560
|
+
// 연결이 수립되면 호출한다. 현 후보의 실패 카운트만 리셋하고 후보는 유지한다 —
|
|
2561
|
+
// 동작하는 경로를 계속 쓰되, 나중에 그 경로가 죽으면 다시 셀 수 있게 한다.
|
|
2562
|
+
recordSuccess() {
|
|
2563
|
+
this.failures = 0;
|
|
2564
|
+
}
|
|
2565
|
+
// 실패를 기록한다.
|
|
2566
|
+
//
|
|
2567
|
+
// retry — 아직 시도할 여지가 있는가. false 면 마지막 후보에서도 실패한
|
|
2568
|
+
// 것이므로 호출자가 포기(프로세스 종료)해도 된다.
|
|
2569
|
+
// switchedFrom — 이번 호출에서 후보를 넘겼다면 직전 후보 URL, 아니면 null.
|
|
2570
|
+
//
|
|
2571
|
+
// 전환을 2회 연속 실패부터 하는 이유는 일시적 네트워크 요동으로 느린 경로에
|
|
2572
|
+
// 눌러앉지 않기 위해서다. 다만 그 사이에도 retry 는 true 여야 한다 — 첫 실패에서
|
|
2573
|
+
// 호출자가 포기해버리면 폴백이 영영 동작하지 않는다.
|
|
2574
|
+
recordFailure() {
|
|
2575
|
+
this.failures++;
|
|
2576
|
+
if (this.idx >= this.candidates.length - 1) {
|
|
2577
|
+
return { retry: false, switchedFrom: null };
|
|
2578
|
+
}
|
|
2579
|
+
if (this.failures < 2) {
|
|
2580
|
+
return { retry: true, switchedFrom: null };
|
|
2581
|
+
}
|
|
2582
|
+
const switchedFrom = this.candidates[this.idx];
|
|
2583
|
+
this.idx++;
|
|
2584
|
+
this.failures = 0;
|
|
2585
|
+
return { retry: true, switchedFrom };
|
|
2586
|
+
}
|
|
2587
|
+
};
|
|
2192
2588
|
async function resolveApp(secretKey, baseUrl, appIdOption) {
|
|
2193
2589
|
if (appIdOption) {
|
|
2194
2590
|
return { appId: appIdOption };
|
|
@@ -2196,11 +2592,9 @@ async function resolveApp(secretKey, baseUrl, appIdOption) {
|
|
|
2196
2592
|
let apps = [];
|
|
2197
2593
|
try {
|
|
2198
2594
|
info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
2199
|
-
const appsRes = await makeRequest(
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
{ "X-Public-Key": secretKey }
|
|
2203
|
-
);
|
|
2595
|
+
const appsRes = await makeRequest(`${baseUrl}/v1/public/cli/apps`, "GET", {
|
|
2596
|
+
"X-Public-Key": secretKey
|
|
2597
|
+
});
|
|
2204
2598
|
if (appsRes.status === 401) {
|
|
2205
2599
|
error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
|
|
2206
2600
|
process.exit(1);
|
|
@@ -2223,19 +2617,25 @@ async function resolveApp(secretKey, baseUrl, appIdOption) {
|
|
|
2223
2617
|
${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
|
|
2224
2618
|
apps.forEach((a, i) => {
|
|
2225
2619
|
const date = a.created_at ? a.created_at.substring(0, 10) : "";
|
|
2226
|
-
log(
|
|
2620
|
+
log(
|
|
2621
|
+
` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`
|
|
2622
|
+
);
|
|
2227
2623
|
});
|
|
2228
2624
|
}
|
|
2229
2625
|
log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
|
|
2230
|
-
const choice = await prompt(
|
|
2231
|
-
|
|
2626
|
+
const choice = await prompt(
|
|
2627
|
+
`
|
|
2628
|
+
${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `
|
|
2629
|
+
);
|
|
2232
2630
|
const num = parseInt(choice, 10);
|
|
2233
2631
|
if (num > 0 && num <= apps.length) {
|
|
2234
2632
|
success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
|
|
2235
2633
|
return { appId: apps[num - 1].id };
|
|
2236
2634
|
}
|
|
2237
2635
|
const projectName = path2.basename(process.cwd());
|
|
2238
|
-
const appName = await prompt(
|
|
2636
|
+
const appName = await prompt(
|
|
2637
|
+
`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `
|
|
2638
|
+
) || projectName;
|
|
2239
2639
|
info("\uC571 \uC0DD\uC131 \uC911...");
|
|
2240
2640
|
const createRes = await makeRequest(
|
|
2241
2641
|
`${baseUrl}/v1/public/cli/apps`,
|
|
@@ -2259,39 +2659,52 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
2259
2659
|
async function registerEndpointBinding(baseUrl, appId, secretKey, tunnelId, label, description) {
|
|
2260
2660
|
try {
|
|
2261
2661
|
const apiBase = baseUrl.replace(/\/+$/, "");
|
|
2262
|
-
const res = await fetch(
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2662
|
+
const res = await fetch(
|
|
2663
|
+
`${apiBase}/v1/apps/${encodeURIComponent(appId)}/endpoints/cli`,
|
|
2664
|
+
{
|
|
2665
|
+
method: "POST",
|
|
2666
|
+
headers: {
|
|
2667
|
+
"Content-Type": "application/json",
|
|
2668
|
+
"X-Public-Key": secretKey
|
|
2669
|
+
},
|
|
2670
|
+
body: JSON.stringify({
|
|
2671
|
+
label,
|
|
2672
|
+
tunnel_id: tunnelId,
|
|
2673
|
+
description: description ?? `CLI tunnel start (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`
|
|
2674
|
+
})
|
|
2675
|
+
}
|
|
2676
|
+
);
|
|
2274
2677
|
if (res.status === 201) {
|
|
2275
2678
|
success(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC644\uB8CC`);
|
|
2276
|
-
log(
|
|
2679
|
+
log(
|
|
2680
|
+
`${colors.green}\u2192${colors.reset} SDK: ${colors.cyan}cb.endpoint.call("${label}", { path: "/...", method: "POST", body: ... })${colors.reset}`
|
|
2681
|
+
);
|
|
2277
2682
|
} else if (res.status === 409) {
|
|
2278
|
-
warn(
|
|
2683
|
+
warn(
|
|
2684
|
+
`"${label}" \uB77C\uBCA8\uC774 \uB2E4\uB978 \uD130\uB110\uC5D0 \uC774\uBBF8 \uBB36\uC5EC \uC788\uC2B5\uB2C8\uB2E4. \uC774 \uD130\uB110\uB85C \uC62E\uAE30\uB824\uBA74 \uCF58\uC194\uC5D0\uC11C endpoint \uC758 tunnel_id \uB97C \uC218\uC815(PATCH)\uD558\uC138\uC694.`
|
|
2685
|
+
);
|
|
2279
2686
|
} else {
|
|
2280
2687
|
const text = await res.text().catch(() => "");
|
|
2281
|
-
error(
|
|
2688
|
+
error(
|
|
2689
|
+
`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC2E4\uD328 (status ${res.status}): ${text || "(\uBE48 \uC751\uB2F5)"}`
|
|
2690
|
+
);
|
|
2282
2691
|
if (res.status === 401) {
|
|
2283
2692
|
info("cb_sk_ Secret Key \uC778\uC9C0, \uCF58\uC194\uC5D0\uC11C \uD68C\uC218\uB418\uC9C0 \uC54A\uC558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.");
|
|
2284
2693
|
}
|
|
2285
2694
|
}
|
|
2286
2695
|
} catch (err) {
|
|
2287
|
-
error(
|
|
2696
|
+
error(
|
|
2697
|
+
`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC911 \uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`
|
|
2698
|
+
);
|
|
2288
2699
|
}
|
|
2289
2700
|
}
|
|
2290
2701
|
function acquireTunnelLock2(appID, port, force) {
|
|
2291
2702
|
const result = acquireTunnelLock(appID, port, force, VERSION);
|
|
2292
|
-
if (result
|
|
2703
|
+
if (result?.startsWith("LOCKED:")) {
|
|
2293
2704
|
const [, pid, startedAt, host] = result.split(":");
|
|
2294
|
-
error(
|
|
2705
|
+
error(
|
|
2706
|
+
`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${pid}, \uC2DC\uC791 ${startedAt}, \uD638\uC2A4\uD2B8 ${host}`
|
|
2707
|
+
);
|
|
2295
2708
|
info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
|
|
2296
2709
|
process.exit(2);
|
|
2297
2710
|
}
|
|
@@ -2312,7 +2725,8 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
2312
2725
|
}
|
|
2313
2726
|
}
|
|
2314
2727
|
rcData.secretKey = sk;
|
|
2315
|
-
fs2.writeFileSync(rcPath2, JSON.stringify(rcData, null, 2)
|
|
2728
|
+
fs2.writeFileSync(rcPath2, `${JSON.stringify(rcData, null, 2)}
|
|
2729
|
+
`);
|
|
2316
2730
|
addToGitignore(".connectbaserc");
|
|
2317
2731
|
success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
|
|
2318
2732
|
tunnelKey = sk;
|
|
@@ -2345,14 +2759,30 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
2345
2759
|
}
|
|
2346
2760
|
if (rcData.tunnelAppId !== appId) {
|
|
2347
2761
|
rcData.tunnelAppId = appId;
|
|
2348
|
-
fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2)
|
|
2762
|
+
fs2.writeFileSync(rcPath, `${JSON.stringify(rcData, null, 2)}
|
|
2763
|
+
`);
|
|
2349
2764
|
}
|
|
2350
2765
|
} catch {
|
|
2351
2766
|
}
|
|
2352
2767
|
const lockPath = acquireTunnelLock2(appId, port, tunnelOpts?.force ?? false);
|
|
2353
|
-
const
|
|
2354
|
-
|
|
2355
|
-
|
|
2768
|
+
const selector = new TunnelConnectSelector(
|
|
2769
|
+
getTunnelConnectCandidates(config.baseUrl)
|
|
2770
|
+
);
|
|
2771
|
+
let announcedFallback = false;
|
|
2772
|
+
const currentUrl = () => new URL(selector.current());
|
|
2773
|
+
const advanceCandidate = (reason) => {
|
|
2774
|
+
const { retry, switchedFrom } = selector.recordFailure();
|
|
2775
|
+
if (switchedFrom && !announcedFallback) {
|
|
2776
|
+
announcedFallback = true;
|
|
2777
|
+
warn(
|
|
2778
|
+
`\uC9C1\uACB0 \uACBD\uB85C(${new URL(switchedFrom).hostname}) \uC5F0\uACB0 \uC2E4\uD328 \u2014 ${reason}`
|
|
2779
|
+
);
|
|
2780
|
+
warn(
|
|
2781
|
+
`Cloudflare \uACBD\uC720(${new URL(selector.current()).hostname})\uB85C \uC804\uD658\uD569\uB2C8\uB2E4. \uB3D9\uC791\uD558\uC9C0\uB9CC \uC9C0\uC5F0\uC774 \uCEE4\uC9D1\uB2C8\uB2E4.`
|
|
2782
|
+
);
|
|
2783
|
+
}
|
|
2784
|
+
return retry;
|
|
2785
|
+
};
|
|
2356
2786
|
let wsPath = `/v2/tunnel/connect?app_id=${encodeURIComponent(appId)}&local_port=${port}&ws_opcode=1`;
|
|
2357
2787
|
if (tunnelOpts?.timeout) {
|
|
2358
2788
|
wsPath += `&timeout=${tunnelOpts.timeout}`;
|
|
@@ -2403,21 +2833,23 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2403
2833
|
log(`${colors.dim}\uB85C\uCEEC \uD3EC\uD2B8 ${port}\uB97C \uC778\uD130\uB137\uC5D0 \uB178\uCD9C\uD569\uB2C8\uB2E4${colors.reset}
|
|
2404
2834
|
`);
|
|
2405
2835
|
function connect() {
|
|
2406
|
-
const
|
|
2836
|
+
const target = currentUrl();
|
|
2837
|
+
const targetHttps = target.protocol === "https:";
|
|
2838
|
+
const lib = targetHttps ? https : http;
|
|
2407
2839
|
const wsKey = crypto.randomBytes(16).toString("base64");
|
|
2408
2840
|
const reqOptions = {
|
|
2409
|
-
hostname:
|
|
2410
|
-
port:
|
|
2841
|
+
hostname: target.hostname,
|
|
2842
|
+
port: target.port || (targetHttps ? 443 : 80),
|
|
2411
2843
|
path: wsPath,
|
|
2412
2844
|
method: "GET",
|
|
2413
2845
|
family: 4,
|
|
2414
2846
|
autoSelectFamily: false,
|
|
2415
2847
|
headers: {
|
|
2416
|
-
|
|
2417
|
-
|
|
2848
|
+
Upgrade: "websocket",
|
|
2849
|
+
Connection: "Upgrade",
|
|
2418
2850
|
"Sec-WebSocket-Key": wsKey,
|
|
2419
2851
|
"Sec-WebSocket-Version": "13",
|
|
2420
|
-
|
|
2852
|
+
Authorization: `Bearer ${config.secretKey ?? config.publicKey}`
|
|
2421
2853
|
}
|
|
2422
2854
|
};
|
|
2423
2855
|
const req = lib.request(reqOptions);
|
|
@@ -2425,6 +2857,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2425
2857
|
socket = sock;
|
|
2426
2858
|
reconnectAttempts = 0;
|
|
2427
2859
|
everConnected = true;
|
|
2860
|
+
selector.recordSuccess();
|
|
2428
2861
|
sock.setNoDelay(true);
|
|
2429
2862
|
sock.setKeepAlive(true, 3e4);
|
|
2430
2863
|
const idleTimeoutMs = 135e3;
|
|
@@ -2432,7 +2865,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2432
2865
|
const armIdleTimer = () => {
|
|
2433
2866
|
if (idleTimer) clearTimeout(idleTimer);
|
|
2434
2867
|
idleTimer = setTimeout(() => {
|
|
2435
|
-
warn(
|
|
2868
|
+
warn(
|
|
2869
|
+
`${idleTimeoutMs / 1e3}\uCD08\uAC04 \uC11C\uBC84 \uC218\uC2E0 \uC5C6\uC74C \u2014 \uB04A\uAE34 \uC5F0\uACB0(half-open)\uB85C \uD310\uB2E8\uD558\uACE0 \uC7AC\uC5F0\uACB0\uD569\uB2C8\uB2E4`
|
|
2870
|
+
);
|
|
2436
2871
|
sock.destroy();
|
|
2437
2872
|
}, idleTimeoutMs);
|
|
2438
2873
|
};
|
|
@@ -2442,7 +2877,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2442
2877
|
try {
|
|
2443
2878
|
const msg = JSON.parse(data);
|
|
2444
2879
|
handleMessage(msg, sock, port).catch((e) => {
|
|
2445
|
-
error(
|
|
2880
|
+
error(
|
|
2881
|
+
`\uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uC608\uC678: ${e instanceof Error ? e.message : e}`
|
|
2882
|
+
);
|
|
2446
2883
|
});
|
|
2447
2884
|
} catch (e) {
|
|
2448
2885
|
warn(`\uBA54\uC2DC\uC9C0 \uD30C\uC2F1 \uC2E4\uD328: ${e}`);
|
|
@@ -2486,6 +2923,7 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2486
2923
|
});
|
|
2487
2924
|
});
|
|
2488
2925
|
req.on("error", (err) => {
|
|
2926
|
+
advanceCandidate(err.message);
|
|
2489
2927
|
if (reconnectAttempts === 0) {
|
|
2490
2928
|
error(`\uD130\uB110 \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328: ${err.message}`);
|
|
2491
2929
|
}
|
|
@@ -2495,7 +2933,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2495
2933
|
});
|
|
2496
2934
|
req.on("response", (res) => {
|
|
2497
2935
|
let body = "";
|
|
2498
|
-
res.on("data", (chunk) =>
|
|
2936
|
+
res.on("data", (chunk) => {
|
|
2937
|
+
body += chunk.toString();
|
|
2938
|
+
});
|
|
2499
2939
|
res.on("end", () => {
|
|
2500
2940
|
const status = res.statusCode ?? 0;
|
|
2501
2941
|
let serverMsg = body;
|
|
@@ -2514,10 +2954,16 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2514
2954
|
process.exit(1);
|
|
2515
2955
|
} else if (status === 403) {
|
|
2516
2956
|
error(`\uC811\uADFC \uAC70\uBD80: ${serverMsg}`);
|
|
2517
|
-
error(
|
|
2957
|
+
error(
|
|
2958
|
+
"\u2192 app_id \uAC00 \uCF58\uC194\uC758 \uC571/\uD504\uB85C\uC81D\uD2B8 ID \uC778\uC9C0 \uD655\uC778\uD558\uC138\uC694 (\uACF5\uAC1C\uD0A4 UUID \uB098 storageId \uAC00 \uC544\uB2D9\uB2C8\uB2E4)."
|
|
2959
|
+
);
|
|
2518
2960
|
shouldReconnect = false;
|
|
2519
2961
|
process.exit(1);
|
|
2520
2962
|
} else if (status >= 400 && status < 500 && status !== 429) {
|
|
2963
|
+
if (advanceCandidate(`HTTP ${status}`) && shouldReconnect) {
|
|
2964
|
+
scheduleReconnect();
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2521
2967
|
error(`\uD130\uB110 \uC5F0\uACB0 \uAC70\uBD80 (${status}): ${serverMsg}`);
|
|
2522
2968
|
shouldReconnect = false;
|
|
2523
2969
|
process.exit(1);
|
|
@@ -2539,7 +2985,10 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2539
2985
|
error("\uCD5C\uB300 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4 \uD69F\uC218 \uCD08\uACFC");
|
|
2540
2986
|
process.exit(1);
|
|
2541
2987
|
}
|
|
2542
|
-
const delay = Math.min(
|
|
2988
|
+
const delay = Math.min(
|
|
2989
|
+
2e3 * 2 ** Math.min(reconnectAttempts - 1, 8),
|
|
2990
|
+
3e4
|
|
2991
|
+
);
|
|
2543
2992
|
const attemptLabel = everConnected ? `\uC2DC\uB3C4 ${reconnectAttempts}` : `${reconnectAttempts}/${maxReconnectAttempts}`;
|
|
2544
2993
|
info(`${(delay / 1e3).toFixed(0)}\uCD08 \uD6C4 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4... (${attemptLabel})`);
|
|
2545
2994
|
reconnectTimer = setTimeout(() => {
|
|
@@ -2578,11 +3027,18 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2578
3027
|
for (const [k, v] of Object.entries(headers)) {
|
|
2579
3028
|
if (k.toLowerCase() !== "host") localHeaders[k] = v;
|
|
2580
3029
|
}
|
|
2581
|
-
localHeaders
|
|
3030
|
+
localHeaders.host = `localhost:${localPort}`;
|
|
2582
3031
|
let started = false;
|
|
2583
3032
|
let cancelled = false;
|
|
2584
3033
|
const localReq = http.request(
|
|
2585
|
-
{
|
|
3034
|
+
{
|
|
3035
|
+
hostname: "127.0.0.1",
|
|
3036
|
+
port: localPort,
|
|
3037
|
+
path: fullPath,
|
|
3038
|
+
method,
|
|
3039
|
+
headers: localHeaders,
|
|
3040
|
+
agent: localAgent
|
|
3041
|
+
},
|
|
2586
3042
|
(res) => {
|
|
2587
3043
|
const respHeaders = {};
|
|
2588
3044
|
for (const [k, v] of Object.entries(res.headers)) {
|
|
@@ -2597,19 +3053,29 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2597
3053
|
headers: respHeaders
|
|
2598
3054
|
});
|
|
2599
3055
|
const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
|
|
2600
|
-
log(
|
|
3056
|
+
log(
|
|
3057
|
+
`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`
|
|
3058
|
+
);
|
|
2601
3059
|
res.on("data", (chunk) => {
|
|
2602
3060
|
if (cancelled) return;
|
|
2603
3061
|
sendBinary(sock, streamId, chunk);
|
|
2604
3062
|
});
|
|
2605
3063
|
res.on("end", () => {
|
|
2606
3064
|
if (cancelled) return;
|
|
2607
|
-
sendControl(sock, {
|
|
3065
|
+
sendControl(sock, {
|
|
3066
|
+
type: "stream_eof",
|
|
3067
|
+
stream_id: streamId,
|
|
3068
|
+
side: "upstream"
|
|
3069
|
+
});
|
|
2608
3070
|
sendControl(sock, { type: "stream_close", stream_id: streamId });
|
|
2609
3071
|
streams.delete(streamId);
|
|
2610
3072
|
});
|
|
2611
3073
|
res.on("error", (err) => {
|
|
2612
|
-
sendControl(sock, {
|
|
3074
|
+
sendControl(sock, {
|
|
3075
|
+
type: "stream_close",
|
|
3076
|
+
stream_id: streamId,
|
|
3077
|
+
error: `upstream_error: ${err.message}`
|
|
3078
|
+
});
|
|
2613
3079
|
streams.delete(streamId);
|
|
2614
3080
|
});
|
|
2615
3081
|
}
|
|
@@ -2629,9 +3095,19 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2629
3095
|
status: 502,
|
|
2630
3096
|
headers: { "content-type": "application/json" }
|
|
2631
3097
|
});
|
|
2632
|
-
sendBinary(
|
|
3098
|
+
sendBinary(
|
|
3099
|
+
sock,
|
|
3100
|
+
streamId,
|
|
3101
|
+
Buffer.from(
|
|
3102
|
+
JSON.stringify({ error: `Local server error: ${err.message}` })
|
|
3103
|
+
)
|
|
3104
|
+
);
|
|
2633
3105
|
}
|
|
2634
|
-
sendControl(sock, {
|
|
3106
|
+
sendControl(sock, {
|
|
3107
|
+
type: "stream_close",
|
|
3108
|
+
stream_id: streamId,
|
|
3109
|
+
error: `upstream_error: ${err.message}`
|
|
3110
|
+
});
|
|
2635
3111
|
streams.delete(streamId);
|
|
2636
3112
|
});
|
|
2637
3113
|
const forwarder = {
|
|
@@ -2664,9 +3140,9 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2664
3140
|
if (lk === "sec-websocket-extensions") continue;
|
|
2665
3141
|
localHeaders[k] = v;
|
|
2666
3142
|
}
|
|
2667
|
-
localHeaders
|
|
2668
|
-
localHeaders
|
|
2669
|
-
localHeaders
|
|
3143
|
+
localHeaders.host = `localhost:${localPort}`;
|
|
3144
|
+
localHeaders.connection = "Upgrade";
|
|
3145
|
+
localHeaders.upgrade = "websocket";
|
|
2670
3146
|
let cancelled = false;
|
|
2671
3147
|
let upstream = null;
|
|
2672
3148
|
const req = http.request({
|
|
@@ -2694,12 +3170,18 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2694
3170
|
headers: respHeaders,
|
|
2695
3171
|
websocket: true
|
|
2696
3172
|
});
|
|
2697
|
-
log(
|
|
3173
|
+
log(
|
|
3174
|
+
`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${colors.cyan}WS${colors.reset} ${reqPath} \u2192 101`
|
|
3175
|
+
);
|
|
2698
3176
|
const upstreamParser = new UpstreamWsFrameParser({
|
|
2699
3177
|
onPayload: (payload, opcode) => {
|
|
2700
3178
|
if (cancelled) return;
|
|
2701
3179
|
if (negotiatedWsOpcode) {
|
|
2702
|
-
sendBinary(
|
|
3180
|
+
sendBinary(
|
|
3181
|
+
sock,
|
|
3182
|
+
streamId,
|
|
3183
|
+
Buffer.concat([Buffer.from([opcode & 15]), payload])
|
|
3184
|
+
);
|
|
2703
3185
|
} else {
|
|
2704
3186
|
sendBinary(sock, streamId, payload);
|
|
2705
3187
|
}
|
|
@@ -2714,7 +3196,11 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2714
3196
|
streams.delete(streamId);
|
|
2715
3197
|
},
|
|
2716
3198
|
onError: (err) => {
|
|
2717
|
-
sendControl(sock, {
|
|
3199
|
+
sendControl(sock, {
|
|
3200
|
+
type: "stream_close",
|
|
3201
|
+
stream_id: streamId,
|
|
3202
|
+
error: `upstream_frame_error: ${err.message}`
|
|
3203
|
+
});
|
|
2718
3204
|
streams.delete(streamId);
|
|
2719
3205
|
}
|
|
2720
3206
|
});
|
|
@@ -2728,7 +3214,11 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2728
3214
|
streams.delete(streamId);
|
|
2729
3215
|
});
|
|
2730
3216
|
sk.on("error", (err) => {
|
|
2731
|
-
sendControl(sock, {
|
|
3217
|
+
sendControl(sock, {
|
|
3218
|
+
type: "stream_close",
|
|
3219
|
+
stream_id: streamId,
|
|
3220
|
+
error: `upstream_error: ${err.message}`
|
|
3221
|
+
});
|
|
2732
3222
|
streams.delete(streamId);
|
|
2733
3223
|
});
|
|
2734
3224
|
});
|
|
@@ -2749,7 +3239,11 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2749
3239
|
streams.delete(streamId);
|
|
2750
3240
|
});
|
|
2751
3241
|
req.on("error", (err) => {
|
|
2752
|
-
sendControl(sock, {
|
|
3242
|
+
sendControl(sock, {
|
|
3243
|
+
type: "stream_close",
|
|
3244
|
+
stream_id: streamId,
|
|
3245
|
+
error: `upstream_error: ${err.message}`
|
|
3246
|
+
});
|
|
2753
3247
|
streams.delete(streamId);
|
|
2754
3248
|
});
|
|
2755
3249
|
req.end();
|
|
@@ -2799,30 +3293,52 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
2799
3293
|
const readyFeatures = Array.isArray(msg.features) ? msg.features : [];
|
|
2800
3294
|
negotiatedWsOpcode = readyFeatures.includes("ws_opcode");
|
|
2801
3295
|
success(`\uD130\uB110 \uD65C\uC131\uD654!`);
|
|
2802
|
-
log(
|
|
2803
|
-
|
|
3296
|
+
log(
|
|
3297
|
+
`${colors.green}\u2192${colors.reset} URL: ${colors.cyan}${tunnelUrl}${colors.reset}`
|
|
3298
|
+
);
|
|
3299
|
+
log(
|
|
3300
|
+
`${colors.green}\u2192${colors.reset} \uB85C\uCEEC: ${colors.cyan}http://localhost:${localPort}${colors.reset}`
|
|
3301
|
+
);
|
|
2804
3302
|
if (msg.timeout || msg.max_body) {
|
|
2805
|
-
log(
|
|
3303
|
+
log(
|
|
3304
|
+
`${colors.green}\u2192${colors.reset} \uC124\uC815: timeout=${colors.cyan}${msg.timeout}s${colors.reset}, max-body=${colors.cyan}${msg.max_body}MB${colors.reset}`
|
|
3305
|
+
);
|
|
2806
3306
|
}
|
|
2807
3307
|
if (isPublic) {
|
|
2808
3308
|
log("");
|
|
2809
|
-
log(
|
|
2810
|
-
|
|
2811
|
-
|
|
3309
|
+
log(
|
|
3310
|
+
`${colors.yellow}\u26A0 \uACF5\uAC1C \uBAA8\uB4DC (--public):${colors.reset} proxy_token \uAC80\uC99D\uC774 \uBE44\uD65C\uC131\uD654\uB410\uC2B5\uB2C8\uB2E4.`
|
|
3311
|
+
);
|
|
3312
|
+
log(
|
|
3313
|
+
` ${colors.dim}tunnelID \uB97C \uC544\uB294 \uB204\uAD6C\uB098 \uC774 URL \uB85C \uB85C\uCEEC \uC11C\uBE44\uC2A4\uC5D0 \uC811\uADFC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`
|
|
3314
|
+
);
|
|
3315
|
+
log(
|
|
3316
|
+
` ${colors.dim}\uC6F9\uD6C5 \uC218\uC2E0 / \uC678\uBD80 \uC11C\uBE44\uC2A4 \uC5F0\uB3D9 \uBAA9\uC801\uC5D0 \uD55C\uD574 \uC0AC\uC6A9\uD558\uACE0, \uBBFC\uAC10 \uB370\uC774\uD130\uB294 \uCDE8\uAE09\uD558\uC9C0 \uB9C8\uC138\uC694.${colors.reset}`
|
|
3317
|
+
);
|
|
2812
3318
|
log("");
|
|
2813
3319
|
log(`${colors.dim} $ curl ${tunnelUrl}/${colors.reset}`);
|
|
2814
3320
|
} else if (tunnelOpts?.showToken && proxyToken && tunnelUrl) {
|
|
2815
3321
|
log("");
|
|
2816
|
-
log(
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
log(
|
|
2820
|
-
|
|
3322
|
+
log(
|
|
3323
|
+
`${colors.green}\u2192${colors.reset} \uD1A0\uD070: ${colors.yellow}${proxyToken}${colors.reset}`
|
|
3324
|
+
);
|
|
3325
|
+
log(
|
|
3326
|
+
`
|
|
3327
|
+
${colors.dim}\uC678\uBD80 \uC9C1\uC811 \uD638\uCD9C \uC2DC \uB2E4\uC74C \uD5E4\uB354 \uB610\uB294 \uCFFC\uB9AC\uB85C \uD1A0\uD070\uC744 \uC804\uB2EC\uD558\uC138\uC694:${colors.reset}`
|
|
3328
|
+
);
|
|
3329
|
+
log(
|
|
3330
|
+
`${colors.dim} $ curl -H "X-Proxy-Token: ${proxyToken}" ${tunnelUrl}/${colors.reset}`
|
|
3331
|
+
);
|
|
3332
|
+
log(
|
|
3333
|
+
`${colors.dim} $ curl "${tunnelUrl}/?proxy_token=${proxyToken}"${colors.reset}`
|
|
3334
|
+
);
|
|
2821
3335
|
}
|
|
2822
3336
|
if (tunnelOpts?.label) {
|
|
2823
3337
|
const tunnelId = typeof msg.tunnel_id === "string" ? msg.tunnel_id : "";
|
|
2824
3338
|
if (!tunnelId) {
|
|
2825
|
-
error(
|
|
3339
|
+
error(
|
|
3340
|
+
`tunnel_ready \uBA54\uC2DC\uC9C0\uC5D0 tunnel_id \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 endpoint "${tunnelOpts.label}" \uC790\uB3D9 \uB4F1\uB85D skip`
|
|
3341
|
+
);
|
|
2826
3342
|
} else {
|
|
2827
3343
|
await registerEndpointBinding(
|
|
2828
3344
|
config.baseUrl,
|
|
@@ -3098,25 +3614,34 @@ async function main() {
|
|
|
3098
3614
|
} else if (parsed.command === "deploy") {
|
|
3099
3615
|
const directory = parsed.args[0] || fileConfig.deployDir || ".";
|
|
3100
3616
|
if (!config.publicKey) {
|
|
3101
|
-
error(
|
|
3617
|
+
error(
|
|
3618
|
+
'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
|
|
3619
|
+
);
|
|
3102
3620
|
process.exit(1);
|
|
3103
3621
|
}
|
|
3104
3622
|
if (!config.storageId) {
|
|
3105
|
-
error(
|
|
3623
|
+
error(
|
|
3624
|
+
'\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
|
|
3625
|
+
);
|
|
3106
3626
|
process.exit(1);
|
|
3107
3627
|
}
|
|
3108
3628
|
const isDev = parsed.options.dev === "true";
|
|
3109
3629
|
const deployOpts = {};
|
|
3110
3630
|
if (parsed.options.timeout) {
|
|
3111
3631
|
const t = parseInt(parsed.options.timeout, 10);
|
|
3112
|
-
if (!isNaN(t) && t > 0) deployOpts.timeoutMs = t * 1e3;
|
|
3113
|
-
else
|
|
3632
|
+
if (!Number.isNaN(t) && t > 0) deployOpts.timeoutMs = t * 1e3;
|
|
3633
|
+
else
|
|
3634
|
+
warn(
|
|
3635
|
+
`--timeout \uAC12\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC544 \uBB34\uC2DC\uB429\uB2C8\uB2E4: ${parsed.options.timeout}`
|
|
3636
|
+
);
|
|
3114
3637
|
}
|
|
3115
3638
|
await deploy(directory, config, isDev, deployOpts);
|
|
3116
3639
|
if (parsed.options.skipDocs !== "true") {
|
|
3117
3640
|
try {
|
|
3118
3641
|
const docsRoot = getGitRoot() || getProjectRoot();
|
|
3119
|
-
if (fs2.existsSync(
|
|
3642
|
+
if (fs2.existsSync(
|
|
3643
|
+
path2.join(docsRoot, ".claude", "docs", DOCS_MANIFEST_FILE)
|
|
3644
|
+
)) {
|
|
3120
3645
|
const refreshed = await downloadDocs(docsRoot, { quiet: true });
|
|
3121
3646
|
if (refreshed === "updated") {
|
|
3122
3647
|
info("SDK \uBB38\uC11C\uAC00 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB418\uC5C8\uC2B5\uB2C8\uB2E4 (.claude/docs/)");
|
|
@@ -3127,11 +3652,15 @@ async function main() {
|
|
|
3127
3652
|
}
|
|
3128
3653
|
} else if (parsed.command === "promote" || parsed.command === "releases") {
|
|
3129
3654
|
if (!config.publicKey) {
|
|
3130
|
-
error(
|
|
3655
|
+
error(
|
|
3656
|
+
'Public Key\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -k \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
|
|
3657
|
+
);
|
|
3131
3658
|
process.exit(1);
|
|
3132
3659
|
}
|
|
3133
3660
|
if (!config.storageId) {
|
|
3134
|
-
error(
|
|
3661
|
+
error(
|
|
3662
|
+
'\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase init"\uC73C\uB85C \uC124\uC815\uD558\uAC70\uB098 -s \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694'
|
|
3663
|
+
);
|
|
3135
3664
|
process.exit(1);
|
|
3136
3665
|
}
|
|
3137
3666
|
if (parsed.command === "promote") {
|
|
@@ -3146,18 +3675,18 @@ async function main() {
|
|
|
3146
3675
|
process.exit(1);
|
|
3147
3676
|
}
|
|
3148
3677
|
const port = parseInt(portStr, 10);
|
|
3149
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
3678
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
3150
3679
|
error("\uC720\uD6A8\uD55C \uD3EC\uD2B8 \uBC88\uD638\uB97C \uC785\uB825\uD558\uC138\uC694 (1-65535)");
|
|
3151
3680
|
process.exit(1);
|
|
3152
3681
|
}
|
|
3153
3682
|
const tunnelOpts = {};
|
|
3154
3683
|
if (parsed.options.timeout) {
|
|
3155
3684
|
const t = parseInt(parsed.options.timeout, 10);
|
|
3156
|
-
if (!isNaN(t) && t > 0) tunnelOpts.timeout = t;
|
|
3685
|
+
if (!Number.isNaN(t) && t > 0) tunnelOpts.timeout = t;
|
|
3157
3686
|
}
|
|
3158
3687
|
if (parsed.options.maxBody) {
|
|
3159
3688
|
const m = parseInt(parsed.options.maxBody, 10);
|
|
3160
|
-
if (!isNaN(m) && m > 0) tunnelOpts.maxBody = m;
|
|
3689
|
+
if (!Number.isNaN(m) && m > 0) tunnelOpts.maxBody = m;
|
|
3161
3690
|
}
|
|
3162
3691
|
if (parsed.options.appId) {
|
|
3163
3692
|
tunnelOpts.appId = parsed.options.appId;
|
|
@@ -3197,7 +3726,9 @@ main().catch((err) => {
|
|
|
3197
3726
|
});
|
|
3198
3727
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3199
3728
|
0 && (module.exports = {
|
|
3729
|
+
TunnelConnectSelector,
|
|
3200
3730
|
computeDeployDiff,
|
|
3731
|
+
getTunnelConnectCandidates,
|
|
3201
3732
|
guessMimeType,
|
|
3202
3733
|
isAllowedDeployExt,
|
|
3203
3734
|
isBinaryDeployExt,
|