connectbase-client 0.13.0 → 0.15.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 +203 -93
- package/dist/connect-base.umd.js +3 -3
- package/dist/index.d.mts +137 -1
- package/dist/index.d.ts +137 -1
- package/dist/index.js +591 -0
- package/dist/index.mjs +590 -0
- package/package.json +4 -2
package/dist/cli.js
CHANGED
|
@@ -24,12 +24,90 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
27
|
+
var fs2 = __toESM(require("fs"));
|
|
28
|
+
var path2 = __toESM(require("path"));
|
|
29
29
|
var crypto = __toESM(require("crypto"));
|
|
30
30
|
var https = __toESM(require("https"));
|
|
31
31
|
var http = __toESM(require("http"));
|
|
32
32
|
var readline = __toESM(require("readline"));
|
|
33
|
+
|
|
34
|
+
// src/tunnel-utils.ts
|
|
35
|
+
var fs = __toESM(require("fs"));
|
|
36
|
+
var path = __toESM(require("path"));
|
|
37
|
+
var os = __toESM(require("os"));
|
|
38
|
+
function getTunnelLockDir() {
|
|
39
|
+
const platform2 = os.platform();
|
|
40
|
+
if (platform2 === "darwin") {
|
|
41
|
+
return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
|
|
42
|
+
} else if (platform2 === "win32") {
|
|
43
|
+
return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
|
|
44
|
+
}
|
|
45
|
+
const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
|
|
46
|
+
return path.join(stateHome, "connectbase", "tunnel-locks");
|
|
47
|
+
}
|
|
48
|
+
function isProcessAlive(pid) {
|
|
49
|
+
try {
|
|
50
|
+
process.kill(pid, 0);
|
|
51
|
+
return true;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return e.code === "EPERM";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function acquireTunnelLock(appID, port, force, version) {
|
|
57
|
+
const lockDir = getTunnelLockDir();
|
|
58
|
+
fs.mkdirSync(lockDir, { recursive: true });
|
|
59
|
+
const lockPath = path.join(lockDir, `${appID}-${port}.lock`);
|
|
60
|
+
const lockData = {
|
|
61
|
+
pid: process.pid,
|
|
62
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
+
appID,
|
|
64
|
+
port,
|
|
65
|
+
host: os.hostname(),
|
|
66
|
+
version
|
|
67
|
+
};
|
|
68
|
+
if (!force) {
|
|
69
|
+
try {
|
|
70
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
71
|
+
fs.writeSync(fd, JSON.stringify(lockData, null, 2));
|
|
72
|
+
fs.closeSync(fd);
|
|
73
|
+
return lockPath;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
if (e.code !== "EEXIST") {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
81
|
+
if (isProcessAlive(existing.pid)) {
|
|
82
|
+
return `LOCKED:${existing.pid}:${existing.startedAt}:${existing.host}`;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
|
|
88
|
+
return lockPath;
|
|
89
|
+
}
|
|
90
|
+
function releaseTunnelLock(lockPath) {
|
|
91
|
+
try {
|
|
92
|
+
fs.unlinkSync(lockPath);
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function handleTunnelError(msg, appId, localPort) {
|
|
97
|
+
if (msg.code === "replaced") {
|
|
98
|
+
return {
|
|
99
|
+
action: "exit",
|
|
100
|
+
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.`,
|
|
101
|
+
exitCode: 1
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
action: "warn",
|
|
106
|
+
message: `\uD130\uB110 \uC5D0\uB7EC: ${msg.error || msg.message || "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC"}`
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/cli.ts
|
|
33
111
|
var VERSION = "0.10.6";
|
|
34
112
|
var DEFAULT_BASE_URL = "https://api.connectbase.world";
|
|
35
113
|
var colors = {
|
|
@@ -118,10 +196,10 @@ function loadConfig() {
|
|
|
118
196
|
storageId: process.env.CONNECTBASE_STORAGE_ID,
|
|
119
197
|
baseUrl: process.env.CONNECTBASE_BASE_URL || DEFAULT_BASE_URL
|
|
120
198
|
};
|
|
121
|
-
const rcPath =
|
|
122
|
-
if (
|
|
199
|
+
const rcPath = path2.join(process.cwd(), ".connectbaserc");
|
|
200
|
+
if (fs2.existsSync(rcPath)) {
|
|
123
201
|
try {
|
|
124
|
-
const rcContent = JSON.parse(
|
|
202
|
+
const rcContent = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
|
|
125
203
|
if (rcContent.publicKey) config.publicKey = rcContent.publicKey;
|
|
126
204
|
if (rcContent.secretKey) config.secretKey = rcContent.secretKey;
|
|
127
205
|
if (rcContent.storageId) config.storageId = rcContent.storageId;
|
|
@@ -135,16 +213,16 @@ function loadConfig() {
|
|
|
135
213
|
}
|
|
136
214
|
function collectFiles(dir, baseDir = dir) {
|
|
137
215
|
const files = [];
|
|
138
|
-
const entries =
|
|
216
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
139
217
|
for (const entry of entries) {
|
|
140
|
-
const fullPath =
|
|
141
|
-
const relativePath =
|
|
218
|
+
const fullPath = path2.join(dir, entry.name);
|
|
219
|
+
const relativePath = path2.relative(baseDir, fullPath);
|
|
142
220
|
if (entry.isDirectory()) {
|
|
143
221
|
if (!entry.name.startsWith(".")) {
|
|
144
222
|
files.push(...collectFiles(fullPath, baseDir));
|
|
145
223
|
}
|
|
146
224
|
} else if (entry.isFile()) {
|
|
147
|
-
const ext =
|
|
225
|
+
const ext = path2.extname(entry.name).toLowerCase();
|
|
148
226
|
if (!ALLOWED_EXTENSIONS.has(ext)) {
|
|
149
227
|
continue;
|
|
150
228
|
}
|
|
@@ -154,9 +232,9 @@ function collectFiles(dir, baseDir = dir) {
|
|
|
154
232
|
const isBinary = BINARY_EXTENSIONS.has(ext);
|
|
155
233
|
let content;
|
|
156
234
|
if (isBinary) {
|
|
157
|
-
content =
|
|
235
|
+
content = fs2.readFileSync(fullPath).toString("base64");
|
|
158
236
|
} else {
|
|
159
|
-
content =
|
|
237
|
+
content = fs2.readFileSync(fullPath, "utf-8");
|
|
160
238
|
}
|
|
161
239
|
files.push({
|
|
162
240
|
path: relativePath.replace(/\\/g, "/"),
|
|
@@ -212,15 +290,15 @@ async function makeRequest(url, method, headers, body) {
|
|
|
212
290
|
});
|
|
213
291
|
}
|
|
214
292
|
async function deploy(directory, config, isDev = false) {
|
|
215
|
-
const dir =
|
|
216
|
-
if (!
|
|
293
|
+
const dir = path2.resolve(directory);
|
|
294
|
+
if (!fs2.existsSync(dir)) {
|
|
217
295
|
error(`\uB514\uB809\uD1A0\uB9AC\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
|
|
218
296
|
process.exit(1);
|
|
219
297
|
}
|
|
220
|
-
const pkgPath =
|
|
221
|
-
if (
|
|
298
|
+
const pkgPath = path2.join(process.cwd(), "package.json");
|
|
299
|
+
if (fs2.existsSync(pkgPath)) {
|
|
222
300
|
try {
|
|
223
|
-
const pkg = JSON.parse(
|
|
301
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
224
302
|
if (pkg.scripts?.build) {
|
|
225
303
|
warn(`\uBC30\uD3EC \uC804 \uBE4C\uB4DC\uB97C \uBA3C\uC800 \uC2E4\uD589\uD558\uC138\uC694: ${colors.cyan}npm run build && npx connectbase-client deploy${colors.reset}`);
|
|
226
304
|
warn(`\uB610\uB294 package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uB4F1\uB85D\uD558\uC138\uC694: ${colors.cyan}npx connectbase-client init${colors.reset}`);
|
|
@@ -228,12 +306,12 @@ async function deploy(directory, config, isDev = false) {
|
|
|
228
306
|
} catch {
|
|
229
307
|
}
|
|
230
308
|
}
|
|
231
|
-
if (!
|
|
309
|
+
if (!fs2.statSync(dir).isDirectory()) {
|
|
232
310
|
error(`${dir}\uB294 \uB514\uB809\uD1A0\uB9AC\uAC00 \uC544\uB2D9\uB2C8\uB2E4`);
|
|
233
311
|
process.exit(1);
|
|
234
312
|
}
|
|
235
|
-
const indexPath =
|
|
236
|
-
if (!
|
|
313
|
+
const indexPath = path2.join(dir, "index.html");
|
|
314
|
+
if (!fs2.existsSync(indexPath)) {
|
|
237
315
|
error("index.html \uD30C\uC77C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4");
|
|
238
316
|
process.exit(1);
|
|
239
317
|
}
|
|
@@ -355,11 +433,11 @@ function getProjectRoot() {
|
|
|
355
433
|
var CONSOLE_URL = DEFAULT_BASE_URL.replace("api.", "");
|
|
356
434
|
function openBrowser(url) {
|
|
357
435
|
const { exec } = require("child_process");
|
|
358
|
-
const
|
|
436
|
+
const platform2 = process.platform;
|
|
359
437
|
let command;
|
|
360
|
-
if (
|
|
438
|
+
if (platform2 === "darwin") {
|
|
361
439
|
command = `open "${url}"`;
|
|
362
|
-
} else if (
|
|
440
|
+
} else if (platform2 === "win32") {
|
|
363
441
|
command = `start "" "${url}"`;
|
|
364
442
|
} else {
|
|
365
443
|
command = `xdg-open "${url}"`;
|
|
@@ -449,8 +527,8 @@ ${colors.cyan}Connect Base \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654${colors.r
|
|
|
449
527
|
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
|
|
450
528
|
`);
|
|
451
529
|
}
|
|
452
|
-
const rcPath =
|
|
453
|
-
if (
|
|
530
|
+
const rcPath = path2.join(cwd, ".connectbaserc");
|
|
531
|
+
if (fs2.existsSync(rcPath)) {
|
|
454
532
|
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): `);
|
|
455
533
|
if (overwrite.toLowerCase() !== "y") {
|
|
456
534
|
info("\uCD08\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4");
|
|
@@ -539,7 +617,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
539
617
|
}
|
|
540
618
|
}
|
|
541
619
|
if (!appId) {
|
|
542
|
-
const projectName =
|
|
620
|
+
const projectName = path2.basename(cwd);
|
|
543
621
|
const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
|
|
544
622
|
info("\uC571 \uC0DD\uC131 \uC911...");
|
|
545
623
|
const createRes = await makeRequest(
|
|
@@ -607,7 +685,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
607
685
|
}
|
|
608
686
|
}
|
|
609
687
|
if (!storageId) {
|
|
610
|
-
const projectName =
|
|
688
|
+
const projectName = path2.basename(cwd);
|
|
611
689
|
const name = await prompt(`${colors.blue}?${colors.reset} \uC2A4\uD1A0\uB9AC\uC9C0 \uC774\uB984 (${projectName}): `) || projectName;
|
|
612
690
|
info("\uC6F9 \uC2A4\uD1A0\uB9AC\uC9C0 \uC0DD\uC131 \uC911...");
|
|
613
691
|
const createRes = await makeRequest(
|
|
@@ -647,7 +725,7 @@ ${colors.blue}?${colors.reset} \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
647
725
|
if (secretKey) {
|
|
648
726
|
config.secretKey = secretKey;
|
|
649
727
|
}
|
|
650
|
-
|
|
728
|
+
fs2.writeFileSync(rcPath, JSON.stringify(config, null, 2) + "\n");
|
|
651
729
|
success(".connectbaserc \uC0DD\uC131 \uC644\uB8CC");
|
|
652
730
|
addToGitignore(".connectbaserc");
|
|
653
731
|
addDeployScript(deployDir);
|
|
@@ -662,21 +740,21 @@ ${colors.dim}Claude Code\uC5D0\uC11C MCP\uAC00 \uC790\uB3D9 \uC5F0\uACB0\uB429\u
|
|
|
662
740
|
`);
|
|
663
741
|
}
|
|
664
742
|
function detectBuildDir() {
|
|
665
|
-
if (
|
|
666
|
-
if (
|
|
667
|
-
if (
|
|
668
|
-
if (
|
|
669
|
-
if (
|
|
670
|
-
if (
|
|
743
|
+
if (fs2.existsSync(path2.join(process.cwd(), "dist"))) return "./dist";
|
|
744
|
+
if (fs2.existsSync(path2.join(process.cwd(), "build"))) return "./build";
|
|
745
|
+
if (fs2.existsSync(path2.join(process.cwd(), "out"))) return "./out";
|
|
746
|
+
if (fs2.existsSync(path2.join(process.cwd(), ".next"))) return "./out";
|
|
747
|
+
if (fs2.existsSync(path2.join(process.cwd(), "vite.config.ts")) || fs2.existsSync(path2.join(process.cwd(), "vite.config.js"))) return "./dist";
|
|
748
|
+
if (fs2.existsSync(path2.join(process.cwd(), "next.config.js")) || fs2.existsSync(path2.join(process.cwd(), "next.config.mjs"))) return "./out";
|
|
671
749
|
return "./dist";
|
|
672
750
|
}
|
|
673
751
|
function addToGitignore(entry, basePath) {
|
|
674
752
|
const dir = basePath || process.cwd();
|
|
675
|
-
const gitignorePath =
|
|
676
|
-
if (
|
|
677
|
-
const content =
|
|
753
|
+
const gitignorePath = path2.join(dir, ".gitignore");
|
|
754
|
+
if (fs2.existsSync(gitignorePath)) {
|
|
755
|
+
const content = fs2.readFileSync(gitignorePath, "utf-8");
|
|
678
756
|
if (!content.includes(entry)) {
|
|
679
|
-
|
|
757
|
+
fs2.appendFileSync(gitignorePath, `
|
|
680
758
|
# Connect Base
|
|
681
759
|
${entry}
|
|
682
760
|
`);
|
|
@@ -685,20 +763,20 @@ ${entry}
|
|
|
685
763
|
info(`.gitignore\uC5D0 \uC774\uBBF8 ${entry}\uAC00 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4`);
|
|
686
764
|
}
|
|
687
765
|
} else {
|
|
688
|
-
|
|
766
|
+
fs2.writeFileSync(gitignorePath, `# Connect Base
|
|
689
767
|
${entry}
|
|
690
768
|
`);
|
|
691
769
|
success(".gitignore \uC0DD\uC131 \uC644\uB8CC");
|
|
692
770
|
}
|
|
693
771
|
}
|
|
694
772
|
function addDeployScript(deployDir) {
|
|
695
|
-
const pkgPath =
|
|
696
|
-
if (!
|
|
773
|
+
const pkgPath = path2.join(process.cwd(), "package.json");
|
|
774
|
+
if (!fs2.existsSync(pkgPath)) {
|
|
697
775
|
warn("package.json\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 deploy \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uCD94\uAC00\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4");
|
|
698
776
|
return;
|
|
699
777
|
}
|
|
700
778
|
try {
|
|
701
|
-
const pkg = JSON.parse(
|
|
779
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
702
780
|
if (!pkg.scripts) pkg.scripts = {};
|
|
703
781
|
if (pkg.scripts.deploy) {
|
|
704
782
|
info(`package.json\uC5D0 \uC774\uBBF8 deploy \uC2A4\uD06C\uB9BD\uD2B8\uAC00 \uC788\uC2B5\uB2C8\uB2E4: "${pkg.scripts.deploy}"`);
|
|
@@ -706,7 +784,7 @@ function addDeployScript(deployDir) {
|
|
|
706
784
|
}
|
|
707
785
|
const deployCmd = `connectbase-client deploy ${deployDir}`;
|
|
708
786
|
pkg.scripts.deploy = pkg.scripts.build ? `${pkg.scripts.build} && ${deployCmd}` : deployCmd;
|
|
709
|
-
|
|
787
|
+
fs2.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
710
788
|
success(`package.json\uC5D0 deploy \uC2A4\uD06C\uB9BD\uD2B8 \uCD94\uAC00 \uC644\uB8CC`);
|
|
711
789
|
} catch {
|
|
712
790
|
warn("package.json \uC218\uC815\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4");
|
|
@@ -723,32 +801,32 @@ function getGitRoot() {
|
|
|
723
801
|
function detectMonorepo(gitRoot) {
|
|
724
802
|
const cwd = process.cwd();
|
|
725
803
|
const result = { type: "none", root: gitRoot, isSubPackage: false };
|
|
726
|
-
if (
|
|
804
|
+
if (fs2.existsSync(path2.join(gitRoot, "pnpm-workspace.yaml"))) {
|
|
727
805
|
result.type = "pnpm";
|
|
728
|
-
} else if (
|
|
806
|
+
} else if (fs2.existsSync(path2.join(gitRoot, "turbo.json"))) {
|
|
729
807
|
result.type = "turborepo";
|
|
730
|
-
} else if (
|
|
808
|
+
} else if (fs2.existsSync(path2.join(gitRoot, "nx.json"))) {
|
|
731
809
|
result.type = "nx";
|
|
732
|
-
} else if (
|
|
810
|
+
} else if (fs2.existsSync(path2.join(gitRoot, "lerna.json"))) {
|
|
733
811
|
result.type = "lerna";
|
|
734
812
|
} else {
|
|
735
|
-
const rootPkgPath =
|
|
736
|
-
if (
|
|
813
|
+
const rootPkgPath = path2.join(gitRoot, "package.json");
|
|
814
|
+
if (fs2.existsSync(rootPkgPath)) {
|
|
737
815
|
try {
|
|
738
|
-
const pkg = JSON.parse(
|
|
816
|
+
const pkg = JSON.parse(fs2.readFileSync(rootPkgPath, "utf-8"));
|
|
739
817
|
if (pkg.workspaces) {
|
|
740
|
-
result.type =
|
|
818
|
+
result.type = fs2.existsSync(path2.join(gitRoot, "yarn.lock")) ? "yarn" : "npm";
|
|
741
819
|
}
|
|
742
820
|
} catch {
|
|
743
821
|
}
|
|
744
822
|
}
|
|
745
823
|
}
|
|
746
824
|
if (result.type !== "none" && cwd !== gitRoot) {
|
|
747
|
-
const cwdPkgPath =
|
|
748
|
-
if (
|
|
825
|
+
const cwdPkgPath = path2.join(cwd, "package.json");
|
|
826
|
+
if (fs2.existsSync(cwdPkgPath)) {
|
|
749
827
|
result.isSubPackage = true;
|
|
750
828
|
try {
|
|
751
|
-
const pkg = JSON.parse(
|
|
829
|
+
const pkg = JSON.parse(fs2.readFileSync(cwdPkgPath, "utf-8"));
|
|
752
830
|
result.subPackageName = pkg.name;
|
|
753
831
|
} catch {
|
|
754
832
|
}
|
|
@@ -765,13 +843,13 @@ async function downloadDocs(publicKey, templates, baseDir) {
|
|
|
765
843
|
if (monorepo.type !== "none") {
|
|
766
844
|
info(`\uBAA8\uB178\uB808\uD3EC \uAC10\uC9C0: ${monorepo.type} (\uB8E8\uD2B8: ${gitRoot})`);
|
|
767
845
|
if (monorepo.isSubPackage) {
|
|
768
|
-
info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName ||
|
|
846
|
+
info(`\uD604\uC7AC \uC11C\uBE0C \uD328\uD0A4\uC9C0: ${monorepo.subPackageName || path2.basename(process.cwd())}`);
|
|
769
847
|
}
|
|
770
848
|
}
|
|
771
|
-
const docsDir =
|
|
772
|
-
const rootClaudeMd =
|
|
773
|
-
if (!
|
|
774
|
-
|
|
849
|
+
const docsDir = path2.join(gitRoot, ".claude", "docs");
|
|
850
|
+
const rootClaudeMd = path2.join(gitRoot, "CLAUDE.md");
|
|
851
|
+
if (!fs2.existsSync(docsDir)) {
|
|
852
|
+
fs2.mkdirSync(docsDir, { recursive: true });
|
|
775
853
|
}
|
|
776
854
|
if (!templates) {
|
|
777
855
|
templates = ["rules"];
|
|
@@ -790,13 +868,13 @@ async function downloadDocs(publicKey, templates, baseDir) {
|
|
|
790
868
|
const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
|
|
791
869
|
const sections = data.sections || [];
|
|
792
870
|
for (const section of sections) {
|
|
793
|
-
const filePath =
|
|
794
|
-
|
|
871
|
+
const filePath = path2.join(docsDir, section.filename);
|
|
872
|
+
fs2.writeFileSync(filePath, section.content);
|
|
795
873
|
}
|
|
796
874
|
success(`${gitRoot}/.claude/docs/ \uC5D0 ${sections.length}\uAC1C \uBB38\uC11C \uC800\uC7A5 \uC644\uB8CC`);
|
|
797
875
|
updateRootClaudeMd(rootClaudeMd);
|
|
798
876
|
if (monorepo.isSubPackage) {
|
|
799
|
-
const subClaudeMd =
|
|
877
|
+
const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
|
|
800
878
|
createSubPackageClaudeMd(subClaudeMd, gitRoot);
|
|
801
879
|
}
|
|
802
880
|
log(`${colors.dim}\u203B SDK \uBB38\uC11C\uB294 MCP search_sdk_docs\uB85C \uAC80\uC0C9\uD558\uC138\uC694${colors.reset}`);
|
|
@@ -808,10 +886,10 @@ async function downloadDocs(publicKey, templates, baseDir) {
|
|
|
808
886
|
|
|
809
887
|
\uC0C1\uC138 \uAD6C\uD604\uBC95\uC740 MCP \`search_sdk_docs\` \uB3C4\uAD6C\uB85C \uAC80\uC0C9\uD558\uC138\uC694.
|
|
810
888
|
`;
|
|
811
|
-
|
|
889
|
+
fs2.writeFileSync(path2.join(docsDir, "project-rules.md"), fallbackContent);
|
|
812
890
|
updateRootClaudeMd(rootClaudeMd);
|
|
813
891
|
if (monorepo.isSubPackage) {
|
|
814
|
-
const subClaudeMd =
|
|
892
|
+
const subClaudeMd = path2.join(process.cwd(), "CLAUDE.md");
|
|
815
893
|
createSubPackageClaudeMd(subClaudeMd, gitRoot);
|
|
816
894
|
}
|
|
817
895
|
success(`.claude/docs/ \uC0DD\uC131 \uC644\uB8CC (\uAE30\uBCF8)`);
|
|
@@ -831,8 +909,8 @@ function updateRootClaudeMd(claudeMdPath) {
|
|
|
831
909
|
2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
|
|
832
910
|
3. \uAC80\uC0C9 \uACB0\uACFC\uC758 \uCF54\uB4DC \uD328\uD134\uC744 \uB530\uB77C \uAD6C\uD604\uD558\uC138\uC694
|
|
833
911
|
${endMarker}`;
|
|
834
|
-
if (
|
|
835
|
-
let content =
|
|
912
|
+
if (fs2.existsSync(claudeMdPath)) {
|
|
913
|
+
let content = fs2.readFileSync(claudeMdPath, "utf-8");
|
|
836
914
|
const startIdx = content.indexOf(startMarker);
|
|
837
915
|
const endIdx = content.indexOf(endMarker);
|
|
838
916
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
@@ -840,10 +918,10 @@ ${endMarker}`;
|
|
|
840
918
|
} else {
|
|
841
919
|
content = sdkBlock + "\n\n" + content.trimStart();
|
|
842
920
|
}
|
|
843
|
-
|
|
921
|
+
fs2.writeFileSync(claudeMdPath, content);
|
|
844
922
|
info("CLAUDE.md\uC5D0 ConnectBase \uCC38\uC870 \uC5C5\uB370\uC774\uD2B8 \uC644\uB8CC");
|
|
845
923
|
} else {
|
|
846
|
-
|
|
924
|
+
fs2.writeFileSync(claudeMdPath, `# \uD504\uB85C\uC81D\uD2B8
|
|
847
925
|
|
|
848
926
|
${sdkBlock}
|
|
849
927
|
`);
|
|
@@ -853,7 +931,7 @@ ${sdkBlock}
|
|
|
853
931
|
function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
|
|
854
932
|
const startMarker = "<!-- CONNECTBASE_SUB_START -->";
|
|
855
933
|
const endMarker = "<!-- CONNECTBASE_SUB_END -->";
|
|
856
|
-
const relPath =
|
|
934
|
+
const relPath = path2.relative(path2.dirname(subClaudeMdPath), gitRoot);
|
|
857
935
|
const subBlock = `${startMarker}
|
|
858
936
|
## ConnectBase SDK
|
|
859
937
|
|
|
@@ -865,8 +943,8 @@ function createSubPackageClaudeMd(subClaudeMdPath, gitRoot) {
|
|
|
865
943
|
2. \`search_sdk_docs("\uD0A4\uC6CC\uB4DC")\`\uB85C SDK \uAD6C\uD604 \uBC29\uBC95\uC744 \uAC80\uC0C9\uD558\uC138\uC694
|
|
866
944
|
3. \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC758 \`${relPath}/CLAUDE.md\`\uB3C4 \uCC38\uACE0\uD558\uC138\uC694
|
|
867
945
|
${endMarker}`;
|
|
868
|
-
if (
|
|
869
|
-
let content =
|
|
946
|
+
if (fs2.existsSync(subClaudeMdPath)) {
|
|
947
|
+
let content = fs2.readFileSync(subClaudeMdPath, "utf-8");
|
|
870
948
|
const startIdx = content.indexOf(startMarker);
|
|
871
949
|
const endIdx = content.indexOf(endMarker);
|
|
872
950
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
@@ -876,9 +954,9 @@ ${endMarker}`;
|
|
|
876
954
|
} else {
|
|
877
955
|
return;
|
|
878
956
|
}
|
|
879
|
-
|
|
957
|
+
fs2.writeFileSync(subClaudeMdPath, content);
|
|
880
958
|
} else {
|
|
881
|
-
|
|
959
|
+
fs2.writeFileSync(subClaudeMdPath, `# ${path2.basename(path2.dirname(subClaudeMdPath))}
|
|
882
960
|
|
|
883
961
|
${subBlock}
|
|
884
962
|
`);
|
|
@@ -895,7 +973,7 @@ async function setupMcp(secretKey) {
|
|
|
895
973
|
info(`MCP \uC124\uC815\uC740 \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8(${root})\uC5D0 \uC0DD\uC131\uB429\uB2C8\uB2E4`);
|
|
896
974
|
}
|
|
897
975
|
}
|
|
898
|
-
const mcpConfigPath =
|
|
976
|
+
const mcpConfigPath = path2.join(root, ".mcp.json");
|
|
899
977
|
if (!secretKey) {
|
|
900
978
|
log(`
|
|
901
979
|
${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${colors.reset}`);
|
|
@@ -906,16 +984,16 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
906
984
|
if (authChoice === "2") {
|
|
907
985
|
try {
|
|
908
986
|
secretKey = await browserAuthFlow();
|
|
909
|
-
const skRcPath =
|
|
987
|
+
const skRcPath = path2.join(process.cwd(), ".connectbaserc");
|
|
910
988
|
let rcData = {};
|
|
911
|
-
if (
|
|
989
|
+
if (fs2.existsSync(skRcPath)) {
|
|
912
990
|
try {
|
|
913
|
-
rcData = JSON.parse(
|
|
991
|
+
rcData = JSON.parse(fs2.readFileSync(skRcPath, "utf-8"));
|
|
914
992
|
} catch {
|
|
915
993
|
}
|
|
916
994
|
}
|
|
917
995
|
rcData.secretKey = secretKey;
|
|
918
|
-
|
|
996
|
+
fs2.writeFileSync(skRcPath, JSON.stringify(rcData, null, 2) + "\n");
|
|
919
997
|
addToGitignore(".connectbaserc", root);
|
|
920
998
|
success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
|
|
921
999
|
} catch (err) {
|
|
@@ -934,21 +1012,21 @@ ${colors.dim}\uC778\uC99D \uBC29\uC2DD\uC744 \uC120\uD0DD\uD558\uC138\uC694:${co
|
|
|
934
1012
|
}
|
|
935
1013
|
};
|
|
936
1014
|
let mcpConfig = { mcpServers: {} };
|
|
937
|
-
if (
|
|
1015
|
+
if (fs2.existsSync(mcpConfigPath)) {
|
|
938
1016
|
try {
|
|
939
|
-
mcpConfig = JSON.parse(
|
|
1017
|
+
mcpConfig = JSON.parse(fs2.readFileSync(mcpConfigPath, "utf-8"));
|
|
940
1018
|
if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") {
|
|
941
1019
|
mcpConfig.mcpServers = {};
|
|
942
1020
|
}
|
|
943
1021
|
} catch {
|
|
944
1022
|
const backupPath = mcpConfigPath + ".backup";
|
|
945
|
-
|
|
1023
|
+
fs2.copyFileSync(mcpConfigPath, backupPath);
|
|
946
1024
|
warn(`.mcp.json \uD30C\uC2F1 \uC2E4\uD328, \uBC31\uC5C5 \uC0DD\uC131: ${backupPath}`);
|
|
947
1025
|
mcpConfig = { mcpServers: {} };
|
|
948
1026
|
}
|
|
949
1027
|
}
|
|
950
1028
|
mcpConfig.mcpServers["connect-base"] = mcpEntry;
|
|
951
|
-
|
|
1029
|
+
fs2.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
952
1030
|
success(`${mcpConfigPath} \uC0DD\uC131 \uC644\uB8CC`);
|
|
953
1031
|
addToGitignore(".mcp.json", root);
|
|
954
1032
|
if (secretKey && secretKey !== "YOUR_SECRET_KEY_HERE") {
|
|
@@ -1124,7 +1202,7 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1124
1202
|
success(`\uC120\uD0DD\uB428: ${apps[num - 1].name}`);
|
|
1125
1203
|
return apps[num - 1].id;
|
|
1126
1204
|
}
|
|
1127
|
-
const projectName =
|
|
1205
|
+
const projectName = path2.basename(process.cwd());
|
|
1128
1206
|
const appName = await prompt(`${colors.blue}?${colors.reset} \uC571 \uC774\uB984 (${projectName}): `) || projectName;
|
|
1129
1207
|
info("\uC571 \uC0DD\uC131 \uC911...");
|
|
1130
1208
|
const createRes = await makeRequest(
|
|
@@ -1146,22 +1224,32 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
|
|
|
1146
1224
|
success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
|
|
1147
1225
|
return createData.app_id;
|
|
1148
1226
|
}
|
|
1227
|
+
function acquireTunnelLock2(appID, port, force) {
|
|
1228
|
+
const result = acquireTunnelLock(appID, port, force, VERSION);
|
|
1229
|
+
if (result && result.startsWith("LOCKED:")) {
|
|
1230
|
+
const [, pid, startedAt, host] = result.split(":");
|
|
1231
|
+
error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${pid}, \uC2DC\uC791 ${startedAt}, \uD638\uC2A4\uD2B8 ${host}`);
|
|
1232
|
+
info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
|
|
1233
|
+
process.exit(2);
|
|
1234
|
+
}
|
|
1235
|
+
return result;
|
|
1236
|
+
}
|
|
1149
1237
|
async function startTunnel(port, config, tunnelOpts) {
|
|
1150
1238
|
let tunnelKey = config.secretKey || (config.publicKey?.startsWith("cb_sk_") ? config.publicKey : "");
|
|
1151
1239
|
if (!tunnelKey) {
|
|
1152
1240
|
info("Secret Key\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778\uC73C\uB85C \uBC1C\uAE09\uD569\uB2C8\uB2E4...");
|
|
1153
1241
|
try {
|
|
1154
1242
|
const sk = await browserAuthFlow();
|
|
1155
|
-
const rcPath2 =
|
|
1243
|
+
const rcPath2 = path2.join(process.cwd(), ".connectbaserc");
|
|
1156
1244
|
let rcData = {};
|
|
1157
|
-
if (
|
|
1245
|
+
if (fs2.existsSync(rcPath2)) {
|
|
1158
1246
|
try {
|
|
1159
|
-
rcData = JSON.parse(
|
|
1247
|
+
rcData = JSON.parse(fs2.readFileSync(rcPath2, "utf-8"));
|
|
1160
1248
|
} catch {
|
|
1161
1249
|
}
|
|
1162
1250
|
}
|
|
1163
1251
|
rcData.secretKey = sk;
|
|
1164
|
-
|
|
1252
|
+
fs2.writeFileSync(rcPath2, JSON.stringify(rcData, null, 2) + "\n");
|
|
1165
1253
|
addToGitignore(".connectbaserc");
|
|
1166
1254
|
success("Secret Key \uBC1C\uAE09 \uBC0F \uC800\uC7A5 \uC644\uB8CC");
|
|
1167
1255
|
tunnelKey = sk;
|
|
@@ -1174,11 +1262,11 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
1174
1262
|
} else {
|
|
1175
1263
|
config.secretKey = tunnelKey;
|
|
1176
1264
|
}
|
|
1177
|
-
const rcPath =
|
|
1265
|
+
const rcPath = path2.join(process.cwd(), ".connectbaserc");
|
|
1178
1266
|
let savedAppId = tunnelOpts?.appId || "";
|
|
1179
1267
|
if (!savedAppId) {
|
|
1180
1268
|
try {
|
|
1181
|
-
const rc = JSON.parse(
|
|
1269
|
+
const rc = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
|
|
1182
1270
|
if (rc.tunnelAppId) savedAppId = rc.tunnelAppId;
|
|
1183
1271
|
} catch {
|
|
1184
1272
|
}
|
|
@@ -1186,18 +1274,19 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
1186
1274
|
const appId = await resolveAppForTunnel(tunnelKey, config.baseUrl, savedAppId);
|
|
1187
1275
|
try {
|
|
1188
1276
|
let rcData = {};
|
|
1189
|
-
if (
|
|
1277
|
+
if (fs2.existsSync(rcPath)) {
|
|
1190
1278
|
try {
|
|
1191
|
-
rcData = JSON.parse(
|
|
1279
|
+
rcData = JSON.parse(fs2.readFileSync(rcPath, "utf-8"));
|
|
1192
1280
|
} catch {
|
|
1193
1281
|
}
|
|
1194
1282
|
}
|
|
1195
1283
|
if (rcData.tunnelAppId !== appId) {
|
|
1196
1284
|
rcData.tunnelAppId = appId;
|
|
1197
|
-
|
|
1285
|
+
fs2.writeFileSync(rcPath, JSON.stringify(rcData, null, 2) + "\n");
|
|
1198
1286
|
}
|
|
1199
1287
|
} catch {
|
|
1200
1288
|
}
|
|
1289
|
+
const lockPath = acquireTunnelLock2(appId, port, tunnelOpts?.force ?? false);
|
|
1201
1290
|
const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
|
|
1202
1291
|
const parsedUrl = new URL(tunnelServerUrl);
|
|
1203
1292
|
const isHttps = parsedUrl.protocol === "https:";
|
|
@@ -1212,8 +1301,17 @@ async function startTunnel(port, config, tunnelOpts) {
|
|
|
1212
1301
|
const maxReconnectAttempts = 10;
|
|
1213
1302
|
let shouldReconnect = true;
|
|
1214
1303
|
let socket = null;
|
|
1304
|
+
let lockReleased = false;
|
|
1305
|
+
const releaseLock = () => {
|
|
1306
|
+
if (lockPath && !lockReleased) {
|
|
1307
|
+
lockReleased = true;
|
|
1308
|
+
releaseTunnelLock(lockPath);
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
process.on("exit", releaseLock);
|
|
1215
1312
|
const cleanup = () => {
|
|
1216
1313
|
shouldReconnect = false;
|
|
1314
|
+
releaseLock();
|
|
1217
1315
|
if (socket) {
|
|
1218
1316
|
try {
|
|
1219
1317
|
socket.write(createWsCloseFrame(1e3));
|
|
@@ -1343,9 +1441,15 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
|
|
|
1343
1441
|
case "http_request":
|
|
1344
1442
|
forwardRequest(msg, sock, localPort);
|
|
1345
1443
|
break;
|
|
1346
|
-
case "tunnel_error":
|
|
1347
|
-
|
|
1444
|
+
case "tunnel_error": {
|
|
1445
|
+
const result = handleTunnelError(msg, appId, localPort);
|
|
1446
|
+
error(result.message);
|
|
1447
|
+
if (result.action === "exit") {
|
|
1448
|
+
shouldReconnect = false;
|
|
1449
|
+
setTimeout(() => process.exit(result.exitCode ?? 1), 500);
|
|
1450
|
+
}
|
|
1348
1451
|
break;
|
|
1452
|
+
}
|
|
1349
1453
|
case "ping":
|
|
1350
1454
|
sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
|
|
1351
1455
|
break;
|
|
@@ -1497,6 +1601,7 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
|
|
|
1497
1601
|
-u, --base-url <url> \uC11C\uBC84 URL (\uAE30\uBCF8: ${DEFAULT_BASE_URL})
|
|
1498
1602
|
-t, --timeout <sec> \uD130\uB110 \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel \uC804\uC6A9)
|
|
1499
1603
|
--max-body <MB> \uD130\uB110 \uCD5C\uB300 \uBC14\uB514 \uD06C\uAE30 (MB, tunnel \uC804\uC6A9)
|
|
1604
|
+
--force \uD130\uB110 lockfile \uBB34\uC2DC (\uC911\uBCF5 \uC2E4\uD589 \uAC15\uC81C, tunnel \uC804\uC6A9)
|
|
1500
1605
|
-d, --dev Dev \uD658\uACBD\uC5D0 \uBC30\uD3EC (deploy \uC804\uC6A9)
|
|
1501
1606
|
(docs, mcp\uB294 \uBAA8\uB178\uB808\uD3EC\uB97C \uC790\uB3D9 \uAC10\uC9C0\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131)
|
|
1502
1607
|
-h, --help \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
|
|
@@ -1562,6 +1667,8 @@ function parseArgs(args) {
|
|
|
1562
1667
|
result.options.maxBody = args[++i];
|
|
1563
1668
|
} else if (arg === "-a" || arg === "--app") {
|
|
1564
1669
|
result.options.appId = args[++i];
|
|
1670
|
+
} else if (arg === "--force") {
|
|
1671
|
+
result.options.force = "true";
|
|
1565
1672
|
} else if (arg === "-d" || arg === "--dev") {
|
|
1566
1673
|
result.options.dev = "true";
|
|
1567
1674
|
} else if (arg === "-h" || arg === "--help") {
|
|
@@ -1645,6 +1752,9 @@ async function main() {
|
|
|
1645
1752
|
if (parsed.options.appId) {
|
|
1646
1753
|
tunnelOpts.appId = parsed.options.appId;
|
|
1647
1754
|
}
|
|
1755
|
+
if (parsed.options.force === "true") {
|
|
1756
|
+
tunnelOpts.force = true;
|
|
1757
|
+
}
|
|
1648
1758
|
await startTunnel(port, config, tunnelOpts);
|
|
1649
1759
|
} else {
|
|
1650
1760
|
error(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC5B4: ${parsed.command}`);
|