comp-hub 0.42.5 → 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/commands.js
CHANGED
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
const { httpGet, httpPost } = require("./http");
|
|
2
|
-
const { ADMIN_API, DEFAULT_PORT } = require("./constants");
|
|
3
|
-
const { readMasterRuntime, colors, apiUrl } = require("./utils");
|
|
4
|
-
const { green, yellow, cyan, white, red } = colors;
|
|
5
|
-
|
|
6
|
-
function formatUptime(seconds) {
|
|
7
|
-
const UNITS = [
|
|
8
|
-
{ label: "d", divisor: 86400 },
|
|
9
|
-
{ label: "h", divisor: 3600 },
|
|
10
|
-
{ label: "m", divisor: 60 },
|
|
11
|
-
{ label: "s", divisor: 1 }
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
let remaining = Math.floor(seconds);
|
|
15
|
-
const parts = [];
|
|
16
|
-
|
|
17
|
-
for (const { label, divisor } of UNITS) {
|
|
18
|
-
if (remaining >= divisor) {
|
|
19
|
-
const value = Math.floor(remaining / divisor);
|
|
20
|
-
remaining = remaining % divisor;
|
|
21
|
-
parts.push(`${value}${label}`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return parts.length > 0 ? parts.join(" ") : "0s";
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function resolveMasterPort(opts) {
|
|
29
|
-
// 用户明确指定了端口,直接使用
|
|
30
|
-
if (opts.port != null) {
|
|
31
|
-
return opts.port;
|
|
32
|
-
}
|
|
33
|
-
// 从 runtime 文件读取 master 实际端口
|
|
34
|
-
const runtime = await readMasterRuntime();
|
|
35
|
-
if (runtime) {
|
|
36
|
-
return runtime.managementPort;
|
|
37
|
-
}
|
|
38
|
-
// 兜底:默认端口
|
|
39
|
-
return DEFAULT_PORT;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function statusCommand(opts) {
|
|
43
|
-
const masterPort = await resolveMasterPort(opts);
|
|
44
|
-
const runtime = await readMasterRuntime();
|
|
45
|
-
const displayPort = runtime ? runtime.publicPort : masterPort;
|
|
46
|
-
|
|
47
|
-
console.log(`\n${yellow("●")} Master Status\n`);
|
|
48
|
-
|
|
49
|
-
const health = await httpGet(apiUrl(masterPort, ADMIN_API.HEALTH));
|
|
50
|
-
if (!health.ok || !health.body || typeof health.body.status !== "string") {
|
|
51
|
-
console.error(`${red("✗")} Master service is not running`);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const info = health.body;
|
|
56
|
-
const statusIcon = info.status === "running" ? green("●") : cyan("●");
|
|
57
|
-
|
|
58
|
-
console.log(` ${green("Status")} ${statusIcon} ${info.status}`);
|
|
59
|
-
console.log(` ${green("Version")} ${white(info.version)}`);
|
|
60
|
-
console.log(` ${green("Uptime")} ${white(formatUptime(info.uptime))}`);
|
|
61
|
-
console.log(` ${green("Projects")} ${white(info.projects)}`);
|
|
62
|
-
console.log("");
|
|
63
|
-
|
|
64
|
-
const projects = await httpGet(apiUrl(masterPort, ADMIN_API.PROJECTS));
|
|
65
|
-
if (projects.ok && projects.body) {
|
|
66
|
-
const list = projects.body.projects;
|
|
67
|
-
if (Array.isArray(list) && list.length > 0) {
|
|
68
|
-
console.log(` ${yellow("▸")} Registered Projects (${list.length}):`);
|
|
69
|
-
console.log(" " + white("─".repeat(56)));
|
|
70
|
-
for (const p of list) {
|
|
71
|
-
console.log(` ${green("hash")} ${white(p.hash)}`);
|
|
72
|
-
console.log(` ${green("path")} ${white(p.cwd)}`);
|
|
73
|
-
// console.log(` ${green('port')} ${p.port}`);
|
|
74
|
-
// if (p.subPort) console.log(` ${green('subPort')} ${p.subPort}`);
|
|
75
|
-
console.log(` ${green("url")} ${cyan(`http://localhost:${displayPort}/${p.hash}/main/`)}`);
|
|
76
|
-
console.log(" " + white("─".repeat(56)));
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
console.log(` ${white("─".repeat(58))}`);
|
|
80
|
-
console.log(` ${white("(No registered projects)")}`);
|
|
81
|
-
console.log(` ${white("─".repeat(58))}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
console.log("");
|
|
85
|
-
process.exit(0);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function stopCommand(hash, opts) {
|
|
89
|
-
const masterPort = await resolveMasterPort(opts);
|
|
90
|
-
|
|
91
|
-
if (opts.all) {
|
|
92
|
-
const projects = await httpGet(apiUrl(masterPort, ADMIN_API.PROJECTS));
|
|
93
|
-
if (!projects.ok || !projects.body) {
|
|
94
|
-
console.error(`${red("✗")} Cannot connect to master service`);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
const list = projects.body.projects;
|
|
98
|
-
if (!Array.isArray(list) || list.length === 0) {
|
|
99
|
-
console.log(`${yellow("ℹ")} No running projects`);
|
|
100
|
-
process.exit(0);
|
|
101
|
-
}
|
|
102
|
-
console.log(`\n${yellow("▸")} Stopping all ${list.length} projects...\n`);
|
|
103
|
-
for (const p of list) {
|
|
104
|
-
const res = await httpPost(apiUrl(masterPort, ADMIN_API.DEREGISTER), { hash: p.hash });
|
|
105
|
-
if (res.ok) {
|
|
106
|
-
console.log(` ${green("✓")} Stopped: ${p.hash} (${p.cwd})`);
|
|
107
|
-
} else {
|
|
108
|
-
console.error(` ${red("✗")} Failed to stop: ${p.hash} - ${res.error}`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
console.log("");
|
|
112
|
-
process.exit(0);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!hash) {
|
|
116
|
-
console.error(`${red("✗")} Please specify project hash or use --all to stop all projects`);
|
|
117
|
-
console.error(` Usage: comphub stop <hash>`);
|
|
118
|
-
console.error(` Usage: comphub stop --all`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
console.log(`\n${yellow("▸")} Stopping project ${hash}...`);
|
|
123
|
-
const res = await httpPost(apiUrl(masterPort, ADMIN_API.DEREGISTER), { hash });
|
|
124
|
-
if (res.ok) {
|
|
125
|
-
console.log(` ${green("✓")} Stopped: ${hash}\n`);
|
|
126
|
-
} else {
|
|
127
|
-
console.error(` ${red("✗")} Failed to stop: ${res.error}\n`);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
process.exit(0);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function killCommand(opts) {
|
|
134
|
-
const masterPort = await resolveMasterPort(opts);
|
|
135
|
-
console.log(`\n${yellow("▸")} Shutting down master process...\n`);
|
|
136
|
-
|
|
137
|
-
const res = await httpPost(apiUrl(masterPort, ADMIN_API.SHUTDOWN), {});
|
|
138
|
-
if (res.ok) {
|
|
139
|
-
console.log(`${green("✓")} Master process has been shut down\n`);
|
|
140
|
-
} else {
|
|
141
|
-
console.error(`${red("✗")} Shutdown failed: ${res.error}\n`);
|
|
142
|
-
process.exit(1);
|
|
143
|
-
}
|
|
144
|
-
process.exit(0);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function fixCommand() {
|
|
148
|
-
const fs = require("fs");
|
|
149
|
-
const { MASTER_RUNTIME_FILE } = require("./constants");
|
|
150
|
-
|
|
151
|
-
console.log(`\n${yellow("▸")} Checking master runtime...\n`);
|
|
152
|
-
|
|
153
|
-
const runtime = await readMasterRuntime();
|
|
154
|
-
if (runtime) {
|
|
155
|
-
console.log(` ${green("✓")} Master is running normally on port ${runtime.publicPort}\n`);
|
|
156
|
-
process.exit(0);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (fs.existsSync(MASTER_RUNTIME_FILE)) {
|
|
160
|
-
try {
|
|
161
|
-
fs.unlinkSync(MASTER_RUNTIME_FILE);
|
|
162
|
-
console.log(` ${green("✓")} Removed stale runtime file\n`);
|
|
163
|
-
} catch {
|
|
164
|
-
console.error(` ${red("✗")} Failed to remove runtime file\n`);
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
console.log(` ${yellow("ℹ")} No stale runtime file found\n`);
|
|
169
|
-
}
|
|
170
|
-
process.exit(0);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
module.exports = { formatUptime, resolveMasterPort, statusCommand, stopCommand, killCommand, fixCommand };
|
|
1
|
+
const { httpGet, httpPost } = require("./http");
|
|
2
|
+
const { ADMIN_API, DEFAULT_PORT } = require("./constants");
|
|
3
|
+
const { readMasterRuntime, colors, apiUrl } = require("./utils");
|
|
4
|
+
const { green, yellow, cyan, white, red } = colors;
|
|
5
|
+
|
|
6
|
+
function formatUptime(seconds) {
|
|
7
|
+
const UNITS = [
|
|
8
|
+
{ label: "d", divisor: 86400 },
|
|
9
|
+
{ label: "h", divisor: 3600 },
|
|
10
|
+
{ label: "m", divisor: 60 },
|
|
11
|
+
{ label: "s", divisor: 1 }
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
let remaining = Math.floor(seconds);
|
|
15
|
+
const parts = [];
|
|
16
|
+
|
|
17
|
+
for (const { label, divisor } of UNITS) {
|
|
18
|
+
if (remaining >= divisor) {
|
|
19
|
+
const value = Math.floor(remaining / divisor);
|
|
20
|
+
remaining = remaining % divisor;
|
|
21
|
+
parts.push(`${value}${label}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return parts.length > 0 ? parts.join(" ") : "0s";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function resolveMasterPort(opts) {
|
|
29
|
+
// 用户明确指定了端口,直接使用
|
|
30
|
+
if (opts.port != null) {
|
|
31
|
+
return opts.port;
|
|
32
|
+
}
|
|
33
|
+
// 从 runtime 文件读取 master 实际端口
|
|
34
|
+
const runtime = await readMasterRuntime();
|
|
35
|
+
if (runtime) {
|
|
36
|
+
return runtime.managementPort;
|
|
37
|
+
}
|
|
38
|
+
// 兜底:默认端口
|
|
39
|
+
return DEFAULT_PORT;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function statusCommand(opts) {
|
|
43
|
+
const masterPort = await resolveMasterPort(opts);
|
|
44
|
+
const runtime = await readMasterRuntime();
|
|
45
|
+
const displayPort = runtime ? runtime.publicPort : masterPort;
|
|
46
|
+
|
|
47
|
+
console.log(`\n${yellow("●")} Master Status\n`);
|
|
48
|
+
|
|
49
|
+
const health = await httpGet(apiUrl(masterPort, ADMIN_API.HEALTH));
|
|
50
|
+
if (!health.ok || !health.body || typeof health.body.status !== "string") {
|
|
51
|
+
console.error(`${red("✗")} Master service is not running`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const info = health.body;
|
|
56
|
+
const statusIcon = info.status === "running" ? green("●") : cyan("●");
|
|
57
|
+
|
|
58
|
+
console.log(` ${green("Status")} ${statusIcon} ${info.status}`);
|
|
59
|
+
console.log(` ${green("Version")} ${white(info.version)}`);
|
|
60
|
+
console.log(` ${green("Uptime")} ${white(formatUptime(info.uptime))}`);
|
|
61
|
+
console.log(` ${green("Projects")} ${white(info.projects)}`);
|
|
62
|
+
console.log("");
|
|
63
|
+
|
|
64
|
+
const projects = await httpGet(apiUrl(masterPort, ADMIN_API.PROJECTS));
|
|
65
|
+
if (projects.ok && projects.body) {
|
|
66
|
+
const list = projects.body.projects;
|
|
67
|
+
if (Array.isArray(list) && list.length > 0) {
|
|
68
|
+
console.log(` ${yellow("▸")} Registered Projects (${list.length}):`);
|
|
69
|
+
console.log(" " + white("─".repeat(56)));
|
|
70
|
+
for (const p of list) {
|
|
71
|
+
console.log(` ${green("hash")} ${white(p.hash)}`);
|
|
72
|
+
console.log(` ${green("path")} ${white(p.cwd)}`);
|
|
73
|
+
// console.log(` ${green('port')} ${p.port}`);
|
|
74
|
+
// if (p.subPort) console.log(` ${green('subPort')} ${p.subPort}`);
|
|
75
|
+
console.log(` ${green("url")} ${cyan(`http://localhost:${displayPort}/${p.hash}/main/`)}`);
|
|
76
|
+
console.log(" " + white("─".repeat(56)));
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
console.log(` ${white("─".repeat(58))}`);
|
|
80
|
+
console.log(` ${white("(No registered projects)")}`);
|
|
81
|
+
console.log(` ${white("─".repeat(58))}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
console.log("");
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function stopCommand(hash, opts) {
|
|
89
|
+
const masterPort = await resolveMasterPort(opts);
|
|
90
|
+
|
|
91
|
+
if (opts.all) {
|
|
92
|
+
const projects = await httpGet(apiUrl(masterPort, ADMIN_API.PROJECTS));
|
|
93
|
+
if (!projects.ok || !projects.body) {
|
|
94
|
+
console.error(`${red("✗")} Cannot connect to master service`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const list = projects.body.projects;
|
|
98
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
99
|
+
console.log(`${yellow("ℹ")} No running projects`);
|
|
100
|
+
process.exit(0);
|
|
101
|
+
}
|
|
102
|
+
console.log(`\n${yellow("▸")} Stopping all ${list.length} projects...\n`);
|
|
103
|
+
for (const p of list) {
|
|
104
|
+
const res = await httpPost(apiUrl(masterPort, ADMIN_API.DEREGISTER), { hash: p.hash });
|
|
105
|
+
if (res.ok) {
|
|
106
|
+
console.log(` ${green("✓")} Stopped: ${p.hash} (${p.cwd})`);
|
|
107
|
+
} else {
|
|
108
|
+
console.error(` ${red("✗")} Failed to stop: ${p.hash} - ${res.error}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
console.log("");
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!hash) {
|
|
116
|
+
console.error(`${red("✗")} Please specify project hash or use --all to stop all projects`);
|
|
117
|
+
console.error(` Usage: comphub stop <hash>`);
|
|
118
|
+
console.error(` Usage: comphub stop --all`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`\n${yellow("▸")} Stopping project ${hash}...`);
|
|
123
|
+
const res = await httpPost(apiUrl(masterPort, ADMIN_API.DEREGISTER), { hash });
|
|
124
|
+
if (res.ok) {
|
|
125
|
+
console.log(` ${green("✓")} Stopped: ${hash}\n`);
|
|
126
|
+
} else {
|
|
127
|
+
console.error(` ${red("✗")} Failed to stop: ${res.error}\n`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function killCommand(opts) {
|
|
134
|
+
const masterPort = await resolveMasterPort(opts);
|
|
135
|
+
console.log(`\n${yellow("▸")} Shutting down master process...\n`);
|
|
136
|
+
|
|
137
|
+
const res = await httpPost(apiUrl(masterPort, ADMIN_API.SHUTDOWN), {});
|
|
138
|
+
if (res.ok) {
|
|
139
|
+
console.log(`${green("✓")} Master process has been shut down\n`);
|
|
140
|
+
} else {
|
|
141
|
+
console.error(`${red("✗")} Shutdown failed: ${res.error}\n`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function fixCommand() {
|
|
148
|
+
const fs = require("fs");
|
|
149
|
+
const { MASTER_RUNTIME_FILE } = require("./constants");
|
|
150
|
+
|
|
151
|
+
console.log(`\n${yellow("▸")} Checking master runtime...\n`);
|
|
152
|
+
|
|
153
|
+
const runtime = await readMasterRuntime();
|
|
154
|
+
if (runtime) {
|
|
155
|
+
console.log(` ${green("✓")} Master is running normally on port ${runtime.publicPort}\n`);
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (fs.existsSync(MASTER_RUNTIME_FILE)) {
|
|
160
|
+
try {
|
|
161
|
+
fs.unlinkSync(MASTER_RUNTIME_FILE);
|
|
162
|
+
console.log(` ${green("✓")} Removed stale runtime file\n`);
|
|
163
|
+
} catch {
|
|
164
|
+
console.error(` ${red("✗")} Failed to remove runtime file\n`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
console.log(` ${yellow("ℹ")} No stale runtime file found\n`);
|
|
169
|
+
}
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { formatUptime, resolveMasterPort, statusCommand, stopCommand, killCommand, fixCommand };
|
package/bin/lib/constants.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
const path = require("path");
|
|
2
|
-
const os = require("os");
|
|
3
|
-
|
|
4
|
-
const DEFAULT_PORT = 5478;
|
|
5
|
-
|
|
6
|
-
const MASTER_RUNTIME_FILE = path.join(os.tmpdir(), ".comphub", "master.json");
|
|
7
|
-
|
|
8
|
-
const ADMIN_API = {
|
|
9
|
-
PREFIX: "/__master__",
|
|
10
|
-
HEALTH: "/__master__/health",
|
|
11
|
-
REGISTER: "/__master__/register",
|
|
12
|
-
DEREGISTER: "/__master__/deregister",
|
|
13
|
-
PROJECTS: "/__master__/projects",
|
|
14
|
-
SHUTDOWN: "/__master__/shutdown"
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const POLL_INTERVAL = 500;
|
|
18
|
-
const MAX_RETRIES = 40;
|
|
19
|
-
const HTTP_TIMEOUT_GET = 2000;
|
|
20
|
-
const HTTP_TIMEOUT_POST = 5000;
|
|
21
|
-
|
|
22
|
-
module.exports = { DEFAULT_PORT, MASTER_RUNTIME_FILE, ADMIN_API, POLL_INTERVAL, MAX_RETRIES, HTTP_TIMEOUT_GET, HTTP_TIMEOUT_POST };
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
|
|
4
|
+
const DEFAULT_PORT = 5478;
|
|
5
|
+
|
|
6
|
+
const MASTER_RUNTIME_FILE = path.join(os.tmpdir(), ".comphub", "master.json");
|
|
7
|
+
|
|
8
|
+
const ADMIN_API = {
|
|
9
|
+
PREFIX: "/__master__",
|
|
10
|
+
HEALTH: "/__master__/health",
|
|
11
|
+
REGISTER: "/__master__/register",
|
|
12
|
+
DEREGISTER: "/__master__/deregister",
|
|
13
|
+
PROJECTS: "/__master__/projects",
|
|
14
|
+
SHUTDOWN: "/__master__/shutdown"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const POLL_INTERVAL = 500;
|
|
18
|
+
const MAX_RETRIES = 40;
|
|
19
|
+
const HTTP_TIMEOUT_GET = 2000;
|
|
20
|
+
const HTTP_TIMEOUT_POST = 5000;
|
|
21
|
+
|
|
22
|
+
module.exports = { DEFAULT_PORT, MASTER_RUNTIME_FILE, ADMIN_API, POLL_INTERVAL, MAX_RETRIES, HTTP_TIMEOUT_GET, HTTP_TIMEOUT_POST };
|
package/bin/lib/http.js
CHANGED
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
const http = require("http");
|
|
2
|
-
const { HTTP_TIMEOUT_GET, HTTP_TIMEOUT_POST } = require("./constants");
|
|
3
|
-
const { apiUrl } = require("./utils");
|
|
4
|
-
|
|
5
|
-
function parseBody(raw) {
|
|
6
|
-
try {
|
|
7
|
-
return JSON.parse(raw);
|
|
8
|
-
} catch {
|
|
9
|
-
return raw;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function httpGet(url) {
|
|
14
|
-
return new Promise(resolve => {
|
|
15
|
-
const req = http.get(url, res => {
|
|
16
|
-
let body = "";
|
|
17
|
-
res.on("data", chunk => (body += chunk));
|
|
18
|
-
res.on("end", () => resolve({ ok: true, status: res.statusCode, body: parseBody(body) }));
|
|
19
|
-
});
|
|
20
|
-
req.on("error", err => resolve({ ok: false, error: err.message }));
|
|
21
|
-
req.setTimeout(HTTP_TIMEOUT_GET, () => {
|
|
22
|
-
req.destroy();
|
|
23
|
-
resolve({ ok: false, error: "请求超时" });
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function httpPost(url, data) {
|
|
29
|
-
return new Promise(resolve => {
|
|
30
|
-
const postData = JSON.stringify(data);
|
|
31
|
-
const urlObj = new URL(url);
|
|
32
|
-
const options = {
|
|
33
|
-
hostname: urlObj.hostname,
|
|
34
|
-
port: urlObj.port,
|
|
35
|
-
path: urlObj.pathname,
|
|
36
|
-
method: "POST",
|
|
37
|
-
headers: {
|
|
38
|
-
"Content-Type": "application/json",
|
|
39
|
-
"Content-Length": Buffer.byteLength(postData)
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
const req = http.request(options, res => {
|
|
43
|
-
let body = "";
|
|
44
|
-
res.on("data", chunk => (body += chunk));
|
|
45
|
-
res.on("end", () => {
|
|
46
|
-
try {
|
|
47
|
-
resolve({ ok: true, body: JSON.parse(body) });
|
|
48
|
-
} catch {
|
|
49
|
-
resolve({ ok: false, error: "Invalid JSON response" });
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
req.on("error", err => resolve({ ok: false, error: err.message }));
|
|
54
|
-
req.setTimeout(HTTP_TIMEOUT_POST, () => {
|
|
55
|
-
req.destroy();
|
|
56
|
-
resolve({ ok: false, error: "请求超时" });
|
|
57
|
-
});
|
|
58
|
-
req.write(postData);
|
|
59
|
-
req.end();
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* SSE 日志流 — 连接 master 的 /__master__/projects/:hash/logs 端点
|
|
65
|
-
* 持续输出 MidwayJS 子进程的实时日志到终端
|
|
66
|
-
* 返回一个 Promise,在连接关闭时 resolve
|
|
67
|
-
*/
|
|
68
|
-
function streamLogs(url, _hash) {
|
|
69
|
-
return new Promise((resolve, reject) => {
|
|
70
|
-
const urlObj = new URL(url);
|
|
71
|
-
const options = {
|
|
72
|
-
hostname: urlObj.hostname,
|
|
73
|
-
port: urlObj.port,
|
|
74
|
-
path: urlObj.pathname,
|
|
75
|
-
method: "GET",
|
|
76
|
-
headers: { Accept: "text/event-stream" }
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const req = http.request(options, res => {
|
|
80
|
-
if (res.statusCode !== 200) {
|
|
81
|
-
reject(new Error(`SSE connection failed (HTTP ${res.statusCode})`));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
let buffer = "";
|
|
86
|
-
|
|
87
|
-
res.on("data", chunk => {
|
|
88
|
-
buffer += chunk.toString();
|
|
89
|
-
// SSE 格式: "data: <json>\n\n"
|
|
90
|
-
const events = buffer.split("\n\n");
|
|
91
|
-
buffer = events.pop() || ""; // 保留不完整的最后一个事件
|
|
92
|
-
|
|
93
|
-
for (const event of events) {
|
|
94
|
-
const dataLine = event.startsWith("data: ") ? event.slice(6) : event;
|
|
95
|
-
try {
|
|
96
|
-
const line = JSON.parse(dataLine);
|
|
97
|
-
// 直接输出日志内容(已有换行符)
|
|
98
|
-
process.stdout.write(line);
|
|
99
|
-
} catch {
|
|
100
|
-
// 非 JSON 数据,忽略
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
res.on("end", () => {
|
|
106
|
-
resolve();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
res.on("error", err => {
|
|
110
|
-
reject(err);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
req.on("error", err => {
|
|
115
|
-
reject(err);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
req.setTimeout(0); // 无超时限制
|
|
119
|
-
req.end();
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 从 master 注销项目(Ctrl+C 时通知 master 停止对应 MidwayJS 子进程)
|
|
125
|
-
*/
|
|
126
|
-
function deregisterProject(masterPort, hash) {
|
|
127
|
-
return httpPost(apiUrl(masterPort, "/__master__/deregister"), { hash });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
module.exports = { httpGet, httpPost, streamLogs, deregisterProject };
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const { HTTP_TIMEOUT_GET, HTTP_TIMEOUT_POST } = require("./constants");
|
|
3
|
+
const { apiUrl } = require("./utils");
|
|
4
|
+
|
|
5
|
+
function parseBody(raw) {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(raw);
|
|
8
|
+
} catch {
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function httpGet(url) {
|
|
14
|
+
return new Promise(resolve => {
|
|
15
|
+
const req = http.get(url, res => {
|
|
16
|
+
let body = "";
|
|
17
|
+
res.on("data", chunk => (body += chunk));
|
|
18
|
+
res.on("end", () => resolve({ ok: true, status: res.statusCode, body: parseBody(body) }));
|
|
19
|
+
});
|
|
20
|
+
req.on("error", err => resolve({ ok: false, error: err.message }));
|
|
21
|
+
req.setTimeout(HTTP_TIMEOUT_GET, () => {
|
|
22
|
+
req.destroy();
|
|
23
|
+
resolve({ ok: false, error: "请求超时" });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function httpPost(url, data) {
|
|
29
|
+
return new Promise(resolve => {
|
|
30
|
+
const postData = JSON.stringify(data);
|
|
31
|
+
const urlObj = new URL(url);
|
|
32
|
+
const options = {
|
|
33
|
+
hostname: urlObj.hostname,
|
|
34
|
+
port: urlObj.port,
|
|
35
|
+
path: urlObj.pathname,
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json",
|
|
39
|
+
"Content-Length": Buffer.byteLength(postData)
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const req = http.request(options, res => {
|
|
43
|
+
let body = "";
|
|
44
|
+
res.on("data", chunk => (body += chunk));
|
|
45
|
+
res.on("end", () => {
|
|
46
|
+
try {
|
|
47
|
+
resolve({ ok: true, body: JSON.parse(body) });
|
|
48
|
+
} catch {
|
|
49
|
+
resolve({ ok: false, error: "Invalid JSON response" });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
req.on("error", err => resolve({ ok: false, error: err.message }));
|
|
54
|
+
req.setTimeout(HTTP_TIMEOUT_POST, () => {
|
|
55
|
+
req.destroy();
|
|
56
|
+
resolve({ ok: false, error: "请求超时" });
|
|
57
|
+
});
|
|
58
|
+
req.write(postData);
|
|
59
|
+
req.end();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* SSE 日志流 — 连接 master 的 /__master__/projects/:hash/logs 端点
|
|
65
|
+
* 持续输出 MidwayJS 子进程的实时日志到终端
|
|
66
|
+
* 返回一个 Promise,在连接关闭时 resolve
|
|
67
|
+
*/
|
|
68
|
+
function streamLogs(url, _hash) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const urlObj = new URL(url);
|
|
71
|
+
const options = {
|
|
72
|
+
hostname: urlObj.hostname,
|
|
73
|
+
port: urlObj.port,
|
|
74
|
+
path: urlObj.pathname,
|
|
75
|
+
method: "GET",
|
|
76
|
+
headers: { Accept: "text/event-stream" }
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const req = http.request(options, res => {
|
|
80
|
+
if (res.statusCode !== 200) {
|
|
81
|
+
reject(new Error(`SSE connection failed (HTTP ${res.statusCode})`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let buffer = "";
|
|
86
|
+
|
|
87
|
+
res.on("data", chunk => {
|
|
88
|
+
buffer += chunk.toString();
|
|
89
|
+
// SSE 格式: "data: <json>\n\n"
|
|
90
|
+
const events = buffer.split("\n\n");
|
|
91
|
+
buffer = events.pop() || ""; // 保留不完整的最后一个事件
|
|
92
|
+
|
|
93
|
+
for (const event of events) {
|
|
94
|
+
const dataLine = event.startsWith("data: ") ? event.slice(6) : event;
|
|
95
|
+
try {
|
|
96
|
+
const line = JSON.parse(dataLine);
|
|
97
|
+
// 直接输出日志内容(已有换行符)
|
|
98
|
+
process.stdout.write(line);
|
|
99
|
+
} catch {
|
|
100
|
+
// 非 JSON 数据,忽略
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
res.on("end", () => {
|
|
106
|
+
resolve();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
res.on("error", err => {
|
|
110
|
+
reject(err);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
req.on("error", err => {
|
|
115
|
+
reject(err);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
req.setTimeout(0); // 无超时限制
|
|
119
|
+
req.end();
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 从 master 注销项目(Ctrl+C 时通知 master 停止对应 MidwayJS 子进程)
|
|
125
|
+
*/
|
|
126
|
+
function deregisterProject(masterPort, hash) {
|
|
127
|
+
return httpPost(apiUrl(masterPort, "/__master__/deregister"), { hash });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { httpGet, httpPost, streamLogs, deregisterProject };
|