connectbase-client 3.25.0 → 3.26.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 +1929 -0
- package/LICENSE +21 -0
- package/MIGRATION-v2.md +149 -0
- package/README.md +575 -52
- package/dist/cli.js +1760 -324
- package/dist/connect-base.umd.js +4 -2
- package/dist/index.d.mts +3270 -336
- package/dist/index.d.ts +3270 -336
- package/dist/index.js +4133 -949
- package/dist/index.mjs +4125 -948
- package/package.json +19 -7
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
9
13
|
var __copyProps = (to, from, except, desc) => {
|
|
10
14
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
15
|
for (let key of __getOwnPropNames(from))
|
|
@@ -22,15 +26,114 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
26
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
27
|
mod
|
|
24
28
|
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
30
|
|
|
26
31
|
// src/cli.ts
|
|
27
|
-
var
|
|
28
|
-
|
|
32
|
+
var cli_exports = {};
|
|
33
|
+
__export(cli_exports, {
|
|
34
|
+
computeDeployDiff: () => computeDeployDiff,
|
|
35
|
+
normalizeRelativePath: () => normalizeRelativePath,
|
|
36
|
+
parseArgs: () => parseArgs,
|
|
37
|
+
registerEndpointBinding: () => registerEndpointBinding,
|
|
38
|
+
sha256Hex: () => sha256Hex
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(cli_exports);
|
|
41
|
+
var fs2 = __toESM(require("fs"));
|
|
42
|
+
var path2 = __toESM(require("path"));
|
|
29
43
|
var crypto = __toESM(require("crypto"));
|
|
30
44
|
var https = __toESM(require("https"));
|
|
31
45
|
var http = __toESM(require("http"));
|
|
32
46
|
var readline = __toESM(require("readline"));
|
|
33
|
-
|
|
47
|
+
|
|
48
|
+
// src/tunnel-utils.ts
|
|
49
|
+
var fs = __toESM(require("fs"));
|
|
50
|
+
var path = __toESM(require("path"));
|
|
51
|
+
var os = __toESM(require("os"));
|
|
52
|
+
function getTunnelLockDir() {
|
|
53
|
+
const platform2 = os.platform();
|
|
54
|
+
if (platform2 === "darwin") {
|
|
55
|
+
return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
|
|
56
|
+
} else if (platform2 === "win32") {
|
|
57
|
+
return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
|
|
58
|
+
}
|
|
59
|
+
const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
60
|
+
return path.join(stateHome, "connectbase", "tunnel-locks");
|
|
61
|
+
}
|
|
62
|
+
function isProcessAlive(pid) {
|
|
63
|
+
try {
|
|
64
|
+
process.kill(pid, 0);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return e.code === "EPERM";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function acquireTunnelLock(appID, port, force, version) {
|
|
71
|
+
const lockDir = getTunnelLockDir();
|
|
72
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
73
|
+
const lockPath = path.join(lockDir, `${appID}-${port}.lock`);
|
|
74
|
+
const lockData = {
|
|
75
|
+
pid: process.pid,
|
|
76
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
77
|
+
appID,
|
|
78
|
+
port,
|
|
79
|
+
host: os.hostname(),
|
|
80
|
+
version
|
|
81
|
+
};
|
|
82
|
+
if (!force) {
|
|
83
|
+
try {
|
|
84
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
85
|
+
fs.writeSync(fd, JSON.stringify(lockData, null, 2));
|
|
86
|
+
fs.closeSync(fd);
|
|
87
|
+
return lockPath;
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (e.code !== "EEXIST") {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
95
|
+
if (isProcessAlive(existing.pid)) {
|
|
96
|
+
return `LOCKED:${existing.pid}:${existing.startedAt}:${existing.host}`;
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
|
|
102
|
+
return lockPath;
|
|
103
|
+
}
|
|
104
|
+
function releaseTunnelLock(lockPath) {
|
|
105
|
+
try {
|
|
106
|
+
fs.unlinkSync(lockPath);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function handleTunnelError(msg, appId, localPort) {
|
|
111
|
+
if (msg.code === "replaced") {
|
|
112
|
+
return {
|
|
113
|
+
action: "exit",
|
|
114
|
+
message: `\u26A0 \uB2E4\uB978 \uC138\uC158\uC774 \uAC19\uC740 \uC571+\uD3EC\uD2B8(${appId}:${localPort})\uB85C \uC5F0\uACB0\uB418\uC5B4 \uC774 \uC138\uC158\uC774 \uC885\uB8CC\uB429\uB2C8\uB2E4. \uB3D9\uC77C \uBA38\uC2E0\uC5D0\uC11C \uB450 \uBC88 \uC2E4\uD589\uD558\uC9C0 \uB9C8\uC138\uC694.`,
|
|
115
|
+
exitCode: 1
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
action: "warn",
|
|
120
|
+
message: `\uD130\uB110 \uC5D0\uB7EC: ${msg.error || msg.message || "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC"}`
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/cli.ts
|
|
125
|
+
function getPackageVersion() {
|
|
126
|
+
try {
|
|
127
|
+
const pkgPath = path2.join(__dirname, "..", "package.json");
|
|
128
|
+
if (fs2.existsSync(pkgPath)) {
|
|
129
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
130
|
+
return pkg.version || "0.0.0";
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
return "3.25.1";
|
|
135
|
+
}
|
|
136
|
+
var VERSION = getPackageVersion();
|
|
34
137
|
var DEFAULT_BASE_URL = "https://api.connectbase.world";
|
|
35
138
|
var colors = {
|
|
36
139
|
reset: "\x1B[0m",
|
|
@@ -111,17 +214,27 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
111
214
|
".zip",
|
|
112
215
|
".wasm"
|
|
113
216
|
]);
|
|
217
|
+
function sha256Hex(value) {
|
|
218
|
+
return crypto.createHash("sha256").update(value, "utf8").digest("hex");
|
|
219
|
+
}
|
|
220
|
+
function normalizeRelativePath(relativePath) {
|
|
221
|
+
let p = relativePath.replace(/\\/g, "/");
|
|
222
|
+
if (!p.startsWith("/")) p = `/${p}`;
|
|
223
|
+
return p;
|
|
224
|
+
}
|
|
114
225
|
function loadConfig() {
|
|
115
226
|
const config = {
|
|
116
|
-
|
|
227
|
+
publicKey: process.env.CONNECTBASE_PUBLIC_KEY,
|
|
228
|
+
secretKey: process.env.CONNECTBASE_SECRET_KEY,
|
|
117
229
|
storageId: process.env.CONNECTBASE_STORAGE_ID,
|
|
118
230
|
baseUrl: process.env.CONNECTBASE_BASE_URL || DEFAULT_BASE_URL
|
|
119
231
|
};
|
|
120
|
-
const rcPath =
|
|
121
|
-
if (
|
|
232
|
+
const rcPath = path2.join(process.cwd(), ".connectbaserc");
|
|
233
|
+
if (fs2.existsSync(rcPath)) {
|
|
122
234
|
try {
|
|
123
|
-
const rcContent = JSON.parse(
|
|
124
|
-
if (rcContent.
|
|
235
|
+
const rcContent = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
|
|
236
|
+
if (rcContent.publicKey) config.publicKey = rcContent.publicKey;
|
|
237
|
+
if (rcContent.secretKey) config.secretKey = rcContent.secretKey;
|
|
125
238
|
if (rcContent.storageId) config.storageId = rcContent.storageId;
|
|
126
239
|
if (rcContent.baseUrl) config.baseUrl = rcContent.baseUrl;
|
|
127
240
|
if (rcContent.deployDir) config.deployDir = rcContent.deployDir;
|
|
@@ -133,16 +246,16 @@ function loadConfig() {
|
|
|
133
246
|
}
|
|
134
247
|
function collectFiles(dir, baseDir = dir) {
|
|
135
248
|
const files = [];
|
|
136
|
-
const entries =
|
|
249
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
137
250
|
for (const entry of entries) {
|
|
138
|
-
const fullPath =
|
|
139
|
-
const relativePath =
|
|
251
|
+
const fullPath = path2.join(dir, entry.name);
|
|
252
|
+
const relativePath = path2.relative(baseDir, fullPath);
|
|
140
253
|
if (entry.isDirectory()) {
|
|
141
254
|
if (!entry.name.startsWith(".")) {
|
|
142
255
|
files.push(...collectFiles(fullPath, baseDir));
|
|
143
256
|
}
|
|
144
257
|
} else if (entry.isFile()) {
|
|
145
|
-
const ext =
|
|
258
|
+
const ext = path2.extname(entry.name).toLowerCase();
|
|
146
259
|
if (!ALLOWED_EXTENSIONS.has(ext)) {
|
|
147
260
|
continue;
|
|
148
261
|
}
|
|
@@ -152,15 +265,16 @@ function collectFiles(dir, baseDir = dir) {
|
|
|
152
265
|
const isBinary = BINARY_EXTENSIONS.has(ext);
|
|
153
266
|
let content;
|
|
154
267
|
if (isBinary) {
|
|
155
|
-
content =
|
|
268
|
+
content = fs2.readFileSync(fullPath).toString("base64");
|
|
156
269
|
} else {
|
|
157
|
-
content =
|
|
270
|
+
content = fs2.readFileSync(fullPath, "utf-8");
|
|
158
271
|
}
|
|
272
|
+
const normalized = normalizeRelativePath(relativePath);
|
|
159
273
|
files.push({
|
|
160
|
-
path:
|
|
161
|
-
// Windows 경로 변환
|
|
274
|
+
path: normalized,
|
|
162
275
|
content,
|
|
163
|
-
isBinary
|
|
276
|
+
isBinary,
|
|
277
|
+
hash: sha256Hex(content)
|
|
164
278
|
});
|
|
165
279
|
}
|
|
166
280
|
}
|
|
@@ -229,28 +343,28 @@ function computeDeployTimeout(totalBytes, override) {
|
|
|
229
343
|
return Math.max(DEPLOY_TIMEOUT_MIN_MS, DEPLOY_TIMEOUT_BASE_MS + sizeBased);
|
|
230
344
|
}
|
|
231
345
|
async function deploy(directory, config, isDev = false, deployOpts = {}) {
|
|
232
|
-
const dir =
|
|
233
|
-
if (!
|
|
346
|
+
const dir = path2.resolve(directory);
|
|
347
|
+
if (!fs2.existsSync(dir)) {
|
|
234
348
|
error(`\uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
|
|
235
349
|
process.exit(1);
|
|
236
350
|
}
|
|
237
|
-
const pkgPath =
|
|
238
|
-
if (
|
|
351
|
+
const pkgPath = path2.join(process.cwd(), "package.json");
|
|
352
|
+
if (fs2.existsSync(pkgPath)) {
|
|
239
353
|
try {
|
|
240
|
-
const pkg = JSON.parse(
|
|
354
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
241
355
|
if (pkg.scripts?.build) {
|
|
242
|
-
warn(`\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase
|
|
243
|
-
warn(`\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase
|
|
356
|
+
warn(`\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase deploy${colors.reset}`);
|
|
357
|
+
warn(`\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase init${colors.reset}`);
|
|
244
358
|
}
|
|
245
359
|
} catch {
|
|
246
360
|
}
|
|
247
361
|
}
|
|
248
|
-
if (!
|
|
362
|
+
if (!fs2.statSync(dir).isDirectory()) {
|
|
249
363
|
error(`${dir}\uB294 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4`);
|
|
250
364
|
process.exit(1);
|
|
251
365
|
}
|
|
252
|
-
const indexPath =
|
|
253
|
-
if (!
|
|
366
|
+
const indexPath = path2.join(dir, "index.html");
|
|
367
|
+
if (!fs2.existsSync(indexPath)) {
|
|
254
368
|
error("index.html \uD30C\uC77C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
255
369
|
process.exit(1);
|
|
256
370
|
}
|
|
@@ -265,60 +379,149 @@ async function deploy(directory, config, isDev = false, deployOpts = {}) {
|
|
|
265
379
|
const totalSize = files.reduce((sum, f) => sum + f.content.length, 0);
|
|
266
380
|
const sizeKB = (totalSize / 1024).toFixed(1);
|
|
267
381
|
log(`${colors.green}\u2713${colors.reset} ${files.length}\uAC1C \uD30C\uC77C \uBC1C\uACAC (${sizeKB} KB)`);
|
|
268
|
-
const deployPath = isDev ? "deploy/dev" : "deploy";
|
|
269
|
-
const url = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}/${deployPath}`;
|
|
270
382
|
const envLabel = isDev ? "Dev" : "Prod";
|
|
383
|
+
const baseStorageUrl = `${config.baseUrl}/v1/public/storages/webs/${config.storageId}`;
|
|
384
|
+
const headers = { "X-Public-Key": config.publicKey };
|
|
271
385
|
const timeoutMs = computeDeployTimeout(totalSize, deployOpts.timeoutMs);
|
|
272
386
|
log(`${colors.dim}\uD0C0\uC784\uC544\uC6C3: ${Math.round(timeoutMs / 1e3)}\uCD08${colors.reset}`);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const startedAt = Date.now();
|
|
277
|
-
heartbeat = setInterval(() => {
|
|
278
|
-
const elapsed = Math.floor((Date.now() - startedAt) / 1e3);
|
|
279
|
-
process.stdout.write(`\r${colors.blue}\u27F3${colors.reset} ${envLabel} \uBC30\uD3EC \uC911... ${colors.dim}(${elapsed}s)${colors.reset}`);
|
|
280
|
-
}, 1e3);
|
|
387
|
+
if (isDev) {
|
|
388
|
+
await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy/dev", timeoutMs);
|
|
389
|
+
return;
|
|
281
390
|
}
|
|
282
391
|
try {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
"
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
);
|
|
392
|
+
const manifest = await tryFetchManifest(baseStorageUrl, headers);
|
|
393
|
+
if (!manifest) {
|
|
394
|
+
await fullDeploy(baseStorageUrl, headers, files, envLabel, "deploy", timeoutMs);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const diff = computeDeployDiff(files, manifest);
|
|
398
|
+
if (diff.upsert.length === 0 && diff.delete.length === 0) {
|
|
399
|
+
success(`\uBCC0\uACBD\uC0AC\uD56D \uC5C6\uC74C (${files.length}\uAC1C \uD30C\uC77C \uC77C\uCE58)`);
|
|
400
|
+
log(`
|
|
401
|
+
${colors.cyan}URL: https://${config.storageId}.web.connectbase.world${colors.reset}
|
|
402
|
+
`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
info(`\uBCC0\uACBD: ${colors.green}+${diff.upsert.length}${colors.reset} / ${colors.red}-${diff.delete.length}${colors.reset} (\uC804\uCCB4 ${files.length}\uAC1C \uC911)`);
|
|
406
|
+
const uploadSize = diff.upsert.reduce((s, f) => s + f.content.length, 0);
|
|
407
|
+
info(`\uC5C5\uB85C\uB4DC \uD06C\uAE30: ${(uploadSize / 1024).toFixed(1)} KB`);
|
|
408
|
+
await incrementalDeploy(baseStorageUrl, headers, diff, manifest.revision, envLabel, timeoutMs);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
process.stdout.write("\r \r");
|
|
411
|
+
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async function tryFetchManifest(baseStorageUrl, headers) {
|
|
416
|
+
const res = await makeRequest(`${baseStorageUrl}/deploy/manifest`, "GET", headers);
|
|
417
|
+
if (res.status === 404) return null;
|
|
418
|
+
if (res.status < 200 || res.status >= 300) {
|
|
419
|
+
const data = res.data;
|
|
420
|
+
const msg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : `HTTP ${res.status}`;
|
|
421
|
+
throw new Error(`manifest \uC870\uD68C \uC2E4\uD328: ${msg}`);
|
|
422
|
+
}
|
|
423
|
+
const parsed = res.data;
|
|
424
|
+
if (!parsed || !Array.isArray(parsed.files)) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
return parsed;
|
|
428
|
+
}
|
|
429
|
+
function computeDeployDiff(local, manifest) {
|
|
430
|
+
const localByPath = /* @__PURE__ */ new Map();
|
|
431
|
+
for (const f of local) localByPath.set(f.path, f);
|
|
432
|
+
const remoteByPath = /* @__PURE__ */ new Map();
|
|
433
|
+
for (const m of manifest.files) remoteByPath.set(m.path, m);
|
|
434
|
+
const upsert = [];
|
|
435
|
+
for (const f of local) {
|
|
436
|
+
const remote = remoteByPath.get(f.path);
|
|
437
|
+
if (!remote || remote.hash !== f.hash || !remote.hash) {
|
|
438
|
+
upsert.push({
|
|
439
|
+
path: f.path,
|
|
440
|
+
content: f.content,
|
|
441
|
+
is_binary: f.isBinary,
|
|
442
|
+
hash: f.hash
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const toDelete = [];
|
|
447
|
+
for (const m of manifest.files) {
|
|
448
|
+
if (!localByPath.has(m.path)) toDelete.push(m.path);
|
|
449
|
+
}
|
|
450
|
+
return { upsert, delete: toDelete };
|
|
451
|
+
}
|
|
452
|
+
function startUploadHeartbeat(envLabel, suffix = "\uBC30\uD3EC \uC911") {
|
|
453
|
+
if (!process.stdout.isTTY) return null;
|
|
454
|
+
const startedAt = Date.now();
|
|
455
|
+
return setInterval(() => {
|
|
456
|
+
const elapsed = Math.floor((Date.now() - startedAt) / 1e3);
|
|
457
|
+
process.stdout.write(`\r${colors.blue}\u27F3${colors.reset} ${envLabel} ${suffix}... ${colors.dim}(${elapsed}s)${colors.reset}`);
|
|
458
|
+
}, 1e3);
|
|
459
|
+
}
|
|
460
|
+
async function incrementalDeploy(baseStorageUrl, headers, diff, baseRevision, envLabel, timeoutMs) {
|
|
461
|
+
process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC911...`);
|
|
462
|
+
let heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC911");
|
|
463
|
+
const body = JSON.stringify({
|
|
464
|
+
upsert: diff.upsert,
|
|
465
|
+
delete: diff.delete,
|
|
466
|
+
base_revision: baseRevision
|
|
467
|
+
});
|
|
468
|
+
const res = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body, { timeoutMs });
|
|
469
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
470
|
+
process.stdout.write("\r \r");
|
|
471
|
+
if (res.status === 409) {
|
|
472
|
+
warn("\uC11C\uBC84 revision \uC774 \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. manifest \uC7AC\uC870\uD68C \uD6C4 \uC7AC\uC2DC\uB3C4\uD569\uB2C8\uB2E4.");
|
|
473
|
+
const manifest = await tryFetchManifest(baseStorageUrl, headers);
|
|
474
|
+
if (!manifest) {
|
|
475
|
+
error("\uC7AC\uC2DC\uB3C4 manifest \uC870\uD68C \uC2E4\uD328");
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
const body2 = JSON.stringify({
|
|
479
|
+
upsert: diff.upsert,
|
|
480
|
+
delete: diff.delete,
|
|
481
|
+
base_revision: manifest.revision
|
|
482
|
+
});
|
|
483
|
+
process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4...`);
|
|
484
|
+
heartbeat = startUploadHeartbeat(envLabel, "\uC99D\uBD84 \uBC30\uD3EC \uC7AC\uC2DC\uB3C4");
|
|
485
|
+
const res2 = await makeRequest(`${baseStorageUrl}/deploy/incremental`, "POST", headers, body2, { timeoutMs });
|
|
298
486
|
if (heartbeat) clearInterval(heartbeat);
|
|
299
|
-
process.stdout.write("\r
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
487
|
+
process.stdout.write("\r \r");
|
|
488
|
+
handleDeployResponse(res2, envLabel);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
handleDeployResponse(res, envLabel);
|
|
492
|
+
}
|
|
493
|
+
async function fullDeploy(baseStorageUrl, headers, files, envLabel, endpoint, timeoutMs) {
|
|
494
|
+
process.stdout.write(`${colors.blue}\u27F3${colors.reset} ${envLabel} \uBC30\uD3EC \uC911...`);
|
|
495
|
+
const heartbeat = startUploadHeartbeat(envLabel);
|
|
496
|
+
const body = JSON.stringify({
|
|
497
|
+
files: files.map((f) => ({
|
|
498
|
+
path: f.path,
|
|
499
|
+
content: f.content,
|
|
500
|
+
is_binary: f.isBinary
|
|
501
|
+
}))
|
|
502
|
+
});
|
|
503
|
+
const res = await makeRequest(`${baseStorageUrl}/${endpoint}`, "POST", headers, body, { timeoutMs });
|
|
504
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
505
|
+
process.stdout.write("\r \r");
|
|
506
|
+
handleDeployResponse(res, envLabel);
|
|
507
|
+
}
|
|
508
|
+
function handleDeployResponse(response, envLabel) {
|
|
509
|
+
if (response.status >= 200 && response.status < 300) {
|
|
510
|
+
success(`${envLabel} \uBC30\uD3EC \uC644\uB8CC!`);
|
|
511
|
+
const data = response.data;
|
|
512
|
+
if (data?.dev_url) {
|
|
513
|
+
log(`
|
|
305
514
|
${colors.yellow}Dev URL: ${data.dev_url}${colors.reset}
|
|
306
515
|
`);
|
|
307
|
-
|
|
308
|
-
|
|
516
|
+
} else if (data?.url) {
|
|
517
|
+
log(`
|
|
309
518
|
${colors.cyan}URL: ${data.url}${colors.reset}
|
|
310
519
|
`);
|
|
311
|
-
}
|
|
312
|
-
} else {
|
|
313
|
-
const data = response.data;
|
|
314
|
-
const errorMsg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : typeof data === "string" ? data : `HTTP ${response.status}`;
|
|
315
|
-
error(`\uBC30\uD3EC \uC2E4\uD328: ${errorMsg}`);
|
|
316
|
-
process.exit(1);
|
|
317
520
|
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
error(`\
|
|
521
|
+
} else {
|
|
522
|
+
const data = response.data;
|
|
523
|
+
const errorMsg = typeof data === "object" && data !== null ? data.message || data.error || JSON.stringify(data) : typeof data === "string" ? data : `HTTP ${response.status}`;
|
|
524
|
+
error(`\uBC30\uD3EC \uC2E4\uD328: ${errorMsg}`);
|
|
322
525
|
process.exit(1);
|
|
323
526
|
}
|
|
324
527
|
}
|
|
@@ -334,77 +537,329 @@ function prompt(question) {
|
|
|
334
537
|
});
|
|
335
538
|
});
|
|
336
539
|
}
|
|
540
|
+
function promptSecret(question) {
|
|
541
|
+
return new Promise((resolve2) => {
|
|
542
|
+
process.stdout.write(question);
|
|
543
|
+
const input = [];
|
|
544
|
+
if (!process.stdin.isTTY) {
|
|
545
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
546
|
+
rl.question("", (answer) => {
|
|
547
|
+
rl.close();
|
|
548
|
+
resolve2(answer.trim());
|
|
549
|
+
});
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
process.stdin.setRawMode(true);
|
|
553
|
+
process.stdin.resume();
|
|
554
|
+
process.stdin.setEncoding("utf-8");
|
|
555
|
+
const onData = (data) => {
|
|
556
|
+
for (const char of data) {
|
|
557
|
+
const code = char.charCodeAt(0);
|
|
558
|
+
if (char === "\r" || char === "\n") {
|
|
559
|
+
process.stdin.setRawMode(false);
|
|
560
|
+
process.stdin.pause();
|
|
561
|
+
process.stdin.removeListener("data", onData);
|
|
562
|
+
process.stdout.write("\n");
|
|
563
|
+
resolve2(input.join("").trim());
|
|
564
|
+
return;
|
|
565
|
+
} else if (code === 3) {
|
|
566
|
+
process.stdin.setRawMode(false);
|
|
567
|
+
process.stdin.pause();
|
|
568
|
+
process.stdout.write("\n");
|
|
569
|
+
process.exit(0);
|
|
570
|
+
} else if (code === 127 || code === 8) {
|
|
571
|
+
if (input.length > 0) {
|
|
572
|
+
input.pop();
|
|
573
|
+
process.stdout.write("\b \b");
|
|
574
|
+
}
|
|
575
|
+
} else if (code >= 32) {
|
|
576
|
+
input.push(char);
|
|
577
|
+
process.stdout.write("*");
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
process.stdin.on("data", onData);
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
function getProjectRoot() {
|
|
585
|
+
const gitRoot = getGitRoot();
|
|
586
|
+
return gitRoot || process.cwd();
|
|
587
|
+
}
|
|
588
|
+
var CONSOLE_URL = DEFAULT_BASE_URL.replace("api.", "");
|
|
589
|
+
function openBrowser(url) {
|
|
590
|
+
const { exec } = require("child_process");
|
|
591
|
+
const platform2 = process.platform;
|
|
592
|
+
let command;
|
|
593
|
+
if (platform2 === "darwin") {
|
|
594
|
+
command = `open "${url}"`;
|
|
595
|
+
} else if (platform2 === "win32") {
|
|
596
|
+
command = `start "" "${url}"`;
|
|
597
|
+
} else {
|
|
598
|
+
command = `xdg-open "${url}"`;
|
|
599
|
+
}
|
|
600
|
+
exec(command, () => {
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
async function browserAuthFlow() {
|
|
604
|
+
info("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uC138\uC158\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4...");
|
|
605
|
+
const startRes = await makeRequest(
|
|
606
|
+
`${DEFAULT_BASE_URL}/v1/public/cli-auth/start`,
|
|
607
|
+
"POST",
|
|
608
|
+
{}
|
|
609
|
+
);
|
|
610
|
+
if (startRes.status !== 200) {
|
|
611
|
+
const errData = startRes.data;
|
|
612
|
+
const detail = errData?.error || errData?.message || `HTTP ${startRes.status}`;
|
|
613
|
+
error(`\uC778\uC99D \uC138\uC158 \uC2DC\uC791\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4: ${detail}`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
const { session_id } = startRes.data;
|
|
617
|
+
const authUrl = `${CONSOLE_URL}/auth/cli-auth?session=${session_id}`;
|
|
618
|
+
log(`
|
|
619
|
+
${colors.cyan}\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uB85C\uADF8\uC778\uD558\uC138\uC694:${colors.reset}`);
|
|
620
|
+
log(`${colors.dim}${authUrl}${colors.reset}
|
|
621
|
+
`);
|
|
622
|
+
openBrowser(authUrl);
|
|
623
|
+
const pollInterval = 3e3;
|
|
624
|
+
const maxAttempts = 100;
|
|
625
|
+
let attempts = 0;
|
|
626
|
+
let consecutive404 = 0;
|
|
627
|
+
const max404Retries = 5;
|
|
628
|
+
const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
629
|
+
while (attempts < maxAttempts) {
|
|
630
|
+
const frame = spinnerFrames[attempts % spinnerFrames.length];
|
|
631
|
+
process.stdout.write(`\r${colors.blue}${frame}${colors.reset} \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC2B9\uC778 \uB300\uAE30 \uC911...`);
|
|
632
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
633
|
+
try {
|
|
634
|
+
const pollRes = await makeRequest(
|
|
635
|
+
`${DEFAULT_BASE_URL}/v1/public/cli-auth/poll/${session_id}`,
|
|
636
|
+
"GET",
|
|
637
|
+
{}
|
|
638
|
+
);
|
|
639
|
+
if (pollRes.status === 200) {
|
|
640
|
+
consecutive404 = 0;
|
|
641
|
+
const data = pollRes.data;
|
|
642
|
+
if (data.status === "approved" && data.secret_key) {
|
|
643
|
+
process.stdout.write("\r \r");
|
|
644
|
+
success("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!");
|
|
645
|
+
return data.secret_key;
|
|
646
|
+
}
|
|
647
|
+
if (data.status === "expired") {
|
|
648
|
+
process.stdout.write("\r \r");
|
|
649
|
+
error("\uC778\uC99D \uC138\uC158\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
if (data.status === "denied") {
|
|
653
|
+
process.stdout.write("\r \r");
|
|
654
|
+
error("\uC778\uC99D\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
655
|
+
process.exit(1);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (pollRes.status === 404) {
|
|
659
|
+
consecutive404++;
|
|
660
|
+
if (consecutive404 >= max404Retries) {
|
|
661
|
+
process.stdout.write("\r \r");
|
|
662
|
+
error("\uC778\uC99D \uC138\uC158\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
|
|
663
|
+
process.exit(1);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
} catch {
|
|
667
|
+
}
|
|
668
|
+
attempts++;
|
|
669
|
+
}
|
|
670
|
+
process.stdout.write("\r \r");
|
|
671
|
+
error("\uC778\uC99D \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.");
|
|
672
|
+
process.exit(1);
|
|
673
|
+
}
|
|
337
674
|
async function init() {
|
|
338
675
|
log(`
|
|
339
676
|
${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.reset}
|
|
340
677
|
`);
|
|
341
|
-
const
|
|
342
|
-
|
|
678
|
+
const cwd = process.cwd();
|
|
679
|
+
const projectRoot = getProjectRoot();
|
|
680
|
+
if (cwd !== projectRoot) {
|
|
681
|
+
info(`\uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 \uAC10\uC9C0: ${projectRoot}`);
|
|
682
|
+
info(`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
|
|
683
|
+
`);
|
|
684
|
+
}
|
|
685
|
+
const rcPath = path2.join(cwd, ".connectbaserc");
|
|
686
|
+
if (fs2.existsSync(rcPath)) {
|
|
343
687
|
const overwrite = await prompt(`${colors.yellow}\u26A0${colors.reset} .connectbaserc\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4. \uB36E\uC5B4\uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/N): `);
|
|
344
688
|
if (overwrite.toLowerCase() !== "y") {
|
|
345
689
|
info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
|
|
346
690
|
return;
|
|
347
691
|
}
|
|
348
692
|
}
|
|
349
|
-
log(
|
|
350
|
-
|
|
693
|
+
log(`
|
|
694
|
+
${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
|
|
695
|
+
log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
|
|
696
|
+
log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
|
|
351
697
|
`);
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
698
|
+
const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
|
|
699
|
+
let secretKey = "";
|
|
700
|
+
if (authChoice === "2") {
|
|
701
|
+
try {
|
|
702
|
+
secretKey = await browserAuthFlow();
|
|
703
|
+
} catch (err) {
|
|
704
|
+
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
705
|
+
info("\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");
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
} else {
|
|
709
|
+
log(`${colors.dim}Secret Key (cb_sk_): \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uBC1C\uAE09${colors.reset}
|
|
710
|
+
`);
|
|
711
|
+
secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
|
|
712
|
+
if (!secretKey) {
|
|
713
|
+
error("Secret Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
|
|
714
|
+
process.exit(1);
|
|
715
|
+
}
|
|
716
|
+
if (secretKey.startsWith("cb_pk_")) {
|
|
717
|
+
error("Public Key\uAC00 \uC544\uB2CC Secret Key(cb_sk_)\uB97C \uC785\uB825\uD558\uC138\uC694");
|
|
718
|
+
info("Secret Key\uB294 \uCF58\uC194 > \uD504\uB85C\uD544 > \uC2DC\uD06C\uB9BF \uD0A4 \uD0ED\uC5D0\uC11C \uC0DD\uC131\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4");
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
if (!secretKey.startsWith("cb_sk_")) {
|
|
722
|
+
error("\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");
|
|
723
|
+
process.exit(1);
|
|
724
|
+
}
|
|
360
725
|
}
|
|
361
|
-
let
|
|
726
|
+
let appId = "";
|
|
727
|
+
let publicKey = "";
|
|
362
728
|
try {
|
|
363
|
-
info("\
|
|
364
|
-
const
|
|
365
|
-
`${DEFAULT_BASE_URL}/v1/public/
|
|
729
|
+
info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
730
|
+
const appsRes = await makeRequest(
|
|
731
|
+
`${DEFAULT_BASE_URL}/v1/public/cli/apps`,
|
|
366
732
|
"GET",
|
|
367
|
-
{ "X-
|
|
733
|
+
{ "X-Public-Key": secretKey }
|
|
368
734
|
);
|
|
369
|
-
if (
|
|
370
|
-
error(
|
|
735
|
+
if (appsRes.status === 401) {
|
|
736
|
+
error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
|
|
371
737
|
process.exit(1);
|
|
372
738
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
739
|
+
if (appsRes.status !== 200) {
|
|
740
|
+
throw new Error(`HTTP ${appsRes.status}`);
|
|
741
|
+
}
|
|
742
|
+
const appsData = appsRes.data;
|
|
743
|
+
const apps = appsData.apps || [];
|
|
744
|
+
if (apps.length > 0) {
|
|
376
745
|
log(`
|
|
377
|
-
${colors.dim}\
|
|
378
|
-
|
|
379
|
-
|
|
746
|
+
${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
|
|
747
|
+
apps.forEach((a, i) => {
|
|
748
|
+
const date = a.created_at ? a.created_at.substring(0, 10) : "";
|
|
749
|
+
log(` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`);
|
|
380
750
|
});
|
|
381
|
-
log(` ${colors.cyan}0${colors.reset}) \uC0C8\
|
|
751
|
+
log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
|
|
382
752
|
const choice = await prompt(`
|
|
383
753
|
${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
384
754
|
const num = parseInt(choice, 10);
|
|
385
|
-
if (num > 0 && num <=
|
|
386
|
-
|
|
387
|
-
success(`\uC120\uD0DD\uB428: ${
|
|
755
|
+
if (num > 0 && num <= apps.length) {
|
|
756
|
+
appId = apps[num - 1].id;
|
|
757
|
+
success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
|
|
758
|
+
info("Public Key \uC0DD\uC131 \uC911...");
|
|
759
|
+
const newKeyRes = await makeRequest(
|
|
760
|
+
`${DEFAULT_BASE_URL}/v1/public/cli/apps/${appId}/public-keys`,
|
|
761
|
+
"POST",
|
|
762
|
+
{ "X-Public-Key": secretKey },
|
|
763
|
+
JSON.stringify({})
|
|
764
|
+
);
|
|
765
|
+
if (newKeyRes.status === 201) {
|
|
766
|
+
const keyData = newKeyRes.data;
|
|
767
|
+
publicKey = keyData.key;
|
|
768
|
+
success("Public Key \uBC1C\uAE09 \uC644\uB8CC");
|
|
769
|
+
} else {
|
|
770
|
+
warn("Public Key \uC790\uB3D9 \uC0DD\uC131 \uC2E4\uD328. \uC218\uB3D9\uC73C\uB85C \uC0DD\uC131\uD558\uC138\uC694");
|
|
771
|
+
}
|
|
388
772
|
}
|
|
389
773
|
}
|
|
390
|
-
if (!
|
|
391
|
-
const projectName =
|
|
392
|
-
const
|
|
393
|
-
info("\
|
|
774
|
+
if (!appId) {
|
|
775
|
+
const projectName = path2.basename(cwd);
|
|
776
|
+
const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
|
|
777
|
+
info("\uC571 \uC0DD\uC131 \uC911...");
|
|
394
778
|
const createRes = await makeRequest(
|
|
395
|
-
`${DEFAULT_BASE_URL}/v1/public/
|
|
779
|
+
`${DEFAULT_BASE_URL}/v1/public/cli/apps`,
|
|
396
780
|
"POST",
|
|
397
|
-
{ "X-
|
|
398
|
-
JSON.stringify({ name })
|
|
781
|
+
{ "X-Public-Key": secretKey },
|
|
782
|
+
JSON.stringify({ name: appName })
|
|
399
783
|
);
|
|
400
|
-
if (createRes.status
|
|
784
|
+
if (createRes.status === 402) {
|
|
785
|
+
error("\uC571 \uC0DD\uC131 \uD55C\uB3C4 \uCD08\uACFC. \uD50C\uB79C \uC5C5\uADF8\uB808\uC774\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
786
|
+
process.exit(1);
|
|
787
|
+
}
|
|
788
|
+
if (createRes.status !== 201) {
|
|
401
789
|
const data = createRes.data;
|
|
402
|
-
error(`\uC0DD\uC131 \uC2E4\uD328: ${data?.error ||
|
|
790
|
+
error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${data?.error || `HTTP ${createRes.status}`}`);
|
|
403
791
|
process.exit(1);
|
|
404
792
|
}
|
|
405
793
|
const createData = createRes.data;
|
|
406
|
-
|
|
407
|
-
|
|
794
|
+
appId = createData.app_id;
|
|
795
|
+
publicKey = createData.public_key;
|
|
796
|
+
success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
|
|
797
|
+
if (publicKey) {
|
|
798
|
+
success("Public Key \uC790\uB3D9 \uBC1C\uAE09 \uC644\uB8CC");
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
} catch (err) {
|
|
802
|
+
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
803
|
+
log(`${colors.dim}\uC218\uB3D9\uC73C\uB85C \uC785\uB825\uD558\uC138\uC694${colors.reset}`);
|
|
804
|
+
appId = await prompt(`${colors.blue}?${colors.reset} App ID: `);
|
|
805
|
+
publicKey = await prompt(`${colors.blue}?${colors.reset} Public Key (cb_pk_): `);
|
|
806
|
+
if (!appId || !publicKey) {
|
|
807
|
+
error("App ID\uC640 Public Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (!publicKey) {
|
|
812
|
+
error("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");
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
const publicKeyForSdk = publicKey;
|
|
816
|
+
let storageId = "";
|
|
817
|
+
try {
|
|
818
|
+
info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC870\uD68C \uC911...");
|
|
819
|
+
const listRes = await makeRequest(
|
|
820
|
+
`${DEFAULT_BASE_URL}/v1/public/storages/webs`,
|
|
821
|
+
"GET",
|
|
822
|
+
{ "X-Public-Key": publicKeyForSdk }
|
|
823
|
+
);
|
|
824
|
+
if (listRes.status === 200) {
|
|
825
|
+
const listData = listRes.data;
|
|
826
|
+
const storages = listData.storages || [];
|
|
827
|
+
if (storages.length > 0) {
|
|
828
|
+
log(`
|
|
829
|
+
${colors.dim}\uAE30\uC874 \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0:${colors.reset}`);
|
|
830
|
+
storages.forEach((s, i) => {
|
|
831
|
+
log(` ${colors.cyan}${i + 1}${colors.reset}) ${s.name} (${colors.dim}${s.id}${colors.reset})`);
|
|
832
|
+
});
|
|
833
|
+
log(` ${colors.cyan}0${colors.reset}) \uC0C8\uB85C \uC0DD\uC131`);
|
|
834
|
+
const choice = await prompt(`
|
|
835
|
+
${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
836
|
+
const num = parseInt(choice, 10);
|
|
837
|
+
if (num > 0 && num <= storages.length) {
|
|
838
|
+
storageId = storages[num - 1].id;
|
|
839
|
+
success(`\uC120\uD0DD\uB428: ${storages[num - 1].name}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
if (!storageId) {
|
|
843
|
+
const projectName = path2.basename(cwd);
|
|
844
|
+
const name = await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
|
|
845
|
+
info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
|
|
846
|
+
const createRes = await makeRequest(
|
|
847
|
+
`${DEFAULT_BASE_URL}/v1/public/storages/webs`,
|
|
848
|
+
"POST",
|
|
849
|
+
{ "X-Public-Key": publicKeyForSdk },
|
|
850
|
+
JSON.stringify({ name })
|
|
851
|
+
);
|
|
852
|
+
if (createRes.status !== 200) {
|
|
853
|
+
const data = createRes.data;
|
|
854
|
+
error(`\uC0DD\uC131 \uC2E4\uD328: ${data?.error || data?.message || `HTTP ${createRes.status}`}`);
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
const createData = createRes.data;
|
|
858
|
+
storageId = createData.storage_web_id;
|
|
859
|
+
success(`\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC644\uB8CC: ${createData.name}`);
|
|
860
|
+
}
|
|
861
|
+
} else {
|
|
862
|
+
throw new Error(`HTTP ${listRes.status}`);
|
|
408
863
|
}
|
|
409
864
|
} catch (err) {
|
|
410
865
|
error(`\uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
@@ -418,41 +873,43 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
418
873
|
const defaultDir = detectBuildDir();
|
|
419
874
|
const deployDir = await prompt(`${colors.blue}?${colors.reset} \uBC30\uD3EC \uB514\uB809\uD1A0\uB9AC (${defaultDir}): `) || defaultDir;
|
|
420
875
|
const config = {
|
|
421
|
-
|
|
876
|
+
publicKey: publicKey || "",
|
|
422
877
|
storageId,
|
|
423
878
|
deployDir
|
|
424
879
|
};
|
|
425
|
-
|
|
880
|
+
if (secretKey) {
|
|
881
|
+
config.secretKey = secretKey;
|
|
882
|
+
}
|
|
883
|
+
fs2.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
|
|
426
884
|
success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
|
|
427
885
|
addToGitignore(".connectbaserc");
|
|
428
886
|
addDeployScript(deployDir);
|
|
429
|
-
|
|
430
|
-
${colors.blue}?${colors.reset} Claude Code \uC124\uC815\uC744 \uCD94\uAC00\uD560\uAE4C\uC694? (CLAUDE.md, MCP \uC124\uC815) (Y/n): `);
|
|
431
|
-
if (setupClaude.toLowerCase() !== "n") {
|
|
432
|
-
await setupClaudeCode(apiKey);
|
|
433
|
-
}
|
|
887
|
+
await setupClaudeCode(publicKeyForSdk, secretKey, projectRoot);
|
|
434
888
|
log(`
|
|
435
889
|
${colors.green}\uCD08\uAE30\uD654 \uC644\uB8CC!${colors.reset}
|
|
436
890
|
`);
|
|
437
891
|
log(`${colors.dim}\uBC30\uD3EC\uD558\uB824\uBA74:${colors.reset}`);
|
|
438
|
-
log(` ${colors.cyan}npm run deploy${colors.reset}
|
|
892
|
+
log(` ${colors.cyan}npm run deploy${colors.reset}`);
|
|
893
|
+
log(`
|
|
894
|
+
${colors.dim}Claude Code\uC5D0\uC11C MCP\uAC00 \uC790\uB3D9 \uC5F0\uACB0\uB429\uB2C8\uB2E4${colors.reset}
|
|
439
895
|
`);
|
|
440
896
|
}
|
|
441
897
|
function detectBuildDir() {
|
|
442
|
-
if (
|
|
443
|
-
if (
|
|
444
|
-
if (
|
|
445
|
-
if (
|
|
446
|
-
if (
|
|
447
|
-
if (
|
|
898
|
+
if (fs2.existsSync(path2.join(process.cwd(), "dist"))) return "./dist";
|
|
899
|
+
if (fs2.existsSync(path2.join(process.cwd(), "build"))) return "./build";
|
|
900
|
+
if (fs2.existsSync(path2.join(process.cwd(), "out"))) return "./out";
|
|
901
|
+
if (fs2.existsSync(path2.join(process.cwd(), ".next"))) return "./out";
|
|
902
|
+
if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js"))) return "./dist";
|
|
903
|
+
if (fs2.existsSync(path2.join(process.cwd(), "next.config.js")) || fs2.existsSync(path2.join(process.cwd(), "next.config.mjs"))) return "./out";
|
|
448
904
|
return "./dist";
|
|
449
905
|
}
|
|
450
|
-
function addToGitignore(entry) {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
906
|
+
function addToGitignore(entry, basePath) {
|
|
907
|
+
const dir = basePath || process.cwd();
|
|
908
|
+
const gitignorePath = path2.join(dir, ".gitignore");
|
|
909
|
+
if (fs2.existsSync(gitignorePath)) {
|
|
910
|
+
const content = fs2.readFileSync(gitignorePath, "utf-8");
|
|
454
911
|
if (!content.includes(entry)) {
|
|
455
|
-
|
|
912
|
+
fs2.appendFileSync(gitignorePath, `
|
|
456
913
|
# Connect Base
|
|
457
914
|
${entry}
|
|
458
915
|
`);
|
|
@@ -461,28 +918,28 @@ ${entry}
|
|
|
461
918
|
info(`.gitignore\uC5D0 \uC774\uBBF8 ${entry}\uAC00 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4`);
|
|
462
919
|
}
|
|
463
920
|
} else {
|
|
464
|
-
|
|
921
|
+
fs2.writeFileSync(gitignorePath, `# Connect Base
|
|
465
922
|
${entry}
|
|
466
923
|
`);
|
|
467
924
|
success(".gitignore \uC0DD\uC131 \uC644\uB8CC");
|
|
468
925
|
}
|
|
469
926
|
}
|
|
470
927
|
function addDeployScript(deployDir) {
|
|
471
|
-
const pkgPath =
|
|
472
|
-
if (!
|
|
928
|
+
const pkgPath = path2.join(process.cwd(), "package.json");
|
|
929
|
+
if (!fs2.existsSync(pkgPath)) {
|
|
473
930
|
warn("package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
|
|
474
931
|
return;
|
|
475
932
|
}
|
|
476
933
|
try {
|
|
477
|
-
const pkg = JSON.parse(
|
|
934
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
478
935
|
if (!pkg.scripts) pkg.scripts = {};
|
|
479
936
|
if (pkg.scripts.deploy) {
|
|
480
937
|
info(`package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`);
|
|
481
938
|
return;
|
|
482
939
|
}
|
|
483
|
-
const deployCmd = `connectbase
|
|
940
|
+
const deployCmd = `connectbase deploy ${deployDir}`;
|
|
484
941
|
pkg.scripts.deploy = pkg.scripts.build ? `${pkg.scripts.build} && ${deployCmd}` : deployCmd;
|
|
485
|
-
|
|
942
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
486
943
|
success(`package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC`);
|
|
487
944
|
} catch {
|
|
488
945
|
warn("package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
|
|
@@ -496,21 +953,58 @@ function getGitRoot() {
|
|
|
496
953
|
return null;
|
|
497
954
|
}
|
|
498
955
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
956
|
+
function detectMonorepo(gitRoot) {
|
|
957
|
+
const cwd = process.cwd();
|
|
958
|
+
const result = { type: "none", root: gitRoot, isSubPackage: false };
|
|
959
|
+
if (fs2.existsSync(path2.join(gitRoot, "pnpm-workspace.yaml"))) {
|
|
960
|
+
result.type = "pnpm";
|
|
961
|
+
} else if (fs2.existsSync(path2.join(gitRoot, "turbo.json"))) {
|
|
962
|
+
result.type = "turborepo";
|
|
963
|
+
} else if (fs2.existsSync(path2.join(gitRoot, "nx.json"))) {
|
|
964
|
+
result.type = "nx";
|
|
965
|
+
} else if (fs2.existsSync(path2.join(gitRoot, "lerna.json"))) {
|
|
966
|
+
result.type = "lerna";
|
|
967
|
+
} else {
|
|
968
|
+
const rootPkgPath = path2.join(gitRoot, "package.json");
|
|
969
|
+
if (fs2.existsSync(rootPkgPath)) {
|
|
970
|
+
try {
|
|
971
|
+
const pkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf-8"));
|
|
972
|
+
if (pkg.workspaces) {
|
|
973
|
+
result.type = fs2.existsSync(path2.join(gitRoot, "yarn.lock")) ? "yarn" : "npm";
|
|
974
|
+
}
|
|
975
|
+
} catch {
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (result.type !== "none" && cwd !== gitRoot) {
|
|
980
|
+
const cwdPkgPath = path2.join(cwd, "package.json");
|
|
981
|
+
if (fs2.existsSync(cwdPkgPath)) {
|
|
982
|
+
result.isSubPackage = true;
|
|
983
|
+
try {
|
|
984
|
+
const pkg = JSON.parse(fs2.readFileSync(cwdPkgPath, "utf-8"));
|
|
985
|
+
result.subPackageName = pkg.name;
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return result;
|
|
991
|
+
}
|
|
992
|
+
async function downloadDocs(publicKey, templates, baseDir) {
|
|
993
|
+
if (!baseDir) {
|
|
994
|
+
baseDir = getProjectRoot();
|
|
995
|
+
}
|
|
996
|
+
const gitRoot = getGitRoot() || baseDir;
|
|
997
|
+
const monorepo = detectMonorepo(gitRoot);
|
|
998
|
+
if (monorepo.type !== "none") {
|
|
999
|
+
info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${gitRoot})`);
|
|
1000
|
+
if (monorepo.isSubPackage) {
|
|
1001
|
+
info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`);
|
|
508
1002
|
}
|
|
509
1003
|
}
|
|
510
|
-
const docsDir =
|
|
511
|
-
const rootClaudeMd =
|
|
512
|
-
if (!
|
|
513
|
-
|
|
1004
|
+
const docsDir = path2.join(gitRoot, ".claude", "docs");
|
|
1005
|
+
const rootClaudeMd = path2.join(gitRoot, "CLAUDE.md");
|
|
1006
|
+
if (!fs2.existsSync(docsDir)) {
|
|
1007
|
+
fs2.mkdirSync(docsDir, { recursive: true });
|
|
514
1008
|
}
|
|
515
1009
|
if (!templates) {
|
|
516
1010
|
templates = ["rules"];
|
|
@@ -519,7 +1013,7 @@ async function downloadDocs(apiKey, templates, monorepo) {
|
|
|
519
1013
|
try {
|
|
520
1014
|
const templateParam = templates.join(",");
|
|
521
1015
|
const res = await makeRequest(
|
|
522
|
-
`${DEFAULT_BASE_URL}/v1/storages/webs/claude-md?template=${encodeURIComponent(templateParam)}&format=sections&
|
|
1016
|
+
`${DEFAULT_BASE_URL}/v1/storages/webs/claude-md?template=${encodeURIComponent(templateParam)}&format=sections&public_key=${encodeURIComponent(publicKey)}`,
|
|
523
1017
|
"GET",
|
|
524
1018
|
{}
|
|
525
1019
|
);
|
|
@@ -529,11 +1023,15 @@ async function downloadDocs(apiKey, templates, monorepo) {
|
|
|
529
1023
|
const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
|
|
530
1024
|
const sections = data.sections || [];
|
|
531
1025
|
for (const section of sections) {
|
|
532
|
-
const filePath =
|
|
533
|
-
|
|
1026
|
+
const filePath = path2.join(docsDir, section.filename);
|
|
1027
|
+
fs2.writeFileSync(filePath, section.content);
|
|
534
1028
|
}
|
|
535
|
-
success(
|
|
1029
|
+
success(`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
|
|
536
1030
|
updateRootClaudeMd(rootClaudeMd);
|
|
1031
|
+
if (monorepo.isSubPackage) {
|
|
1032
|
+
const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
|
|
1033
|
+
createSubPackageClaudeMd(subClaudeMd, gitRoot);
|
|
1034
|
+
}
|
|
537
1035
|
log(`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`);
|
|
538
1036
|
} catch {
|
|
539
1037
|
warn("SDK \uAC00\uC774\uB4DC \uB2E4\uC6B4\uB85C\uB4DC \uC2E4\uD328, \uAE30\uBCF8 \uBB38\uC11C\uB97C \uC0DD\uC131\uD569\uB2C8\uB2E4");
|
|
@@ -543,8 +1041,12 @@ async function downloadDocs(apiKey, templates, monorepo) {
|
|
|
543
1041
|
|
|
544
1042
|
\uC0C1\uC138 \uAD6C\uD604\uBC95\uC740 MCP \`search_sdk_docs\` \uB3C4\uAD6C\uB85C \uAC80\uC0C9\uD558\uC138\uC694.
|
|
545
1043
|
`;
|
|
546
|
-
|
|
1044
|
+
fs2.writeFileSync(path2.join(docsDir, "project-rules.md"), fallbackContent);
|
|
547
1045
|
updateRootClaudeMd(rootClaudeMd);
|
|
1046
|
+
if (monorepo.isSubPackage) {
|
|
1047
|
+
const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
|
|
1048
|
+
createSubPackageClaudeMd(subClaudeMd, gitRoot);
|
|
1049
|
+
}
|
|
548
1050
|
success(`.claude/docs/ \uC0DD\uC131 \uC644\uB8CC (\uAE30\uBCF8)`);
|
|
549
1051
|
}
|
|
550
1052
|
}
|
|
@@ -555,93 +1057,401 @@ function updateRootClaudeMd(claudeMdPath) {
|
|
|
555
1057
|
## ConnectBase SDK
|
|
556
1058
|
|
|
557
1059
|
\uC774 \uD504\uB85C\uC81D\uD2B8\uB294 **ConnectBase**\uB97C \uBC31\uC5D4\uB4DC\uB85C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.
|
|
558
|
-
\uC0AC\uC6A9\uC790\uB294 **\uBE44\uAC1C\uBC1C\uC790**\uC785\uB2C8\uB2E4. \uC26C\uC6B4 \uB9D0\uB85C \uC124\uBA85\uD558\uACE0, \uBC14\uB85C \uC2E4\uD589 \uAC00\uB2A5\uD55C \uC644\uC131\uB41C \uCF54\uB4DC\uB97C \uC81C\uACF5\uD558\uC138\uC694.
|
|
559
1060
|
|
|
560
1061
|
### AI \uD544\uB3C5 \u2014 \uCF54\uB529 \uC804 \uBC18\uB4DC\uC2DC \uC218\uD589
|
|
561
1062
|
|
|
562
|
-
1. \`.claude/docs/project-rules.md
|
|
563
|
-
2. \
|
|
564
|
-
3.
|
|
565
|
-
4. \uC644\uC131\uB41C \uCF54\uB4DC\uB97C \uC791\uC131\uD558\uC138\uC694 (\uC2A4\uB2C8\uD3AB X, \uBC14\uB85C \uC2E4\uD589 \uAC00\uB2A5\uD55C \uC804\uCCB4 \uCF54\uB4DC)
|
|
566
|
-
|
|
567
|
-
### \uBE44\uAC1C\uBC1C\uC790 \uC751\uB300 \uC6D0\uCE59
|
|
568
|
-
|
|
569
|
-
- \uC804\uBB38 \uC6A9\uC5B4 \uB300\uC2E0 \uC26C\uC6B4 \uB9D0 \uC0AC\uC6A9 ("API" \u2192 "\uAE30\uB2A5", "DB" \u2192 "\uC800\uC7A5\uC18C")
|
|
570
|
-
- \uC5D0\uB7EC \uBC1C\uC0DD \uC2DC \uC6D0\uC778\uACFC \uD574\uACB0\uCC45\uC744 \uD55C \uC904\uB85C \uC124\uBA85
|
|
571
|
-
- \uCF54\uB4DC \uC791\uC131 \uC804 "\uC774\uB807\uAC8C \uB9CC\uB4E4\uAC8C\uC694~" \uD55C \uC904 \uC694\uC57D \uBA3C\uC800
|
|
572
|
-
- \uBAA8\uB974\uB294 \uAC8C \uC788\uC73C\uBA74 \uB9CC\uB4E4\uAE30 \uC804\uC5D0 \uBA3C\uC800 \uC9C8\uBB38
|
|
1063
|
+
1. \`.claude/docs/project-rules.md\`\uB97C **Read tool\uB85C \uC77D\uC73C\uC138\uC694** (\uAE30\uB2A5 \uB9E4\uD551\uD45C, \uBCF4\uC548 \uADDC\uCE59, \uC5D0\uB7EC \uAC00\uC774\uB4DC)
|
|
1064
|
+
2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
|
|
1065
|
+
3. \uAC80\uC0C9 \uACB0\uACFC\uC758 \uCF54\uB4DC \uD328\uD134\uC744 \uB530\uB77C \uAD6C\uD604\uD558\uC138\uC694
|
|
573
1066
|
${endMarker}`;
|
|
574
|
-
if (
|
|
575
|
-
let content =
|
|
1067
|
+
if (fs2.existsSync(claudeMdPath)) {
|
|
1068
|
+
let content = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
576
1069
|
const startIdx = content.indexOf(startMarker);
|
|
577
1070
|
const endIdx = content.indexOf(endMarker);
|
|
578
1071
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
579
1072
|
content = content.substring(0, startIdx) + sdkBlock + content.substring(endIdx + endMarker.length);
|
|
580
1073
|
} else {
|
|
581
|
-
content =
|
|
1074
|
+
content = sdkBlock + "\n\n" + content.trimStart();
|
|
582
1075
|
}
|
|
583
|
-
|
|
1076
|
+
fs2.writeFileSync(claudeMdPath, content);
|
|
584
1077
|
info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
|
|
585
1078
|
} else {
|
|
586
|
-
|
|
1079
|
+
fs2.writeFileSync(claudeMdPath, `# \uD504\uB85C\uC81D\uD2B8
|
|
587
1080
|
|
|
588
1081
|
${sdkBlock}
|
|
589
1082
|
`);
|
|
590
1083
|
success("CLAUDE.md \uC0DD\uC131 \uC644\uB8CC");
|
|
591
1084
|
}
|
|
592
1085
|
}
|
|
593
|
-
|
|
594
|
-
const
|
|
595
|
-
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
1086
|
+
function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
|
|
1087
|
+
const startMarker = "<!-- CONNECTBASE_SUB_START -->";
|
|
1088
|
+
const endMarker = "<!-- CONNECTBASE_SUB_END -->";
|
|
1089
|
+
const relPath = path2.relative(path2.dirname(subClaudeMdPath), gitRoot);
|
|
1090
|
+
const subBlock = `${startMarker}
|
|
1091
|
+
## ConnectBase SDK
|
|
1092
|
+
|
|
1093
|
+
\uC774 \uD328\uD0A4\uC9C0\uB294 **ConnectBase**\uB97C \uBC31\uC5D4\uB4DC\uB85C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.
|
|
1094
|
+
|
|
1095
|
+
### AI \uD544\uB3C5
|
|
1096
|
+
|
|
1097
|
+
1. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/.claude/docs/project-rules.md\`\uB97C **Read tool\uB85C \uC77D\uC73C\uC138\uC694**
|
|
1098
|
+
2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
|
|
1099
|
+
3. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/CLAUDE.md\`\uB3C4 \uCC38\uACE0\uD558\uC138\uC694
|
|
1100
|
+
${endMarker}`;
|
|
1101
|
+
if (fs2.existsSync(subClaudeMdPath)) {
|
|
1102
|
+
let content = fs2.readFileSync(subClaudeMdPath, "utf-8");
|
|
1103
|
+
const startIdx = content.indexOf(startMarker);
|
|
1104
|
+
const endIdx = content.indexOf(endMarker);
|
|
1105
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
1106
|
+
content = content.substring(0, startIdx) + subBlock + content.substring(endIdx + endMarker.length);
|
|
1107
|
+
} else if (content.indexOf("<!-- CONNECTBASE_START -->") === -1) {
|
|
1108
|
+
content = content.trimEnd() + "\n\n" + subBlock + "\n";
|
|
1109
|
+
} else {
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
fs2.writeFileSync(subClaudeMdPath, content);
|
|
1113
|
+
} else {
|
|
1114
|
+
fs2.writeFileSync(subClaudeMdPath, `# ${path2.basename(path2.dirname(subClaudeMdPath))}
|
|
1115
|
+
|
|
1116
|
+
${subBlock}
|
|
1117
|
+
`);
|
|
1118
|
+
}
|
|
1119
|
+
success(`\uC11C\uBE0C \uD328\uD0A4\uC9C0 CLAUDE.md \uC0DD\uC131 \uC644\uB8CC: ${subClaudeMdPath}`);
|
|
1120
|
+
}
|
|
1121
|
+
async function setupMcp(secretKey) {
|
|
1122
|
+
const gitRoot = getGitRoot();
|
|
1123
|
+
const root = gitRoot || process.cwd();
|
|
1124
|
+
const monorepo = detectMonorepo(root);
|
|
1125
|
+
if (monorepo.type !== "none") {
|
|
1126
|
+
info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${root})`);
|
|
1127
|
+
if (monorepo.isSubPackage) {
|
|
1128
|
+
info(`MCP \uC124\uC815\uC740 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8(${root})\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const mcpConfigPath = path2.join(root, ".mcp.json");
|
|
1132
|
+
if (!secretKey) {
|
|
1133
|
+
log(`
|
|
1134
|
+
${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
|
|
1135
|
+
log(` ${colors.cyan}1${colors.reset}) Secret Key \uC9C1\uC811 \uC785\uB825 (cb_sk_...)`);
|
|
1136
|
+
log(` ${colors.cyan}2${colors.reset}) \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uC790\uB3D9 \uBC1C\uAE09
|
|
1137
|
+
`);
|
|
1138
|
+
const authChoice = await prompt(`${colors.blue}?${colors.reset} \uC120\uD0DD (1/2): `);
|
|
1139
|
+
if (authChoice === "2") {
|
|
1140
|
+
try {
|
|
1141
|
+
secretKey = await browserAuthFlow();
|
|
1142
|
+
const skRcPath = path2.join(process.cwd(), ".connectbaserc");
|
|
1143
|
+
let rcData = {};
|
|
1144
|
+
if (fs2.existsSync(skRcPath)) {
|
|
1145
|
+
try {
|
|
1146
|
+
rcData = JSON.parse(fs2.readFileSync(skRcPath, "utf-8"));
|
|
1147
|
+
} catch {
|
|
1148
|
+
}
|
|
603
1149
|
}
|
|
1150
|
+
rcData.secretKey = secretKey;
|
|
1151
|
+
fs2.writeFileSync(skRcPath, JSON.stringify(rcData, null, 2) + "\n");
|
|
1152
|
+
addToGitignore(".connectbaserc", root);
|
|
1153
|
+
success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
error(`\uC778\uC99D \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
|
|
1156
|
+
process.exit(1);
|
|
604
1157
|
}
|
|
1158
|
+
} else {
|
|
1159
|
+
secretKey = await promptSecret(`${colors.blue}?${colors.reset} Secret Key: `);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
const mcpEntry = {
|
|
1163
|
+
type: "http",
|
|
1164
|
+
url: "https://mcp.connectbase.world/mcp",
|
|
1165
|
+
headers: {
|
|
1166
|
+
Authorization: `Bearer ${secretKey || "YOUR_SECRET_KEY_HERE"}`
|
|
605
1167
|
}
|
|
606
1168
|
};
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
header = Buffer.alloc(2);
|
|
621
|
-
header[0] = 129;
|
|
622
|
-
header[1] = 128 | len;
|
|
623
|
-
} else if (len < 65536) {
|
|
624
|
-
header = Buffer.alloc(4);
|
|
625
|
-
header[0] = 129;
|
|
626
|
-
header[1] = 128 | 126;
|
|
627
|
-
header.writeUInt16BE(len, 2);
|
|
628
|
-
} else {
|
|
629
|
-
header = Buffer.alloc(10);
|
|
630
|
-
header[0] = 129;
|
|
631
|
-
header[1] = 128 | 127;
|
|
632
|
-
header.writeBigUInt64BE(BigInt(len), 2);
|
|
1169
|
+
let mcpConfig = { mcpServers: {} };
|
|
1170
|
+
if (fs2.existsSync(mcpConfigPath)) {
|
|
1171
|
+
try {
|
|
1172
|
+
mcpConfig = JSON.parse(fs2.readFileSync(mcpConfigPath, "utf-8"));
|
|
1173
|
+
if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") {
|
|
1174
|
+
mcpConfig.mcpServers = {};
|
|
1175
|
+
}
|
|
1176
|
+
} catch {
|
|
1177
|
+
const backupPath = mcpConfigPath + ".backup";
|
|
1178
|
+
fs2.copyFileSync(mcpConfigPath, backupPath);
|
|
1179
|
+
warn(`.mcp.json \uD30C\uC2F1 \uC2E4\uD328, \uBC31\uC5C5 \uC0DD\uC131: ${backupPath}`);
|
|
1180
|
+
mcpConfig = { mcpServers: {} };
|
|
1181
|
+
}
|
|
633
1182
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
1183
|
+
mcpConfig.mcpServers["connect-base"] = mcpEntry;
|
|
1184
|
+
fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
1185
|
+
success(`${mcpConfigPath} \uC0DD\uC131 \uC644\uB8CC`);
|
|
1186
|
+
addToGitignore(".mcp.json", root);
|
|
1187
|
+
if (secretKey && secretKey !== "YOUR_SECRET_KEY_HERE") {
|
|
1188
|
+
success("MCP \uC11C\uBC84 \uC124\uC815 \uC644\uB8CC (Secret Key \uC801\uC6A9\uB428)");
|
|
1189
|
+
} else {
|
|
1190
|
+
warn("MCP \uC11C\uBC84\uB294 Secret Key (cb_sk_)\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4.");
|
|
1191
|
+
info(".mcp.json \uD30C\uC77C\uC758 YOUR_SECRET_KEY_HERE\uB97C Secret Key\uB85C \uAD50\uCCB4\uD558\uC138\uC694.");
|
|
637
1192
|
}
|
|
638
|
-
|
|
1193
|
+
log(`
|
|
1194
|
+
${colors.dim}Claude Code\uC5D0\uC11C ConnectBase MCP \uB3C4\uAD6C\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.${colors.reset}`);
|
|
639
1195
|
}
|
|
640
|
-
function
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1196
|
+
async function setupClaudeCode(publicKey, secretKey, projectRoot) {
|
|
1197
|
+
const root = projectRoot || getProjectRoot();
|
|
1198
|
+
await downloadDocs(publicKey, void 0, root);
|
|
1199
|
+
await setupMcp(secretKey);
|
|
1200
|
+
}
|
|
1201
|
+
async function fetchLatestVersion(packageName) {
|
|
1202
|
+
try {
|
|
1203
|
+
const res = await makeRequest(
|
|
1204
|
+
`https://registry.npmjs.org/${packageName}/latest`,
|
|
1205
|
+
"GET",
|
|
1206
|
+
{}
|
|
1207
|
+
);
|
|
1208
|
+
if (res.status === 200) {
|
|
1209
|
+
const data = res.data;
|
|
1210
|
+
return data.version || null;
|
|
1211
|
+
}
|
|
1212
|
+
} catch {
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
function compareVersions(a, b) {
|
|
1217
|
+
const pa = a.split(".").map(Number);
|
|
1218
|
+
const pb = b.split(".").map(Number);
|
|
1219
|
+
for (let i = 0; i < 3; i++) {
|
|
1220
|
+
const diff = (pa[i] || 0) - (pb[i] || 0);
|
|
1221
|
+
if (diff !== 0) return diff > 0 ? 1 : -1;
|
|
1222
|
+
}
|
|
1223
|
+
return 0;
|
|
1224
|
+
}
|
|
1225
|
+
function findSubPackagesWithConfig(gitRoot) {
|
|
1226
|
+
const results = [];
|
|
1227
|
+
const candidates = ["apps", "packages", "projects", "services", "libs"];
|
|
1228
|
+
for (const candidate of candidates) {
|
|
1229
|
+
const dir = path2.join(gitRoot, candidate);
|
|
1230
|
+
if (!fs2.existsSync(dir)) continue;
|
|
1231
|
+
try {
|
|
1232
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
1233
|
+
for (const entry of entries) {
|
|
1234
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
1235
|
+
const subDir = path2.join(dir, entry.name);
|
|
1236
|
+
if (fs2.existsSync(path2.join(subDir, ".connectbaserc"))) {
|
|
1237
|
+
results.push(subDir);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
try {
|
|
1244
|
+
const entries = fs2.readdirSync(gitRoot, { withFileTypes: true });
|
|
1245
|
+
for (const entry of entries) {
|
|
1246
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || candidates.includes(entry.name)) continue;
|
|
1247
|
+
if (entry.name === "node_modules") continue;
|
|
1248
|
+
const subDir = path2.join(gitRoot, entry.name);
|
|
1249
|
+
if (fs2.existsSync(path2.join(subDir, ".connectbaserc")) && !results.includes(subDir)) {
|
|
1250
|
+
results.push(subDir);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
return results;
|
|
1256
|
+
}
|
|
1257
|
+
async function update(config, opts) {
|
|
1258
|
+
log(`
|
|
1259
|
+
${colors.cyan}ConnectBase Update${colors.reset}
|
|
1260
|
+
`);
|
|
1261
|
+
const gitRoot = getGitRoot();
|
|
1262
|
+
const projectRoot = gitRoot || process.cwd();
|
|
1263
|
+
const monorepo = detectMonorepo(projectRoot);
|
|
1264
|
+
info("\uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778 \uC911...");
|
|
1265
|
+
const latestVersion = await fetchLatestVersion("connectbase-client");
|
|
1266
|
+
if (latestVersion) {
|
|
1267
|
+
const cmp = compareVersions(VERSION, latestVersion);
|
|
1268
|
+
if (cmp >= 0) {
|
|
1269
|
+
success(`\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${VERSION})`);
|
|
1270
|
+
} else {
|
|
1271
|
+
warn(`\uC0C8 \uBC84\uC804\uC774 \uC788\uC2B5\uB2C8\uB2E4: v${VERSION} \u2192 v${colors.green}${latestVersion}${colors.reset}`);
|
|
1272
|
+
log(` ${colors.cyan}npm install connectbase-client@latest${colors.reset}`);
|
|
1273
|
+
log(` ${colors.cyan}pnpm add connectbase-client@latest${colors.reset}`);
|
|
1274
|
+
log("");
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
warn("npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0\uC11C \uBC84\uC804 \uC815\uBCF4\uB97C \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
|
|
1278
|
+
}
|
|
1279
|
+
if (opts.checkOnly) {
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (monorepo.type !== "none") {
|
|
1283
|
+
info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${projectRoot})`);
|
|
1284
|
+
const subPackages = findSubPackagesWithConfig(projectRoot);
|
|
1285
|
+
if (subPackages.length > 0) {
|
|
1286
|
+
info(`ConnectBase\uB97C \uC0AC\uC6A9\uD558\uB294 \uC11C\uBE0C \uD328\uD0A4\uC9C0 ${subPackages.length}\uAC1C \uBC1C\uACAC:`);
|
|
1287
|
+
for (const sp of subPackages) {
|
|
1288
|
+
log(` ${colors.dim}\u2022 ${path2.relative(projectRoot, sp)}${colors.reset}`);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (!opts.skipDocs) {
|
|
1293
|
+
log("");
|
|
1294
|
+
info("SDK \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uC911...");
|
|
1295
|
+
let docsKey = config.publicKey || config.secretKey;
|
|
1296
|
+
if (!docsKey) {
|
|
1297
|
+
const rootRcPath = path2.join(projectRoot, ".connectbaserc");
|
|
1298
|
+
if (fs2.existsSync(rootRcPath)) {
|
|
1299
|
+
try {
|
|
1300
|
+
const rc = JSON.parse(fs2.readFileSync(rootRcPath, "utf-8"));
|
|
1301
|
+
docsKey = rc.publicKey || rc.secretKey;
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (!docsKey && monorepo.type !== "none") {
|
|
1307
|
+
const subPackages = findSubPackagesWithConfig(projectRoot);
|
|
1308
|
+
for (const sp of subPackages) {
|
|
1309
|
+
try {
|
|
1310
|
+
const rc = JSON.parse(fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8"));
|
|
1311
|
+
if (rc.publicKey || rc.secretKey) {
|
|
1312
|
+
docsKey = rc.publicKey || rc.secretKey;
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
} catch {
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (docsKey) {
|
|
1320
|
+
await downloadDocs(docsKey, void 0, projectRoot);
|
|
1321
|
+
} else {
|
|
1322
|
+
warn("Public Key\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC5B4 \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4");
|
|
1323
|
+
info(".connectbaserc \uD30C\uC77C\uC774\uB098 --public-key \uC635\uC158\uC73C\uB85C \uD0A4\uB97C \uC9C0\uC815\uD558\uC138\uC694");
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if (!opts.skipMcp) {
|
|
1327
|
+
log("");
|
|
1328
|
+
const mcpConfigPath = path2.join(projectRoot, ".mcp.json");
|
|
1329
|
+
if (fs2.existsSync(mcpConfigPath)) {
|
|
1330
|
+
let secretKey = config.secretKey;
|
|
1331
|
+
if (!secretKey) {
|
|
1332
|
+
const rootRcPath = path2.join(projectRoot, ".connectbaserc");
|
|
1333
|
+
if (fs2.existsSync(rootRcPath)) {
|
|
1334
|
+
try {
|
|
1335
|
+
const rc = JSON.parse(fs2.readFileSync(rootRcPath, "utf-8"));
|
|
1336
|
+
secretKey = rc.secretKey;
|
|
1337
|
+
} catch {
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
if (!secretKey && monorepo.type !== "none") {
|
|
1342
|
+
const subPackages = findSubPackagesWithConfig(projectRoot);
|
|
1343
|
+
for (const sp of subPackages) {
|
|
1344
|
+
try {
|
|
1345
|
+
const rc = JSON.parse(fs2.readFileSync(path2.join(sp, ".connectbaserc"), "utf-8"));
|
|
1346
|
+
if (rc.secretKey) {
|
|
1347
|
+
secretKey = rc.secretKey;
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
} catch {
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
if (secretKey) {
|
|
1355
|
+
try {
|
|
1356
|
+
const mcpConfig = JSON.parse(fs2.readFileSync(mcpConfigPath, "utf-8"));
|
|
1357
|
+
const servers = mcpConfig.mcpServers || {};
|
|
1358
|
+
if (servers["connect-base"]) {
|
|
1359
|
+
servers["connect-base"] = {
|
|
1360
|
+
type: "http",
|
|
1361
|
+
url: "https://mcp.connectbase.world/mcp",
|
|
1362
|
+
headers: { Authorization: `Bearer ${secretKey}` }
|
|
1363
|
+
};
|
|
1364
|
+
mcpConfig.mcpServers = servers;
|
|
1365
|
+
fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
1366
|
+
success("MCP \uC124\uC815 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
|
|
1367
|
+
} else {
|
|
1368
|
+
info('.mcp.json\uC5D0 connect-base \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. "connectbase mcp"\uB85C \uC124\uC815\uD558\uC138\uC694');
|
|
1369
|
+
}
|
|
1370
|
+
} catch {
|
|
1371
|
+
warn(".mcp.json \uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328");
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
info("Secret Key\uAC00 \uC5C6\uC5B4 MCP \uC124\uC815 \uC5C5\uB370\uC774\uD2B8\uB97C \uAC74\uB108\uB701\uB2C8\uB2E4");
|
|
1375
|
+
}
|
|
1376
|
+
} else {
|
|
1377
|
+
info('.mcp.json\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. "connectbase mcp"\uB85C \uCD5C\uCD08 \uC124\uC815\uD558\uC138\uC694');
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
if (opts.setupRoot || monorepo.type !== "none" && monorepo.isSubPackage) {
|
|
1381
|
+
log("");
|
|
1382
|
+
setupMonorepoRoot(projectRoot, monorepo);
|
|
1383
|
+
}
|
|
1384
|
+
log(`
|
|
1385
|
+
${colors.green}\uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC!${colors.reset}
|
|
1386
|
+
`);
|
|
1387
|
+
}
|
|
1388
|
+
function setupMonorepoRoot(projectRoot, monorepo) {
|
|
1389
|
+
const rootPkgPath = path2.join(projectRoot, "package.json");
|
|
1390
|
+
if (!fs2.existsSync(rootPkgPath)) {
|
|
1391
|
+
warn("\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");
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
try {
|
|
1395
|
+
const pkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf-8"));
|
|
1396
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
1397
|
+
let changed = false;
|
|
1398
|
+
if (!pkg.scripts["cb:update"]) {
|
|
1399
|
+
pkg.scripts["cb:update"] = "npx connectbase update";
|
|
1400
|
+
changed = true;
|
|
1401
|
+
}
|
|
1402
|
+
if (!pkg.scripts["cb:docs"]) {
|
|
1403
|
+
pkg.scripts["cb:docs"] = "npx connectbase docs";
|
|
1404
|
+
changed = true;
|
|
1405
|
+
}
|
|
1406
|
+
if (!pkg.scripts["cb:mcp"]) {
|
|
1407
|
+
pkg.scripts["cb:mcp"] = "npx connectbase mcp";
|
|
1408
|
+
changed = true;
|
|
1409
|
+
}
|
|
1410
|
+
if (changed) {
|
|
1411
|
+
fs2.writeFileSync(rootPkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
1412
|
+
success("\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC");
|
|
1413
|
+
log(` ${colors.dim}\uC0AC\uC6A9\uBC95:${colors.reset}`);
|
|
1414
|
+
log(` ${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`);
|
|
1415
|
+
log(` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:docs${colors.reset} \u2014 \uBB38\uC11C\uB9CC \uAC31\uC2E0`);
|
|
1416
|
+
log(` ${colors.cyan}${monorepo.type === "pnpm" ? "pnpm" : monorepo.type === "yarn" ? "yarn" : "npm run"} cb:mcp${colors.reset} \u2014 MCP \uC124\uC815`);
|
|
1417
|
+
} else {
|
|
1418
|
+
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");
|
|
1419
|
+
}
|
|
1420
|
+
} catch {
|
|
1421
|
+
warn("\uB8E8\uD2B8 package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
function createWsTextFrame(payload) {
|
|
1425
|
+
const data = Buffer.from(payload, "utf-8");
|
|
1426
|
+
const len = data.length;
|
|
1427
|
+
const maskKey = crypto.randomBytes(4);
|
|
1428
|
+
let header;
|
|
1429
|
+
if (len < 126) {
|
|
1430
|
+
header = Buffer.alloc(2);
|
|
1431
|
+
header[0] = 129;
|
|
1432
|
+
header[1] = 128 | len;
|
|
1433
|
+
} else if (len < 65536) {
|
|
1434
|
+
header = Buffer.alloc(4);
|
|
1435
|
+
header[0] = 129;
|
|
1436
|
+
header[1] = 128 | 126;
|
|
1437
|
+
header.writeUInt16BE(len, 2);
|
|
1438
|
+
} else {
|
|
1439
|
+
header = Buffer.alloc(10);
|
|
1440
|
+
header[0] = 129;
|
|
1441
|
+
header[1] = 128 | 127;
|
|
1442
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
1443
|
+
}
|
|
1444
|
+
const masked = Buffer.alloc(data.length);
|
|
1445
|
+
for (let i = 0; i < data.length; i++) {
|
|
1446
|
+
masked[i] = data[i] ^ maskKey[i % 4];
|
|
1447
|
+
}
|
|
1448
|
+
return Buffer.concat([header, maskKey, masked]);
|
|
1449
|
+
}
|
|
1450
|
+
function createWsCloseFrame(code) {
|
|
1451
|
+
const maskKey = crypto.randomBytes(4);
|
|
1452
|
+
const payload = Buffer.alloc(2);
|
|
1453
|
+
payload.writeUInt16BE(code, 0);
|
|
1454
|
+
const masked = Buffer.alloc(2);
|
|
645
1455
|
masked[0] = payload[0] ^ maskKey[0];
|
|
646
1456
|
masked[1] = payload[1] ^ maskKey[1];
|
|
647
1457
|
const header = Buffer.alloc(2);
|
|
@@ -656,10 +1466,159 @@ function createWsPongFrame() {
|
|
|
656
1466
|
header[1] = 128 | 0;
|
|
657
1467
|
return Buffer.concat([header, maskKey]);
|
|
658
1468
|
}
|
|
1469
|
+
function createWsBinaryFrame(payload) {
|
|
1470
|
+
const len = payload.length;
|
|
1471
|
+
const maskKey = crypto.randomBytes(4);
|
|
1472
|
+
let header;
|
|
1473
|
+
if (len < 126) {
|
|
1474
|
+
header = Buffer.alloc(2);
|
|
1475
|
+
header[0] = 130;
|
|
1476
|
+
header[1] = 128 | len;
|
|
1477
|
+
} else if (len < 65536) {
|
|
1478
|
+
header = Buffer.alloc(4);
|
|
1479
|
+
header[0] = 130;
|
|
1480
|
+
header[1] = 128 | 126;
|
|
1481
|
+
header.writeUInt16BE(len, 2);
|
|
1482
|
+
} else {
|
|
1483
|
+
header = Buffer.alloc(10);
|
|
1484
|
+
header[0] = 130;
|
|
1485
|
+
header[1] = 128 | 127;
|
|
1486
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
1487
|
+
}
|
|
1488
|
+
const masked = Buffer.alloc(len);
|
|
1489
|
+
for (let i = 0; i < len; i++) {
|
|
1490
|
+
masked[i] = payload[i] ^ maskKey[i % 4];
|
|
1491
|
+
}
|
|
1492
|
+
return Buffer.concat([header, maskKey, masked]);
|
|
1493
|
+
}
|
|
1494
|
+
var UpstreamWsFrameParser = class {
|
|
1495
|
+
constructor(handlers) {
|
|
1496
|
+
this.buffer = Buffer.alloc(0);
|
|
1497
|
+
this.fragmentBuffer = null;
|
|
1498
|
+
this.h = handlers;
|
|
1499
|
+
}
|
|
1500
|
+
feed(chunk) {
|
|
1501
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
1502
|
+
try {
|
|
1503
|
+
this.parse();
|
|
1504
|
+
} catch (e) {
|
|
1505
|
+
this.h.onError(e instanceof Error ? e : new Error(String(e)));
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
parse() {
|
|
1509
|
+
while (this.buffer.length >= 2) {
|
|
1510
|
+
const firstByte = this.buffer[0];
|
|
1511
|
+
const secondByte = this.buffer[1];
|
|
1512
|
+
const fin = (firstByte & 128) !== 0;
|
|
1513
|
+
const rsv1 = (firstByte & 64) !== 0;
|
|
1514
|
+
const opcode = firstByte & 15;
|
|
1515
|
+
const isMasked = (secondByte & 128) !== 0;
|
|
1516
|
+
let payloadLen = secondByte & 127;
|
|
1517
|
+
let offset = 2;
|
|
1518
|
+
if (payloadLen === 126) {
|
|
1519
|
+
if (this.buffer.length < 4) return;
|
|
1520
|
+
payloadLen = this.buffer.readUInt16BE(2);
|
|
1521
|
+
offset = 4;
|
|
1522
|
+
} else if (payloadLen === 127) {
|
|
1523
|
+
if (this.buffer.length < 10) return;
|
|
1524
|
+
const big = this.buffer.readBigUInt64BE(2);
|
|
1525
|
+
if (big > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
1526
|
+
throw new Error(`payload too large: ${big}`);
|
|
1527
|
+
}
|
|
1528
|
+
payloadLen = Number(big);
|
|
1529
|
+
offset = 10;
|
|
1530
|
+
}
|
|
1531
|
+
let maskKey = null;
|
|
1532
|
+
if (isMasked) {
|
|
1533
|
+
if (this.buffer.length < offset + 4) return;
|
|
1534
|
+
maskKey = this.buffer.subarray(offset, offset + 4);
|
|
1535
|
+
offset += 4;
|
|
1536
|
+
}
|
|
1537
|
+
if (this.buffer.length < offset + payloadLen) return;
|
|
1538
|
+
if (rsv1 && (opcode === 1 || opcode === 2 || opcode === 0)) {
|
|
1539
|
+
throw new Error("upstream sent compressed (RSV1) frame without negotiation");
|
|
1540
|
+
}
|
|
1541
|
+
let payload = this.buffer.subarray(offset, offset + payloadLen);
|
|
1542
|
+
if (maskKey) {
|
|
1543
|
+
const unmasked = Buffer.alloc(payloadLen);
|
|
1544
|
+
for (let i = 0; i < payloadLen; i++) {
|
|
1545
|
+
unmasked[i] = payload[i] ^ maskKey[i % 4];
|
|
1546
|
+
}
|
|
1547
|
+
payload = unmasked;
|
|
1548
|
+
}
|
|
1549
|
+
this.buffer = this.buffer.subarray(offset + payloadLen);
|
|
1550
|
+
const payloadCopy = Buffer.from(payload);
|
|
1551
|
+
switch (opcode) {
|
|
1552
|
+
case 0:
|
|
1553
|
+
if (this.fragmentBuffer == null) {
|
|
1554
|
+
throw new Error("continuation frame without preceding non-final data frame");
|
|
1555
|
+
}
|
|
1556
|
+
this.fragmentBuffer = Buffer.concat([this.fragmentBuffer, payloadCopy]);
|
|
1557
|
+
if (fin) {
|
|
1558
|
+
const out = this.fragmentBuffer;
|
|
1559
|
+
this.fragmentBuffer = null;
|
|
1560
|
+
this.h.onPayload(out);
|
|
1561
|
+
}
|
|
1562
|
+
break;
|
|
1563
|
+
case 1:
|
|
1564
|
+
// TEXT
|
|
1565
|
+
case 2:
|
|
1566
|
+
if (fin) {
|
|
1567
|
+
this.h.onPayload(payloadCopy);
|
|
1568
|
+
} else {
|
|
1569
|
+
this.fragmentBuffer = payloadCopy;
|
|
1570
|
+
}
|
|
1571
|
+
break;
|
|
1572
|
+
case 8:
|
|
1573
|
+
this.h.onClose();
|
|
1574
|
+
break;
|
|
1575
|
+
case 9:
|
|
1576
|
+
this.h.onPing(payloadCopy);
|
|
1577
|
+
break;
|
|
1578
|
+
case 10:
|
|
1579
|
+
break;
|
|
1580
|
+
default:
|
|
1581
|
+
throw new Error(`unknown WS opcode 0x${opcode.toString(16)}`);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
function createUpstreamTextFrame(payload) {
|
|
1587
|
+
return buildClientFrame(129, payload);
|
|
1588
|
+
}
|
|
1589
|
+
function createUpstreamPongFrame(payload) {
|
|
1590
|
+
return buildClientFrame(138, payload);
|
|
1591
|
+
}
|
|
1592
|
+
function buildClientFrame(firstByte, payload) {
|
|
1593
|
+
const len = payload.length;
|
|
1594
|
+
const maskKey = crypto.randomBytes(4);
|
|
1595
|
+
let header;
|
|
1596
|
+
if (len < 126) {
|
|
1597
|
+
header = Buffer.alloc(2);
|
|
1598
|
+
header[0] = firstByte;
|
|
1599
|
+
header[1] = 128 | len;
|
|
1600
|
+
} else if (len < 65536) {
|
|
1601
|
+
header = Buffer.alloc(4);
|
|
1602
|
+
header[0] = firstByte;
|
|
1603
|
+
header[1] = 128 | 126;
|
|
1604
|
+
header.writeUInt16BE(len, 2);
|
|
1605
|
+
} else {
|
|
1606
|
+
header = Buffer.alloc(10);
|
|
1607
|
+
header[0] = firstByte;
|
|
1608
|
+
header[1] = 128 | 127;
|
|
1609
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
1610
|
+
}
|
|
1611
|
+
const masked = Buffer.alloc(len);
|
|
1612
|
+
for (let i = 0; i < len; i++) {
|
|
1613
|
+
masked[i] = payload[i] ^ maskKey[i % 4];
|
|
1614
|
+
}
|
|
1615
|
+
return Buffer.concat([header, maskKey, masked]);
|
|
1616
|
+
}
|
|
659
1617
|
var WsFrameParser = class {
|
|
660
1618
|
constructor(handlers) {
|
|
661
1619
|
this.buffer = Buffer.alloc(0);
|
|
662
1620
|
this.onMessage = handlers.onMessage;
|
|
1621
|
+
this.onBinary = handlers.onBinary;
|
|
663
1622
|
this.onClose = handlers.onClose;
|
|
664
1623
|
this.onPing = handlers.onPing;
|
|
665
1624
|
}
|
|
@@ -704,6 +1663,13 @@ var WsFrameParser = class {
|
|
|
704
1663
|
case 1:
|
|
705
1664
|
this.onMessage(payload.toString("utf-8"));
|
|
706
1665
|
break;
|
|
1666
|
+
case 2:
|
|
1667
|
+
if (this.onBinary) {
|
|
1668
|
+
const copy = Buffer.alloc(payload.length);
|
|
1669
|
+
payload.copy(copy);
|
|
1670
|
+
this.onBinary(copy);
|
|
1671
|
+
}
|
|
1672
|
+
break;
|
|
707
1673
|
case 8:
|
|
708
1674
|
this.onClose();
|
|
709
1675
|
break;
|
|
@@ -725,27 +1691,195 @@ function getTunnelServerUrl(baseUrl) {
|
|
|
725
1691
|
}
|
|
726
1692
|
return baseUrl.replace(/:\d+/, ":8090");
|
|
727
1693
|
}
|
|
728
|
-
async function
|
|
729
|
-
if (
|
|
730
|
-
|
|
1694
|
+
async function resolveApp(secretKey, baseUrl, appIdOption) {
|
|
1695
|
+
if (appIdOption) {
|
|
1696
|
+
return { appId: appIdOption };
|
|
1697
|
+
}
|
|
1698
|
+
let apps = [];
|
|
1699
|
+
try {
|
|
1700
|
+
info("\uC571 \uBAA9\uB85D \uC870\uD68C \uC911...");
|
|
1701
|
+
const appsRes = await makeRequest(
|
|
1702
|
+
`${baseUrl}/v1/public/cli/apps`,
|
|
1703
|
+
"GET",
|
|
1704
|
+
{ "X-Public-Key": secretKey }
|
|
1705
|
+
);
|
|
1706
|
+
if (appsRes.status === 401) {
|
|
1707
|
+
error("Secret Key\uAC00 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uCF58\uC194\uC5D0\uC11C \uD0A4\uB97C \uD655\uC778\uD558\uC138\uC694");
|
|
1708
|
+
process.exit(1);
|
|
1709
|
+
}
|
|
1710
|
+
if (appsRes.status !== 200) {
|
|
1711
|
+
throw new Error(`HTTP ${appsRes.status}`);
|
|
1712
|
+
}
|
|
1713
|
+
const appsData = appsRes.data;
|
|
1714
|
+
apps = appsData.apps || [];
|
|
1715
|
+
} catch (err) {
|
|
1716
|
+
error(`\uC571 \uBAA9\uB85D \uC870\uD68C \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
|
|
731
1717
|
process.exit(1);
|
|
732
1718
|
}
|
|
1719
|
+
if (apps.length === 1) {
|
|
1720
|
+
success(`\uC571 \uC790\uB3D9 \uC120\uD0DD: ${apps[0].name}`);
|
|
1721
|
+
return { appId: apps[0].id };
|
|
1722
|
+
}
|
|
1723
|
+
if (apps.length > 0) {
|
|
1724
|
+
log(`
|
|
1725
|
+
${colors.dim}\uB0B4 \uC571 \uBAA9\uB85D:${colors.reset}`);
|
|
1726
|
+
apps.forEach((a, i) => {
|
|
1727
|
+
const date = a.created_at ? a.created_at.substring(0, 10) : "";
|
|
1728
|
+
log(` ${colors.cyan}${i + 1}${colors.reset}) ${a.name} ${colors.dim}(${date})${colors.reset}`);
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
log(` ${colors.cyan}0${colors.reset}) \uC0C8 \uC571 \uB9CC\uB4E4\uAE30`);
|
|
1732
|
+
const choice = await prompt(`
|
|
1733
|
+
${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
|
|
1734
|
+
const num = parseInt(choice, 10);
|
|
1735
|
+
if (num > 0 && num <= apps.length) {
|
|
1736
|
+
success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
|
|
1737
|
+
return { appId: apps[num - 1].id };
|
|
1738
|
+
}
|
|
1739
|
+
const projectName = path2.basename(process.cwd());
|
|
1740
|
+
const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
|
|
1741
|
+
info("\uC571 \uC0DD\uC131 \uC911...");
|
|
1742
|
+
const createRes = await makeRequest(
|
|
1743
|
+
`${baseUrl}/v1/public/cli/apps`,
|
|
1744
|
+
"POST",
|
|
1745
|
+
{ "X-Public-Key": secretKey },
|
|
1746
|
+
JSON.stringify({ name: appName })
|
|
1747
|
+
);
|
|
1748
|
+
if (createRes.status === 402) {
|
|
1749
|
+
error("\uC571 \uC0DD\uC131 \uD55C\uB3C4 \uCD08\uACFC. \uD50C\uB79C \uC5C5\uADF8\uB808\uC774\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
1750
|
+
process.exit(1);
|
|
1751
|
+
}
|
|
1752
|
+
if (createRes.status !== 201) {
|
|
1753
|
+
const data = createRes.data;
|
|
1754
|
+
error(`\uC571 \uC0DD\uC131 \uC2E4\uD328: ${data?.error || `HTTP ${createRes.status}`}`);
|
|
1755
|
+
process.exit(1);
|
|
1756
|
+
}
|
|
1757
|
+
const createData = createRes.data;
|
|
1758
|
+
success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
|
|
1759
|
+
return { appId: createData.app_id, publicKey: createData.public_key };
|
|
1760
|
+
}
|
|
1761
|
+
async function registerEndpointBinding(baseUrl, appId, secretKey, tunnelId, label, description) {
|
|
1762
|
+
try {
|
|
1763
|
+
const apiBase = baseUrl.replace(/\/+$/, "");
|
|
1764
|
+
const res = await fetch(`${apiBase}/v1/apps/${encodeURIComponent(appId)}/endpoints/cli`, {
|
|
1765
|
+
method: "POST",
|
|
1766
|
+
headers: {
|
|
1767
|
+
"Content-Type": "application/json",
|
|
1768
|
+
"X-Public-Key": secretKey
|
|
1769
|
+
},
|
|
1770
|
+
body: JSON.stringify({
|
|
1771
|
+
label,
|
|
1772
|
+
tunnel_id: tunnelId,
|
|
1773
|
+
description: description ?? `CLI tunnel start (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`
|
|
1774
|
+
})
|
|
1775
|
+
});
|
|
1776
|
+
if (res.status === 201) {
|
|
1777
|
+
success(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC644\uB8CC`);
|
|
1778
|
+
log(`${colors.green}\u2192${colors.reset} SDK: ${colors.cyan}cb.endpoint.call("${label}", { path: "/...", method: "POST", body: ... })${colors.reset}`);
|
|
1779
|
+
} else if (res.status === 409) {
|
|
1780
|
+
warn(`"${label}" \uB77C\uBCA8\uC774 \uC774\uBBF8 \uB4F1\uB85D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4. \uC0C8 tunnel_id \uB85C \uAC31\uC2E0\uD558\uB824\uBA74 \uCF58\uC194\uC5D0\uC11C \uC218\uB3D9 PATCH.`);
|
|
1781
|
+
} else {
|
|
1782
|
+
const text = await res.text().catch(() => "");
|
|
1783
|
+
error(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC2E4\uD328 (status ${res.status}): ${text || "(\uBE48 \uC751\uB2F5)"}`);
|
|
1784
|
+
if (res.status === 401) {
|
|
1785
|
+
info("cb_sk_ Secret Key \uC778\uC9C0, \uCF58\uC194\uC5D0\uC11C \uD68C\uC218\uB418\uC9C0 \uC54A\uC558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.");
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
} catch (err) {
|
|
1789
|
+
error(`Endpoint "${label}" \uC790\uB3D9 \uB4F1\uB85D \uC911 \uB124\uD2B8\uC6CC\uD06C \uC624\uB958: ${err instanceof Error ? err.message : err}`);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function acquireTunnelLock2(appID, port, force) {
|
|
1793
|
+
const result = acquireTunnelLock(appID, port, force, VERSION);
|
|
1794
|
+
if (result && result.startsWith("LOCKED:")) {
|
|
1795
|
+
const [, pid, startedAt, host] = result.split(":");
|
|
1796
|
+
error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${pid}, \uC2DC\uC791 ${startedAt}, \uD638\uC2A4\uD2B8 ${host}`);
|
|
1797
|
+
info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
|
|
1798
|
+
process.exit(2);
|
|
1799
|
+
}
|
|
1800
|
+
return result;
|
|
1801
|
+
}
|
|
1802
|
+
async function startTunnel(port, config, tunnelOpts) {
|
|
1803
|
+
let tunnelKey = config.secretKey || (config.publicKey?.startsWith("cb_sk_") ? config.publicKey : "");
|
|
1804
|
+
if (!tunnelKey) {
|
|
1805
|
+
info("Secret Key\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uBC1C\uAE09\uD569\uB2C8\uB2E4...");
|
|
1806
|
+
try {
|
|
1807
|
+
const sk = await browserAuthFlow();
|
|
1808
|
+
const rcPath2 = path2.join(process.cwd(), ".connectbaserc");
|
|
1809
|
+
let rcData = {};
|
|
1810
|
+
if (fs2.existsSync(rcPath2)) {
|
|
1811
|
+
try {
|
|
1812
|
+
rcData = JSON.parse(fs2.readFileSync(rcPath2, "utf-8"));
|
|
1813
|
+
} catch {
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
rcData.secretKey = sk;
|
|
1817
|
+
fs2.writeFileSync(rcPath2, JSON.stringify(rcData, null, 2) + "\n");
|
|
1818
|
+
addToGitignore(".connectbaserc");
|
|
1819
|
+
success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
|
|
1820
|
+
tunnelKey = sk;
|
|
1821
|
+
config.secretKey = sk;
|
|
1822
|
+
} catch (err) {
|
|
1823
|
+
error(`\uC778\uC99D \uC2E4\uD328: ${err instanceof Error ? err.message : err}`);
|
|
1824
|
+
info("-k \uC635\uC158\uC73C\uB85C Secret Key\uB97C \uC9C1\uC811 \uC804\uB2EC\uD560 \uC218\uB3C4 \uC788\uC2B5\uB2C8\uB2E4");
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
}
|
|
1827
|
+
} else {
|
|
1828
|
+
config.secretKey = tunnelKey;
|
|
1829
|
+
}
|
|
1830
|
+
const rcPath = path2.join(process.cwd(), ".connectbaserc");
|
|
1831
|
+
let savedAppId = tunnelOpts?.appId || "";
|
|
1832
|
+
if (!savedAppId) {
|
|
1833
|
+
try {
|
|
1834
|
+
const rc = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
|
|
1835
|
+
if (rc.tunnelAppId) savedAppId = rc.tunnelAppId;
|
|
1836
|
+
} catch {
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
const { appId } = await resolveApp(tunnelKey, config.baseUrl, savedAppId);
|
|
1840
|
+
try {
|
|
1841
|
+
let rcData = {};
|
|
1842
|
+
if (fs2.existsSync(rcPath)) {
|
|
1843
|
+
try {
|
|
1844
|
+
rcData = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
|
|
1845
|
+
} catch {
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if (rcData.tunnelAppId !== appId) {
|
|
1849
|
+
rcData.tunnelAppId = appId;
|
|
1850
|
+
fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
|
|
1851
|
+
}
|
|
1852
|
+
} catch {
|
|
1853
|
+
}
|
|
1854
|
+
const lockPath = acquireTunnelLock2(appId, port, tunnelOpts?.force ?? false);
|
|
733
1855
|
const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
|
|
734
1856
|
const parsedUrl = new URL(tunnelServerUrl);
|
|
735
1857
|
const isHttps = parsedUrl.protocol === "https:";
|
|
736
|
-
let wsPath = `/
|
|
1858
|
+
let wsPath = `/v2/tunnel/connect?app_id=${encodeURIComponent(appId)}&local_port=${port}`;
|
|
737
1859
|
if (tunnelOpts?.timeout) {
|
|
738
1860
|
wsPath += `&timeout=${tunnelOpts.timeout}`;
|
|
739
1861
|
}
|
|
740
1862
|
if (tunnelOpts?.maxBody) {
|
|
741
1863
|
wsPath += `&max_body=${tunnelOpts.maxBody}`;
|
|
742
1864
|
}
|
|
1865
|
+
if (tunnelOpts?.public) {
|
|
1866
|
+
wsPath += `&public=1`;
|
|
1867
|
+
}
|
|
743
1868
|
let reconnectAttempts = 0;
|
|
744
1869
|
const maxReconnectAttempts = 10;
|
|
745
1870
|
let shouldReconnect = true;
|
|
746
1871
|
let socket = null;
|
|
1872
|
+
let lockReleased = false;
|
|
1873
|
+
const releaseLock = () => {
|
|
1874
|
+
if (lockPath && !lockReleased) {
|
|
1875
|
+
lockReleased = true;
|
|
1876
|
+
releaseTunnelLock(lockPath);
|
|
1877
|
+
}
|
|
1878
|
+
};
|
|
1879
|
+
process.on("exit", releaseLock);
|
|
747
1880
|
const cleanup = () => {
|
|
748
1881
|
shouldReconnect = false;
|
|
1882
|
+
releaseLock();
|
|
749
1883
|
if (socket) {
|
|
750
1884
|
try {
|
|
751
1885
|
socket.write(createWsCloseFrame(1e3));
|
|
@@ -779,7 +1913,8 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
779
1913
|
"Upgrade": "websocket",
|
|
780
1914
|
"Connection": "Upgrade",
|
|
781
1915
|
"Sec-WebSocket-Key": wsKey,
|
|
782
|
-
"Sec-WebSocket-Version": "13"
|
|
1916
|
+
"Sec-WebSocket-Version": "13",
|
|
1917
|
+
"Authorization": `Bearer ${config.secretKey ?? config.publicKey}`
|
|
783
1918
|
}
|
|
784
1919
|
};
|
|
785
1920
|
const req = lib.request(reqOptions);
|
|
@@ -790,11 +1925,22 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
790
1925
|
onMessage: (data) => {
|
|
791
1926
|
try {
|
|
792
1927
|
const msg = JSON.parse(data);
|
|
793
|
-
handleMessage(msg, sock, port)
|
|
1928
|
+
handleMessage(msg, sock, port).catch((e) => {
|
|
1929
|
+
error(`\uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uC608\uC678: ${e instanceof Error ? e.message : e}`);
|
|
1930
|
+
});
|
|
794
1931
|
} catch (e) {
|
|
795
1932
|
warn(`\uBA54\uC2DC\uC9C0 \uD30C\uC2F1 \uC2E4\uD328: ${e}`);
|
|
796
1933
|
}
|
|
797
1934
|
},
|
|
1935
|
+
onBinary: (data) => {
|
|
1936
|
+
if (data.length < 8) {
|
|
1937
|
+
warn(`v2 binary frame too short (${data.length} bytes)`);
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const streamId = data.readBigUInt64BE(0).toString();
|
|
1941
|
+
const payload = data.subarray(8);
|
|
1942
|
+
routeStreamData(streamId, payload);
|
|
1943
|
+
},
|
|
798
1944
|
onClose: () => {
|
|
799
1945
|
info("\uC11C\uBC84\uAC00 \uC5F0\uACB0\uC744 \uC885\uB8CC\uD588\uC2B5\uB2C8\uB2E4");
|
|
800
1946
|
sock.destroy();
|
|
@@ -858,95 +2004,314 @@ ${colors.cyan}ConnectBase Tunnel${colors.reset}`);
|
|
|
858
2004
|
info(`${(delay / 1e3).toFixed(0)}\uCD08 \uD6C4 \uC7AC\uC5F0\uACB0 \uC2DC\uB3C4... (${reconnectAttempts}/${maxReconnectAttempts})`);
|
|
859
2005
|
setTimeout(connect, delay);
|
|
860
2006
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
|
|
872
|
-
`);
|
|
873
|
-
break;
|
|
874
|
-
case "http_request":
|
|
875
|
-
forwardRequest(msg, sock, localPort);
|
|
876
|
-
break;
|
|
877
|
-
case "tunnel_error":
|
|
878
|
-
error(`\uD130\uB110 \uC5D0\uB7EC: ${msg.message}`);
|
|
879
|
-
break;
|
|
880
|
-
case "ping":
|
|
881
|
-
sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
|
|
882
|
-
break;
|
|
2007
|
+
const streams = /* @__PURE__ */ new Map();
|
|
2008
|
+
function routeStreamData(streamId, payload) {
|
|
2009
|
+
const f = streams.get(streamId);
|
|
2010
|
+
if (!f) return;
|
|
2011
|
+
f.feedBody(payload);
|
|
2012
|
+
}
|
|
2013
|
+
function sendControl(sock, msg) {
|
|
2014
|
+
try {
|
|
2015
|
+
sock.write(createWsTextFrame(JSON.stringify(msg)));
|
|
2016
|
+
} catch {
|
|
883
2017
|
}
|
|
884
2018
|
}
|
|
885
|
-
function
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
2019
|
+
function sendBinary(sock, streamIdStr, payload) {
|
|
2020
|
+
try {
|
|
2021
|
+
const header = Buffer.alloc(8);
|
|
2022
|
+
header.writeBigUInt64BE(BigInt(streamIdStr), 0);
|
|
2023
|
+
sock.write(createWsBinaryFrame(Buffer.concat([header, payload])));
|
|
2024
|
+
} catch {
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
function startHTTPStream(sock, streamId, open, localPort) {
|
|
2028
|
+
const method = open.method || "GET";
|
|
2029
|
+
const reqPath = open.path || "/";
|
|
2030
|
+
const query = open.query || "";
|
|
2031
|
+
const headers = open.headers || {};
|
|
892
2032
|
const fullPath = query ? `${reqPath}?${query}` : reqPath;
|
|
893
2033
|
const localHeaders = {};
|
|
894
|
-
for (const [
|
|
895
|
-
if (
|
|
896
|
-
|
|
2034
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
2035
|
+
if (k.toLowerCase() !== "host") localHeaders[k] = v;
|
|
2036
|
+
}
|
|
2037
|
+
localHeaders["host"] = `localhost:${localPort}`;
|
|
2038
|
+
let started = false;
|
|
2039
|
+
let cancelled = false;
|
|
2040
|
+
const localReq = http.request(
|
|
2041
|
+
{ hostname: "127.0.0.1", port: localPort, path: fullPath, method, headers: localHeaders },
|
|
2042
|
+
(res) => {
|
|
2043
|
+
const respHeaders = {};
|
|
2044
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
2045
|
+
if (v == null) continue;
|
|
2046
|
+
respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
2047
|
+
}
|
|
2048
|
+
started = true;
|
|
2049
|
+
sendControl(sock, {
|
|
2050
|
+
type: "stream_response",
|
|
2051
|
+
stream_id: streamId,
|
|
2052
|
+
status: res.statusCode || 502,
|
|
2053
|
+
headers: respHeaders
|
|
2054
|
+
});
|
|
2055
|
+
const methodColor = method === "GET" ? colors.green : method === "POST" ? colors.blue : colors.yellow;
|
|
2056
|
+
log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${methodColor}${method}${colors.reset} ${reqPath} \u2192 ${res.statusCode}`);
|
|
2057
|
+
res.on("data", (chunk) => {
|
|
2058
|
+
if (cancelled) return;
|
|
2059
|
+
sendBinary(sock, streamId, chunk);
|
|
2060
|
+
});
|
|
2061
|
+
res.on("end", () => {
|
|
2062
|
+
if (cancelled) return;
|
|
2063
|
+
sendControl(sock, { type: "stream_eof", stream_id: streamId, side: "upstream" });
|
|
2064
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId });
|
|
2065
|
+
streams.delete(streamId);
|
|
2066
|
+
});
|
|
2067
|
+
res.on("error", (err) => {
|
|
2068
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
|
|
2069
|
+
streams.delete(streamId);
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
);
|
|
2073
|
+
localReq.on("error", (err) => {
|
|
2074
|
+
warn(`\uB85C\uCEEC \uC11C\uBC84 \uC5F0\uACB0 \uC2E4\uD328 (${method} ${reqPath}): ${err.message}`);
|
|
2075
|
+
if (!started) {
|
|
2076
|
+
sendControl(sock, {
|
|
2077
|
+
type: "stream_response",
|
|
2078
|
+
stream_id: streamId,
|
|
2079
|
+
status: 502,
|
|
2080
|
+
headers: { "content-type": "application/json" }
|
|
2081
|
+
});
|
|
2082
|
+
sendBinary(sock, streamId, Buffer.from(JSON.stringify({ error: `Local server error: ${err.message}` })));
|
|
2083
|
+
}
|
|
2084
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
|
|
2085
|
+
streams.delete(streamId);
|
|
2086
|
+
});
|
|
2087
|
+
const forwarder = {
|
|
2088
|
+
kind: "http",
|
|
2089
|
+
feedBody: (chunk) => {
|
|
2090
|
+
if (!cancelled) localReq.write(chunk);
|
|
2091
|
+
},
|
|
2092
|
+
endBody: () => {
|
|
2093
|
+
if (!cancelled) localReq.end();
|
|
2094
|
+
},
|
|
2095
|
+
cancel: () => {
|
|
2096
|
+
cancelled = true;
|
|
2097
|
+
try {
|
|
2098
|
+
localReq.destroy();
|
|
2099
|
+
} catch {
|
|
2100
|
+
}
|
|
897
2101
|
}
|
|
2102
|
+
};
|
|
2103
|
+
streams.set(streamId, forwarder);
|
|
2104
|
+
}
|
|
2105
|
+
function startWSStream(sock, streamId, open, localPort) {
|
|
2106
|
+
const reqPath = open.path || "/";
|
|
2107
|
+
const query = open.query || "";
|
|
2108
|
+
const headers = open.headers || {};
|
|
2109
|
+
const fullPath = query ? `${reqPath}?${query}` : reqPath;
|
|
2110
|
+
const localHeaders = {};
|
|
2111
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
2112
|
+
const lk = k.toLowerCase();
|
|
2113
|
+
if (lk === "host") continue;
|
|
2114
|
+
if (lk === "sec-websocket-extensions") continue;
|
|
2115
|
+
localHeaders[k] = v;
|
|
898
2116
|
}
|
|
899
2117
|
localHeaders["host"] = `localhost:${localPort}`;
|
|
900
|
-
|
|
2118
|
+
localHeaders["connection"] = "Upgrade";
|
|
2119
|
+
localHeaders["upgrade"] = "websocket";
|
|
2120
|
+
let cancelled = false;
|
|
2121
|
+
let upstream = null;
|
|
2122
|
+
const req = http.request({
|
|
901
2123
|
hostname: "127.0.0.1",
|
|
902
2124
|
port: localPort,
|
|
903
2125
|
path: fullPath,
|
|
904
|
-
method,
|
|
2126
|
+
method: "GET",
|
|
905
2127
|
headers: localHeaders
|
|
906
|
-
};
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
2128
|
+
});
|
|
2129
|
+
req.on("upgrade", (res, sk) => {
|
|
2130
|
+
upstream = sk;
|
|
2131
|
+
const respHeaders = {};
|
|
2132
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
2133
|
+
if (v == null) continue;
|
|
2134
|
+
respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
2135
|
+
}
|
|
2136
|
+
sendControl(sock, {
|
|
2137
|
+
type: "stream_response",
|
|
2138
|
+
stream_id: streamId,
|
|
2139
|
+
status: res.statusCode || 101,
|
|
2140
|
+
headers: respHeaders,
|
|
2141
|
+
websocket: true
|
|
2142
|
+
});
|
|
2143
|
+
log(`${colors.dim}${(/* @__PURE__ */ new Date()).toLocaleTimeString()}${colors.reset} ${colors.cyan}WS${colors.reset} ${reqPath} \u2192 101`);
|
|
2144
|
+
const upstreamParser = new UpstreamWsFrameParser({
|
|
2145
|
+
onPayload: (payload) => {
|
|
2146
|
+
if (cancelled) return;
|
|
2147
|
+
sendBinary(sock, streamId, payload);
|
|
2148
|
+
},
|
|
2149
|
+
onPing: (payload) => {
|
|
2150
|
+
if (cancelled || !upstream) return;
|
|
2151
|
+
upstream.write(createUpstreamPongFrame(payload));
|
|
2152
|
+
},
|
|
2153
|
+
onClose: () => {
|
|
2154
|
+
if (cancelled) return;
|
|
2155
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId });
|
|
2156
|
+
streams.delete(streamId);
|
|
2157
|
+
},
|
|
2158
|
+
onError: (err) => {
|
|
2159
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_frame_error: ${err.message}` });
|
|
2160
|
+
streams.delete(streamId);
|
|
929
2161
|
}
|
|
930
2162
|
});
|
|
2163
|
+
sk.on("data", (chunk) => {
|
|
2164
|
+
if (cancelled) return;
|
|
2165
|
+
upstreamParser.feed(chunk);
|
|
2166
|
+
});
|
|
2167
|
+
sk.on("close", () => {
|
|
2168
|
+
if (cancelled) return;
|
|
2169
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId });
|
|
2170
|
+
streams.delete(streamId);
|
|
2171
|
+
});
|
|
2172
|
+
sk.on("error", (err) => {
|
|
2173
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
|
|
2174
|
+
streams.delete(streamId);
|
|
2175
|
+
});
|
|
931
2176
|
});
|
|
932
|
-
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
headers: { "content-type": "application/json" },
|
|
938
|
-
body: Buffer.from(JSON.stringify({ error: `Local server error: ${err.message}` })).toString("base64")
|
|
939
|
-
};
|
|
940
|
-
try {
|
|
941
|
-
sock.write(createWsTextFrame(JSON.stringify(response)));
|
|
942
|
-
} catch {
|
|
2177
|
+
req.on("response", (res) => {
|
|
2178
|
+
const respHeaders = {};
|
|
2179
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
2180
|
+
if (v == null) continue;
|
|
2181
|
+
respHeaders[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
943
2182
|
}
|
|
944
|
-
|
|
2183
|
+
sendControl(sock, {
|
|
2184
|
+
type: "stream_response",
|
|
2185
|
+
stream_id: streamId,
|
|
2186
|
+
status: res.statusCode || 502,
|
|
2187
|
+
headers: respHeaders,
|
|
2188
|
+
websocket: false
|
|
2189
|
+
});
|
|
2190
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId });
|
|
2191
|
+
streams.delete(streamId);
|
|
2192
|
+
});
|
|
2193
|
+
req.on("error", (err) => {
|
|
2194
|
+
sendControl(sock, { type: "stream_close", stream_id: streamId, error: `upstream_error: ${err.message}` });
|
|
2195
|
+
streams.delete(streamId);
|
|
945
2196
|
});
|
|
946
|
-
|
|
947
|
-
|
|
2197
|
+
req.end();
|
|
2198
|
+
const forwarder = {
|
|
2199
|
+
kind: "ws",
|
|
2200
|
+
// Encode the v2 payload as an upstream WS frame BEFORE writing to
|
|
2201
|
+
// the socket. The prior implementation wrote raw payload to the
|
|
2202
|
+
// socket which is not a valid WS frame; upstream WS servers (e.g.
|
|
2203
|
+
// ComfyUI) would error out. Default to TEXT opcode — the v2
|
|
2204
|
+
// protocol does not propagate the original opcode from the client,
|
|
2205
|
+
// and most WS APIs (JSON-based) use text. Binary client→upstream
|
|
2206
|
+
// is a known limitation pending opcode propagation in v2.
|
|
2207
|
+
feedBody: (chunk) => {
|
|
2208
|
+
if (cancelled || !upstream) return;
|
|
2209
|
+
upstream.write(createUpstreamTextFrame(chunk));
|
|
2210
|
+
},
|
|
2211
|
+
endBody: () => {
|
|
2212
|
+
},
|
|
2213
|
+
cancel: () => {
|
|
2214
|
+
cancelled = true;
|
|
2215
|
+
try {
|
|
2216
|
+
upstream?.destroy();
|
|
2217
|
+
} catch {
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
streams.set(streamId, forwarder);
|
|
2222
|
+
}
|
|
2223
|
+
async function handleMessage(msg, sock, localPort) {
|
|
2224
|
+
switch (msg.type) {
|
|
2225
|
+
case "tunnel_ready": {
|
|
2226
|
+
const proxyToken = typeof msg.proxy_token === "string" ? msg.proxy_token : "";
|
|
2227
|
+
const tunnelUrl = typeof msg.url === "string" ? msg.url : "";
|
|
2228
|
+
const isPublic = msg.public === true;
|
|
2229
|
+
success(`\uD130\uB110 \uD65C\uC131\uD654!`);
|
|
2230
|
+
log(`${colors.green}\u2192${colors.reset} URL: ${colors.cyan}${tunnelUrl}${colors.reset}`);
|
|
2231
|
+
log(`${colors.green}\u2192${colors.reset} \uB85C\uCEEC: ${colors.cyan}http://localhost:${localPort}${colors.reset}`);
|
|
2232
|
+
if (msg.timeout || msg.max_body) {
|
|
2233
|
+
log(`${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}`);
|
|
2234
|
+
}
|
|
2235
|
+
if (isPublic) {
|
|
2236
|
+
log("");
|
|
2237
|
+
log(`${colors.yellow}\u26A0 \uACF5\uAC1C \uBAA8\uB4DC (--public):${colors.reset} proxy_token \uAC80\uC99D\uC774 \uBE44\uD65C\uC131\uD654\uB410\uC2B5\uB2C8\uB2E4.`);
|
|
2238
|
+
log(` ${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}`);
|
|
2239
|
+
log(` ${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}`);
|
|
2240
|
+
log("");
|
|
2241
|
+
log(`${colors.dim} $ curl ${tunnelUrl}/${colors.reset}`);
|
|
2242
|
+
} else if (tunnelOpts?.showToken && proxyToken && tunnelUrl) {
|
|
2243
|
+
log("");
|
|
2244
|
+
log(`${colors.green}\u2192${colors.reset} \uD1A0\uD070: ${colors.yellow}${proxyToken}${colors.reset}`);
|
|
2245
|
+
log(`
|
|
2246
|
+
${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}`);
|
|
2247
|
+
log(`${colors.dim} $ curl -H "X-Proxy-Token: ${proxyToken}" ${tunnelUrl}/${colors.reset}`);
|
|
2248
|
+
log(`${colors.dim} $ curl "${tunnelUrl}/?proxy_token=${proxyToken}"${colors.reset}`);
|
|
2249
|
+
}
|
|
2250
|
+
if (tunnelOpts?.label) {
|
|
2251
|
+
const tunnelId = typeof msg.tunnel_id === "string" ? msg.tunnel_id : "";
|
|
2252
|
+
if (!tunnelId) {
|
|
2253
|
+
error(`tunnel_ready \uBA54\uC2DC\uC9C0\uC5D0 tunnel_id \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 endpoint "${tunnelOpts.label}" \uC790\uB3D9 \uB4F1\uB85D skip`);
|
|
2254
|
+
} else {
|
|
2255
|
+
await registerEndpointBinding(
|
|
2256
|
+
config.baseUrl,
|
|
2257
|
+
appId,
|
|
2258
|
+
tunnelKey,
|
|
2259
|
+
tunnelId,
|
|
2260
|
+
tunnelOpts.label,
|
|
2261
|
+
tunnelOpts.description
|
|
2262
|
+
);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
log(`
|
|
2266
|
+
${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
|
|
2267
|
+
`);
|
|
2268
|
+
break;
|
|
2269
|
+
}
|
|
2270
|
+
case "stream_open": {
|
|
2271
|
+
const sid = String(msg.stream_id ?? "");
|
|
2272
|
+
if (!sid) {
|
|
2273
|
+
warn("stream_open with empty stream_id");
|
|
2274
|
+
break;
|
|
2275
|
+
}
|
|
2276
|
+
const kind = msg.kind || "http";
|
|
2277
|
+
if (kind === "ws") {
|
|
2278
|
+
startWSStream(sock, sid, msg, localPort);
|
|
2279
|
+
} else {
|
|
2280
|
+
startHTTPStream(sock, sid, msg, localPort);
|
|
2281
|
+
}
|
|
2282
|
+
break;
|
|
2283
|
+
}
|
|
2284
|
+
case "stream_eof": {
|
|
2285
|
+
const sid = String(msg.stream_id ?? "");
|
|
2286
|
+
const side = msg.side;
|
|
2287
|
+
if (side === "client") {
|
|
2288
|
+
const f = streams.get(sid);
|
|
2289
|
+
f?.endBody();
|
|
2290
|
+
}
|
|
2291
|
+
break;
|
|
2292
|
+
}
|
|
2293
|
+
case "stream_close": {
|
|
2294
|
+
const sid = String(msg.stream_id ?? "");
|
|
2295
|
+
const f = streams.get(sid);
|
|
2296
|
+
if (f) {
|
|
2297
|
+
f.cancel();
|
|
2298
|
+
streams.delete(sid);
|
|
2299
|
+
}
|
|
2300
|
+
break;
|
|
2301
|
+
}
|
|
2302
|
+
case "tunnel_error": {
|
|
2303
|
+
const result = handleTunnelError(msg, appId, localPort);
|
|
2304
|
+
error(result.message);
|
|
2305
|
+
if (result.action === "exit") {
|
|
2306
|
+
shouldReconnect = false;
|
|
2307
|
+
setTimeout(() => process.exit(result.exitCode ?? 1), 500);
|
|
2308
|
+
}
|
|
2309
|
+
break;
|
|
2310
|
+
}
|
|
2311
|
+
case "ping":
|
|
2312
|
+
sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
|
|
2313
|
+
break;
|
|
948
2314
|
}
|
|
949
|
-
localReq.end();
|
|
950
2315
|
}
|
|
951
2316
|
connect();
|
|
952
2317
|
await new Promise(() => {
|
|
@@ -954,14 +2319,16 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
|
|
|
954
2319
|
}
|
|
955
2320
|
function showHelp() {
|
|
956
2321
|
log(`
|
|
957
|
-
${colors.cyan}connectbase
|
|
2322
|
+
${colors.cyan}connectbase${colors.reset} - Connect Base SDK & CLI
|
|
958
2323
|
|
|
959
2324
|
${colors.yellow}\uC0AC\uC6A9\uBC95:${colors.reset}
|
|
960
|
-
npx connectbase
|
|
2325
|
+
npx connectbase <command> [options]
|
|
961
2326
|
|
|
962
2327
|
${colors.yellow}\uBA85\uB839\uC5B4:${colors.reset}
|
|
963
|
-
init \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654 (\uC124\uC815 \
|
|
964
|
-
|
|
2328
|
+
init \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654 (\uC571 \uC0DD\uC131, MCP \uC124\uC815, SDK \uBB38\uC11C \uB2E4\uC6B4\uB85C\uB4DC)
|
|
2329
|
+
update SDK \uBC84\uC804 \uCCB4\uD06C + \uBB38\uC11C/MCP \uC77C\uAD04 \uC5C5\uB370\uC774\uD2B8 (\uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8 \uC9C0\uC6D0)
|
|
2330
|
+
docs SDK \uBB38\uC11C \uB2E4\uC6B4\uB85C\uB4DC/\uC5C5\uB370\uC774\uD2B8 (\uBAA8\uB178\uB808\uD3EC \uC790\uB3D9 \uAC10\uC9C0)
|
|
2331
|
+
mcp MCP \uC11C\uBC84 \uC124\uC815 (.mcp.json \uC0DD\uC131/\uC5C5\uB370\uC774\uD2B8, \uBAA8\uB178\uB808\uD3EC \uC790\uB3D9 \uAC10\uC9C0)
|
|
965
2332
|
deploy <directory> \uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0\uC5D0 \uD30C\uC77C \uBC30\uD3EC (--dev: Dev \uD658\uACBD)
|
|
966
2333
|
tunnel <port> \uB85C\uCEEC \uC11C\uBE44\uC2A4\uB97C \uC778\uD130\uB137\uC5D0 \uB178\uCD9C
|
|
967
2334
|
|
|
@@ -974,27 +2341,45 @@ ${colors.yellow}\uCF54\uB4DC \uBD84\uC11D (MCP \uD1B5\uD574 \uC0AC\uC6A9):${colo
|
|
|
974
2341
|
|
|
975
2342
|
${colors.yellow}\uC635\uC158:${colors.reset}
|
|
976
2343
|
-s, --storage <id> \uC2A4\uD1A0\uB9AC\uC9C0 ID
|
|
977
|
-
-k, --
|
|
2344
|
+
-k, --public-key <key> API Key
|
|
978
2345
|
-u, --base-url <url> \uC11C\uBC84 URL (\uAE30\uBCF8: ${DEFAULT_BASE_URL})
|
|
979
2346
|
-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)
|
|
980
2347
|
--max-body <MB> \uD130\uB110 \uCD5C\uB300 \uBC14\uB514 \uD06C\uAE30 (MB, tunnel \uC804\uC6A9)
|
|
2348
|
+
--force \uD130\uB110 lockfile \uBB34\uC2DC (\uC911\uBCF5 \uC2E4\uD589 \uAC15\uC81C, tunnel \uC804\uC6A9)
|
|
2349
|
+
--public proxy_token \uAC80\uC99D \uBE44\uD65C\uC131\uD654 \u2014 \uC6F9\uD6C5/\uC678\uBD80 \uC9C1\uC811 \uD638\uCD9C\uC6A9 (tunnel \uC804\uC6A9)
|
|
2350
|
+
--show-token proxy_token \uACFC curl \uC608\uC2DC\uB97C \uCD9C\uB825 (--public \uC544\uB2CC \uACBD\uC6B0)
|
|
2351
|
+
--label <name> Endpoint binding \uC790\uB3D9 \uB4F1\uB85D (tunnel \uC804\uC6A9) \u2014 SDK \uC758 cb.endpoint.call(label) \uD638\uCD9C \uAC00\uB2A5
|
|
2352
|
+
--description <text> Endpoint binding \uC124\uBA85 (--label \uB3D9\uBC18 \uC2DC\uB9CC)
|
|
981
2353
|
-d, --dev Dev \uD658\uACBD\uC5D0 \uBC30\uD3EC (deploy \uC804\uC6A9)
|
|
982
|
-
--
|
|
2354
|
+
--check \uBC84\uC804\uB9CC \uD655\uC778 (update \uC804\uC6A9)
|
|
2355
|
+
--skip-docs \uBB38\uC11C \uC5C5\uB370\uC774\uD2B8 \uAC74\uB108\uB6F0\uAE30 (update \uC804\uC6A9)
|
|
2356
|
+
--skip-mcp MCP \uC5C5\uB370\uC774\uD2B8 \uAC74\uB108\uB6F0\uAE30 (update \uC804\uC6A9)
|
|
2357
|
+
--setup-root \uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 (update \uC804\uC6A9)
|
|
2358
|
+
(docs, mcp, update\uB294 \uBAA8\uB178\uB808\uD3EC\uB97C \uC790\uB3D9 \uAC10\uC9C0\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131)
|
|
983
2359
|
-h, --help \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
|
|
984
2360
|
-v, --version \uBC84\uC804 \uD45C\uC2DC
|
|
985
2361
|
|
|
986
2362
|
${colors.yellow}\uBE60\uB978 \uC2DC\uC791:${colors.reset}
|
|
987
2363
|
${colors.dim}# 1. \uCD08\uAE30\uD654 (\uCD5C\uCD08 1\uD68C)${colors.reset}
|
|
988
|
-
npx connectbase
|
|
2364
|
+
npx connectbase init
|
|
2365
|
+
|
|
2366
|
+
${colors.dim}# 2. SDK \uC804\uCCB4 \uC5C5\uB370\uC774\uD2B8 (\uBC84\uC804 \uCCB4\uD06C + \uBB38\uC11C + MCP)${colors.reset}
|
|
2367
|
+
npx connectbase update
|
|
2368
|
+
|
|
2369
|
+
${colors.dim}# 2-1. \uBAA8\uB178\uB808\uD3EC \uB8E8\uD2B8\uC5D0 \uD3B8\uC758 \uC2A4\uD06C\uB9BD\uD2B8 \uC124\uCE58${colors.reset}
|
|
2370
|
+
npx connectbase update --setup-root
|
|
989
2371
|
|
|
990
|
-
${colors.dim}#
|
|
2372
|
+
${colors.dim}# 3. SDK \uBB38\uC11C\uB9CC \uC5C5\uB370\uC774\uD2B8${colors.reset}
|
|
991
2373
|
npx connectbase docs
|
|
992
2374
|
|
|
993
|
-
${colors.dim}#
|
|
2375
|
+
${colors.dim}# 4. MCP \uC11C\uBC84 \uC124\uC815${colors.reset}
|
|
2376
|
+
npx connectbase mcp
|
|
2377
|
+
|
|
2378
|
+
${colors.dim}# 4. Prod \uBC30\uD3EC${colors.reset}
|
|
994
2379
|
npm run deploy
|
|
995
2380
|
|
|
996
2381
|
${colors.dim}# 4. Dev \uD658\uACBD \uBC30\uD3EC (\uB0B4\uBD80 QA\uC6A9)${colors.reset}
|
|
997
|
-
npx connectbase
|
|
2382
|
+
npx connectbase deploy ./dist --dev
|
|
998
2383
|
|
|
999
2384
|
${colors.dim}# 5. \uD130\uB110 (\uAE30\uBCF8)${colors.reset}
|
|
1000
2385
|
npx connectbase tunnel 3000
|
|
@@ -1003,13 +2388,16 @@ ${colors.yellow}\uBE60\uB978 \uC2DC\uC791:${colors.reset}
|
|
|
1003
2388
|
npx connectbase tunnel 7860 --timeout 300 --max-body 50
|
|
1004
2389
|
|
|
1005
2390
|
${colors.yellow}\uD658\uACBD\uBCC0\uC218:${colors.reset}
|
|
1006
|
-
|
|
2391
|
+
CONNECTBASE_PUBLIC_KEY Public Key (cb_pk_*)
|
|
2392
|
+
CONNECTBASE_SECRET_KEY Secret Key (cb_sk_*, \uD130\uB110\uC6A9)
|
|
2393
|
+
CONNECTBASE_PUBLIC_KEY (deprecated) \uB808\uAC70\uC2DC \u2014 publicKey \uB610\uB294 secretKey \uB85C \uC0AC\uC6A9
|
|
1007
2394
|
CONNECTBASE_STORAGE_ID \uC2A4\uD1A0\uB9AC\uC9C0 ID
|
|
1008
2395
|
CONNECTBASE_BASE_URL \uC11C\uBC84 URL
|
|
1009
2396
|
|
|
1010
2397
|
${colors.yellow}\uC124\uC815 \uD30C\uC77C (.connectbaserc):${colors.reset}
|
|
1011
2398
|
{
|
|
1012
|
-
"
|
|
2399
|
+
"publicKey": "cb_pk_...",
|
|
2400
|
+
"secretKey": "cb_sk_...",
|
|
1013
2401
|
"storageId": "your-storage-id",
|
|
1014
2402
|
"deployDir": "./dist"
|
|
1015
2403
|
}
|
|
@@ -1025,18 +2413,38 @@ function parseArgs(args) {
|
|
|
1025
2413
|
const arg = args[i];
|
|
1026
2414
|
if (arg === "-s" || arg === "--storage") {
|
|
1027
2415
|
result.options.storageId = args[++i];
|
|
1028
|
-
} else if (arg === "-k" || arg === "--
|
|
1029
|
-
result.options.
|
|
2416
|
+
} else if (arg === "-k" || arg === "--public-key" || arg === "--public-key") {
|
|
2417
|
+
result.options.publicKey = args[++i];
|
|
2418
|
+
} else if (arg === "--secret-key") {
|
|
2419
|
+
result.options.secretKey = args[++i];
|
|
1030
2420
|
} else if (arg === "-u" || arg === "--base-url") {
|
|
1031
2421
|
result.options.baseUrl = args[++i];
|
|
1032
2422
|
} else if (arg === "-t" || arg === "--timeout") {
|
|
1033
2423
|
result.options.timeout = args[++i];
|
|
1034
2424
|
} else if (arg === "--max-body") {
|
|
1035
2425
|
result.options.maxBody = args[++i];
|
|
2426
|
+
} else if (arg === "--label") {
|
|
2427
|
+
result.options.label = args[++i];
|
|
2428
|
+
} else if (arg === "--description") {
|
|
2429
|
+
result.options.description = args[++i];
|
|
2430
|
+
} else if (arg === "-a" || arg === "--app") {
|
|
2431
|
+
result.options.appId = args[++i];
|
|
2432
|
+
} else if (arg === "--force") {
|
|
2433
|
+
result.options.force = "true";
|
|
2434
|
+
} else if (arg === "--public") {
|
|
2435
|
+
result.options.public = "true";
|
|
2436
|
+
} else if (arg === "--show-token") {
|
|
2437
|
+
result.options.showToken = "true";
|
|
1036
2438
|
} else if (arg === "-d" || arg === "--dev") {
|
|
1037
2439
|
result.options.dev = "true";
|
|
1038
|
-
} else if (arg === "--
|
|
1039
|
-
result.options.
|
|
2440
|
+
} else if (arg === "--check") {
|
|
2441
|
+
result.options.check = "true";
|
|
2442
|
+
} else if (arg === "--skip-docs") {
|
|
2443
|
+
result.options.skipDocs = "true";
|
|
2444
|
+
} else if (arg === "--skip-mcp") {
|
|
2445
|
+
result.options.skipMcp = "true";
|
|
2446
|
+
} else if (arg === "--setup-root") {
|
|
2447
|
+
result.options.setupRoot = "true";
|
|
1040
2448
|
} else if (arg === "-h" || arg === "--help") {
|
|
1041
2449
|
result.options.help = "true";
|
|
1042
2450
|
} else if (arg === "-v" || arg === "--version") {
|
|
@@ -1055,7 +2463,7 @@ function parseArgs(args) {
|
|
|
1055
2463
|
async function main() {
|
|
1056
2464
|
const parsed = parseArgs(process.argv.slice(2));
|
|
1057
2465
|
if (parsed.options.version) {
|
|
1058
|
-
log(`connectbase
|
|
2466
|
+
log(`connectbase v${VERSION}`);
|
|
1059
2467
|
return;
|
|
1060
2468
|
}
|
|
1061
2469
|
if (parsed.options.help || !parsed.command) {
|
|
@@ -1064,30 +2472,32 @@ async function main() {
|
|
|
1064
2472
|
}
|
|
1065
2473
|
const fileConfig = loadConfig();
|
|
1066
2474
|
const config = {
|
|
1067
|
-
|
|
2475
|
+
publicKey: parsed.options.publicKey || fileConfig.publicKey,
|
|
2476
|
+
secretKey: parsed.options.secretKey || fileConfig.secretKey,
|
|
1068
2477
|
storageId: parsed.options.storageId || fileConfig.storageId,
|
|
1069
2478
|
baseUrl: parsed.options.baseUrl || fileConfig.baseUrl || DEFAULT_BASE_URL
|
|
1070
2479
|
};
|
|
1071
2480
|
if (parsed.command === "init") {
|
|
1072
2481
|
await init();
|
|
2482
|
+
} else if (parsed.command === "update") {
|
|
2483
|
+
await update(config, {
|
|
2484
|
+
checkOnly: parsed.options.check === "true",
|
|
2485
|
+
skipDocs: parsed.options.skipDocs === "true",
|
|
2486
|
+
skipMcp: parsed.options.skipMcp === "true",
|
|
2487
|
+
setupRoot: parsed.options.setupRoot === "true"
|
|
2488
|
+
});
|
|
1073
2489
|
} else if (parsed.command === "docs") {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (!docsApiKey) {
|
|
1078
|
-
error("API Key\uB294 \uD544\uC218\uC785\uB2C8\uB2E4");
|
|
1079
|
-
process.exit(1);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
await downloadDocs(docsApiKey, void 0, parsed.options.monorepo === "true");
|
|
2490
|
+
await downloadDocs(config.publicKey || "");
|
|
2491
|
+
} else if (parsed.command === "mcp") {
|
|
2492
|
+
await setupMcp();
|
|
1083
2493
|
} else if (parsed.command === "deploy") {
|
|
1084
2494
|
const directory = parsed.args[0] || fileConfig.deployDir || ".";
|
|
1085
|
-
if (!config.
|
|
1086
|
-
error('
|
|
2495
|
+
if (!config.publicKey) {
|
|
2496
|
+
error('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');
|
|
1087
2497
|
process.exit(1);
|
|
1088
2498
|
}
|
|
1089
2499
|
if (!config.storageId) {
|
|
1090
|
-
error('\uC2A4\uD1A0\uB9AC\uC9C0 ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. "npx connectbase
|
|
2500
|
+
error('\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');
|
|
1091
2501
|
process.exit(1);
|
|
1092
2502
|
}
|
|
1093
2503
|
const isDev = parsed.options.dev === "true";
|
|
@@ -1101,7 +2511,7 @@ async function main() {
|
|
|
1101
2511
|
} else if (parsed.command === "tunnel") {
|
|
1102
2512
|
const portStr = parsed.args[0];
|
|
1103
2513
|
if (!portStr) {
|
|
1104
|
-
error("\uD3EC\uD2B8 \uBC88\uD638\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: npx connectbase
|
|
2514
|
+
error("\uD3EC\uD2B8 \uBC88\uD638\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: npx connectbase tunnel 8084");
|
|
1105
2515
|
process.exit(1);
|
|
1106
2516
|
}
|
|
1107
2517
|
const port = parseInt(portStr, 10);
|
|
@@ -1118,6 +2528,24 @@ async function main() {
|
|
|
1118
2528
|
const m = parseInt(parsed.options.maxBody, 10);
|
|
1119
2529
|
if (!isNaN(m) && m > 0) tunnelOpts.maxBody = m;
|
|
1120
2530
|
}
|
|
2531
|
+
if (parsed.options.appId) {
|
|
2532
|
+
tunnelOpts.appId = parsed.options.appId;
|
|
2533
|
+
}
|
|
2534
|
+
if (parsed.options.force === "true") {
|
|
2535
|
+
tunnelOpts.force = true;
|
|
2536
|
+
}
|
|
2537
|
+
if (parsed.options.public === "true") {
|
|
2538
|
+
tunnelOpts.public = true;
|
|
2539
|
+
}
|
|
2540
|
+
if (parsed.options.showToken === "true") {
|
|
2541
|
+
tunnelOpts.showToken = true;
|
|
2542
|
+
}
|
|
2543
|
+
if (parsed.options.label) {
|
|
2544
|
+
tunnelOpts.label = parsed.options.label;
|
|
2545
|
+
}
|
|
2546
|
+
if (parsed.options.description) {
|
|
2547
|
+
tunnelOpts.description = parsed.options.description;
|
|
2548
|
+
}
|
|
1121
2549
|
await startTunnel(port, config, tunnelOpts);
|
|
1122
2550
|
} else {
|
|
1123
2551
|
error(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC5B4: ${parsed.command}`);
|
|
@@ -1129,3 +2557,11 @@ main().catch((err) => {
|
|
|
1129
2557
|
error(err.message);
|
|
1130
2558
|
process.exit(1);
|
|
1131
2559
|
});
|
|
2560
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2561
|
+
0 && (module.exports = {
|
|
2562
|
+
computeDeployDiff,
|
|
2563
|
+
normalizeRelativePath,
|
|
2564
|
+
parseArgs,
|
|
2565
|
+
registerEndpointBinding,
|
|
2566
|
+
sha256Hex
|
|
2567
|
+
});
|