termdock 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{abnfDiagram-VRR7QNED-BjNCcQvm.js → abnfDiagram-VRR7QNED-BVuFNukd.js} +1 -1
- package/dist/client/assets/{arc-xcCAKMnT.js → arc-JFOslCNs.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-ijtAKPsW.js → architectureDiagram-ZJ3FMSHR-B-TyEAOJ.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-GVIbuo6R.js → blockDiagram-677ZJIJ3-5ycSMMTY.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-B3IbU0ga.js → c4Diagram-LMCZKHZV-BvXzrvb_.js} +1 -1
- package/dist/client/assets/channel-Cnn-8i2b.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-DEwVqhvo.js → chunk-2Q5K7J3B-00s3llQG.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-CGNuIJuG.js → chunk-32BRIVSS-BJMhtTym.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-DliA2ku3.js → chunk-5VM5RSS4-DedB9Vaa.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-DzFXj1M3.js → chunk-EX3LRPZG-DWGG3NoJ.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-h3s-pxcn.js → chunk-JWPE2WC7-D34JiLTf.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-CQST9jsX.js → chunk-MOJQB5TN-D6Ywmh0z.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-CXjnUh8k.js → chunk-RYQCIY6F-BH6yURdq.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-CI7QyRAs.js → chunk-V7JOEXUC-DBSqOhi7.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-DYQ8EokS.js → chunk-VR4S4FIN-DzIaqkfm.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-BM9jxTTb.js → chunk-XXDRQBXY-9iVmQCNp.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-DiAxD67S.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-DiAxD67S.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-C4nYmp4I.js → cose-bilkent-JH36ORCC-1Yqlhf0s.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-DnlhMeVt.js → cynefin-VYW2F7L2-DIvwKU3S.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-DJuHDI6A.js → cynefinDiagram-TSTJHNR4-C_tvEhnO.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-CsBEz_S1.js → dagre-VKFMJZFB-BW6zPwqv.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-BDNTGROP.js → diagram-FQU43EPY-BPdiUHXh.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-CJJwi6PP.js → diagram-G47NLZAW-DcSplQyb.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-CXJWffic.js → diagram-NH7WQ7WH-DJfc5gP9.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-wOtL7NBA.js → diagram-OA4YK3LP-BMhJGhOv.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-ClOt2G7I.js → diagram-WEI45ONY-BfvX5Q8Q.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-DoXhiNjK.js → ebnfDiagram-CCIWWBDH-CHXVkELN.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-DB5a-Aw4.js → erDiagram-Q63AITRT-BsY8w4KG.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-Cz9w-F5K.js → flowDiagram-23GEKE2U-BkzZO16s.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-CDykkj3o.js → ganttDiagram-NO4QXBWP-CYc_FSmb.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-CNkfDkzA.js → gitGraphDiagram-IHSO6WYX-DZVYJm6Q.js} +1 -1
- package/dist/client/assets/{index-Cv7q4nZR.css → index-B1I3x8rE.css} +1 -1
- package/dist/client/assets/{index-0d_16SRD.js → index-DIbF-DRk.js} +40 -40
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-CfrwrocT.js → infoDiagram-FWYZ7A6U-Dnv8kWO8.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-C2AD_Ae2.js → ishikawaDiagram-FXEZZL3T-2olMVSmL.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-DzQvMG3N.js → journeyDiagram-5HDEW3XC-B_GL15RA.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-CUVhWmNF.js → kanban-definition-HUTT4EX6-B6_36kLK.js} +1 -1
- package/dist/client/assets/{linear-D5qZ3q5c.js → linear-CE3AYySp.js} +1 -1
- package/dist/client/assets/{mermaid.core-D-e_wdPJ.js → mermaid.core-mNpsdXrC.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-CDHLWnzb.js → mindmap-definition-LN4V7U3C-BRH7DD07.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-9_bnKT8u.js → pegDiagram-2B236MQR-CPWaVwbR.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-gw-xIG1b.js → pieDiagram-ENE6RG2P-Dg488vOr.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-Cn_lstJf.js → quadrantDiagram-ABIIQ3AL-X9TiKVp4.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-yZM8qXUp.js → railroadDiagram-RFXS5EU6-Dm_BeOAW.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-WQ1iDS_X.js → requirementDiagram-TGXJPOKE-lkU41AKk.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-UVz3wsFF.js → sankeyDiagram-HTMAVEWB-CltVgAob.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BuJBBGcB.js → sequenceDiagram-DBY2YBRQ-D65aOt87.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-CysR04La.js → sizeCapture-X5ZJPWSS-B_zcJ4qw.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-DYC8TYxP.js → stateDiagram-2N3HPSRC-DMF21ttG.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-CUgSOIEq.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-FT83sDlI.js → swimlanes-5IMT3BWC-t01s2riN.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-DHbd0hST.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-C-o8uRSU.js → timeline-definition-FHXFAJF6-DTvM_hB8.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-Cw-g3-Li.js → vennDiagram-L72KCM5P-BTXCVT1w.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-B_JEirkb.js → wardleyDiagram-EHGQE667-DASx33Ug.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-B1LkJSxz.js → xychartDiagram-FW5EYKEG-C6ZvW88N.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/cli.js +14 -5
- package/dist/server/config.js +50 -4
- package/dist/server/routes/terminal.js +25 -14
- package/dist/server/utils/bootCheck.js +299 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +102 -34
- package/dist/client/assets/channel-Fzeh-Sl6.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-DzF9pacK.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-DzF9pacK.js +0 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Cru4nzfa.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-DUvRCWFX.js +0 -8
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Termdock 启动自检 — 确保原生模块和系统依赖在启动时可用
|
|
3
|
+
*
|
|
4
|
+
* 设计目标:覆盖 npm 全局安装不跑 postinstall 的缺口。
|
|
5
|
+
* 每次 CLI 启动时检查,通过后写 marker 避免重复检查。
|
|
6
|
+
*
|
|
7
|
+
* 检查项目:
|
|
8
|
+
* 1. node-pty : 实际 spawn 测试 → 失败则从源码重编译
|
|
9
|
+
* 2. tmux : 检测是否安装 → 缺失则尝试自动安装
|
|
10
|
+
*
|
|
11
|
+
* —— 如何新增检查项 ——
|
|
12
|
+
* 在 BootChecks 接口加字段,在 runBootChecks() 加 step,
|
|
13
|
+
* 在 formatBootCheckReport() 加对应的输出行。
|
|
14
|
+
*/
|
|
15
|
+
import { execSync, spawnSync } from 'child_process';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import os from 'os';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { createRequire } from 'module';
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const MARKER_FILE = path.join(os.homedir(), '.termdock', '.boot-check-ok');
|
|
24
|
+
function which(bin) {
|
|
25
|
+
// 先查 PATH
|
|
26
|
+
try {
|
|
27
|
+
const result = execSync(`command -v ${bin} 2>/dev/null`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
28
|
+
if (result)
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
catch { /* not in PATH */ }
|
|
32
|
+
// macOS non-interactive SSH 的 PATH 不包含 Homebrew 路径,手动检查
|
|
33
|
+
const extraPaths = [
|
|
34
|
+
'/opt/homebrew/bin',
|
|
35
|
+
'/usr/local/bin',
|
|
36
|
+
'/opt/local/bin', // MacPorts
|
|
37
|
+
];
|
|
38
|
+
for (const dir of extraPaths) {
|
|
39
|
+
const fullPath = path.join(dir, bin);
|
|
40
|
+
try {
|
|
41
|
+
fs.accessSync(fullPath, fs.constants.X_OK);
|
|
42
|
+
return fullPath;
|
|
43
|
+
}
|
|
44
|
+
catch { /* not here */ }
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function runQuiet(cmd, timeoutMs = 30_000) {
|
|
49
|
+
try {
|
|
50
|
+
const r = spawnSync(cmd, [], {
|
|
51
|
+
shell: true,
|
|
52
|
+
timeout: timeoutMs,
|
|
53
|
+
encoding: 'utf-8',
|
|
54
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
55
|
+
});
|
|
56
|
+
return { ok: r.status === 0, stdout: r.stdout, stderr: r.stderr };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { ok: false, stdout: '', stderr: '' };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// 1. node-pty 验证 + 重建
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
function checkNodePty() {
|
|
66
|
+
const result = { ok: false, rebuilt: false };
|
|
67
|
+
try {
|
|
68
|
+
const require_ = createRequire(import.meta.url);
|
|
69
|
+
let ptyPath;
|
|
70
|
+
try {
|
|
71
|
+
ptyPath = require_.resolve('node-pty/package.json').replace('/package.json', '');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
result.error = 'node-pty not found in module resolution path';
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
const pty = require_(ptyPath);
|
|
78
|
+
// 实际 spawn 测试(不仅仅是 require,macOS ARM 预编译包可能 require 成功但 spawn 失败)
|
|
79
|
+
const proc = pty.spawn(process.env.SHELL || '/bin/bash', [], { name: 'xterm', cols: 80, rows: 24 });
|
|
80
|
+
proc.kill();
|
|
81
|
+
result.ok = true;
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
// require 失败或 spawn 失败 → 尝试重编译
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const require_ = createRequire(import.meta.url);
|
|
89
|
+
let ptyPath;
|
|
90
|
+
try {
|
|
91
|
+
ptyPath = require_.resolve('node-pty/package.json').replace('/package.json', '');
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
result.error = 'node-pty not found in module resolution path';
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
// 清除预编译二进制缓存
|
|
98
|
+
const prebuildsDir = path.join(ptyPath, 'prebuilds');
|
|
99
|
+
if (fs.existsSync(prebuildsDir)) {
|
|
100
|
+
fs.rmSync(prebuildsDir, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
const rebuild = runQuiet(`cd ${ptyPath} && npx --yes node-gyp rebuild`, 120_000);
|
|
103
|
+
if (rebuild.ok) {
|
|
104
|
+
// 重试 spawn
|
|
105
|
+
try {
|
|
106
|
+
const pty = require_(ptyPath);
|
|
107
|
+
const proc = pty.spawn(process.env.SHELL || '/bin/bash', [], { name: 'xterm', cols: 80, rows: 24 });
|
|
108
|
+
proc.kill();
|
|
109
|
+
result.ok = true;
|
|
110
|
+
result.rebuilt = true;
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
result.error = 'node-pty 重编译成功但 spawn 仍然失败';
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
result.error = `node-pty 重编译失败: ${rebuild.stderr.slice(0, 200)}`;
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
result.error = `node-pty 未找到或无法初始化: ${String(e).slice(0, 200)}`;
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// 2. tmux 检测 + 自动安装
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
function checkTmux() {
|
|
130
|
+
const result = { ok: false, installed: false };
|
|
131
|
+
// 先检查是否已安装
|
|
132
|
+
const tmuxPath = which('tmux');
|
|
133
|
+
if (tmuxPath) {
|
|
134
|
+
try {
|
|
135
|
+
result.version = execSync('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
136
|
+
}
|
|
137
|
+
catch { /* ignore */ }
|
|
138
|
+
result.ok = true;
|
|
139
|
+
result.installed = false; // 没有新安装
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
// —— 尝试自动安装 ——
|
|
143
|
+
const platform = process.platform;
|
|
144
|
+
if (platform === 'darwin') {
|
|
145
|
+
// macOS: 尝试多个包管理器
|
|
146
|
+
const brew = which('brew');
|
|
147
|
+
if (brew) {
|
|
148
|
+
const r = runQuiet(`${brew} install tmux 2>&1`, 120_000);
|
|
149
|
+
if (r.ok || which('tmux')) {
|
|
150
|
+
try {
|
|
151
|
+
result.version = execSync('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
152
|
+
}
|
|
153
|
+
catch { /* ignore */ }
|
|
154
|
+
result.ok = true;
|
|
155
|
+
result.installed = true;
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const port = which('port'); // MacPorts
|
|
160
|
+
if (port) {
|
|
161
|
+
const r = runQuiet(`${port} install tmux 2>&1`, 120_000);
|
|
162
|
+
if (r.ok || which('tmux')) {
|
|
163
|
+
try {
|
|
164
|
+
result.version = execSync('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
165
|
+
}
|
|
166
|
+
catch { /* ignore */ }
|
|
167
|
+
result.ok = true;
|
|
168
|
+
result.installed = true;
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const nixEnv = which('nix-env');
|
|
173
|
+
if (nixEnv) {
|
|
174
|
+
const r = runQuiet(`${nixEnv} -iA nixpkgs.tmux 2>&1`, 120_000);
|
|
175
|
+
if (r.ok || which('tmux')) {
|
|
176
|
+
try {
|
|
177
|
+
result.version = execSync('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
178
|
+
}
|
|
179
|
+
catch { /* ignore */ }
|
|
180
|
+
result.ok = true;
|
|
181
|
+
result.installed = true;
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
result.error = 'tmux 未安装且未找到包管理器 (brew/port/nix)。请安装 Homebrew: https://brew.sh';
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
if (platform === 'linux') {
|
|
189
|
+
// 尝试多种 Linux 包管理器
|
|
190
|
+
const managers = [
|
|
191
|
+
{ bin: 'apt-get', cmd: 'apt-get install -y tmux' },
|
|
192
|
+
{ bin: 'dnf', cmd: 'dnf install -y tmux' },
|
|
193
|
+
{ bin: 'yum', cmd: 'yum install -y tmux' },
|
|
194
|
+
{ bin: 'apk', cmd: 'apk add tmux' },
|
|
195
|
+
{ bin: 'pacman', cmd: 'pacman -S --noconfirm tmux' },
|
|
196
|
+
{ bin: 'zypper', cmd: 'zypper install -y tmux' },
|
|
197
|
+
];
|
|
198
|
+
for (const m of managers) {
|
|
199
|
+
if (!which(m.bin))
|
|
200
|
+
continue;
|
|
201
|
+
// 需要 root 权限
|
|
202
|
+
const r = runQuiet(`sudo ${m.cmd} 2>&1`, 120_000);
|
|
203
|
+
if (r.ok || which('tmux')) {
|
|
204
|
+
try {
|
|
205
|
+
result.version = execSync('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
206
|
+
}
|
|
207
|
+
catch { /* ignore */ }
|
|
208
|
+
result.ok = true;
|
|
209
|
+
result.installed = true;
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
// 也试一下不用 sudo
|
|
213
|
+
const r2 = runQuiet(`${m.cmd} 2>&1`, 120_000);
|
|
214
|
+
if (r2.ok || which('tmux')) {
|
|
215
|
+
try {
|
|
216
|
+
result.version = execSync('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
217
|
+
}
|
|
218
|
+
catch { /* ignore */ }
|
|
219
|
+
result.ok = true;
|
|
220
|
+
result.installed = true;
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
result.error = 'tmux 未安装。请用系统包管理器安装,例如: sudo apt install tmux';
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
// 其他平台
|
|
228
|
+
result.error = `不支持在 ${platform} 上自动安装 tmux。请手动安装。`;
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// 主入口
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
let _cachedResult = null;
|
|
235
|
+
export function runBootChecks() {
|
|
236
|
+
if (_cachedResult)
|
|
237
|
+
return _cachedResult;
|
|
238
|
+
// 如果 marker 存在且有效,读取缓存的检查结果
|
|
239
|
+
if (fs.existsSync(MARKER_FILE)) {
|
|
240
|
+
try {
|
|
241
|
+
const cached = JSON.parse(fs.readFileSync(MARKER_FILE, 'utf-8'));
|
|
242
|
+
if (cached.nodePty?.ok && cached.tmux?.ok) {
|
|
243
|
+
_cachedResult = {
|
|
244
|
+
nodePty: cached.nodePty,
|
|
245
|
+
tmux: cached.tmux,
|
|
246
|
+
};
|
|
247
|
+
return _cachedResult;
|
|
248
|
+
}
|
|
249
|
+
// marker 存在但状态不完整(例如 postinstall 部分失败)→ 重新检查
|
|
250
|
+
}
|
|
251
|
+
catch { /* marker 损坏 → 重新检查 */ }
|
|
252
|
+
}
|
|
253
|
+
const nodePty = checkNodePty();
|
|
254
|
+
const tmux = checkTmux();
|
|
255
|
+
_cachedResult = { nodePty, tmux };
|
|
256
|
+
// 全部通过则写 marker
|
|
257
|
+
if (nodePty.ok && tmux.ok) {
|
|
258
|
+
try {
|
|
259
|
+
fs.mkdirSync(path.dirname(MARKER_FILE), { recursive: true });
|
|
260
|
+
fs.writeFileSync(MARKER_FILE, JSON.stringify({ checkedAt: new Date().toISOString(), nodePty, tmux }), 'utf-8');
|
|
261
|
+
}
|
|
262
|
+
catch { /* 非关键 */ }
|
|
263
|
+
}
|
|
264
|
+
return _cachedResult;
|
|
265
|
+
}
|
|
266
|
+
export function getBootChecks() {
|
|
267
|
+
return _cachedResult ?? runBootChecks();
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 格式化启动检查报告,用于 CLI 启动时显示给用户。
|
|
271
|
+
*/
|
|
272
|
+
export function formatBootCheckReport(checks, enableColor) {
|
|
273
|
+
const lines = [];
|
|
274
|
+
const g = (s) => enableColor ? `\x1b[32m${s}\x1b[0m` : s;
|
|
275
|
+
const y = (s) => enableColor ? `\x1b[33m${s}\x1b[0m` : s;
|
|
276
|
+
const r = (s) => enableColor ? `\x1b[31m${s}\x1b[0m` : s;
|
|
277
|
+
const dim = (s) => enableColor ? `\x1b[2m${s}\x1b[0m` : s;
|
|
278
|
+
// node-pty
|
|
279
|
+
if (checks.nodePty.ok) {
|
|
280
|
+
const tag = checks.nodePty.rebuilt ? ' (rebuilt)' : '';
|
|
281
|
+
lines.push(` ${g('✓')} node-pty${dim(tag)}`);
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
lines.push(` ${r('✗')} node-pty — ${checks.nodePty.error || 'unknown'}`);
|
|
285
|
+
}
|
|
286
|
+
// tmux
|
|
287
|
+
if (checks.tmux.ok) {
|
|
288
|
+
const detail = checks.tmux.installed
|
|
289
|
+
? ' (just installed)'
|
|
290
|
+
: checks.tmux.version
|
|
291
|
+
? ` ${dim(checks.tmux.version)}`
|
|
292
|
+
: '';
|
|
293
|
+
lines.push(` ${g('✓')} tmux${detail}`);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
lines.push(` ${y('!')} tmux — ${checks.tmux.error || 'not found'} (shell mode will be used)`);
|
|
297
|
+
}
|
|
298
|
+
return lines;
|
|
299
|
+
}
|
package/package.json
CHANGED
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,45 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Termdock postinstall —
|
|
4
|
+
* Termdock postinstall — 确保原生模块可用 + 自动安装 tmux
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* 对于 npm 全局安装 (npm i -g termdock) 的场景,npm 10+ 默认不运行
|
|
7
|
+
* lifecycle scripts。此时 postinstall 不会执行,但 cli.ts 启动时的
|
|
8
|
+
* bootCheck 会做同样的检查并写入 marker,确保首次启动时一切就绪。
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
|
-
import { existsSync } from 'fs';
|
|
11
|
+
import { execSync, spawnSync } from 'child_process';
|
|
11
12
|
import { createRequire } from 'module';
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import path from 'path';
|
|
12
16
|
|
|
13
17
|
const require_ = createRequire(import.meta.url);
|
|
18
|
+
const pkgRoot = new URL('..', import.meta.url).pathname;
|
|
14
19
|
|
|
15
20
|
function run(cmd, opts = {}) {
|
|
16
21
|
console.log(` $ ${cmd}`);
|
|
17
22
|
return execSync(cmd, { stdio: 'inherit', ...opts });
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
function runOk(cmd) {
|
|
21
|
-
try { run(cmd); return true; } catch { return false; }
|
|
25
|
+
function runOk(cmd, opts = {}) {
|
|
26
|
+
try { run(cmd, opts); return true; } catch { return false; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runQuiet(cmd, timeoutMs = 30_000) {
|
|
30
|
+
try {
|
|
31
|
+
const r = spawnSync(cmd, [], { shell: true, timeout: timeoutMs, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
32
|
+
return { ok: r.status === 0, stdout: r.stdout, stderr: r.stderr };
|
|
33
|
+
} catch {
|
|
34
|
+
return { ok: false, stdout: '', stderr: '' };
|
|
35
|
+
}
|
|
22
36
|
}
|
|
23
37
|
|
|
24
38
|
function which(bin) {
|
|
25
|
-
try { execSync(`
|
|
39
|
+
try { execSync(`command -v ${bin} 2>/dev/null`, { stdio: 'pipe' }); return true; } catch { /* not in PATH */ }
|
|
40
|
+
// macOS non-interactive SSH 的 PATH 不含 Homebrew,手动探测
|
|
41
|
+
const extraPaths = ['/opt/homebrew/bin', '/usr/local/bin', '/opt/local/bin'];
|
|
42
|
+
for (const dir of extraPaths) {
|
|
43
|
+
try { fs.accessSync(path.join(dir, bin), fs.constants.X_OK); return true; } catch { /* not here */ }
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
|
-
// —— native modules ——
|
|
48
|
+
// —— native modules: 实际 spawn 测试,不只是 require ——
|
|
29
49
|
console.log('\n=== Rebuilding native modules ===');
|
|
30
|
-
const pkgRoot = new URL('..', import.meta.url).pathname;
|
|
31
50
|
|
|
32
|
-
|
|
51
|
+
const PTY_PATH = require_.resolve('node-pty/package.json', { paths: [pkgRoot] }).replace('/package.json', '');
|
|
52
|
+
function testPty() {
|
|
33
53
|
try {
|
|
34
|
-
require_(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
54
|
+
const pty = require_(PTY_PATH);
|
|
55
|
+
const proc = pty.spawn(process.env.SHELL || '/bin/bash', [], { name: 'xterm', cols: 80, rows: 24 });
|
|
56
|
+
proc.kill();
|
|
57
|
+
return true;
|
|
58
|
+
} catch { return false; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (testPty()) {
|
|
62
|
+
console.log(' node-pty OK');
|
|
63
|
+
} else {
|
|
64
|
+
console.log(' node-pty needs rebuild (prebuilt binary incompatible with this OS/Node version)...');
|
|
65
|
+
runOk(`rm -rf ${PTY_PATH}/prebuilds/*`);
|
|
66
|
+
run(`cd ${PTY_PATH} && npx --yes node-gyp rebuild`, { timeout: 120_000 });
|
|
67
|
+
if (testPty()) {
|
|
68
|
+
console.log(' ✓ node-pty rebuilt successfully');
|
|
69
|
+
} else {
|
|
70
|
+
console.log(' ⚠ node-pty rebuild failed — terminal shell spawn may not work.');
|
|
43
71
|
}
|
|
44
72
|
}
|
|
45
73
|
|
|
@@ -51,30 +79,70 @@ if (!which('tmux')) {
|
|
|
51
79
|
let installed = false;
|
|
52
80
|
|
|
53
81
|
if (platform === 'darwin') {
|
|
54
|
-
// macOS: try brew
|
|
55
|
-
|
|
56
|
-
|
|
82
|
+
// macOS: try brew → macports → nix, in that order
|
|
83
|
+
const brew = which('brew');
|
|
84
|
+
if (brew) {
|
|
85
|
+
installed = runOk('brew install tmux 2>&1', { timeout: 120_000 });
|
|
86
|
+
} else if (which('port')) {
|
|
87
|
+
installed = runOk('port install tmux 2>&1', { timeout: 120_000 });
|
|
88
|
+
} else if (which('nix-env')) {
|
|
89
|
+
installed = runOk('nix-env -iA nixpkgs.tmux 2>&1', { timeout: 120_000 });
|
|
57
90
|
} else {
|
|
58
|
-
console.log('
|
|
91
|
+
console.log(' No package manager found (brew/port/nix).');
|
|
92
|
+
console.log(' Install Homebrew: https://brew.sh — then reinstall termdock.');
|
|
59
93
|
}
|
|
60
94
|
} else if (platform === 'linux') {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
95
|
+
const managers = [
|
|
96
|
+
{ bin: 'apt-get', cmd: 'apt-get install -y tmux', sudo: true },
|
|
97
|
+
{ bin: 'dnf', cmd: 'dnf install -y tmux', sudo: true },
|
|
98
|
+
{ bin: 'yum', cmd: 'yum install -y tmux', sudo: true },
|
|
99
|
+
{ bin: 'apk', cmd: 'apk add tmux', sudo: false },
|
|
100
|
+
{ bin: 'pacman', cmd: 'pacman -S --noconfirm tmux', sudo: true },
|
|
101
|
+
{ bin: 'zypper', cmd: 'zypper install -y tmux', sudo: true },
|
|
102
|
+
];
|
|
103
|
+
for (const m of managers) {
|
|
104
|
+
if (!which(m.bin)) continue;
|
|
105
|
+
const cmd = m.sudo ? `sudo ${m.cmd}` : m.cmd;
|
|
106
|
+
if (runOk(`${cmd} 2>&1`, { timeout: 120_000 })) {
|
|
107
|
+
installed = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
// retry without sudo
|
|
111
|
+
if (m.sudo && runOk(`${m.cmd} 2>&1`, { timeout: 120_000 })) {
|
|
112
|
+
installed = true;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
67
115
|
}
|
|
68
116
|
}
|
|
69
117
|
|
|
70
118
|
if (installed) {
|
|
71
|
-
console.log(' ✓ tmux installed
|
|
72
|
-
} else
|
|
73
|
-
console.log(' ⚠
|
|
74
|
-
console.log('
|
|
119
|
+
console.log(' ✓ tmux installed');
|
|
120
|
+
} else {
|
|
121
|
+
console.log(' ⚠ tmux not available — shell mode will be used.');
|
|
122
|
+
console.log(' Termdock will check again on next start and try to fix automatically.');
|
|
75
123
|
}
|
|
76
124
|
} else {
|
|
77
|
-
|
|
125
|
+
const ver = execSync('tmux -V', { encoding: 'utf-8' }).trim();
|
|
126
|
+
console.log(` ✓ tmux ${ver}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// —— 写入 boot-check marker,避免 CLI 启动时重复检查 ——
|
|
130
|
+
// 只有 node-pty 和 tmux 都就绪才写 marker;否则 CLI 启动时 bootCheck 会补做
|
|
131
|
+
const markerDir = path.join(os.homedir(), '.termdock');
|
|
132
|
+
const markerFile = path.join(markerDir, '.boot-check-ok');
|
|
133
|
+
if (testPty() && which('tmux')) {
|
|
134
|
+
try {
|
|
135
|
+
fs.mkdirSync(markerDir, { recursive: true });
|
|
136
|
+
fs.writeFileSync(markerFile, JSON.stringify({
|
|
137
|
+
checkedAt: new Date().toISOString(),
|
|
138
|
+
source: 'postinstall',
|
|
139
|
+
nodePty: { ok: true, rebuilt: false },
|
|
140
|
+
tmux: { ok: true, version: execSync('tmux -V', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim() },
|
|
141
|
+
}), 'utf-8');
|
|
142
|
+
} catch { /* non-critical */ }
|
|
143
|
+
} else {
|
|
144
|
+
console.log(' ℹ Boot check marker not written (some checks failed).');
|
|
145
|
+
console.log(' Termdock will re-check on first CLI start.');
|
|
78
146
|
}
|
|
79
147
|
|
|
80
148
|
console.log('\n✓ Termdock postinstall complete\n');
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-D-e_wdPJ.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-CI7QyRAs.js";import{b as i}from"./mermaid.core-D-e_wdPJ.js";import"./chunk-5VM5RSS4-DliA2ku3.js";import"./chunk-XXDRQBXY-BM9jxTTb.js";import"./chunk-VR4S4FIN-DYQ8EokS.js";import"./chunk-32BRIVSS-CGNuIJuG.js";import"./purify.es-CC4Brkr_.js";import"./index-0d_16SRD.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-CI7QyRAs.js";import{b as i}from"./mermaid.core-D-e_wdPJ.js";import"./chunk-5VM5RSS4-DliA2ku3.js";import"./chunk-XXDRQBXY-BM9jxTTb.js";import"./chunk-VR4S4FIN-DYQ8EokS.js";import"./chunk-32BRIVSS-CGNuIJuG.js";import"./purify.es-CC4Brkr_.js";import"./index-0d_16SRD.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as r,a as e,s as a,S as i}from"./chunk-EX3LRPZG-DzFXj1M3.js";import{b as s}from"./mermaid.core-D-e_wdPJ.js";import"./chunk-XXDRQBXY-BM9jxTTb.js";import"./chunk-VR4S4FIN-DYQ8EokS.js";import"./chunk-32BRIVSS-CGNuIJuG.js";import"./purify.es-CC4Brkr_.js";import"./index-0d_16SRD.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:a,get db(){return new i(2)},renderer:e,styles:r,init:s(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{g as diagram};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{c as r,s as e}from"./flowDiagram-23GEKE2U-Cz9w-F5K.js";import{b as o}from"./mermaid.core-D-e_wdPJ.js";import"./chunk-5VM5RSS4-DliA2ku3.js";import"./chunk-XXDRQBXY-BM9jxTTb.js";import"./chunk-VR4S4FIN-DYQ8EokS.js";import"./chunk-32BRIVSS-CGNuIJuG.js";import"./purify.es-CC4Brkr_.js";import"./channel-Fzeh-Sl6.js";import"./index-0d_16SRD.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var a=o(t=>`${e(t)}
|
|
2
|
-
.swimlane.cluster rect {
|
|
3
|
-
stroke: ${t.clusterBorder} !important;
|
|
4
|
-
}
|
|
5
|
-
[data-look="neo"].cluster rect {
|
|
6
|
-
filter: none;
|
|
7
|
-
}
|
|
8
|
-
`,"getStyles"),m=a,v=r({defaultLayout:"swimlane",styles:m});export{v as diagram};
|