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.
Files changed (70) hide show
  1. package/dist/client/assets/{abnfDiagram-VRR7QNED-BjNCcQvm.js → abnfDiagram-VRR7QNED-BVuFNukd.js} +1 -1
  2. package/dist/client/assets/{arc-xcCAKMnT.js → arc-JFOslCNs.js} +1 -1
  3. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-ijtAKPsW.js → architectureDiagram-ZJ3FMSHR-B-TyEAOJ.js} +1 -1
  4. package/dist/client/assets/{blockDiagram-677ZJIJ3-GVIbuo6R.js → blockDiagram-677ZJIJ3-5ycSMMTY.js} +1 -1
  5. package/dist/client/assets/{c4Diagram-LMCZKHZV-B3IbU0ga.js → c4Diagram-LMCZKHZV-BvXzrvb_.js} +1 -1
  6. package/dist/client/assets/channel-Cnn-8i2b.js +1 -0
  7. package/dist/client/assets/{chunk-2Q5K7J3B-DEwVqhvo.js → chunk-2Q5K7J3B-00s3llQG.js} +1 -1
  8. package/dist/client/assets/{chunk-32BRIVSS-CGNuIJuG.js → chunk-32BRIVSS-BJMhtTym.js} +1 -1
  9. package/dist/client/assets/{chunk-5VM5RSS4-DliA2ku3.js → chunk-5VM5RSS4-DedB9Vaa.js} +1 -1
  10. package/dist/client/assets/{chunk-EX3LRPZG-DzFXj1M3.js → chunk-EX3LRPZG-DWGG3NoJ.js} +1 -1
  11. package/dist/client/assets/{chunk-JWPE2WC7-h3s-pxcn.js → chunk-JWPE2WC7-D34JiLTf.js} +1 -1
  12. package/dist/client/assets/{chunk-MOJQB5TN-CQST9jsX.js → chunk-MOJQB5TN-D6Ywmh0z.js} +1 -1
  13. package/dist/client/assets/{chunk-RYQCIY6F-CXjnUh8k.js → chunk-RYQCIY6F-BH6yURdq.js} +1 -1
  14. package/dist/client/assets/{chunk-V7JOEXUC-CI7QyRAs.js → chunk-V7JOEXUC-DBSqOhi7.js} +1 -1
  15. package/dist/client/assets/{chunk-VR4S4FIN-DYQ8EokS.js → chunk-VR4S4FIN-DzIaqkfm.js} +1 -1
  16. package/dist/client/assets/{chunk-XXDRQBXY-BM9jxTTb.js → chunk-XXDRQBXY-9iVmQCNp.js} +1 -1
  17. package/dist/client/assets/classDiagram-OUVF2IWQ-DiAxD67S.js +1 -0
  18. package/dist/client/assets/classDiagram-v2-EOCWNBFH-DiAxD67S.js +1 -0
  19. package/dist/client/assets/{cose-bilkent-JH36ORCC-C4nYmp4I.js → cose-bilkent-JH36ORCC-1Yqlhf0s.js} +1 -1
  20. package/dist/client/assets/{cynefin-VYW2F7L2-DnlhMeVt.js → cynefin-VYW2F7L2-DIvwKU3S.js} +1 -1
  21. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-DJuHDI6A.js → cynefinDiagram-TSTJHNR4-C_tvEhnO.js} +1 -1
  22. package/dist/client/assets/{dagre-VKFMJZFB-CsBEz_S1.js → dagre-VKFMJZFB-BW6zPwqv.js} +1 -1
  23. package/dist/client/assets/{diagram-FQU43EPY-BDNTGROP.js → diagram-FQU43EPY-BPdiUHXh.js} +1 -1
  24. package/dist/client/assets/{diagram-G47NLZAW-CJJwi6PP.js → diagram-G47NLZAW-DcSplQyb.js} +1 -1
  25. package/dist/client/assets/{diagram-NH7WQ7WH-CXJWffic.js → diagram-NH7WQ7WH-DJfc5gP9.js} +1 -1
  26. package/dist/client/assets/{diagram-OA4YK3LP-wOtL7NBA.js → diagram-OA4YK3LP-BMhJGhOv.js} +1 -1
  27. package/dist/client/assets/{diagram-WEI45ONY-ClOt2G7I.js → diagram-WEI45ONY-BfvX5Q8Q.js} +1 -1
  28. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-DoXhiNjK.js → ebnfDiagram-CCIWWBDH-CHXVkELN.js} +1 -1
  29. package/dist/client/assets/{erDiagram-Q63AITRT-DB5a-Aw4.js → erDiagram-Q63AITRT-BsY8w4KG.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-23GEKE2U-Cz9w-F5K.js → flowDiagram-23GEKE2U-BkzZO16s.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-NO4QXBWP-CDykkj3o.js → ganttDiagram-NO4QXBWP-CYc_FSmb.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-CNkfDkzA.js → gitGraphDiagram-IHSO6WYX-DZVYJm6Q.js} +1 -1
  33. package/dist/client/assets/{index-Cv7q4nZR.css → index-B1I3x8rE.css} +1 -1
  34. package/dist/client/assets/{index-0d_16SRD.js → index-DIbF-DRk.js} +40 -40
  35. package/dist/client/assets/{infoDiagram-FWYZ7A6U-CfrwrocT.js → infoDiagram-FWYZ7A6U-Dnv8kWO8.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-C2AD_Ae2.js → ishikawaDiagram-FXEZZL3T-2olMVSmL.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-5HDEW3XC-DzQvMG3N.js → journeyDiagram-5HDEW3XC-B_GL15RA.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-HUTT4EX6-CUVhWmNF.js → kanban-definition-HUTT4EX6-B6_36kLK.js} +1 -1
  39. package/dist/client/assets/{linear-D5qZ3q5c.js → linear-CE3AYySp.js} +1 -1
  40. package/dist/client/assets/{mermaid.core-D-e_wdPJ.js → mermaid.core-mNpsdXrC.js} +4 -4
  41. package/dist/client/assets/{mindmap-definition-LN4V7U3C-CDHLWnzb.js → mindmap-definition-LN4V7U3C-BRH7DD07.js} +1 -1
  42. package/dist/client/assets/{pegDiagram-2B236MQR-9_bnKT8u.js → pegDiagram-2B236MQR-CPWaVwbR.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-ENE6RG2P-gw-xIG1b.js → pieDiagram-ENE6RG2P-Dg488vOr.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-Cn_lstJf.js → quadrantDiagram-ABIIQ3AL-X9TiKVp4.js} +1 -1
  45. package/dist/client/assets/{railroadDiagram-RFXS5EU6-yZM8qXUp.js → railroadDiagram-RFXS5EU6-Dm_BeOAW.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-TGXJPOKE-WQ1iDS_X.js → requirementDiagram-TGXJPOKE-lkU41AKk.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-UVz3wsFF.js → sankeyDiagram-HTMAVEWB-CltVgAob.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BuJBBGcB.js → sequenceDiagram-DBY2YBRQ-D65aOt87.js} +1 -1
  49. package/dist/client/assets/{sizeCapture-X5ZJPWSS-CysR04La.js → sizeCapture-X5ZJPWSS-B_zcJ4qw.js} +1 -1
  50. package/dist/client/assets/{stateDiagram-2N3HPSRC-DYC8TYxP.js → stateDiagram-2N3HPSRC-DMF21ttG.js} +1 -1
  51. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-CUgSOIEq.js +1 -0
  52. package/dist/client/assets/{swimlanes-5IMT3BWC-FT83sDlI.js → swimlanes-5IMT3BWC-t01s2riN.js} +2 -2
  53. package/dist/client/assets/swimlanesDiagram-G3AALYLV-DHbd0hST.js +8 -0
  54. package/dist/client/assets/{timeline-definition-FHXFAJF6-C-o8uRSU.js → timeline-definition-FHXFAJF6-DTvM_hB8.js} +1 -1
  55. package/dist/client/assets/{vennDiagram-L72KCM5P-Cw-g3-Li.js → vennDiagram-L72KCM5P-BTXCVT1w.js} +1 -1
  56. package/dist/client/assets/{wardleyDiagram-EHGQE667-B_JEirkb.js → wardleyDiagram-EHGQE667-DASx33Ug.js} +1 -1
  57. package/dist/client/assets/{xychartDiagram-FW5EYKEG-B1LkJSxz.js → xychartDiagram-FW5EYKEG-C6ZvW88N.js} +1 -1
  58. package/dist/client/index.html +2 -2
  59. package/dist/client/sw.js +1 -1
  60. package/dist/server/cli.js +14 -5
  61. package/dist/server/config.js +50 -4
  62. package/dist/server/routes/terminal.js +25 -14
  63. package/dist/server/utils/bootCheck.js +299 -0
  64. package/package.json +1 -1
  65. package/scripts/postinstall.mjs +102 -34
  66. package/dist/client/assets/channel-Fzeh-Sl6.js +0 -1
  67. package/dist/client/assets/classDiagram-OUVF2IWQ-DzF9pacK.js +0 -1
  68. package/dist/client/assets/classDiagram-v2-EOCWNBFH-DzF9pacK.js +0 -1
  69. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Cru4nzfa.js +0 -1
  70. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termdock",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "A complete web-based terminal application with modern UI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,45 +1,73 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Termdock postinstall — 确保原生模块编译 + tmux 可用
4
+ * Termdock postinstall — 确保原生模块可用 + 自动安装 tmux
5
5
  *
6
- * 执行时机:每次 `npm install -g termdock`(含首次安装和更新)
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(`which ${bin}`, { stdio: 'pipe' }); return true; } catch { return false; }
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
- for (const mod of ['node-pty', '@parcel/watcher']) {
51
+ const PTY_PATH = require_.resolve('node-pty/package.json', { paths: [pkgRoot] }).replace('/package.json', '');
52
+ function testPty() {
33
53
  try {
34
- require_(`${mod}`);
35
- console.log(` ${mod} already usable, skip rebuild`);
36
- } catch {
37
- console.log(` ${mod} needs rebuild...`);
38
- const modDir = require_.resolve(`${mod}/package.json`, { paths: [pkgRoot] });
39
- const modRoot = modDir.replace('/package.json', '');
40
- // 删掉预编译二进制,强制从源码构建
41
- runOk(`rm -rf ${modRoot}/prebuilds/*`);
42
- run(`cd ${modRoot} && npx --yes node-gyp rebuild`, { timeout: 120_000 });
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 first, then fallback message
55
- if (which('brew')) {
56
- installed = runOk('brew install tmux');
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(' Homebrew not found. Install it: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
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
- if (runOk('apt-get install -y tmux 2>/dev/null')) {
62
- installed = true;
63
- } else if (runOk('yum install -y tmux 2>/dev/null')) {
64
- installed = true;
65
- } else if (runOk('apk add tmux 2>/dev/null')) {
66
- installed = true;
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 successfully');
72
- } else if (!which('tmux')) {
73
- console.log(' ⚠ Could not auto-install tmux. Termdock works without it (shell mode will be default).');
74
- console.log(' To use tmux features: install tmux then restart termdock.');
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
- console.log(` ✓ tmux already available: ${execSync('tmux -V', { encoding: 'utf-8' }).trim()}`);
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};