tmex-cli 0.12.2 → 0.12.3-beta1
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/CHANGELOG.md +9 -13
- package/dist/cli-node.js +287 -98
- package/dist/runtime/{cpufeatures-dxrn1j88.node → cpufeatures-nvqtn6ne.node} +0 -0
- package/dist/runtime/server.js +25 -16
- package/dist/runtime/{sshcrypto-fjcj736m.node → sshcrypto-rfw667cw.node} +0 -0
- package/package.json +1 -1
- package/resources/fe-dist/assets/{DevicePage-B0fQ6yWB.js → DevicePage-Cc6BDsCB.js} +2 -2
- package/resources/fe-dist/assets/{DevicesPage-C7THDzTX.js → DevicesPage-BXVj9MYn.js} +2 -2
- package/resources/fe-dist/assets/{FilePage-D8V8P2IA.js → FilePage-DFvyeBg8.js} +2 -2
- package/resources/fe-dist/assets/{SettingsPage-BOTyBhHH.js → SettingsPage-B8daCB8Y.js} +2 -2
- package/resources/fe-dist/assets/{arc-DcujnLz7.js → arc-B20iCBM1.js} +2 -2
- package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-i8499Wfc.js → architectureDiagram-3BPJPVTR-DkJzy--B.js} +2 -2
- package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-BFh4xZY2.js → blockDiagram-GPEHLZMM-BqP9dRVx.js} +2 -2
- package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-nUyGK7Ke.js → c4Diagram-AAUBKEIU-DbqKeZkc.js} +2 -2
- package/resources/fe-dist/assets/{card-BkRqR9zz.js → card-DnWixV3q.js} +2 -2
- package/resources/fe-dist/assets/channel-B9gGKu0d.js +2 -0
- package/resources/fe-dist/assets/{chunk-2J33WTMH-z2oMKYMn.js → chunk-2J33WTMH-BHZUy1d7.js} +2 -2
- package/resources/fe-dist/assets/{chunk-4BX2VUAB-CCD_pyAh.js → chunk-4BX2VUAB-QKjwiFAV.js} +2 -2
- package/resources/fe-dist/assets/{chunk-55IACEB6-Dx2_9d-z.js → chunk-55IACEB6-aUtPslwp.js} +2 -2
- package/resources/fe-dist/assets/{chunk-727SXJPM-DA36poir.js → chunk-727SXJPM-DetOPTEw.js} +2 -2
- package/resources/fe-dist/assets/{chunk-AQP2D5EJ-CQoXkVd4.js → chunk-AQP2D5EJ-Cj4pjkf1.js} +2 -2
- package/resources/fe-dist/assets/{chunk-FMBD7UC4-A6Yn7A44.js → chunk-FMBD7UC4-DuMfjqmS.js} +2 -2
- package/resources/fe-dist/assets/{chunk-ND2GUHAM-BsNl2EMT.js → chunk-ND2GUHAM-De6BOrhf.js} +2 -2
- package/resources/fe-dist/assets/{chunk-QZHKN3VN-BM_QNxf6.js → chunk-QZHKN3VN-C6UKMTXf.js} +2 -2
- package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-C5AFJpT6.js +2 -0
- package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-C5AFJpT6.js +2 -0
- package/resources/fe-dist/assets/{cose-bilkent-S5V4N54A-1lMO_Dod.js → cose-bilkent-S5V4N54A-giRkzps1.js} +2 -2
- package/resources/fe-dist/assets/{dagre-BM42HDAG-C9Up3ckQ.js → dagre-BM42HDAG-DLE28RsS.js} +2 -2
- package/resources/fe-dist/assets/{diagram-2AECGRRQ-CtuDndmY.js → diagram-2AECGRRQ-CrloCw3m.js} +2 -2
- package/resources/fe-dist/assets/{diagram-5GNKFQAL-BQfskK8S.js → diagram-5GNKFQAL-BGEyXIkF.js} +2 -2
- package/resources/fe-dist/assets/{diagram-KO2AKTUF-CGJi8q81.js → diagram-KO2AKTUF-CZa19ANY.js} +2 -2
- package/resources/fe-dist/assets/{diagram-LMA3HP47-DAMQIoLQ.js → diagram-LMA3HP47-DC7BsuGl.js} +2 -2
- package/resources/fe-dist/assets/{diagram-OG6HWLK6-D2s__dFc.js → diagram-OG6HWLK6-BH4wPrwj.js} +2 -2
- package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-_UJYLIQN.js → erDiagram-TEJ5UH35-CsQIlfSm.js} +2 -2
- package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-DN-qktDh.js → flowDiagram-I6XJVG4X-CkjuxggR.js} +2 -2
- package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-CNuuuhXX.js → ganttDiagram-6RSMTGT7-eEGLUumD.js} +2 -2
- package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-Qar2sVRo.js → gitGraphDiagram-PVQCEYII-0Ni5AA_m.js} +2 -2
- package/resources/fe-dist/assets/{index-Ra6pj0rt.js → index-96GlCNDm.js} +3 -3
- package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-2e6tU_v9.js → infoDiagram-5YYISTIA-tIpZWSDh.js} +2 -2
- package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-BghKvoM4.js → ishikawaDiagram-YF4QCWOH-BLnWokk9.js} +2 -2
- package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-D-cHSGb_.js → journeyDiagram-JHISSGLW-NBzojNKg.js} +2 -2
- package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-BE6Cb-OK.js → kanban-definition-UN3LZRKU-BFDAtm6I.js} +2 -2
- package/resources/fe-dist/assets/{linear-CwDBnUH0.js → linear-B5DOEe_O.js} +2 -2
- package/resources/fe-dist/assets/{markdown-preview-BlQiC8ND.js → markdown-preview-DD3cdI4r.js} +4 -4
- package/resources/fe-dist/assets/{mermaid.core-DDMkLXrp.js → mermaid.core-BWq4oZoC.js} +6 -6
- package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-C9xVR7Ut.js → mindmap-definition-RKZ34NQL-Bi2fp38o.js} +2 -2
- package/resources/fe-dist/assets/{pieDiagram-4H26LBE5-DW_4RcBv.js → pieDiagram-4H26LBE5-CKK8p15L.js} +2 -2
- package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-DoIrZmk2.js → quadrantDiagram-W4KKPZXB-QslRPIFi.js} +2 -2
- package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-CYcJSuPe.js → requirementDiagram-4Y6WPE33-BRo6E1BH.js} +2 -2
- package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-CrIkKM6h.js → sankeyDiagram-5OEKKPKP-B16TNk4Z.js} +2 -2
- package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-DgzGHhGk.js → sequenceDiagram-3UESZ5HK-DiZuSmvo.js} +2 -2
- package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-C6Dhr3fY.js → stateDiagram-AJRCARHV-BOn6CbQ6.js} +2 -2
- package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-DA0F4iLh.js +2 -0
- package/resources/fe-dist/assets/{terminal-settings-panel-CpnmWQtf.js → terminal-settings-panel-DVSfGH3m.js} +2 -2
- package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-jJ1iauQ0.js → timeline-definition-PNZ67QCA-B74c_y_8.js} +2 -2
- package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-DLRoaoJ9.js → vennDiagram-CIIHVFJN-BZvPBIfU.js} +2 -2
- package/resources/fe-dist/assets/{wardley-L42UT6IY-CykeHWp8.js → wardley-L42UT6IY-CgvxGMxD.js} +2 -2
- package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-U22jzD8a.js → wardleyDiagram-YWT4CUSO-BI54Re1E.js} +2 -2
- package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-CiXS9PJ2.js → xychartDiagram-2RQKCTM6-C9AmQfwS.js} +2 -2
- package/resources/fe-dist/index.html +1 -1
- package/resources/fe-dist/assets/channel-DqmfsuLY.js +0 -2
- package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-D3Z9BeOE.js +0 -2
- package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-D3Z9BeOE.js +0 -2
- package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-BiSh2azE.js +0 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
# 0.12.
|
|
1
|
+
# 0.12.3-beta1
|
|
2
2
|
|
|
3
3
|
_2026-06-15_
|
|
4
4
|
|
|
5
5
|
## English
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### Fixes
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- Mobile keyboard behavior: choose how the page reacts when the on-screen keyboard opens — shift the page up, resize the terminal, or keep the cursor above the keyboard — for smoother typing on phones.
|
|
9
|
+
- Bun detection is now reliable across install methods (Homebrew, the official installer, etc.). It no longer reports Bun as "not installed" when your shell startup prints to the terminal.
|
|
11
10
|
|
|
12
|
-
###
|
|
11
|
+
### New
|
|
13
12
|
|
|
14
|
-
-
|
|
15
|
-
- UI polish and refinements (spacing, dark-mode readability, settings tabs on mobile).
|
|
13
|
+
- You can now point tmex at a specific Bun binary via `--bun-path <path>` (or the `TMEX_BUN_PATH` environment variable) for `init`, `doctor`, and `upgrade`. The chosen Bun is remembered and reused on later upgrades.
|
|
16
14
|
|
|
17
15
|
---
|
|
18
16
|
|
|
19
17
|
## 中文
|
|
20
18
|
|
|
21
|
-
###
|
|
19
|
+
### 修复
|
|
22
20
|
|
|
23
|
-
-
|
|
24
|
-
- 手机键盘行为:可选择软键盘弹出时页面的避让方式——整页上移、缩放终端、或让光标始终在键盘上方——手机上输入更顺手。
|
|
21
|
+
- 修复 Bun 检测:Homebrew、官方安装脚本等各种方式装的 Bun 现在都能被正确识别,不再因为 shell 启动时向终端打印内容而误判为「未安装」。
|
|
25
22
|
|
|
26
|
-
###
|
|
23
|
+
### 新增
|
|
27
24
|
|
|
28
|
-
-
|
|
29
|
-
- 界面细节优化(间距、深色模式可读性、移动端设置 tab 等)。
|
|
25
|
+
- `init` / `doctor` / `upgrade` 现在支持用 `--bun-path <路径>`(或环境变量 `TMEX_BUN_PATH`)显式指定 Bun 可执行文件;指定后的 Bun 会被记住并在后续升级中复用。
|
package/dist/cli-node.js
CHANGED
|
@@ -29,9 +29,9 @@ var MESSAGES = {
|
|
|
29
29
|
"cli.help": `tmex CLI
|
|
30
30
|
|
|
31
31
|
Usage:
|
|
32
|
-
tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false>]
|
|
33
|
-
tmex doctor [--install-dir <path>] [--json]
|
|
34
|
-
tmex upgrade [--version <version>] [--install-dir <path>]
|
|
32
|
+
tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path>]
|
|
33
|
+
tmex doctor [--install-dir <path>] [--json] [--bun-path <path>]
|
|
34
|
+
tmex upgrade [--version <version>] [--install-dir <path>] [--bun-path <path>]
|
|
35
35
|
tmex uninstall [--install-dir <path>] [--yes] [--purge]
|
|
36
36
|
|
|
37
37
|
Global flags:
|
|
@@ -52,6 +52,8 @@ Global flags:
|
|
|
52
52
|
"bun.versionExecFailed": "Failed to execute bun --version. Please verify Bun installation.",
|
|
53
53
|
"bun.versionTooLow": "Bun version too low: current {{version}}, required >= {{minVersion}}",
|
|
54
54
|
"bun.checkFailed": "Bun check failed.",
|
|
55
|
+
"bun.explicitInvalid": "Specified bun path is invalid or not executable: {{path}}",
|
|
56
|
+
"bun.unsafePath": "Unsafe bun path (contains shell metacharacters): {{path}}",
|
|
55
57
|
"service.install.unsupportedPlatform": "Automatic service installation is not supported on this platform: {{platform}}",
|
|
56
58
|
"service.systemd.daemonReloadFailed": "systemctl daemon-reload failed: {{detail}}",
|
|
57
59
|
"service.systemd.enableFailed": "systemctl enable failed: {{detail}}",
|
|
@@ -128,9 +130,9 @@ Global flags:
|
|
|
128
130
|
"cli.help": `tmex CLI
|
|
129
131
|
|
|
130
132
|
用法:
|
|
131
|
-
tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false>]
|
|
132
|
-
tmex doctor [--install-dir <path>] [--json]
|
|
133
|
-
tmex upgrade [--version <version>] [--install-dir <path>]
|
|
133
|
+
tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path>]
|
|
134
|
+
tmex doctor [--install-dir <path>] [--json] [--bun-path <path>]
|
|
135
|
+
tmex upgrade [--version <version>] [--install-dir <path>] [--bun-path <path>]
|
|
134
136
|
tmex uninstall [--install-dir <path>] [--yes] [--purge]
|
|
135
137
|
|
|
136
138
|
全局参数:
|
|
@@ -151,6 +153,8 @@ Global flags:
|
|
|
151
153
|
"bun.versionExecFailed": "无法执行 bun --version,请检查 Bun 安装是否完整。",
|
|
152
154
|
"bun.versionTooLow": "Bun 版本过低:当前 {{version}},要求 >= {{minVersion}}",
|
|
153
155
|
"bun.checkFailed": "Bun 检查失败。",
|
|
156
|
+
"bun.explicitInvalid": "指定的 bun 路径无效或不可执行:{{path}}",
|
|
157
|
+
"bun.unsafePath": "bun 路径包含 shell 特殊字符,不安全:{{path}}",
|
|
154
158
|
"service.install.unsupportedPlatform": "当前平台不支持自动安装服务:{{platform}}",
|
|
155
159
|
"service.systemd.daemonReloadFailed": "systemctl daemon-reload 失败:{{detail}}",
|
|
156
160
|
"service.systemd.enableFailed": "systemctl enable 失败:{{detail}}",
|
|
@@ -259,7 +263,7 @@ function t(key, vars) {
|
|
|
259
263
|
// src/lib/bun.ts
|
|
260
264
|
import { existsSync } from "node:fs";
|
|
261
265
|
import { homedir as homedir2 } from "node:os";
|
|
262
|
-
import { join } from "node:path";
|
|
266
|
+
import { isAbsolute, join } from "node:path";
|
|
263
267
|
|
|
264
268
|
// src/lib/process.ts
|
|
265
269
|
import { spawn } from "node:child_process";
|
|
@@ -269,10 +273,21 @@ async function runCommand(command, args, options = {}) {
|
|
|
269
273
|
const child = spawn(command, args, {
|
|
270
274
|
cwd: options.cwd,
|
|
271
275
|
env: options.env,
|
|
272
|
-
stdio
|
|
276
|
+
stdio: stdio === "pipe" ? ["ignore", "pipe", "pipe"] : stdio
|
|
273
277
|
});
|
|
274
278
|
let stdout = "";
|
|
275
279
|
let stderr = "";
|
|
280
|
+
let settled = false;
|
|
281
|
+
let timer = null;
|
|
282
|
+
const finish = (fn) => {
|
|
283
|
+
if (settled)
|
|
284
|
+
return;
|
|
285
|
+
settled = true;
|
|
286
|
+
if (timer) {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
}
|
|
289
|
+
fn();
|
|
290
|
+
};
|
|
276
291
|
if (stdio === "pipe") {
|
|
277
292
|
child.stdout?.on("data", (chunk) => {
|
|
278
293
|
stdout += chunk.toString();
|
|
@@ -281,13 +296,21 @@ async function runCommand(command, args, options = {}) {
|
|
|
281
296
|
stderr += chunk.toString();
|
|
282
297
|
});
|
|
283
298
|
}
|
|
284
|
-
|
|
299
|
+
if (options.timeoutMs && options.timeoutMs > 0) {
|
|
300
|
+
timer = setTimeout(() => {
|
|
301
|
+
child.kill("SIGKILL");
|
|
302
|
+
finish(() => reject(new Error(`Command timed out after ${options.timeoutMs}ms: ${command}`)));
|
|
303
|
+
}, options.timeoutMs);
|
|
304
|
+
}
|
|
305
|
+
child.on("error", (error) => {
|
|
306
|
+
finish(() => reject(error));
|
|
307
|
+
});
|
|
285
308
|
child.on("close", (code) => {
|
|
286
|
-
resolve2({
|
|
309
|
+
finish(() => resolve2({
|
|
287
310
|
code: code ?? 1,
|
|
288
311
|
stdout,
|
|
289
312
|
stderr
|
|
290
|
-
});
|
|
313
|
+
}));
|
|
291
314
|
});
|
|
292
315
|
});
|
|
293
316
|
}
|
|
@@ -316,63 +339,239 @@ function compareSemver(left, right) {
|
|
|
316
339
|
return 0;
|
|
317
340
|
}
|
|
318
341
|
|
|
342
|
+
// src/lib/validate.ts
|
|
343
|
+
function asString(value) {
|
|
344
|
+
if (typeof value === "string")
|
|
345
|
+
return value;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
function asBoolean(value) {
|
|
349
|
+
if (typeof value === "boolean")
|
|
350
|
+
return value;
|
|
351
|
+
if (typeof value !== "string")
|
|
352
|
+
return;
|
|
353
|
+
const normalized = value.trim().toLowerCase();
|
|
354
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "y") {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "n") {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
function parsePort(value) {
|
|
363
|
+
const port = Number(value);
|
|
364
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
365
|
+
throw new Error(t("errors.validate.invalidPort", { value }));
|
|
366
|
+
}
|
|
367
|
+
return port;
|
|
368
|
+
}
|
|
369
|
+
function assertNonEmpty(value, fieldName) {
|
|
370
|
+
const trimmed = value.trim();
|
|
371
|
+
if (!trimmed) {
|
|
372
|
+
throw new Error(t("errors.validate.emptyField", { field: fieldName }));
|
|
373
|
+
}
|
|
374
|
+
return trimmed;
|
|
375
|
+
}
|
|
376
|
+
|
|
319
377
|
// src/lib/bun.ts
|
|
320
|
-
|
|
321
|
-
|
|
378
|
+
var ESC = 27;
|
|
379
|
+
var CSI_OPEN = 91;
|
|
380
|
+
var OSC_OPEN = 93;
|
|
381
|
+
var BEL = 7;
|
|
382
|
+
var ST_TAIL = 92;
|
|
383
|
+
var LF = 10;
|
|
384
|
+
var CR = 13;
|
|
385
|
+
var DEL = 127;
|
|
386
|
+
var SLASH = 47;
|
|
387
|
+
var PROBE_TIMEOUT_MS = 5000;
|
|
388
|
+
function sanitizeBunPath(raw) {
|
|
389
|
+
let stripped = "";
|
|
390
|
+
for (let i = 0;i < raw.length; i += 1) {
|
|
391
|
+
const code = raw.charCodeAt(i);
|
|
392
|
+
if (code === ESC) {
|
|
393
|
+
const next = raw.charCodeAt(i + 1);
|
|
394
|
+
if (next === CSI_OPEN) {
|
|
395
|
+
i += 1;
|
|
396
|
+
while (i + 1 < raw.length) {
|
|
397
|
+
const c = raw.charCodeAt(i + 1);
|
|
398
|
+
i += 1;
|
|
399
|
+
if (c >= 64 && c <= 126) {
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} else if (next === OSC_OPEN) {
|
|
404
|
+
i += 1;
|
|
405
|
+
while (i + 1 < raw.length) {
|
|
406
|
+
const c = raw.charCodeAt(i + 1);
|
|
407
|
+
if (c === BEL) {
|
|
408
|
+
i += 1;
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
if (c === ESC && raw.charCodeAt(i + 2) === ST_TAIL) {
|
|
412
|
+
i += 2;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
i += 1;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
stripped += raw[i];
|
|
421
|
+
}
|
|
422
|
+
const lines = [];
|
|
423
|
+
let current = "";
|
|
424
|
+
const flush = () => {
|
|
425
|
+
let cleaned = "";
|
|
426
|
+
for (let i = 0;i < current.length; i += 1) {
|
|
427
|
+
const code = current.charCodeAt(i);
|
|
428
|
+
if (code <= 31 || code === DEL) {
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
cleaned += current[i];
|
|
432
|
+
}
|
|
433
|
+
cleaned = cleaned.trim();
|
|
434
|
+
if (cleaned.length > 0) {
|
|
435
|
+
lines.push(cleaned);
|
|
436
|
+
}
|
|
437
|
+
current = "";
|
|
438
|
+
};
|
|
439
|
+
for (let i = 0;i < stripped.length; i += 1) {
|
|
440
|
+
const code = stripped.charCodeAt(i);
|
|
441
|
+
if (code === LF || code === CR) {
|
|
442
|
+
flush();
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
current += stripped[i];
|
|
446
|
+
}
|
|
447
|
+
flush();
|
|
448
|
+
if (lines.length === 0) {
|
|
449
|
+
return "";
|
|
450
|
+
}
|
|
451
|
+
for (let i = lines.length - 1;i >= 0; i -= 1) {
|
|
452
|
+
if (lines[i].charCodeAt(0) === SLASH) {
|
|
453
|
+
return lines[i];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return lines[lines.length - 1];
|
|
457
|
+
}
|
|
458
|
+
function readExplicitBunPath(flags) {
|
|
459
|
+
return asString(flags["bun-path"]) || process.env.TMEX_BUN_PATH;
|
|
460
|
+
}
|
|
461
|
+
function isBunRuntime() {
|
|
462
|
+
const bunVersion = process.versions.bun;
|
|
463
|
+
return typeof bunVersion === "string" && bunVersion.length > 0;
|
|
464
|
+
}
|
|
465
|
+
function hardCandidatePaths() {
|
|
466
|
+
return [
|
|
467
|
+
join(homedir2(), ".bun", "bin", "bun"),
|
|
468
|
+
"/opt/homebrew/bin/bun",
|
|
469
|
+
"/usr/local/bin/bun",
|
|
470
|
+
"/home/linuxbrew/.linuxbrew/bin/bun"
|
|
471
|
+
];
|
|
472
|
+
}
|
|
473
|
+
async function locateBunFromShellWith(shell) {
|
|
474
|
+
const result = await runCommand(shell, ["-lic", "command -v bun"], {
|
|
475
|
+
stdio: "pipe",
|
|
476
|
+
timeoutMs: PROBE_TIMEOUT_MS
|
|
477
|
+
}).catch(() => null);
|
|
322
478
|
if (!result || result.code !== 0) {
|
|
323
479
|
return null;
|
|
324
480
|
}
|
|
325
|
-
const bin = result.stdout
|
|
326
|
-
if (!bin) {
|
|
481
|
+
const bin = sanitizeBunPath(result.stdout);
|
|
482
|
+
if (!bin || !isAbsolute(bin) || !existsSync(bin)) {
|
|
327
483
|
return null;
|
|
328
484
|
}
|
|
329
485
|
return bin;
|
|
330
486
|
}
|
|
331
|
-
async function
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
487
|
+
async function locateBunFromShell() {
|
|
488
|
+
const shells = [];
|
|
489
|
+
const envShell = process.env.SHELL?.trim();
|
|
490
|
+
if (envShell) {
|
|
491
|
+
shells.push(envShell);
|
|
492
|
+
}
|
|
493
|
+
for (const fallbackShell of ["zsh", "bash"]) {
|
|
494
|
+
if (!shells.includes(fallbackShell)) {
|
|
495
|
+
shells.push(fallbackShell);
|
|
496
|
+
}
|
|
339
497
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
498
|
+
for (const shell of shells) {
|
|
499
|
+
const found = await locateBunFromShellWith(shell);
|
|
500
|
+
if (found) {
|
|
501
|
+
return found;
|
|
502
|
+
}
|
|
343
503
|
}
|
|
344
504
|
return null;
|
|
345
505
|
}
|
|
346
|
-
async function
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
506
|
+
async function probeBunCandidates(metaBunPath) {
|
|
507
|
+
const candidates = [];
|
|
508
|
+
const add = (value) => {
|
|
509
|
+
if (!value) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const sanitized = sanitizeBunPath(value);
|
|
513
|
+
if (sanitized && !candidates.includes(sanitized)) {
|
|
514
|
+
candidates.push(sanitized);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
if (isBunRuntime()) {
|
|
518
|
+
add(process.execPath);
|
|
353
519
|
}
|
|
354
|
-
|
|
520
|
+
add(metaBunPath);
|
|
521
|
+
add(await locateBunFromShell());
|
|
522
|
+
candidates.push("bun");
|
|
523
|
+
for (const candidate of hardCandidatePaths()) {
|
|
524
|
+
add(candidate);
|
|
525
|
+
}
|
|
526
|
+
return candidates;
|
|
527
|
+
}
|
|
528
|
+
async function validateBunAt(candidate, minVersion) {
|
|
529
|
+
const versionResult = await runCommand(candidate, ["--version"], {
|
|
530
|
+
stdio: "pipe",
|
|
531
|
+
timeoutMs: PROBE_TIMEOUT_MS
|
|
532
|
+
}).catch(() => null);
|
|
355
533
|
if (!versionResult || versionResult.code !== 0) {
|
|
356
|
-
return {
|
|
357
|
-
ok: false,
|
|
358
|
-
reason: t("bun.versionExecFailed"),
|
|
359
|
-
path: bunPath
|
|
360
|
-
};
|
|
534
|
+
return { ok: false, path: candidate, reason: t("bun.versionExecFailed") };
|
|
361
535
|
}
|
|
362
|
-
const version = versionResult.stdout
|
|
536
|
+
const version = sanitizeBunPath(versionResult.stdout);
|
|
363
537
|
if (compareSemver(version, minVersion) < 0) {
|
|
364
538
|
return {
|
|
365
539
|
ok: false,
|
|
366
|
-
path:
|
|
540
|
+
path: candidate,
|
|
367
541
|
version,
|
|
368
542
|
reason: t("bun.versionTooLow", { version, minVersion })
|
|
369
543
|
};
|
|
370
544
|
}
|
|
371
|
-
return {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
545
|
+
return { ok: true, path: candidate, version };
|
|
546
|
+
}
|
|
547
|
+
async function checkBunVersion(minVersion = MIN_BUN_VERSION, opts = {}) {
|
|
548
|
+
if (opts.explicitPath !== undefined && opts.explicitPath !== "") {
|
|
549
|
+
const explicit = sanitizeBunPath(opts.explicitPath);
|
|
550
|
+
if (!explicit || !isAbsolute(explicit) || !existsSync(explicit)) {
|
|
551
|
+
const reported = explicit || opts.explicitPath;
|
|
552
|
+
return {
|
|
553
|
+
ok: false,
|
|
554
|
+
path: reported,
|
|
555
|
+
reason: t("bun.explicitInvalid", { path: reported })
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return await validateBunAt(explicit, minVersion);
|
|
559
|
+
}
|
|
560
|
+
const candidates = await probeBunCandidates(opts.metaBunPath);
|
|
561
|
+
let firstFailure = null;
|
|
562
|
+
for (const candidate of candidates) {
|
|
563
|
+
if (candidate !== "bun" && isAbsolute(candidate) && !existsSync(candidate)) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const result = await validateBunAt(candidate, minVersion);
|
|
567
|
+
if (result.ok) {
|
|
568
|
+
return result;
|
|
569
|
+
}
|
|
570
|
+
if (!firstFailure) {
|
|
571
|
+
firstFailure = result;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return firstFailure ?? { ok: false, reason: t("bun.notFound") };
|
|
376
575
|
}
|
|
377
576
|
|
|
378
577
|
// src/lib/env-file.ts
|
|
@@ -768,41 +967,6 @@ function serviceHint(serviceName) {
|
|
|
768
967
|
return t("service.hint.none");
|
|
769
968
|
}
|
|
770
969
|
|
|
771
|
-
// src/lib/validate.ts
|
|
772
|
-
function asString(value) {
|
|
773
|
-
if (typeof value === "string")
|
|
774
|
-
return value;
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
function asBoolean(value) {
|
|
778
|
-
if (typeof value === "boolean")
|
|
779
|
-
return value;
|
|
780
|
-
if (typeof value !== "string")
|
|
781
|
-
return;
|
|
782
|
-
const normalized = value.trim().toLowerCase();
|
|
783
|
-
if (normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "y") {
|
|
784
|
-
return true;
|
|
785
|
-
}
|
|
786
|
-
if (normalized === "false" || normalized === "0" || normalized === "no" || normalized === "n") {
|
|
787
|
-
return false;
|
|
788
|
-
}
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
function parsePort(value) {
|
|
792
|
-
const port = Number(value);
|
|
793
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
794
|
-
throw new Error(t("errors.validate.invalidPort", { value }));
|
|
795
|
-
}
|
|
796
|
-
return port;
|
|
797
|
-
}
|
|
798
|
-
function assertNonEmpty(value, fieldName) {
|
|
799
|
-
const trimmed = value.trim();
|
|
800
|
-
if (!trimmed) {
|
|
801
|
-
throw new Error(t("errors.validate.emptyField", { field: fieldName }));
|
|
802
|
-
}
|
|
803
|
-
return trimmed;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
970
|
// src/commands/doctor.ts
|
|
807
971
|
async function checkCommandExists(bin, args, id, okMessage, failMessage) {
|
|
808
972
|
const result = await runCommand(bin, args, { stdio: "pipe" }).catch(() => null);
|
|
@@ -843,7 +1007,12 @@ async function runDoctor(parsed) {
|
|
|
843
1007
|
message: t("doctor.platform.supported", { platform: process.platform })
|
|
844
1008
|
});
|
|
845
1009
|
}
|
|
846
|
-
const
|
|
1010
|
+
const explicitBunPath = readExplicitBunPath(parsed.flags);
|
|
1011
|
+
const meta = await pathExists(installLayout.metaPath) ? await readJsonFile(installLayout.metaPath).catch(() => null) : null;
|
|
1012
|
+
const bun = await checkBunVersion(undefined, {
|
|
1013
|
+
explicitPath: explicitBunPath,
|
|
1014
|
+
metaBunPath: meta?.bunPath
|
|
1015
|
+
});
|
|
847
1016
|
if (bun.ok) {
|
|
848
1017
|
checks.push({
|
|
849
1018
|
id: "bun",
|
|
@@ -946,11 +1115,8 @@ async function runDoctor(parsed) {
|
|
|
946
1115
|
});
|
|
947
1116
|
}
|
|
948
1117
|
let serviceName = asString(parsed.flags["service-name"]) || "tmex";
|
|
949
|
-
if (
|
|
950
|
-
|
|
951
|
-
if (meta?.serviceName) {
|
|
952
|
-
serviceName = meta.serviceName;
|
|
953
|
-
}
|
|
1118
|
+
if (meta?.serviceName) {
|
|
1119
|
+
serviceName = meta.serviceName;
|
|
954
1120
|
}
|
|
955
1121
|
const status = await getServiceStatus(serviceName, installDir);
|
|
956
1122
|
if (status.manager === "none") {
|
|
@@ -1008,12 +1174,13 @@ async function runDoctor(parsed) {
|
|
|
1008
1174
|
|
|
1009
1175
|
// src/commands/init.ts
|
|
1010
1176
|
import { readdir } from "node:fs/promises";
|
|
1011
|
-
import { dirname as
|
|
1177
|
+
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
1012
1178
|
|
|
1013
1179
|
// src/lib/install.ts
|
|
1014
1180
|
import { randomBytes } from "node:crypto";
|
|
1015
1181
|
import { chmod, copyFile, rm as rm2 } from "node:fs/promises";
|
|
1016
|
-
import {
|
|
1182
|
+
import { homedir as homedir4 } from "node:os";
|
|
1183
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join4, resolve as resolve5 } from "node:path";
|
|
1017
1184
|
function generateMasterKey() {
|
|
1018
1185
|
return randomBytes(32).toString("base64");
|
|
1019
1186
|
}
|
|
@@ -1050,6 +1217,20 @@ async function deployRuntimeFiles(packageLayout, installLayout) {
|
|
|
1050
1217
|
await copyDirectory(packageLayout.resourceDrizzlePath, installLayout.drizzleDir);
|
|
1051
1218
|
}
|
|
1052
1219
|
async function writeRunScript(installLayout, bunPath) {
|
|
1220
|
+
for (let i = 0;i < bunPath.length; i += 1) {
|
|
1221
|
+
const code = bunPath.charCodeAt(i);
|
|
1222
|
+
if (code === 34 || code === 96 || code === 36 || code === 92 || code === 10 || code === 13) {
|
|
1223
|
+
throw new Error(t("bun.unsafePath", { path: bunPath }));
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const homeBunBin = join4(homedir4(), ".bun", "bin");
|
|
1227
|
+
const bunDir = isAbsolute2(bunPath) ? dirname4(bunPath) : "";
|
|
1228
|
+
const extraPathDirs = [
|
|
1229
|
+
bunDir,
|
|
1230
|
+
"/opt/homebrew/bin",
|
|
1231
|
+
"/usr/local/bin",
|
|
1232
|
+
"/home/linuxbrew/.linuxbrew/bin"
|
|
1233
|
+
].filter((dir, index, arr) => dir.length > 0 && dir !== homeBunBin && arr.indexOf(dir) === index);
|
|
1053
1234
|
const lines = [
|
|
1054
1235
|
"#!/usr/bin/env bash",
|
|
1055
1236
|
"set -euo pipefail",
|
|
@@ -1065,6 +1246,7 @@ async function writeRunScript(installLayout, bunPath) {
|
|
|
1065
1246
|
'if [[ -n "${HOME:-}" ]] && [[ -d "${HOME}/.bun/bin" ]]; then',
|
|
1066
1247
|
' export PATH="${HOME}/.bun/bin:${PATH:-}"',
|
|
1067
1248
|
"fi",
|
|
1249
|
+
`export PATH="${[...extraPathDirs, "${PATH:-}"].join(":")}"`,
|
|
1068
1250
|
"",
|
|
1069
1251
|
`export TMEX_FE_DIST_DIR="${installLayout.feDir}"`,
|
|
1070
1252
|
`export TMEX_MIGRATIONS_DIR="${installLayout.drizzleDir}"`,
|
|
@@ -1154,9 +1336,9 @@ async function promptConfirm(ctx, message, defaultValue) {
|
|
|
1154
1336
|
}
|
|
1155
1337
|
|
|
1156
1338
|
// src/lib/version.ts
|
|
1157
|
-
import { join as
|
|
1339
|
+
import { join as join5 } from "node:path";
|
|
1158
1340
|
async function readPackageVersion(packageRoot) {
|
|
1159
|
-
const pkg = await readJsonFile(
|
|
1341
|
+
const pkg = await readJsonFile(join5(packageRoot, "package.json"));
|
|
1160
1342
|
return pkg.version || "0.0.0";
|
|
1161
1343
|
}
|
|
1162
1344
|
|
|
@@ -1228,7 +1410,8 @@ async function buildInitConfig(parsed) {
|
|
|
1228
1410
|
}
|
|
1229
1411
|
async function runInit(parsed) {
|
|
1230
1412
|
const config = await buildInitConfig(parsed);
|
|
1231
|
-
const
|
|
1413
|
+
const explicitBunPath = readExplicitBunPath(parsed.flags);
|
|
1414
|
+
const bun = await checkBunVersion(undefined, { explicitPath: explicitBunPath });
|
|
1232
1415
|
if (!bun.ok || !bun.path) {
|
|
1233
1416
|
throw new Error(bun.reason || t("bun.checkFailed"));
|
|
1234
1417
|
}
|
|
@@ -1244,7 +1427,7 @@ async function runInit(parsed) {
|
|
|
1244
1427
|
const packageLayout = await resolvePackageLayout(import.meta.url);
|
|
1245
1428
|
const installLayout = createInstallLayout(config.installDir);
|
|
1246
1429
|
await ensureInstallDir(config.installDir, config.force);
|
|
1247
|
-
await ensureDir(
|
|
1430
|
+
await ensureDir(dirname5(config.databasePath));
|
|
1248
1431
|
await deployRuntimeFiles(packageLayout, installLayout);
|
|
1249
1432
|
const masterKey = generateMasterKey();
|
|
1250
1433
|
const envValues = buildAppEnvValues({
|
|
@@ -1273,7 +1456,8 @@ async function runInit(parsed) {
|
|
|
1273
1456
|
autostart: config.autostart,
|
|
1274
1457
|
installDir: config.installDir,
|
|
1275
1458
|
updatedAt: new Date().toISOString(),
|
|
1276
|
-
cliVersion
|
|
1459
|
+
cliVersion,
|
|
1460
|
+
bunPath: bun.path
|
|
1277
1461
|
};
|
|
1278
1462
|
await writeInstallMeta(installLayout, meta);
|
|
1279
1463
|
console.log(`[tmex] ${t("init.done")}`);
|
|
@@ -1345,7 +1529,7 @@ async function runUninstall(parsed) {
|
|
|
1345
1529
|
// src/commands/upgrade.ts
|
|
1346
1530
|
import { mkdtemp, rm as rm4 } from "node:fs/promises";
|
|
1347
1531
|
import { tmpdir } from "node:os";
|
|
1348
|
-
import { join as
|
|
1532
|
+
import { join as join6 } from "node:path";
|
|
1349
1533
|
async function delegateUpgrade(parsed, targetVersion) {
|
|
1350
1534
|
const args = ["--yes", `tmex-cli@${targetVersion}`, "upgrade", "--apply-current-package"];
|
|
1351
1535
|
const passthrough = ["install-dir", "service-name", "yes", "lang"];
|
|
@@ -1401,13 +1585,17 @@ async function runUpgrade(parsed) {
|
|
|
1401
1585
|
if (!await pathExists(installLayout.metaPath)) {
|
|
1402
1586
|
throw new Error(t("upgrade.missingMeta", { path: installLayout.metaPath }));
|
|
1403
1587
|
}
|
|
1404
|
-
const
|
|
1588
|
+
const meta = await readJsonFile(installLayout.metaPath);
|
|
1589
|
+
const explicitBunPath = readExplicitBunPath(parsed.flags);
|
|
1590
|
+
const bun = await checkBunVersion(undefined, {
|
|
1591
|
+
explicitPath: explicitBunPath,
|
|
1592
|
+
metaBunPath: meta.bunPath
|
|
1593
|
+
});
|
|
1405
1594
|
if (!bun.ok || !bun.path) {
|
|
1406
1595
|
throw new Error(bun.reason || t("bun.checkFailed"));
|
|
1407
1596
|
}
|
|
1408
|
-
const meta = await readJsonFile(installLayout.metaPath);
|
|
1409
1597
|
const packageLayout = await resolvePackageLayout(import.meta.url);
|
|
1410
|
-
const backupDir = await mkdtemp(
|
|
1598
|
+
const backupDir = await mkdtemp(join6(tmpdir(), "tmex-upgrade-"));
|
|
1411
1599
|
try {
|
|
1412
1600
|
await stopService(meta.serviceName, installDir);
|
|
1413
1601
|
await backupInstallArtifacts(installLayout, backupDir);
|
|
@@ -1416,6 +1604,7 @@ async function runUpgrade(parsed) {
|
|
|
1416
1604
|
const cliVersion = await readPackageVersion(packageLayout.packageRoot);
|
|
1417
1605
|
meta.updatedAt = new Date().toISOString();
|
|
1418
1606
|
meta.cliVersion = cliVersion;
|
|
1607
|
+
meta.bunPath = bun.path;
|
|
1419
1608
|
await writeInstallMeta(installLayout, meta);
|
|
1420
1609
|
await installService({
|
|
1421
1610
|
serviceName: meta.serviceName,
|
|
Binary file
|