connectbase-client 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +89 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var fs = __toESM(require("fs"));
|
|
28
28
|
var path = __toESM(require("path"));
|
|
29
|
+
var os = __toESM(require("os"));
|
|
29
30
|
var crypto = __toESM(require("crypto"));
|
|
30
31
|
var https = __toESM(require("https"));
|
|
31
32
|
var http = __toESM(require("http"));
|
|
@@ -355,11 +356,11 @@ function getProjectRoot() {
|
|
|
355
356
|
var CONSOLE_URL = DEFAULT_BASE_URL.replace("api.", "");
|
|
356
357
|
function openBrowser(url) {
|
|
357
358
|
const { exec } = require("child_process");
|
|
358
|
-
const
|
|
359
|
+
const platform2 = process.platform;
|
|
359
360
|
let command;
|
|
360
|
-
if (
|
|
361
|
+
if (platform2 === "darwin") {
|
|
361
362
|
command = `open "${url}"`;
|
|
362
|
-
} else if (
|
|
363
|
+
} else if (platform2 === "win32") {
|
|
363
364
|
command = `start "" "${url}"`;
|
|
364
365
|
} else {
|
|
365
366
|
command = `xdg-open "${url}"`;
|
|
@@ -1146,6 +1147,68 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1146
1147
|
success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
|
|
1147
1148
|
return createData.app_id;
|
|
1148
1149
|
}
|
|
1150
|
+
function getTunnelLockDir() {
|
|
1151
|
+
const platform2 = os.platform();
|
|
1152
|
+
if (platform2 === "darwin") {
|
|
1153
|
+
return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
|
|
1154
|
+
} else if (platform2 === "win32") {
|
|
1155
|
+
return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
|
|
1156
|
+
}
|
|
1157
|
+
const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
1158
|
+
return path.join(stateHome, "connectbase", "tunnel-locks");
|
|
1159
|
+
}
|
|
1160
|
+
function isProcessAlive(pid) {
|
|
1161
|
+
try {
|
|
1162
|
+
process.kill(pid, 0);
|
|
1163
|
+
return true;
|
|
1164
|
+
} catch (e) {
|
|
1165
|
+
return e.code === "EPERM";
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function acquireTunnelLock(appID, port, force) {
|
|
1169
|
+
const lockDir = getTunnelLockDir();
|
|
1170
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
1171
|
+
const lockPath = path.join(lockDir, `${appID}-${port}.lock`);
|
|
1172
|
+
const lockData = {
|
|
1173
|
+
pid: process.pid,
|
|
1174
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1175
|
+
appID,
|
|
1176
|
+
port,
|
|
1177
|
+
host: os.hostname(),
|
|
1178
|
+
version: VERSION
|
|
1179
|
+
};
|
|
1180
|
+
if (!force) {
|
|
1181
|
+
try {
|
|
1182
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
1183
|
+
fs.writeSync(fd, JSON.stringify(lockData, null, 2));
|
|
1184
|
+
fs.closeSync(fd);
|
|
1185
|
+
return lockPath;
|
|
1186
|
+
} catch (e) {
|
|
1187
|
+
if (e.code !== "EEXIST") {
|
|
1188
|
+
warn(`lockfile \uC0DD\uC131 \uC2E4\uD328: ${e.message}`);
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
try {
|
|
1193
|
+
const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
1194
|
+
if (isProcessAlive(existing.pid)) {
|
|
1195
|
+
error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${existing.pid}, \uC2DC\uC791 ${existing.startedAt}, \uD638\uC2A4\uD2B8 ${existing.host}`);
|
|
1196
|
+
info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
|
|
1197
|
+
process.exit(2);
|
|
1198
|
+
}
|
|
1199
|
+
warn(`stale lockfile \uBC1C\uACAC (PID ${existing.pid} \uC885\uB8CC\uB428), \uB36E\uC5B4\uC501\uB2C8\uB2E4`);
|
|
1200
|
+
} catch {
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
|
|
1204
|
+
return lockPath;
|
|
1205
|
+
}
|
|
1206
|
+
function releaseTunnelLock(lockPath) {
|
|
1207
|
+
try {
|
|
1208
|
+
fs.unlinkSync(lockPath);
|
|
1209
|
+
} catch {
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1149
1212
|
async function startTunnel(port, config, tunnelOpts) {
|
|
1150
1213
|
let tunnelKey = config.secretKey || (config.publicKey?.startsWith("cb_sk_") ? config.publicKey : "");
|
|
1151
1214
|
if (!tunnelKey) {
|
|
@@ -1198,6 +1261,7 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
1198
1261
|
}
|
|
1199
1262
|
} catch {
|
|
1200
1263
|
}
|
|
1264
|
+
const lockPath = acquireTunnelLock(appId, port, tunnelOpts?.force ?? false);
|
|
1201
1265
|
const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
|
|
1202
1266
|
const parsedUrl = new URL(tunnelServerUrl);
|
|
1203
1267
|
const isHttps = parsedUrl.protocol === "https:";
|
|
@@ -1212,8 +1276,17 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
1212
1276
|
const maxReconnectAttempts = 10;
|
|
1213
1277
|
let shouldReconnect = true;
|
|
1214
1278
|
let socket = null;
|
|
1279
|
+
let lockReleased = false;
|
|
1280
|
+
const releaseLock = () => {
|
|
1281
|
+
if (lockPath && !lockReleased) {
|
|
1282
|
+
lockReleased = true;
|
|
1283
|
+
releaseTunnelLock(lockPath);
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
process.on("exit", releaseLock);
|
|
1215
1287
|
const cleanup = () => {
|
|
1216
1288
|
shouldReconnect = false;
|
|
1289
|
+
releaseLock();
|
|
1217
1290
|
if (socket) {
|
|
1218
1291
|
try {
|
|
1219
1292
|
socket.write(createWsCloseFrame(1e3));
|
|
@@ -1344,7 +1417,13 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
|
|
|
1344
1417
|
forwardRequest(msg, sock, localPort);
|
|
1345
1418
|
break;
|
|
1346
1419
|
case "tunnel_error":
|
|
1347
|
-
|
|
1420
|
+
if (msg.code === "replaced") {
|
|
1421
|
+
error(`\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.`);
|
|
1422
|
+
shouldReconnect = false;
|
|
1423
|
+
setTimeout(() => process.exit(1), 500);
|
|
1424
|
+
} else {
|
|
1425
|
+
error(`\uD130\uB110 \uC5D0\uB7EC: ${msg.error || msg.message || "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC"}`);
|
|
1426
|
+
}
|
|
1348
1427
|
break;
|
|
1349
1428
|
case "ping":
|
|
1350
1429
|
sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
|
|
@@ -1497,6 +1576,7 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
|
|
|
1497
1576
|
-u, --base-url <url> \uC11C\uBC84 URL (\uAE30\uBCF8: ${DEFAULT_BASE_URL})
|
|
1498
1577
|
-t, --timeout <sec> \uD130\uB110 \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel \uC804\uC6A9)
|
|
1499
1578
|
--max-body <MB> \uD130\uB110 \uCD5C\uB300 \uBC14\uB514 \uD06C\uAE30 (MB, tunnel \uC804\uC6A9)
|
|
1579
|
+
--force \uD130\uB110 lockfile \uBB34\uC2DC (\uC911\uBCF5 \uC2E4\uD589 \uAC15\uC81C, tunnel \uC804\uC6A9)
|
|
1500
1580
|
-d, --dev Dev \uD658\uACBD\uC5D0 \uBC30\uD3EC (deploy \uC804\uC6A9)
|
|
1501
1581
|
(docs, mcp\uB294 \uBAA8\uB178\uB808\uD3EC\uB97C \uC790\uB3D9 \uAC10\uC9C0\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131)
|
|
1502
1582
|
-h, --help \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
|
|
@@ -1562,6 +1642,8 @@ function parseArgs(args) {
|
|
|
1562
1642
|
result.options.maxBody = args[++i];
|
|
1563
1643
|
} else if (arg === "-a" || arg === "--app") {
|
|
1564
1644
|
result.options.appId = args[++i];
|
|
1645
|
+
} else if (arg === "--force") {
|
|
1646
|
+
result.options.force = "true";
|
|
1565
1647
|
} else if (arg === "-d" || arg === "--dev") {
|
|
1566
1648
|
result.options.dev = "true";
|
|
1567
1649
|
} else if (arg === "-h" || arg === "--help") {
|
|
@@ -1645,6 +1727,9 @@ async function main() {
|
|
|
1645
1727
|
if (parsed.options.appId) {
|
|
1646
1728
|
tunnelOpts.appId = parsed.options.appId;
|
|
1647
1729
|
}
|
|
1730
|
+
if (parsed.options.force === "true") {
|
|
1731
|
+
tunnelOpts.force = true;
|
|
1732
|
+
}
|
|
1648
1733
|
await startTunnel(port, config, tunnelOpts);
|
|
1649
1734
|
} else {
|
|
1650
1735
|
error(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC5B4: ${parsed.command}`);
|