comp-hub 0.28.4 → 0.28.5
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/README.md +132 -132
- package/bin/cli.js +54 -54
- package/bin/lib/commands.js +173 -173
- package/bin/lib/constants.js +22 -22
- package/bin/lib/http.js +62 -62
- package/bin/lib/startup.js +183 -183
- package/bin/lib/utils.js +112 -112
- package/dist/website/container/assets/index-D6braxue.css +1 -0
- package/dist/website/container/assets/{index-CVxgDysj.js → index-DW_KDaXl.js} +1 -1
- package/dist/website/container/index.html +32 -32
- package/dist/website/container/js/vue2-sfc-loader.js +260 -260
- package/dist/website/container/js/vue3-sfc-loader.js +289 -289
- package/dist/website/item/assets/index-BWvMfSxu.css +1 -0
- package/dist/website/item/assets/{index-CGgDhzaN.js → index-Cqs5iLeY.js} +3 -3
- package/dist/website/item/index.html +30 -30
- package/dist/website/main/assets/{CanvasAnimation.vue_vue_type_script_setup_true_lang-gAGMSPxq.js → CanvasAnimation.vue_vue_type_script_setup_true_lang-Xr4R8ONt.js} +1 -1
- package/dist/website/main/assets/{index-eyM0Chsl.js → index-9E0N8vk2.js} +1 -1
- package/dist/website/main/assets/{index-JcGCRZej.js → index-B8QVBEgQ.js} +1 -1
- package/dist/website/main/assets/{index-D_mLoa5V.js → index-BEVcnNvI.js} +1 -1
- package/dist/website/main/assets/{index-BGAeKS26.js → index-BGYcKO_k.js} +2 -2
- package/dist/website/main/assets/{index-XEEaWsyW.js → index-BHUDEWGt.js} +1 -1
- package/dist/website/main/assets/{index-DKb_LuTB.js → index-BMSnGhSG.js} +1 -1
- package/dist/website/main/assets/{index-CKuqpXAR.js → index-BVcp4iEn.js} +1 -1
- package/dist/website/main/assets/{index-DcbQ_ahf.js → index-BYC9CuLh.js} +1 -1
- package/dist/website/main/assets/{index-DroKXXhR.js → index-BblV7ZcF.js} +1 -1
- package/dist/website/main/assets/{index-C1KlLhF1.js → index-BqUY_Pbs.js} +1 -1
- package/dist/website/main/assets/{index-DNWHN_5F.js → index-BwgqBNRu.js} +1 -1
- package/dist/website/main/assets/index-CG7uKVdX.js +1 -0
- package/dist/website/main/assets/{index-BbVoROfE.js → index-Cc3zDN4S.js} +1 -1
- package/dist/website/main/assets/{index-Cd1GqG9-.js → index-CdLdwW7r.js} +3 -3
- package/dist/website/main/assets/{index-BjMNHNZQ.js → index-CsUtZbBp.js} +1 -1
- package/dist/website/main/assets/index-CzTcYkCn.css +1 -0
- package/dist/website/main/assets/index-D-uwf4yo.css +1 -0
- package/dist/website/main/assets/{index-BUoUeU3q.js → index-D6MA33Hi.js} +1 -1
- package/dist/website/main/assets/{index-gswaBdwW.js → index-DGaY7dL7.js} +1 -1
- package/dist/website/main/assets/{index-B-RD5UzD.css → index-DIKKFNiV.css} +1 -1
- package/dist/website/main/assets/{index-DGh9vJu4.js → index-DbmFA7Z2.js} +1 -1
- package/dist/website/main/assets/{index-Dp1XrCJD.js → index-Dd8ha0TC.js} +1 -1
- package/dist/website/main/assets/{index-WPdppQti.js → index-Dfkpa19a.js} +1 -1
- package/dist/website/main/assets/{index-D4vHIupw.js → index-Diq3BceD.js} +1 -1
- package/dist/website/main/assets/{index-Bb4dzB_t.js → index-DkakQhTy.js} +1 -1
- package/dist/website/main/assets/{index-Cl0SK7oa.css → index-DxyEzkir.css} +1 -1
- package/dist/website/main/assets/{index-CEizeGti.js → index-Dz2nPzxk.js} +1 -1
- package/dist/website/main/assets/index-FFTkVbVX.css +1 -0
- package/dist/website/main/assets/{index-RTrUAzmO.js → index-RcR0UXkh.js} +1 -1
- package/dist/website/main/assets/{index-Cdv-VxRL.js → index-X1ZIWbVW.js} +2 -2
- package/dist/website/main/assets/{index-Car8xTN1.js → index-bdWvkmhc.js} +1 -1
- package/dist/website/main/assets/{index-D26LViqE.js → index-qSRqezAW.js} +1 -1
- package/dist/website/main/assets/{index-BXRWapP9.js → index-qsIY_6aA.js} +1 -1
- package/dist/website/main/assets/{index-D62zqHYv.js → index-rWF_BNTH.js} +1 -1
- package/dist/website/main/assets/{index-Cacmn-le.js → index-tKPA5N_x.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-CVJdloX3.js → index.vue_vue_type_script_setup_true_lang-BYKlY94g.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-DhkIAGmf.js → index.vue_vue_type_script_setup_true_lang-CCaH-GEy.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-CK2dnh4X.js → index.vue_vue_type_script_setup_true_lang-DPbqR98S.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-0kHDvgX-.js → index.vue_vue_type_script_setup_true_lang-DTrkhAMQ.js} +2 -2
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-_M-s3_Ie.js → index.vue_vue_type_script_setup_true_lang-DtCu4Izc.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-Q7u6DEB4.js → index.vue_vue_type_script_setup_true_lang-DzvWwe2g.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_style_index_0_lang-iFwx3NZQ.js → index.vue_vue_type_style_index_0_lang-1ZphBFBL.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_style_index_0_lang-CIN0y80j.js → index.vue_vue_type_style_index_0_lang-BNPcBMvg.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_style_index_0_lang-DTKsCZO_.js → index.vue_vue_type_style_index_0_lang-CwaGkvXy.js} +1 -1
- package/dist/website/main/assets/{useCompEditStore-CA87jjE-.js → useCompEditStore-DI3vFtGm.js} +2 -2
- package/dist/website/main/assets/{useCompEditStore-CiX1ghqX.css → useCompEditStore-EVZp94pg.css} +1 -1
- package/dist/website/main/assets/{useKeyword-CmJvOYMB.js → useKeyword-bWHMALoZ.js} +1 -1
- package/dist/website/main/assets/{useMessage-DRWIT9X-.js → useMessage-BVw3M4ej.js} +1 -1
- package/dist/website/main/assets/{useVscodeOpen-PzIPfNxl.js → useVscodeOpen-SZpyzVHG.js} +1 -1
- package/dist/website/main/index.html +2 -2
- package/package.json +45 -45
- package/dist/website/container/assets/index-h6S5dyy4.css +0 -1
- package/dist/website/item/assets/index-lq3uP4DP.css +0 -1
- package/dist/website/main/assets/index-CP6sOfKF.css +0 -1
- package/dist/website/main/assets/index-CmSN0r9I.css +0 -1
- package/dist/website/main/assets/index-DJ4AvgpT.css +0 -1
- package/dist/website/main/assets/index-tCnrZXQC.js +0 -1
package/bin/lib/startup.js
CHANGED
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const { spawn } = require("child_process");
|
|
4
|
-
const { httpPost } = require("./http");
|
|
5
|
-
const { computeHash, projectRoot, readMasterRuntime, loadComphubConfig, colors, apiUrl } = require("./utils");
|
|
6
|
-
const { cyan, green, white, yellow, red } = colors;
|
|
7
|
-
const { ADMIN_API, POLL_INTERVAL, MAX_RETRIES, DEFAULT_PORT } = require("./constants");
|
|
8
|
-
const getPort = require("get-port");
|
|
9
|
-
|
|
10
|
-
// ---- Config ----
|
|
11
|
-
|
|
12
|
-
function mergeConfig(opts, cwd) {
|
|
13
|
-
// 加载项目配置文件,作为命令行参数的 fallback
|
|
14
|
-
const fileConfig = loadComphubConfig(cwd);
|
|
15
|
-
// 优先级:命令行 > 配置文件 > 默认值
|
|
16
|
-
return {
|
|
17
|
-
dir: opts.dir || fileConfig.dir || "./",
|
|
18
|
-
api: opts.api || fileConfig.api,
|
|
19
|
-
allowDebug: opts.allowDebug != null ? opts.allowDebug : fileConfig.allowDebug != null ? fileConfig.allowDebug : false,
|
|
20
|
-
proxy: opts.proxy || fileConfig.proxy,
|
|
21
|
-
port: opts.port
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ---- Master lifecycle ----
|
|
26
|
-
|
|
27
|
-
async function waitForMasterRuntime() {
|
|
28
|
-
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
29
|
-
const runtime = await readMasterRuntime();
|
|
30
|
-
if (runtime) return runtime;
|
|
31
|
-
await new Promise(r => setTimeout(r, POLL_INTERVAL));
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function spawnMaster(publicPort, pkg, cwd) {
|
|
37
|
-
const masterEntry = path.join(projectRoot, "dist/master.js");
|
|
38
|
-
|
|
39
|
-
if (!fs.existsSync(masterEntry)) {
|
|
40
|
-
throw new Error("Master entry file not found, startup failed");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const masterArgs = ["--port", String(publicPort), "--public-port", String(publicPort)];
|
|
44
|
-
const child = spawn(process.execPath, [masterEntry, ...masterArgs], {
|
|
45
|
-
stdio: "ignore",
|
|
46
|
-
detached: true,
|
|
47
|
-
windowsHide: true,
|
|
48
|
-
cwd,
|
|
49
|
-
env: {
|
|
50
|
-
...process.env,
|
|
51
|
-
COMP_HUB_VERSION: pkg.version
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
child.unref();
|
|
56
|
-
|
|
57
|
-
const runtime = await waitForMasterRuntime();
|
|
58
|
-
if (!runtime) {
|
|
59
|
-
child.kill();
|
|
60
|
-
throw new Error("Master startup timeout");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return runtime;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* 确保 master 在运行,返回运行时信息。
|
|
68
|
-
* 如果已在运行则复用,否则 spawn 新的 master 进程。
|
|
69
|
-
*/
|
|
70
|
-
async function ensureMasterRunning(opts, pkg) {
|
|
71
|
-
let portMismatch = false;
|
|
72
|
-
let runningPublicPort = 0;
|
|
73
|
-
|
|
74
|
-
const existingRuntime = await readMasterRuntime();
|
|
75
|
-
|
|
76
|
-
if (existingRuntime) {
|
|
77
|
-
// Master 已在运行,检查用户指定的端口是否匹配
|
|
78
|
-
const expectedPort = opts.port != null ? opts.port : existingRuntime.publicPort;
|
|
79
|
-
if (opts.port != null && existingRuntime.publicPort !== expectedPort) {
|
|
80
|
-
portMismatch = true;
|
|
81
|
-
runningPublicPort = existingRuntime.publicPort;
|
|
82
|
-
}
|
|
83
|
-
return { runtime: existingRuntime, portMismatch, runningPublicPort };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 启动新 master
|
|
87
|
-
const portHint = opts.port != null ? opts.port : DEFAULT_PORT;
|
|
88
|
-
const publicPort = await getPort({ port: getPort.makeRange(portHint, portHint + 100) });
|
|
89
|
-
|
|
90
|
-
const runtime = await spawnMaster(publicPort, pkg, process.cwd());
|
|
91
|
-
return { runtime, portMismatch, runningPublicPort };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// ---- Registration ----
|
|
95
|
-
|
|
96
|
-
async function registerProject(masterPort, hash, cwd, opts) {
|
|
97
|
-
const body = {
|
|
98
|
-
hash,
|
|
99
|
-
cwd,
|
|
100
|
-
dir: opts.dir || "./"
|
|
101
|
-
};
|
|
102
|
-
if (opts.api) body.api = opts.api;
|
|
103
|
-
if (opts.allowDebug) body.allowDebug = true;
|
|
104
|
-
if (opts.proxy) body.proxy = opts.proxy;
|
|
105
|
-
|
|
106
|
-
const res = await httpPost(apiUrl(masterPort, ADMIN_API.REGISTER), body);
|
|
107
|
-
if (!res.ok) {
|
|
108
|
-
throw new Error(res.error || "Registration failed");
|
|
109
|
-
}
|
|
110
|
-
console.log(`${green("✓")} Project registered (hash: ${hash})`);
|
|
111
|
-
return res;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ---- Banner ----
|
|
115
|
-
|
|
116
|
-
function printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort }) {
|
|
117
|
-
const url = `http://localhost:${displayPort}/${hash}/main/`;
|
|
118
|
-
|
|
119
|
-
const lines = [
|
|
120
|
-
"",
|
|
121
|
-
cyan(" ██████╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ ██╗ ██╗ ██████╗ "),
|
|
122
|
-
cyan(" ██╔════╝ ██╔═══██╗ ████╗ ████║ ██╔══██╗ ██║ ██║ ██║ ██║ ██╔══██╗"),
|
|
123
|
-
cyan(" ██║ ██║ ██║ ██╔████╔██║ ██████╔╝ ███████║ ██║ ██║ ██████╔╝"),
|
|
124
|
-
cyan(" ██║ ██║ ██║ ██║╚██╔╝██║ ██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██╗"),
|
|
125
|
-
cyan(" ╚██████╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝"),
|
|
126
|
-
cyan(" ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "),
|
|
127
|
-
"",
|
|
128
|
-
"",
|
|
129
|
-
` ${green("Version")} ${pkg.version}`,
|
|
130
|
-
` ${green("Project")} ${cwd}`,
|
|
131
|
-
` ${green("Hash")} ${hash}`,
|
|
132
|
-
` ${green("Port")} ${displayPort}`,
|
|
133
|
-
"",
|
|
134
|
-
` ${yellow("URL")} ${cyan(url)}`,
|
|
135
|
-
"",
|
|
136
|
-
white(" ─────────────────────────────────────────────"),
|
|
137
|
-
"",
|
|
138
|
-
` ${"comphub status".padEnd(30)}View all projects`,
|
|
139
|
-
` ${`comphub stop ${hash}`.padEnd(30)}Stop current project`,
|
|
140
|
-
` ${"comphub stop --all".padEnd(30)}Stop all projects`,
|
|
141
|
-
` ${"comphub kill".padEnd(30)}Shutdown master`,
|
|
142
|
-
""
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
if (portMismatch) {
|
|
146
|
-
lines.push(
|
|
147
|
-
white(" ─────────────────────────────────────────────"),
|
|
148
|
-
"",
|
|
149
|
-
red(` ⚠ Master is already running on port ${runningPublicPort}, ignoring -p ${opts.port}`),
|
|
150
|
-
red(` To change port, please run comphub kill first`),
|
|
151
|
-
""
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
console.log(lines.join("\n"));
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ---- Main ----
|
|
159
|
-
|
|
160
|
-
async function main(pkg, opts) {
|
|
161
|
-
if (!fs.existsSync(path.join(process.cwd(), "package.json"))) {
|
|
162
|
-
console.error(`${red("✗")} Current directory is not a Node.js project`);
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const cwd = process.cwd();
|
|
167
|
-
const hash = computeHash(cwd);
|
|
168
|
-
|
|
169
|
-
opts = mergeConfig(opts, cwd);
|
|
170
|
-
|
|
171
|
-
console.log(`${green("✓")} ${pkg.name} v${pkg.version}`);
|
|
172
|
-
|
|
173
|
-
const { runtime, portMismatch, runningPublicPort } = await ensureMasterRunning(opts, pkg);
|
|
174
|
-
|
|
175
|
-
const masterPort = runtime.managementPort;
|
|
176
|
-
const displayPort = runtime.publicPort;
|
|
177
|
-
|
|
178
|
-
await registerProject(masterPort, hash, cwd, opts);
|
|
179
|
-
|
|
180
|
-
printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort });
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
module.exports = { main };
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const { httpPost } = require("./http");
|
|
5
|
+
const { computeHash, projectRoot, readMasterRuntime, loadComphubConfig, colors, apiUrl } = require("./utils");
|
|
6
|
+
const { cyan, green, white, yellow, red } = colors;
|
|
7
|
+
const { ADMIN_API, POLL_INTERVAL, MAX_RETRIES, DEFAULT_PORT } = require("./constants");
|
|
8
|
+
const getPort = require("get-port");
|
|
9
|
+
|
|
10
|
+
// ---- Config ----
|
|
11
|
+
|
|
12
|
+
function mergeConfig(opts, cwd) {
|
|
13
|
+
// 加载项目配置文件,作为命令行参数的 fallback
|
|
14
|
+
const fileConfig = loadComphubConfig(cwd);
|
|
15
|
+
// 优先级:命令行 > 配置文件 > 默认值
|
|
16
|
+
return {
|
|
17
|
+
dir: opts.dir || fileConfig.dir || "./",
|
|
18
|
+
api: opts.api || fileConfig.api,
|
|
19
|
+
allowDebug: opts.allowDebug != null ? opts.allowDebug : fileConfig.allowDebug != null ? fileConfig.allowDebug : false,
|
|
20
|
+
proxy: opts.proxy || fileConfig.proxy,
|
|
21
|
+
port: opts.port
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---- Master lifecycle ----
|
|
26
|
+
|
|
27
|
+
async function waitForMasterRuntime() {
|
|
28
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
29
|
+
const runtime = await readMasterRuntime();
|
|
30
|
+
if (runtime) return runtime;
|
|
31
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL));
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function spawnMaster(publicPort, pkg, cwd) {
|
|
37
|
+
const masterEntry = path.join(projectRoot, "dist/master.js");
|
|
38
|
+
|
|
39
|
+
if (!fs.existsSync(masterEntry)) {
|
|
40
|
+
throw new Error("Master entry file not found, startup failed");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const masterArgs = ["--port", String(publicPort), "--public-port", String(publicPort)];
|
|
44
|
+
const child = spawn(process.execPath, [masterEntry, ...masterArgs], {
|
|
45
|
+
stdio: "ignore",
|
|
46
|
+
detached: true,
|
|
47
|
+
windowsHide: true,
|
|
48
|
+
cwd,
|
|
49
|
+
env: {
|
|
50
|
+
...process.env,
|
|
51
|
+
COMP_HUB_VERSION: pkg.version
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
child.unref();
|
|
56
|
+
|
|
57
|
+
const runtime = await waitForMasterRuntime();
|
|
58
|
+
if (!runtime) {
|
|
59
|
+
child.kill();
|
|
60
|
+
throw new Error("Master startup timeout");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return runtime;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 确保 master 在运行,返回运行时信息。
|
|
68
|
+
* 如果已在运行则复用,否则 spawn 新的 master 进程。
|
|
69
|
+
*/
|
|
70
|
+
async function ensureMasterRunning(opts, pkg) {
|
|
71
|
+
let portMismatch = false;
|
|
72
|
+
let runningPublicPort = 0;
|
|
73
|
+
|
|
74
|
+
const existingRuntime = await readMasterRuntime();
|
|
75
|
+
|
|
76
|
+
if (existingRuntime) {
|
|
77
|
+
// Master 已在运行,检查用户指定的端口是否匹配
|
|
78
|
+
const expectedPort = opts.port != null ? opts.port : existingRuntime.publicPort;
|
|
79
|
+
if (opts.port != null && existingRuntime.publicPort !== expectedPort) {
|
|
80
|
+
portMismatch = true;
|
|
81
|
+
runningPublicPort = existingRuntime.publicPort;
|
|
82
|
+
}
|
|
83
|
+
return { runtime: existingRuntime, portMismatch, runningPublicPort };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 启动新 master
|
|
87
|
+
const portHint = opts.port != null ? opts.port : DEFAULT_PORT;
|
|
88
|
+
const publicPort = await getPort({ port: getPort.makeRange(portHint, portHint + 100) });
|
|
89
|
+
|
|
90
|
+
const runtime = await spawnMaster(publicPort, pkg, process.cwd());
|
|
91
|
+
return { runtime, portMismatch, runningPublicPort };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---- Registration ----
|
|
95
|
+
|
|
96
|
+
async function registerProject(masterPort, hash, cwd, opts) {
|
|
97
|
+
const body = {
|
|
98
|
+
hash,
|
|
99
|
+
cwd,
|
|
100
|
+
dir: opts.dir || "./"
|
|
101
|
+
};
|
|
102
|
+
if (opts.api) body.api = opts.api;
|
|
103
|
+
if (opts.allowDebug) body.allowDebug = true;
|
|
104
|
+
if (opts.proxy) body.proxy = opts.proxy;
|
|
105
|
+
|
|
106
|
+
const res = await httpPost(apiUrl(masterPort, ADMIN_API.REGISTER), body);
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
throw new Error(res.error || "Registration failed");
|
|
109
|
+
}
|
|
110
|
+
console.log(`${green("✓")} Project registered (hash: ${hash})`);
|
|
111
|
+
return res;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---- Banner ----
|
|
115
|
+
|
|
116
|
+
function printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort }) {
|
|
117
|
+
const url = `http://localhost:${displayPort}/${hash}/main/`;
|
|
118
|
+
|
|
119
|
+
const lines = [
|
|
120
|
+
"",
|
|
121
|
+
cyan(" ██████╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ ██╗ ██╗ ██████╗ "),
|
|
122
|
+
cyan(" ██╔════╝ ██╔═══██╗ ████╗ ████║ ██╔══██╗ ██║ ██║ ██║ ██║ ██╔══██╗"),
|
|
123
|
+
cyan(" ██║ ██║ ██║ ██╔████╔██║ ██████╔╝ ███████║ ██║ ██║ ██████╔╝"),
|
|
124
|
+
cyan(" ██║ ██║ ██║ ██║╚██╔╝██║ ██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██╗"),
|
|
125
|
+
cyan(" ╚██████╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝"),
|
|
126
|
+
cyan(" ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "),
|
|
127
|
+
"",
|
|
128
|
+
"",
|
|
129
|
+
` ${green("Version")} ${pkg.version}`,
|
|
130
|
+
` ${green("Project")} ${cwd}`,
|
|
131
|
+
` ${green("Hash")} ${hash}`,
|
|
132
|
+
` ${green("Port")} ${displayPort}`,
|
|
133
|
+
"",
|
|
134
|
+
` ${yellow("URL")} ${cyan(url)}`,
|
|
135
|
+
"",
|
|
136
|
+
white(" ─────────────────────────────────────────────"),
|
|
137
|
+
"",
|
|
138
|
+
` ${"comphub status".padEnd(30)}View all projects`,
|
|
139
|
+
` ${`comphub stop ${hash}`.padEnd(30)}Stop current project`,
|
|
140
|
+
` ${"comphub stop --all".padEnd(30)}Stop all projects`,
|
|
141
|
+
` ${"comphub kill".padEnd(30)}Shutdown master`,
|
|
142
|
+
""
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
if (portMismatch) {
|
|
146
|
+
lines.push(
|
|
147
|
+
white(" ─────────────────────────────────────────────"),
|
|
148
|
+
"",
|
|
149
|
+
red(` ⚠ Master is already running on port ${runningPublicPort}, ignoring -p ${opts.port}`),
|
|
150
|
+
red(` To change port, please run comphub kill first`),
|
|
151
|
+
""
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(lines.join("\n"));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---- Main ----
|
|
159
|
+
|
|
160
|
+
async function main(pkg, opts) {
|
|
161
|
+
if (!fs.existsSync(path.join(process.cwd(), "package.json"))) {
|
|
162
|
+
console.error(`${red("✗")} Current directory is not a Node.js project`);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const cwd = process.cwd();
|
|
167
|
+
const hash = computeHash(cwd);
|
|
168
|
+
|
|
169
|
+
opts = mergeConfig(opts, cwd);
|
|
170
|
+
|
|
171
|
+
console.log(`${green("✓")} ${pkg.name} v${pkg.version}`);
|
|
172
|
+
|
|
173
|
+
const { runtime, portMismatch, runningPublicPort } = await ensureMasterRunning(opts, pkg);
|
|
174
|
+
|
|
175
|
+
const masterPort = runtime.managementPort;
|
|
176
|
+
const displayPort = runtime.publicPort;
|
|
177
|
+
|
|
178
|
+
await registerProject(masterPort, hash, cwd, opts);
|
|
179
|
+
|
|
180
|
+
printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = { main };
|
package/bin/lib/utils.js
CHANGED
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const crypto = require("crypto");
|
|
4
|
-
const { MASTER_RUNTIME_FILE } = require("./constants");
|
|
5
|
-
|
|
6
|
-
const projectRoot = path.resolve(__dirname, "../..");
|
|
7
|
-
|
|
8
|
-
function readJson(p) {
|
|
9
|
-
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 加载项目根目录的 .comphub.json 或 .comphub.js
|
|
14
|
-
* 作为命令行参数的 fallback,优先级低于 CLI 选项
|
|
15
|
-
*/
|
|
16
|
-
function loadComphubConfig(cwd) {
|
|
17
|
-
const jsonPath = path.join(cwd, ".comphub.json");
|
|
18
|
-
const jsPath = path.join(cwd, ".comphub.js");
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
if (fs.existsSync(jsonPath)) {
|
|
22
|
-
return readJson(jsonPath);
|
|
23
|
-
}
|
|
24
|
-
} catch {
|
|
25
|
-
/* corrupted json, try js */
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
if (fs.existsSync(jsPath)) {
|
|
30
|
-
const mod = require(jsPath);
|
|
31
|
-
return mod.default || mod;
|
|
32
|
-
}
|
|
33
|
-
} catch {
|
|
34
|
-
/* ignore */
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function getLocalPkgInfo() {
|
|
41
|
-
const pkgPath = path.join(projectRoot, "package.json");
|
|
42
|
-
const pkg = readJson(pkgPath);
|
|
43
|
-
const entry = path.join(projectRoot, pkg.main || "dist/index.js");
|
|
44
|
-
return { pkg, entry };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function computeHash(dir) {
|
|
48
|
-
return crypto.createHash("sha256").update(dir).digest("hex").slice(0, 8);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function readMasterRuntime() {
|
|
52
|
-
try {
|
|
53
|
-
if (fs.existsSync(MASTER_RUNTIME_FILE)) {
|
|
54
|
-
const data = JSON.parse(fs.readFileSync(MASTER_RUNTIME_FILE, "utf8"));
|
|
55
|
-
try {
|
|
56
|
-
process.kill(data.pid, 0);
|
|
57
|
-
// PID 存活不一定是 master 进程(PID 可能被复用),
|
|
58
|
-
// 额外通过健康检查确认端口确实在服务
|
|
59
|
-
const alive = await checkMasterAlive(data.managementPort);
|
|
60
|
-
if (alive) return data;
|
|
61
|
-
} catch {
|
|
62
|
-
/* PID not found */
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
fs.unlinkSync(MASTER_RUNTIME_FILE);
|
|
66
|
-
} catch {
|
|
67
|
-
/* best effort */
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
} catch {
|
|
71
|
-
/* corrupted file */
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function checkMasterAlive(port) {
|
|
77
|
-
const { ADMIN_API } = require("./constants");
|
|
78
|
-
const http = require("http");
|
|
79
|
-
return new Promise(resolve => {
|
|
80
|
-
const req = http.get(`http://localhost:${port}${ADMIN_API.HEALTH}`, res => {
|
|
81
|
-
let body = "";
|
|
82
|
-
res.on("data", chunk => (body += chunk));
|
|
83
|
-
res.on("end", () => {
|
|
84
|
-
try {
|
|
85
|
-
const json = JSON.parse(body);
|
|
86
|
-
resolve(typeof json.status === "string");
|
|
87
|
-
} catch {
|
|
88
|
-
resolve(false);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
req.on("error", () => resolve(false));
|
|
93
|
-
req.setTimeout(2000, () => {
|
|
94
|
-
req.destroy();
|
|
95
|
-
resolve(false);
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const colors = {
|
|
101
|
-
cyan: s => `\x1b[36m${s}\x1b[0m`,
|
|
102
|
-
green: s => `\x1b[32m${s}\x1b[0m`,
|
|
103
|
-
white: s => `\x1b[37m${s}\x1b[0m`,
|
|
104
|
-
yellow: s => `\x1b[33m${s}\x1b[0m`,
|
|
105
|
-
red: s => `\x1b[31m${s}\x1b[0m`
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
function apiUrl(port, pathname) {
|
|
109
|
-
return `http://localhost:${port}${pathname}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = { getLocalPkgInfo, computeHash, projectRoot, readMasterRuntime, loadComphubConfig, colors, apiUrl };
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const crypto = require("crypto");
|
|
4
|
+
const { MASTER_RUNTIME_FILE } = require("./constants");
|
|
5
|
+
|
|
6
|
+
const projectRoot = path.resolve(__dirname, "../..");
|
|
7
|
+
|
|
8
|
+
function readJson(p) {
|
|
9
|
+
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 加载项目根目录的 .comphub.json 或 .comphub.js
|
|
14
|
+
* 作为命令行参数的 fallback,优先级低于 CLI 选项
|
|
15
|
+
*/
|
|
16
|
+
function loadComphubConfig(cwd) {
|
|
17
|
+
const jsonPath = path.join(cwd, ".comphub.json");
|
|
18
|
+
const jsPath = path.join(cwd, ".comphub.js");
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
if (fs.existsSync(jsonPath)) {
|
|
22
|
+
return readJson(jsonPath);
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
/* corrupted json, try js */
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(jsPath)) {
|
|
30
|
+
const mod = require(jsPath);
|
|
31
|
+
return mod.default || mod;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
/* ignore */
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getLocalPkgInfo() {
|
|
41
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
42
|
+
const pkg = readJson(pkgPath);
|
|
43
|
+
const entry = path.join(projectRoot, pkg.main || "dist/index.js");
|
|
44
|
+
return { pkg, entry };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function computeHash(dir) {
|
|
48
|
+
return crypto.createHash("sha256").update(dir).digest("hex").slice(0, 8);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function readMasterRuntime() {
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(MASTER_RUNTIME_FILE)) {
|
|
54
|
+
const data = JSON.parse(fs.readFileSync(MASTER_RUNTIME_FILE, "utf8"));
|
|
55
|
+
try {
|
|
56
|
+
process.kill(data.pid, 0);
|
|
57
|
+
// PID 存活不一定是 master 进程(PID 可能被复用),
|
|
58
|
+
// 额外通过健康检查确认端口确实在服务
|
|
59
|
+
const alive = await checkMasterAlive(data.managementPort);
|
|
60
|
+
if (alive) return data;
|
|
61
|
+
} catch {
|
|
62
|
+
/* PID not found */
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
fs.unlinkSync(MASTER_RUNTIME_FILE);
|
|
66
|
+
} catch {
|
|
67
|
+
/* best effort */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
/* corrupted file */
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function checkMasterAlive(port) {
|
|
77
|
+
const { ADMIN_API } = require("./constants");
|
|
78
|
+
const http = require("http");
|
|
79
|
+
return new Promise(resolve => {
|
|
80
|
+
const req = http.get(`http://localhost:${port}${ADMIN_API.HEALTH}`, res => {
|
|
81
|
+
let body = "";
|
|
82
|
+
res.on("data", chunk => (body += chunk));
|
|
83
|
+
res.on("end", () => {
|
|
84
|
+
try {
|
|
85
|
+
const json = JSON.parse(body);
|
|
86
|
+
resolve(typeof json.status === "string");
|
|
87
|
+
} catch {
|
|
88
|
+
resolve(false);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
req.on("error", () => resolve(false));
|
|
93
|
+
req.setTimeout(2000, () => {
|
|
94
|
+
req.destroy();
|
|
95
|
+
resolve(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const colors = {
|
|
101
|
+
cyan: s => `\x1b[36m${s}\x1b[0m`,
|
|
102
|
+
green: s => `\x1b[32m${s}\x1b[0m`,
|
|
103
|
+
white: s => `\x1b[37m${s}\x1b[0m`,
|
|
104
|
+
yellow: s => `\x1b[33m${s}\x1b[0m`,
|
|
105
|
+
red: s => `\x1b[31m${s}\x1b[0m`
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function apiUrl(port, pathname) {
|
|
109
|
+
return `http://localhost:${port}${pathname}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { getLocalPkgInfo, computeHash, projectRoot, readMasterRuntime, loadComphubConfig, colors, apiUrl };
|