comp-hub 0.42.6 → 0.42.7
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 +133 -133
- package/bin/cli.js +58 -58
- package/bin/lib/commands.js +173 -173
- package/bin/lib/constants.js +22 -22
- package/bin/lib/http.js +130 -130
- package/bin/lib/startup.js +254 -254
- package/bin/lib/utils.js +112 -112
- package/dist/index.js +2 -2
- package/dist/master.js +1 -1
- package/dist/website/container/assets/index-BIcqvqGy.js +33 -0
- package/dist/website/container/assets/index-BmW7jge6.css +1 -0
- package/dist/website/container/index.html +35 -35
- package/dist/website/container/js/react-jsx-loader.js +232 -232
- 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-CfkvQ31V.css +1 -0
- package/dist/website/item/assets/{index-ydOZoQ8L.js → index-DDuyXuC_.js} +32 -32
- package/dist/website/item/index.html +30 -30
- package/dist/website/main/assets/AuthCodeSendBtn.vue_vue_type_script_setup_true_lang-D-jhE3yA.js +1 -0
- package/dist/website/main/assets/BaseBadge-Dwav8-_V.js +1 -0
- package/dist/website/main/assets/{BaseBadge-Cs4OMkMY.css → BaseBadge-ledSuDnX.css} +1 -1
- package/dist/website/main/assets/BaseDialog.vue_vue_type_script_setup_true_lang-BK7dbjCA.js +1 -0
- package/dist/website/main/assets/BaseSegmented.vue_vue_type_script_setup_true_lang-CcSntY2B.js +1 -0
- package/dist/website/main/assets/CanvasAnimation.vue_vue_type_script_setup_true_lang-Br1ZlT-C.js +1 -0
- package/dist/website/main/assets/{CodeMirrorEditor-Cqow8ROO-Cp4gUJc9.js → CodeMirrorEditor-Cqow8ROO-D5a3bJrn.js} +3 -3
- package/dist/website/main/assets/{EmptyStatusContainer.vue_vue_type_script_setup_true_lang-BesoRTEn.js → EmptyStatusContainer.vue_vue_type_script_setup_true_lang-CNOUTt3S.js} +1 -1
- package/dist/website/main/assets/GithubCallback-DRfvlj0J.js +1 -0
- package/dist/website/main/assets/{LoadingWrapper.vue_vue_type_script_setup_true_lang-CGbl_jEe.js → LoadingWrapper.vue_vue_type_script_setup_true_lang-BYA4gUOI.js} +1 -1
- package/dist/website/main/assets/ReplyForm.vue_vue_type_script_setup_true_lang-DztoCP65.js +1 -0
- package/dist/website/main/assets/SiteLogo-BK2LmBdP.js +1 -0
- package/dist/website/main/assets/SiteLogo-CszHjM2d.css +1 -0
- package/dist/website/main/assets/{cloud-upload-D1ObRbGg.js → cloud-upload-BqJFdj_J.js} +1 -1
- package/dist/website/main/assets/el-table-column-C2GQUl4R.js +1 -0
- package/dist/website/main/assets/{home-rounded-DxDBjzlk.js → home-rounded-CN0qCDnE.js} +1 -1
- package/dist/website/main/assets/index--Gl7kxRA.js +1 -0
- package/dist/website/main/assets/index-3thCh3qq.js +6 -0
- package/dist/website/main/assets/index-4DalWgib.js +1 -0
- package/dist/website/main/assets/{index-72rReQ2I-BkBRAKfL.js → index-72rReQ2I-DT4t3yv7.js} +1 -1
- package/dist/website/main/assets/index-APbKgJd9.js +1 -0
- package/dist/website/main/assets/index-B0gjb0CJ.js +1 -0
- package/dist/website/main/assets/index-B4Denke_.js +1 -0
- package/dist/website/main/assets/index-BDXFoym_.js +1 -0
- package/dist/website/main/assets/index-BGAAZcsT.js +1 -0
- package/dist/website/main/assets/index-BIIfOsTM.js +1 -0
- package/dist/website/main/assets/index-BLCWm3Fn.js +1 -0
- package/dist/website/main/assets/index-BU8KZHUA.js +1 -0
- package/dist/website/main/assets/index-BYr_uRik.js +1 -0
- package/dist/website/main/assets/{index-uNuyW4Nf.js → index-BZrH5MtZ.js} +3 -3
- package/dist/website/main/assets/{index-D3slGitA.js → index-B_R6um8d.js} +1 -1
- package/dist/website/main/assets/index-B_rFxdlu.js +1 -0
- package/dist/website/main/assets/index-BiXGgf9p.js +1 -0
- package/dist/website/main/assets/{index-D_l0AXZ5.js → index-BkWR9AZK.js} +21 -21
- package/dist/website/main/assets/index-C0QhLzHP.js +1 -0
- package/dist/website/main/assets/index-C1XabFrJ.js +1 -0
- package/dist/website/main/assets/{index-D8C7Y4kK.js → index-C7kbImye.js} +1 -1
- package/dist/website/main/assets/index-CAhGpcw0.js +1 -0
- package/dist/website/main/assets/index-CGL710hV.js +1 -0
- package/dist/website/main/assets/index-CKtWjh5B.js +1 -0
- package/dist/website/main/assets/index-CLT68CCM.css +1 -0
- package/dist/website/main/assets/index-CQTH6cXs.js +1 -0
- package/dist/website/main/assets/index-CRerG_jj.js +3 -0
- package/dist/website/main/assets/index-Cey7IuDj.js +1 -0
- package/dist/website/main/assets/{index-leNHmgTy.js → index-CkHWSI2b.js} +2 -2
- package/dist/website/main/assets/index-CmDlT9OG.js +1 -0
- package/dist/website/main/assets/index-Cp8ViCIR.js +1 -0
- package/dist/website/main/assets/index-CwoRYDBS.js +1 -0
- package/dist/website/main/assets/index-D-zBzPPy.css +1 -0
- package/dist/website/main/assets/index-D2YpsmLA.js +1 -0
- package/dist/website/main/assets/index-D6zFRika.js +1 -0
- package/dist/website/main/assets/{index-D76eWsZ1-DzwZi5Q3.js → index-D76eWsZ1-_nyu7eQb.js} +1 -1
- package/dist/website/main/assets/index-DCw86ogq.js +1 -0
- package/dist/website/main/assets/index-DFFxrR-b.js +7 -0
- package/dist/website/main/assets/index-DGc9WQeN.js +2 -0
- package/dist/website/main/assets/index-DKrCvuOe.js +1 -0
- package/dist/website/main/assets/index-DOR-FWWa.js +1 -0
- package/dist/website/main/assets/index-DRLluzuR.js +1 -0
- package/dist/website/main/assets/index-DShvoWo5.js +1 -0
- package/dist/website/main/assets/index-DXwxbpG5.js +1 -0
- package/dist/website/main/assets/index-DlkXaOkE.css +1 -0
- package/dist/website/main/assets/{index-Dms7ZQ2h-BnAar77P.js → index-Dms7ZQ2h-CXFhQfmI.js} +1 -1
- package/dist/website/main/assets/index-DpUmZw4S.js +1 -0
- package/dist/website/main/assets/{index-DqiFCfGx-D9TcJHnH.js → index-DqiFCfGx-BdEBAtzU.js} +1 -1
- package/dist/website/main/assets/index-DvXl2xci.js +1 -0
- package/dist/website/main/assets/{index-DxypSQ0K-BPBBAlML.js → index-DxypSQ0K-wm4wOntm.js} +1 -1
- package/dist/website/main/assets/index-F2VTlbBR.js +1 -0
- package/dist/website/main/assets/index-GF-tJPqO.js +1 -0
- package/dist/website/main/assets/{index-B2Zubotl.css → index-Ji2WmMDE.css} +1 -1
- package/dist/website/main/assets/index-K6SUsq-d.js +1 -0
- package/dist/website/main/assets/{index-D0PH3BvM.js → index-KhSB9wFh.js} +1 -1
- package/dist/website/main/assets/index-Mnu1wKgu.css +1 -0
- package/dist/website/main/assets/{index-T76vWLBU-CH1SE5a0.js → index-T76vWLBU-C0PuuEJA.js} +1 -1
- package/dist/website/main/assets/index-_FSb4t-p.js +1 -0
- package/dist/website/main/assets/index-bwN_igRO.js +1 -0
- package/dist/website/main/assets/{index-fZBcgzqR-2hQDo6Qk.js → index-fZBcgzqR-CRYn0SF1.js} +1 -1
- package/dist/website/main/assets/{index-mqeGrL6B-CniizjGa.js → index-mqeGrL6B-BF63ossB.js} +1 -1
- package/dist/website/main/assets/index-ruR13HOB.js +2 -0
- package/dist/website/main/assets/index-sFKst8yj.js +1 -0
- package/dist/website/main/assets/index.vue_vue_type_script_setup_true_lang-C9gk_QQc.js +2 -0
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-CS2m0nhD.js → index.vue_vue_type_script_setup_true_lang-CJ6uny7o.js} +1 -1
- package/dist/website/main/assets/index.vue_vue_type_script_setup_true_lang-CsPwDCDo.js +1 -0
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-30dEaEsH.js → index.vue_vue_type_script_setup_true_lang-D1Yenn0_.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-CbFazsFi.js → index.vue_vue_type_script_setup_true_lang-DVDnFtUl.js} +1 -1
- package/dist/website/main/assets/{index.vue_vue_type_script_setup_true_lang-BDWcS5BK.js → index.vue_vue_type_script_setup_true_lang-Do3aUu26.js} +1 -1
- package/dist/website/main/assets/index.vue_vue_type_style_index_0_lang-C7Y8qEDg.js +1 -0
- package/dist/website/main/assets/index.vue_vue_type_style_index_0_lang-CMD4VvG4.js +6 -0
- package/dist/website/main/assets/index3-BiHnTK1Z.js +1 -0
- package/dist/website/main/assets/{kid-star-CFee_wmK.js → kid-star-CiMfD7Sa.js} +1 -1
- package/dist/website/main/assets/{md5-Cswfg7iQ.js → md5-Dkj6ZNQB.js} +1 -1
- package/dist/website/main/assets/{settings-9laCRsDB.js → settings-CSr4nQM1.js} +1 -1
- package/dist/website/main/assets/{supervised-user-circle-CjLjPJgX.js → supervised-user-circle-BwUSx6w_.js} +1 -1
- package/dist/website/main/assets/{sync-saved-locally-rounded-lTPGDNrR.js → sync-saved-locally-rounded-DyESI323.js} +1 -1
- package/dist/website/main/assets/{terminal-fRNgCBAo.js → terminal-DAZ0JqKA.js} +1 -1
- package/dist/website/main/assets/{useCacheOssStore-a1U35_yC.js → useCacheOssStore-BovkXuwo.js} +1 -1
- package/dist/website/main/assets/useCompEditStore-C2cDxPfw.css +1 -0
- package/dist/website/main/assets/{useCompEditStore-CrmHmY3O.js → useCompEditStore-C47KFq3s.js} +13 -13
- package/dist/website/main/assets/{useCompMetaLocalStore-DScrCqkw.js → useCompMetaLocalStore-DUMtt_yz.js} +1 -1
- package/dist/website/main/assets/{useKeyword-DPrVS0y6.js → useKeyword-C0diQZGk.js} +1 -1
- package/dist/website/main/assets/useMessage-Dxhw28ry.js +1 -0
- package/dist/website/main/assets/{useTeamHeaders-DPQN2GDa.css → useTeamHeaders-BMHQzwDq.css} +1 -1
- package/dist/website/main/assets/{useTeamHeaders-Bh9dFUuA.js → useTeamHeaders-BuIG232Y.js} +2 -2
- package/dist/website/main/assets/{useUnreadStore-dPE0KFd2.js → useUnreadStore-CmdbW-yt.js} +1 -1
- package/dist/website/main/assets/{useVscodeOpen-CZclwKHe.js → useVscodeOpen-0AeELCuF.js} +1 -1
- package/dist/website/main/assets/{vendor-core-0VX1BwM6.js → vendor-core-CNXB6kVe.js} +3 -3
- package/dist/website/main/assets/{vendor-ui-CRHKNWIQ.js → vendor-ui-Cvgmewma.js} +15 -15
- package/dist/website/main/index.html +4 -4
- package/package.json +52 -52
- package/dist/website/container/assets/index-D3DdLVqK.css +0 -1
- package/dist/website/container/assets/index-eRcJ8qeF.js +0 -33
- package/dist/website/item/assets/index-CcAhYDgG.css +0 -1
- package/dist/website/main/assets/AuthCodeSendBtn.vue_vue_type_script_setup_true_lang-Nj-eqxJP.js +0 -1
- package/dist/website/main/assets/BaseBadge-UKoV0gRD.js +0 -1
- package/dist/website/main/assets/BaseDialog.vue_vue_type_script_setup_true_lang-Bry1evkv.js +0 -1
- package/dist/website/main/assets/BaseSegmented.vue_vue_type_script_setup_true_lang-B5FydYbm.js +0 -1
- package/dist/website/main/assets/CanvasAnimation.vue_vue_type_script_setup_true_lang-BJ1WLWiR.js +0 -1
- package/dist/website/main/assets/GithubCallback-CxWdJQOK.js +0 -1
- package/dist/website/main/assets/ReplyForm.vue_vue_type_script_setup_true_lang-8UmMm5tC.js +0 -1
- package/dist/website/main/assets/el-table-column-DzGAH4eh.js +0 -1
- package/dist/website/main/assets/index--iTpYFYc.js +0 -1
- package/dist/website/main/assets/index-6RTiuZ_N.js +0 -1
- package/dist/website/main/assets/index-B7DPM0mK.js +0 -1
- package/dist/website/main/assets/index-BD4yGNF1.js +0 -1
- package/dist/website/main/assets/index-BOYLNx_j.js +0 -3
- package/dist/website/main/assets/index-BOmQK5id.js +0 -1
- package/dist/website/main/assets/index-BSzGziWF.js +0 -1
- package/dist/website/main/assets/index-B_K1LoXG.css +0 -1
- package/dist/website/main/assets/index-Barm7Xqt.js +0 -1
- package/dist/website/main/assets/index-Bknt6b_4.js +0 -1
- package/dist/website/main/assets/index-BsU1mqnT.js +0 -1
- package/dist/website/main/assets/index-Bth_SWAt.css +0 -1
- package/dist/website/main/assets/index-CCI9Rc7z.js +0 -1
- package/dist/website/main/assets/index-CD5eg05L.js +0 -1
- package/dist/website/main/assets/index-CDafjgED.js +0 -1
- package/dist/website/main/assets/index-CLOuN4ur.js +0 -1
- package/dist/website/main/assets/index-CNwHp9a1.js +0 -1
- package/dist/website/main/assets/index-COWiImKY.js +0 -1
- package/dist/website/main/assets/index-CgXxpwQw.js +0 -1
- package/dist/website/main/assets/index-CkOnRg_y.js +0 -1
- package/dist/website/main/assets/index-CmQK-Jf9.js +0 -1
- package/dist/website/main/assets/index-CnITVEbA.js +0 -1
- package/dist/website/main/assets/index-Cu67NMeG.css +0 -1
- package/dist/website/main/assets/index-D0e6xuyQ.js +0 -1
- package/dist/website/main/assets/index-D6CmgY15.js +0 -1
- package/dist/website/main/assets/index-D7c69zQF.js +0 -1
- package/dist/website/main/assets/index-DDPLszjZ.js +0 -1
- package/dist/website/main/assets/index-DKUt2N0Q.js +0 -1
- package/dist/website/main/assets/index-DNjrI1LU.js +0 -1
- package/dist/website/main/assets/index-DV_sHy81.js +0 -6
- package/dist/website/main/assets/index-D_Q0Ab96.js +0 -1
- package/dist/website/main/assets/index-DaCJE4Tc.js +0 -1
- package/dist/website/main/assets/index-Ddy0owpx.js +0 -7
- package/dist/website/main/assets/index-DiifzDSt.css +0 -1
- package/dist/website/main/assets/index-Dm5v9g0S.js +0 -1
- package/dist/website/main/assets/index-Dnvm4BY6.js +0 -1
- package/dist/website/main/assets/index-DoO3bCVS.js +0 -1
- package/dist/website/main/assets/index-Dr7Yk5Fw.js +0 -1
- package/dist/website/main/assets/index-DrqsuuZe.js +0 -1
- package/dist/website/main/assets/index-Du13eAUb.js +0 -1
- package/dist/website/main/assets/index-Dw4CVULY.js +0 -1
- package/dist/website/main/assets/index-DzEOWouw.js +0 -1
- package/dist/website/main/assets/index-VDJso98_.js +0 -1
- package/dist/website/main/assets/index-WXVFqbJx.js +0 -1
- package/dist/website/main/assets/index-hN9vFUxU.js +0 -1
- package/dist/website/main/assets/index-jaaimYtM.js +0 -1
- package/dist/website/main/assets/index-pGXTuXlq.js +0 -2
- package/dist/website/main/assets/index-qht0HNE9.js +0 -2
- package/dist/website/main/assets/index.vue_vue_type_script_setup_true_lang-Bwcjk_yE.js +0 -1
- package/dist/website/main/assets/index.vue_vue_type_script_setup_true_lang-D1YUVpY-.js +0 -1
- package/dist/website/main/assets/index.vue_vue_type_script_setup_true_lang-DWiaL7Bp.js +0 -2
- package/dist/website/main/assets/index.vue_vue_type_style_index_0_lang-BNUYhtju.js +0 -1
- package/dist/website/main/assets/index.vue_vue_type_style_index_0_lang-DOF1fd4X.js +0 -6
- package/dist/website/main/assets/useCompEditStore-ZAdO-kT7.css +0 -1
- package/dist/website/main/assets/useMarkdownText-9CjtixYv.js +0 -1
- package/dist/website/main/assets/useMessage-FHsgvLin.js +0 -1
package/bin/lib/startup.js
CHANGED
|
@@ -1,254 +1,254 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const { spawn } = require("child_process");
|
|
4
|
-
const { httpPost, streamLogs, deregisterProject } = 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
|
-
logLevel: opts.logLevel || fileConfig.logLevel || "info"
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ---- Master lifecycle ----
|
|
27
|
-
|
|
28
|
-
async function waitForMasterRuntime() {
|
|
29
|
-
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
30
|
-
const runtime = await readMasterRuntime();
|
|
31
|
-
if (runtime) return runtime;
|
|
32
|
-
await new Promise(r => setTimeout(r, POLL_INTERVAL));
|
|
33
|
-
}
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function spawnMaster(publicPort, pkg, cwd) {
|
|
38
|
-
const masterEntry = path.join(projectRoot, "dist/master.js");
|
|
39
|
-
|
|
40
|
-
if (!fs.existsSync(masterEntry)) {
|
|
41
|
-
throw new Error("Master entry file not found, startup failed");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const masterArgs = ["--port", String(publicPort), "--public-port", String(publicPort)];
|
|
45
|
-
// 不再使用 stdio:inherit,改为 pipe stdout/stderr:
|
|
46
|
-
// - stdin: inherit(Ctrl+C 信号需要)
|
|
47
|
-
// - stdout: pipe(过滤掉 MidwayJS 子进程日志,只保留 master 自身消息)
|
|
48
|
-
// - stderr: pipe(转发给 CLI 用于崩溃诊断)
|
|
49
|
-
const child = spawn(process.execPath, ["--no-deprecation", masterEntry, ...masterArgs], {
|
|
50
|
-
stdio: ["inherit", "pipe", "pipe"],
|
|
51
|
-
cwd,
|
|
52
|
-
env: {
|
|
53
|
-
...process.env,
|
|
54
|
-
COMP_HUB_VERSION: pkg.version
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// stdout 行缓冲(跨 data 事件的未完成行)
|
|
59
|
-
let stdoutPending = "";
|
|
60
|
-
|
|
61
|
-
// 转发 stderr 给 CLI(用于崩溃诊断)
|
|
62
|
-
if (child.stderr) {
|
|
63
|
-
child.stderr.on("data", chunk => {
|
|
64
|
-
process.stderr.write(chunk);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 过滤 stdout:只转发 master 自身消息,跳过带 [hash] 前缀的 MidwayJS 子进程日志
|
|
69
|
-
// MidwayJS 日志会通过 SSE 按项目过滤后展示
|
|
70
|
-
if (child.stdout) {
|
|
71
|
-
child.stdout.on("data", chunk => {
|
|
72
|
-
stdoutPending += chunk.toString();
|
|
73
|
-
const lines = stdoutPending.split("\n");
|
|
74
|
-
stdoutPending = lines.pop() || ""; // 保留不完整行
|
|
75
|
-
for (const line of lines) {
|
|
76
|
-
// 跳过 [8位hex_hash] 前缀的行(属于 MidwayJS 子进程日志)
|
|
77
|
-
if (!/^\[[0-9a-f]{8}\]/.test(line)) {
|
|
78
|
-
process.stdout.write(line + "\n");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 转发父进程信号,确保 Ctrl+C 时 master 也能优雅退出
|
|
85
|
-
["SIGINT", "SIGTERM"].forEach(sig => {
|
|
86
|
-
process.on(sig, () => {
|
|
87
|
-
child.kill(sig);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Master 退出时 CLI 也跟着退出
|
|
92
|
-
child.on("exit", code => {
|
|
93
|
-
// 输出剩余的 pending 数据
|
|
94
|
-
if (stdoutPending && !/^\[[0-9a-f]{8}\]/.test(stdoutPending)) {
|
|
95
|
-
process.stdout.write(stdoutPending);
|
|
96
|
-
}
|
|
97
|
-
process.exit(code || 0);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const runtime = await waitForMasterRuntime();
|
|
101
|
-
if (!runtime) {
|
|
102
|
-
child.kill();
|
|
103
|
-
throw new Error("Master startup timeout");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return { runtime };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 确保 master 在运行,返回运行时信息。
|
|
111
|
-
* 如果已在运行则复用,否则 spawn 新的 master 进程。
|
|
112
|
-
*/
|
|
113
|
-
async function ensureMasterRunning(opts, pkg) {
|
|
114
|
-
let portMismatch = false;
|
|
115
|
-
let runningPublicPort = 0;
|
|
116
|
-
|
|
117
|
-
const existingRuntime = await readMasterRuntime();
|
|
118
|
-
|
|
119
|
-
if (existingRuntime) {
|
|
120
|
-
// Master 已在运行,检查用户指定的端口是否匹配
|
|
121
|
-
const expectedPort = opts.port != null ? opts.port : existingRuntime.publicPort;
|
|
122
|
-
if (opts.port != null && existingRuntime.publicPort !== expectedPort) {
|
|
123
|
-
portMismatch = true;
|
|
124
|
-
runningPublicPort = existingRuntime.publicPort;
|
|
125
|
-
}
|
|
126
|
-
return { runtime: existingRuntime, portMismatch, runningPublicPort };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 启动新 master(前台运行,日志输出到终端)
|
|
130
|
-
const portHint = opts.port != null ? opts.port : DEFAULT_PORT;
|
|
131
|
-
const publicPort = await getPort({ port: getPort.makeRange(portHint, portHint + 100) });
|
|
132
|
-
|
|
133
|
-
const { runtime } = await spawnMaster(publicPort, pkg, process.cwd());
|
|
134
|
-
|
|
135
|
-
return { runtime, portMismatch, runningPublicPort };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ---- Registration ----
|
|
139
|
-
|
|
140
|
-
async function registerProject(masterPort, hash, cwd, opts) {
|
|
141
|
-
const body = {
|
|
142
|
-
hash,
|
|
143
|
-
cwd,
|
|
144
|
-
dir: opts.dir || "./"
|
|
145
|
-
};
|
|
146
|
-
if (opts.api) body.api = opts.api;
|
|
147
|
-
if (opts.allowDebug) body.allowDebug = true;
|
|
148
|
-
if (opts.proxy) body.proxy = opts.proxy;
|
|
149
|
-
if (opts.logLevel) body.logLevel = opts.logLevel;
|
|
150
|
-
|
|
151
|
-
const res = await httpPost(apiUrl(masterPort, ADMIN_API.REGISTER), body);
|
|
152
|
-
if (!res.ok) {
|
|
153
|
-
throw new Error(res.error || "Registration failed");
|
|
154
|
-
}
|
|
155
|
-
console.log(`${green("✓")} Project registered (hash: ${hash})`);
|
|
156
|
-
return res;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ---- Banner ----
|
|
160
|
-
|
|
161
|
-
function printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort }) {
|
|
162
|
-
const url = `http://localhost:${displayPort}/${hash}/main/`;
|
|
163
|
-
|
|
164
|
-
const lines = [
|
|
165
|
-
"",
|
|
166
|
-
cyan(" ██████╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ ██╗ ██╗ ██████╗ "),
|
|
167
|
-
cyan(" ██╔════╝ ██╔═══██╗ ████╗ ████║ ██╔══██╗ ██║ ██║ ██║ ██║ ██╔══██╗"),
|
|
168
|
-
cyan(" ██║ ██║ ██║ ██╔████╔██║ ██████╔╝ ███████║ ██║ ██║ ██████╔╝"),
|
|
169
|
-
cyan(" ██║ ██║ ██║ ██║╚██╔╝██║ ██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██╗"),
|
|
170
|
-
cyan(" ╚██████╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝"),
|
|
171
|
-
cyan(" ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "),
|
|
172
|
-
"",
|
|
173
|
-
"",
|
|
174
|
-
` ${green("Version")} ${pkg.version}`,
|
|
175
|
-
` ${green("Project")} ${cwd}`,
|
|
176
|
-
` ${green("Hash")} ${hash}`,
|
|
177
|
-
` ${green("Port")} ${displayPort}`,
|
|
178
|
-
"",
|
|
179
|
-
` ${yellow("URL")} ${cyan(url)}`,
|
|
180
|
-
"",
|
|
181
|
-
white(" ─────────────────────────────────────────────"),
|
|
182
|
-
"",
|
|
183
|
-
` ${"comphub status".padEnd(30)}View all projects`,
|
|
184
|
-
` ${`comphub stop ${hash}`.padEnd(30)}Stop current project`,
|
|
185
|
-
` ${"comphub stop --all".padEnd(30)}Stop all projects`,
|
|
186
|
-
` ${"comphub kill".padEnd(30)}Shutdown master`,
|
|
187
|
-
""
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
if (portMismatch) {
|
|
191
|
-
lines.push(
|
|
192
|
-
white(" ─────────────────────────────────────────────"),
|
|
193
|
-
"",
|
|
194
|
-
red(` ⚠ Master is already running on port ${runningPublicPort}, ignoring -p ${opts.port}`),
|
|
195
|
-
red(` To change port, please run comphub kill first`),
|
|
196
|
-
""
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
console.log(lines.join("\n"));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ---- Main ----
|
|
204
|
-
|
|
205
|
-
async function main(pkg, opts) {
|
|
206
|
-
if (!fs.existsSync(path.join(process.cwd(), "package.json"))) {
|
|
207
|
-
console.error(`${red("✗")} Current directory is not a Node.js project`);
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const cwd = process.cwd();
|
|
212
|
-
const hash = computeHash(cwd);
|
|
213
|
-
|
|
214
|
-
opts = mergeConfig(opts, cwd);
|
|
215
|
-
|
|
216
|
-
console.log(`${green("✓")} ${pkg.name} v${pkg.version}`);
|
|
217
|
-
|
|
218
|
-
const { runtime, portMismatch, runningPublicPort } = await ensureMasterRunning(opts, pkg);
|
|
219
|
-
|
|
220
|
-
const masterPort = runtime.managementPort;
|
|
221
|
-
const displayPort = runtime.publicPort;
|
|
222
|
-
|
|
223
|
-
await registerProject(masterPort, hash, cwd, opts);
|
|
224
|
-
|
|
225
|
-
printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort });
|
|
226
|
-
|
|
227
|
-
// Ctrl+C / kill 时通知 master 注销本项目,使 MidwayJS 子进程能够停止
|
|
228
|
-
let deregistered = false;
|
|
229
|
-
const cleanup = async () => {
|
|
230
|
-
if (deregistered) return;
|
|
231
|
-
deregistered = true;
|
|
232
|
-
try {
|
|
233
|
-
await deregisterProject(masterPort, hash);
|
|
234
|
-
} catch {
|
|
235
|
-
// 忽略注销失败(master 可能已关闭)
|
|
236
|
-
}
|
|
237
|
-
process.exit(0);
|
|
238
|
-
};
|
|
239
|
-
process.on("SIGINT", cleanup);
|
|
240
|
-
process.on("SIGTERM", cleanup);
|
|
241
|
-
|
|
242
|
-
// 统一通过 SSE 实时接收本项目 MidwayJS 子进程日志(按 hash 过滤)
|
|
243
|
-
// 第一个 CLI(spawn master 的)不再通过 stdio:inherit 看到其他项目的日志
|
|
244
|
-
console.log(`\n ${white("─")} Streaming logs from MidwayJS (${hash}) ${white("─")}\n`);
|
|
245
|
-
|
|
246
|
-
const logsUrl = `http://localhost:${masterPort}/__master__/projects/${hash}/logs`;
|
|
247
|
-
try {
|
|
248
|
-
await streamLogs(logsUrl, hash);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
console.error(`\n ${red("✗")} Log stream error: ${err.message}`);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
module.exports = { mergeConfig, main };
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const { httpPost, streamLogs, deregisterProject } = 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
|
+
logLevel: opts.logLevel || fileConfig.logLevel || "info"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ---- Master lifecycle ----
|
|
27
|
+
|
|
28
|
+
async function waitForMasterRuntime() {
|
|
29
|
+
for (let i = 0; i < MAX_RETRIES; i++) {
|
|
30
|
+
const runtime = await readMasterRuntime();
|
|
31
|
+
if (runtime) return runtime;
|
|
32
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL));
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function spawnMaster(publicPort, pkg, cwd) {
|
|
38
|
+
const masterEntry = path.join(projectRoot, "dist/master.js");
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(masterEntry)) {
|
|
41
|
+
throw new Error("Master entry file not found, startup failed");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const masterArgs = ["--port", String(publicPort), "--public-port", String(publicPort)];
|
|
45
|
+
// 不再使用 stdio:inherit,改为 pipe stdout/stderr:
|
|
46
|
+
// - stdin: inherit(Ctrl+C 信号需要)
|
|
47
|
+
// - stdout: pipe(过滤掉 MidwayJS 子进程日志,只保留 master 自身消息)
|
|
48
|
+
// - stderr: pipe(转发给 CLI 用于崩溃诊断)
|
|
49
|
+
const child = spawn(process.execPath, ["--no-deprecation", masterEntry, ...masterArgs], {
|
|
50
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
51
|
+
cwd,
|
|
52
|
+
env: {
|
|
53
|
+
...process.env,
|
|
54
|
+
COMP_HUB_VERSION: pkg.version
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// stdout 行缓冲(跨 data 事件的未完成行)
|
|
59
|
+
let stdoutPending = "";
|
|
60
|
+
|
|
61
|
+
// 转发 stderr 给 CLI(用于崩溃诊断)
|
|
62
|
+
if (child.stderr) {
|
|
63
|
+
child.stderr.on("data", chunk => {
|
|
64
|
+
process.stderr.write(chunk);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 过滤 stdout:只转发 master 自身消息,跳过带 [hash] 前缀的 MidwayJS 子进程日志
|
|
69
|
+
// MidwayJS 日志会通过 SSE 按项目过滤后展示
|
|
70
|
+
if (child.stdout) {
|
|
71
|
+
child.stdout.on("data", chunk => {
|
|
72
|
+
stdoutPending += chunk.toString();
|
|
73
|
+
const lines = stdoutPending.split("\n");
|
|
74
|
+
stdoutPending = lines.pop() || ""; // 保留不完整行
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
// 跳过 [8位hex_hash] 前缀的行(属于 MidwayJS 子进程日志)
|
|
77
|
+
if (!/^\[[0-9a-f]{8}\]/.test(line)) {
|
|
78
|
+
process.stdout.write(line + "\n");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 转发父进程信号,确保 Ctrl+C 时 master 也能优雅退出
|
|
85
|
+
["SIGINT", "SIGTERM"].forEach(sig => {
|
|
86
|
+
process.on(sig, () => {
|
|
87
|
+
child.kill(sig);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Master 退出时 CLI 也跟着退出
|
|
92
|
+
child.on("exit", code => {
|
|
93
|
+
// 输出剩余的 pending 数据
|
|
94
|
+
if (stdoutPending && !/^\[[0-9a-f]{8}\]/.test(stdoutPending)) {
|
|
95
|
+
process.stdout.write(stdoutPending);
|
|
96
|
+
}
|
|
97
|
+
process.exit(code || 0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const runtime = await waitForMasterRuntime();
|
|
101
|
+
if (!runtime) {
|
|
102
|
+
child.kill();
|
|
103
|
+
throw new Error("Master startup timeout");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { runtime };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 确保 master 在运行,返回运行时信息。
|
|
111
|
+
* 如果已在运行则复用,否则 spawn 新的 master 进程。
|
|
112
|
+
*/
|
|
113
|
+
async function ensureMasterRunning(opts, pkg) {
|
|
114
|
+
let portMismatch = false;
|
|
115
|
+
let runningPublicPort = 0;
|
|
116
|
+
|
|
117
|
+
const existingRuntime = await readMasterRuntime();
|
|
118
|
+
|
|
119
|
+
if (existingRuntime) {
|
|
120
|
+
// Master 已在运行,检查用户指定的端口是否匹配
|
|
121
|
+
const expectedPort = opts.port != null ? opts.port : existingRuntime.publicPort;
|
|
122
|
+
if (opts.port != null && existingRuntime.publicPort !== expectedPort) {
|
|
123
|
+
portMismatch = true;
|
|
124
|
+
runningPublicPort = existingRuntime.publicPort;
|
|
125
|
+
}
|
|
126
|
+
return { runtime: existingRuntime, portMismatch, runningPublicPort };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 启动新 master(前台运行,日志输出到终端)
|
|
130
|
+
const portHint = opts.port != null ? opts.port : DEFAULT_PORT;
|
|
131
|
+
const publicPort = await getPort({ port: getPort.makeRange(portHint, portHint + 100) });
|
|
132
|
+
|
|
133
|
+
const { runtime } = await spawnMaster(publicPort, pkg, process.cwd());
|
|
134
|
+
|
|
135
|
+
return { runtime, portMismatch, runningPublicPort };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---- Registration ----
|
|
139
|
+
|
|
140
|
+
async function registerProject(masterPort, hash, cwd, opts) {
|
|
141
|
+
const body = {
|
|
142
|
+
hash,
|
|
143
|
+
cwd,
|
|
144
|
+
dir: opts.dir || "./"
|
|
145
|
+
};
|
|
146
|
+
if (opts.api) body.api = opts.api;
|
|
147
|
+
if (opts.allowDebug) body.allowDebug = true;
|
|
148
|
+
if (opts.proxy) body.proxy = opts.proxy;
|
|
149
|
+
if (opts.logLevel) body.logLevel = opts.logLevel;
|
|
150
|
+
|
|
151
|
+
const res = await httpPost(apiUrl(masterPort, ADMIN_API.REGISTER), body);
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
throw new Error(res.error || "Registration failed");
|
|
154
|
+
}
|
|
155
|
+
console.log(`${green("✓")} Project registered (hash: ${hash})`);
|
|
156
|
+
return res;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ---- Banner ----
|
|
160
|
+
|
|
161
|
+
function printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort }) {
|
|
162
|
+
const url = `http://localhost:${displayPort}/${hash}/main/`;
|
|
163
|
+
|
|
164
|
+
const lines = [
|
|
165
|
+
"",
|
|
166
|
+
cyan(" ██████╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ ██╗ ██╗ ██████╗ "),
|
|
167
|
+
cyan(" ██╔════╝ ██╔═══██╗ ████╗ ████║ ██╔══██╗ ██║ ██║ ██║ ██║ ██╔══██╗"),
|
|
168
|
+
cyan(" ██║ ██║ ██║ ██╔████╔██║ ██████╔╝ ███████║ ██║ ██║ ██████╔╝"),
|
|
169
|
+
cyan(" ██║ ██║ ██║ ██║╚██╔╝██║ ██╔═══╝ ██╔══██║ ██║ ██║ ██╔══██╗"),
|
|
170
|
+
cyan(" ╚██████╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ██║ ██║ ╚██████╔╝ ██████╔╝"),
|
|
171
|
+
cyan(" ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ "),
|
|
172
|
+
"",
|
|
173
|
+
"",
|
|
174
|
+
` ${green("Version")} ${pkg.version}`,
|
|
175
|
+
` ${green("Project")} ${cwd}`,
|
|
176
|
+
` ${green("Hash")} ${hash}`,
|
|
177
|
+
` ${green("Port")} ${displayPort}`,
|
|
178
|
+
"",
|
|
179
|
+
` ${yellow("URL")} ${cyan(url)}`,
|
|
180
|
+
"",
|
|
181
|
+
white(" ─────────────────────────────────────────────"),
|
|
182
|
+
"",
|
|
183
|
+
` ${"comphub status".padEnd(30)}View all projects`,
|
|
184
|
+
` ${`comphub stop ${hash}`.padEnd(30)}Stop current project`,
|
|
185
|
+
` ${"comphub stop --all".padEnd(30)}Stop all projects`,
|
|
186
|
+
` ${"comphub kill".padEnd(30)}Shutdown master`,
|
|
187
|
+
""
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
if (portMismatch) {
|
|
191
|
+
lines.push(
|
|
192
|
+
white(" ─────────────────────────────────────────────"),
|
|
193
|
+
"",
|
|
194
|
+
red(` ⚠ Master is already running on port ${runningPublicPort}, ignoring -p ${opts.port}`),
|
|
195
|
+
red(` To change port, please run comphub kill first`),
|
|
196
|
+
""
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.log(lines.join("\n"));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---- Main ----
|
|
204
|
+
|
|
205
|
+
async function main(pkg, opts) {
|
|
206
|
+
if (!fs.existsSync(path.join(process.cwd(), "package.json"))) {
|
|
207
|
+
console.error(`${red("✗")} Current directory is not a Node.js project`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const cwd = process.cwd();
|
|
212
|
+
const hash = computeHash(cwd);
|
|
213
|
+
|
|
214
|
+
opts = mergeConfig(opts, cwd);
|
|
215
|
+
|
|
216
|
+
console.log(`${green("✓")} ${pkg.name} v${pkg.version}`);
|
|
217
|
+
|
|
218
|
+
const { runtime, portMismatch, runningPublicPort } = await ensureMasterRunning(opts, pkg);
|
|
219
|
+
|
|
220
|
+
const masterPort = runtime.managementPort;
|
|
221
|
+
const displayPort = runtime.publicPort;
|
|
222
|
+
|
|
223
|
+
await registerProject(masterPort, hash, cwd, opts);
|
|
224
|
+
|
|
225
|
+
printBanner(pkg, cwd, hash, displayPort, opts, { portMismatch, runningPublicPort });
|
|
226
|
+
|
|
227
|
+
// Ctrl+C / kill 时通知 master 注销本项目,使 MidwayJS 子进程能够停止
|
|
228
|
+
let deregistered = false;
|
|
229
|
+
const cleanup = async () => {
|
|
230
|
+
if (deregistered) return;
|
|
231
|
+
deregistered = true;
|
|
232
|
+
try {
|
|
233
|
+
await deregisterProject(masterPort, hash);
|
|
234
|
+
} catch {
|
|
235
|
+
// 忽略注销失败(master 可能已关闭)
|
|
236
|
+
}
|
|
237
|
+
process.exit(0);
|
|
238
|
+
};
|
|
239
|
+
process.on("SIGINT", cleanup);
|
|
240
|
+
process.on("SIGTERM", cleanup);
|
|
241
|
+
|
|
242
|
+
// 统一通过 SSE 实时接收本项目 MidwayJS 子进程日志(按 hash 过滤)
|
|
243
|
+
// 第一个 CLI(spawn master 的)不再通过 stdio:inherit 看到其他项目的日志
|
|
244
|
+
console.log(`\n ${white("─")} Streaming logs from MidwayJS (${hash}) ${white("─")}\n`);
|
|
245
|
+
|
|
246
|
+
const logsUrl = `http://localhost:${masterPort}/__master__/projects/${hash}/logs`;
|
|
247
|
+
try {
|
|
248
|
+
await streamLogs(logsUrl, hash);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.error(`\n ${red("✗")} Log stream error: ${err.message}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = { mergeConfig, main };
|