tmuxclihook 0.5.0 → 0.5.1
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 +8 -3
- package/bin/cli.js +3 -2
- package/package.json +1 -1
- package/src/update.js +270 -32
package/README.md
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
npm install -g tmuxclihook
|
|
10
10
|
tmuxclihook
|
|
11
11
|
|
|
12
|
-
#
|
|
12
|
+
# 按当前安装方式(npm/volta)检查并更新到最新版
|
|
13
13
|
tmuxclihook update
|
|
14
14
|
|
|
15
|
+
# 升级并直接删除不生效的另一份安装(默认会交互确认)
|
|
16
|
+
tmuxclihook update --prune
|
|
17
|
+
|
|
15
18
|
# hooks 健康明细(非交互):每个 CLI 的完整性、判定真源、注册路径、漂移警告
|
|
16
19
|
# 可选参数 claude|codex|opencode 只看一个;exit 1 = 有缺失或漂移
|
|
17
20
|
tmuxclihook check
|
|
@@ -34,9 +37,11 @@ npm start
|
|
|
34
37
|
|
|
35
38
|
hooks 安装、tmux 集成写入的路径由「source」决定:环境变量 `TMUXCLIHOOK_SOURCE` > 持久化配置 `~/.config/tmuxclihook/config.json`(TUI「source 管理」菜单写入)> 包内自带 scripts/(生产默认)> 仓库根 scripts/(`cd installer && npm start` 时的开发回落)。见下方「source 管理」一节。
|
|
36
39
|
|
|
37
|
-
`tmuxclihook update` 会显示当前版本,通过 `npm view tmuxclihook version`
|
|
40
|
+
`tmuxclihook update` 会显示当前版本,通过 `npm view tmuxclihook version` 查询最新版;版本相同则不做修改。需要升级时,它按 PATH 上第一个可执行的 `tmuxclihook` 判断当前生效的安装方式:入口位于 `$VOLTA_HOME/bin/`(默认 `~/.volta/bin/`)或 volta 的 packages 镜像内时,只执行 `volta install tmuxclihook@latest`;其余入口(npm 全局、nvm/fnm 等 node 工具链目录)只执行 `npm install -g tmuxclihook@latest`。不会同时写入两套安装。网络查询或安装失败时命令以非零状态退出。
|
|
41
|
+
|
|
42
|
+
安装完成后会按 PATH 自检实际生效版本。若生效入口不是 volta、自检仍未反映新版,且本机可执行 volta,则补执行一次 `volta install tmuxclihook@latest` 作为兜底并复检;仍不一致时打印警告(不影响退出码)。
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
升级确认成功后,update 会检查另一套安装(volta packages 镜像或当前 npm 全局 prefix)是否还留有副本。发现副本时:TTY 下询问是否删除(默认不删);非交互环境只打印警告;`--prune` 跳过询问直接删除。删除前会校验该副本未被任何 hooks 与 `~/.tmux.conf` 引用——任一检查无法确认就保留并提示先重装 hooks。**生效入口永远不删**,清理失败不影响升级结果。也可以手动收敛:卸载不再使用的那份(`npm uninstall -g tmuxclihook` 或 `volta uninstall tmuxclihook`),再从生效入口重新安装 hooks。
|
|
40
45
|
|
|
41
46
|
## 功能
|
|
42
47
|
|
package/bin/cli.js
CHANGED
|
@@ -491,7 +491,7 @@ async function runCheckCommand(only) {
|
|
|
491
491
|
|
|
492
492
|
const arg = process.argv[2];
|
|
493
493
|
if (arg === 'update') {
|
|
494
|
-
updatePackage().then((ok) => {
|
|
494
|
+
updatePackage({ prune: process.argv.includes('--prune') }).then((ok) => {
|
|
495
495
|
if (!ok) process.exitCode = 1;
|
|
496
496
|
}).catch((err) => {
|
|
497
497
|
console.error(err);
|
|
@@ -511,7 +511,8 @@ if (arg === 'update') {
|
|
|
511
511
|
'用法:\n' +
|
|
512
512
|
' tmuxclihook 打开交互式 TUI 安装器\n' +
|
|
513
513
|
' tmuxclihook check hooks 健康明细(可跟 claude|codex|opencode;exit 1 = 有缺失或漂移)\n' +
|
|
514
|
-
' tmuxclihook update
|
|
514
|
+
' tmuxclihook update 按当前安装方式(npm/volta)检查并更新到最新版本\n' +
|
|
515
|
+
' tmuxclihook update --prune 升级后直接删除不生效的另一份安装(默认交互确认)\n' +
|
|
515
516
|
' tmuxclihook version 显示版本(--version / -v 等价)\n' +
|
|
516
517
|
' tmuxclihook help 显示本帮助(--help / -h 等价)',
|
|
517
518
|
);
|
package/package.json
CHANGED
package/src/update.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFileSync, accessSync, constants as fsConstants, realpathSync } from 'node:fs';
|
|
2
|
-
import { delimiter, dirname, join } from 'node:path';
|
|
1
|
+
import { readFileSync, accessSync, existsSync, constants as fsConstants, realpathSync } from 'node:fs';
|
|
2
|
+
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
3
4
|
import { execa } from 'execa';
|
|
4
5
|
|
|
5
6
|
const PACKAGE_NAME = 'tmuxclihook';
|
|
@@ -9,12 +10,13 @@ export const CURRENT_VERSION = packageJson.version;
|
|
|
9
10
|
|
|
10
11
|
// —— 生效版本自检 ————————————————————————————————————————————————
|
|
11
12
|
//
|
|
12
|
-
// `npm install -g` 在版本管理器(volta
|
|
13
|
-
// volta 的
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
13
|
+
// `npm install -g` 在版本管理器(volta 等)环境下只写 node 工具链目录,
|
|
14
|
+
// 不更新 volta 的 shim 镜像(shim 指向 packages/)。因此升级前先按 PATH
|
|
15
|
+
// 上第一个可执行 tmuxclihook 判断「当前生效入口」属于哪套安装机制,只走
|
|
16
|
+
// 对应那一套(见 detectInstallSource / updatePackage),避免同一版本被写
|
|
17
|
+
// 成两份、hooks 指向其中一份而 PATH 命中另一份造成漂移。
|
|
18
|
+
// 本节自检只是事后确认:按 update 进程的 PATH 解析一份,尽力读取真实版本
|
|
19
|
+
// 并与 registry latest 比对,无法确认或版本不符时打警告。
|
|
18
20
|
// 验证是尽力而为的提示,不改变 update 的返回值(install 本身已成功)。
|
|
19
21
|
|
|
20
22
|
function findOnPath(name, env) {
|
|
@@ -31,6 +33,200 @@ function findOnPath(name, env) {
|
|
|
31
33
|
return null;
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
// realpath 失败时退回字面绝对路径,保证包含判断仍可进行(不抛)。
|
|
37
|
+
function realOrResolved(p) {
|
|
38
|
+
if (!p) return null;
|
|
39
|
+
try {
|
|
40
|
+
return realpathSync(p);
|
|
41
|
+
} catch {
|
|
42
|
+
return resolve(p);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// child 是否位于 parent 目录内(含相等)。用 relative 而非 startsWith,
|
|
47
|
+
// 避免 `$VOLTA_HOME/bin-other` 被误判为 `$VOLTA_HOME/bin` 的子路径。
|
|
48
|
+
function isPathInside(child, parent) {
|
|
49
|
+
const rel = relative(parent, child);
|
|
50
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const VOLTA_SHIM_NAMES = new Set(['volta-shim', 'volta-shim.exe']);
|
|
54
|
+
|
|
55
|
+
// 判断 PATH 上第一个可执行的 tmuxclihook 来自哪套安装机制:
|
|
56
|
+
// 'volta' — $VOLTA_HOME/bin 下的 shim、或 volta packages 镜像
|
|
57
|
+
// 'npm' — npm 全局 / nvm / fnm 等 node 工具链目录
|
|
58
|
+
// 'unknown' — PATH 上找不到可执行入口
|
|
59
|
+
// 跟随「当前生效入口」是 update 的核心语义:只升级用户实际会执行的那一份。
|
|
60
|
+
export function detectInstallSource(env = process.env) {
|
|
61
|
+
try {
|
|
62
|
+
const entry = findOnPath(PACKAGE_NAME, env);
|
|
63
|
+
if (!entry) return 'unknown';
|
|
64
|
+
|
|
65
|
+
const voltaHome = env.VOLTA_HOME || (env.HOME ? join(env.HOME, '.volta') : null);
|
|
66
|
+
const realEntry = realOrResolved(entry);
|
|
67
|
+
|
|
68
|
+
if (voltaHome) {
|
|
69
|
+
// macOS 的 /var → /private/var 等:原始路径与 canonical 路径都要比对
|
|
70
|
+
const homes = [voltaHome];
|
|
71
|
+
const realHome = realOrResolved(voltaHome);
|
|
72
|
+
if (realHome && realHome !== voltaHome) homes.push(realHome);
|
|
73
|
+
|
|
74
|
+
for (const home of homes) {
|
|
75
|
+
const binDir = join(home, 'bin');
|
|
76
|
+
const pkgDir = join(home, 'tools', 'image', 'packages', PACKAGE_NAME);
|
|
77
|
+
for (const candidate of [entry, realEntry]) {
|
|
78
|
+
if (!candidate) continue;
|
|
79
|
+
if (isPathInside(candidate, binDir) || isPathInside(candidate, pkgDir)) return 'volta';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (realEntry && VOLTA_SHIM_NAMES.has(basename(realEntry))) return 'volta';
|
|
85
|
+
return 'npm';
|
|
86
|
+
} catch {
|
|
87
|
+
return 'unknown';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// —— 非生效副本检测与可选收敛 ——————————————————————————————————————
|
|
92
|
+
//
|
|
93
|
+
// update 只升级生效入口,不主动动另一套安装。历史遗留的双份副本会让后续
|
|
94
|
+
// 手动交叉升级「升级到不生效的那份」,因此提供可选收敛:只删同时满足以下
|
|
95
|
+
// 条件的副本——不是生效入口、未被 hooks 引用、未被 ~/.tmux.conf 引用、
|
|
96
|
+
// 位于已知标准位置。TTY 下交互确认(默认 No);--prune 跳过确认;
|
|
97
|
+
// 非交互环境只警告,绝不静默删除。
|
|
98
|
+
|
|
99
|
+
const TOOL_IDS = ['claude', 'codex', 'opencode'];
|
|
100
|
+
|
|
101
|
+
function voltaPackagesRoot(env) {
|
|
102
|
+
const voltaHome = env.VOLTA_HOME || (env.HOME ? join(env.HOME, '.volta') : null);
|
|
103
|
+
return voltaHome ? join(voltaHome, 'tools', 'image', 'packages', PACKAGE_NAME) : null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function readNpmGlobalPrefix(run) {
|
|
107
|
+
try {
|
|
108
|
+
const r = await run('npm', ['prefix', '-g']);
|
|
109
|
+
return (r.stdout || '').trim() || null;
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 返回「非生效 + 位于标准位置」的副本。source 为 unknown 时不做任何
|
|
116
|
+
// 检测——找不到生效入口时删除任何一份都可能让用户彻底没有命令。
|
|
117
|
+
export async function detectStaleInstalls(source, env = process.env, run = execa) {
|
|
118
|
+
if (source === 'unknown') return [];
|
|
119
|
+
|
|
120
|
+
const stale = [];
|
|
121
|
+
if (source === 'npm') {
|
|
122
|
+
const voltaRoot = voltaPackagesRoot(env);
|
|
123
|
+
if (voltaRoot && existsSync(voltaRoot)) stale.push({ kind: 'volta', root: voltaRoot });
|
|
124
|
+
}
|
|
125
|
+
if (source === 'volta') {
|
|
126
|
+
const prefix = await readNpmGlobalPrefix(run);
|
|
127
|
+
const npmRoot = prefix ? join(prefix, 'lib', 'node_modules', PACKAGE_NAME) : null;
|
|
128
|
+
if (npmRoot && existsSync(npmRoot)) stale.push({ kind: 'npm', root: npmRoot });
|
|
129
|
+
}
|
|
130
|
+
return stale;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// hooks 是否引用该副本。走三个 wrapper 的 check --json(与 TUI 同源),
|
|
134
|
+
// 输出里出现目标路径即视为引用。任何异常都按「有引用」处理,宁可不动。
|
|
135
|
+
async function hooksReference(root, env, run) {
|
|
136
|
+
const scriptsDir = fileURLToPath(new URL('../scripts/', import.meta.url));
|
|
137
|
+
for (const tool of TOOL_IDS) {
|
|
138
|
+
const script = join(scriptsDir, `install-${tool}-hooks.sh`);
|
|
139
|
+
if (!existsSync(script)) continue;
|
|
140
|
+
try {
|
|
141
|
+
const r = await run('bash', [script, 'check', '--json'], { env });
|
|
142
|
+
if ((r.stdout || '').includes(root)) return tool;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if ((err.stdout || '').includes(root)) return tool;
|
|
145
|
+
return tool;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function tmuxConfReferences(root, env) {
|
|
152
|
+
if (!env.HOME) return true;
|
|
153
|
+
try {
|
|
154
|
+
return readFileSync(join(env.HOME, '.tmux.conf'), 'utf8').includes(root);
|
|
155
|
+
} catch {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function uninstallStale(item, run) {
|
|
161
|
+
try {
|
|
162
|
+
if (item.kind === 'volta') {
|
|
163
|
+
await run('volta', ['uninstall', PACKAGE_NAME], { stdio: 'inherit' });
|
|
164
|
+
} else {
|
|
165
|
+
await run('npm', ['uninstall', '-g', PACKAGE_NAME], { stdio: 'inherit' });
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
} catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 交互确认的默认实现:读一行,默认 No。测试通过 options.confirm 注入。
|
|
174
|
+
async function defaultConfirm(question) {
|
|
175
|
+
const { createInterface } = await import('node:readline/promises');
|
|
176
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
177
|
+
try {
|
|
178
|
+
const answer = await rl.question(`${question} [y/N] `);
|
|
179
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
180
|
+
} finally {
|
|
181
|
+
rl.close();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 清理非生效副本。返回是否实际删除过任何副本。
|
|
186
|
+
export async function cleanupStale(options) {
|
|
187
|
+
const {
|
|
188
|
+
source, env, run, log, prune, isTTY, confirm, checkHooks, checkTmuxConf,
|
|
189
|
+
} = options;
|
|
190
|
+
|
|
191
|
+
const stale = await options.detectStale(source, env, run);
|
|
192
|
+
if (stale.length === 0) return false;
|
|
193
|
+
|
|
194
|
+
let removed = false;
|
|
195
|
+
for (const item of stale) {
|
|
196
|
+
const label = `${item.kind} install at ${item.root}`;
|
|
197
|
+
|
|
198
|
+
const hookTool = await checkHooks(item.root, env, run);
|
|
199
|
+
if (hookTool) {
|
|
200
|
+
log.error(`Not removing ${label}: still referenced by ${hookTool} hooks. Reinstall hooks from the effective entry first.`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (checkTmuxConf(item.root, env)) {
|
|
204
|
+
log.error(`Not removing ${label}: still referenced by ~/.tmux.conf.`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!prune) {
|
|
209
|
+
if (!isTTY) {
|
|
210
|
+
log.error(`Another install found: ${label}. Run "tmuxclihook update --prune" in a terminal to remove it.`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const yes = await confirm(`Remove the non-effective ${label}?`);
|
|
214
|
+
if (!yes) {
|
|
215
|
+
log.log(`Keeping ${label}.`);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (await uninstallStale(item, run)) {
|
|
221
|
+
log.log(`Removed ${label}.`);
|
|
222
|
+
removed = true;
|
|
223
|
+
} else {
|
|
224
|
+
log.error(`Failed to remove ${label}.`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return removed;
|
|
228
|
+
}
|
|
229
|
+
|
|
34
230
|
// npm 布局下入口是 .../lib/node_modules/<pkg>/bin/cli.js(或其 symlink):
|
|
35
231
|
// realpath 后向上最多 3 层找 name 匹配的 package.json 读版本。版本管理器
|
|
36
232
|
// shim 是独立二进制,向上找不到包 → 返回 null。
|
|
@@ -94,14 +290,11 @@ function warnNotEffective(log, result) {
|
|
|
94
290
|
log.error(`If you use volta, run: volta install ${PACKAGE_NAME}@latest`);
|
|
95
291
|
}
|
|
96
292
|
|
|
97
|
-
// volta
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
// 可能恰好命中 npm -g 刚写的那份而误判 ok(实测 0.3.3 → 0.3.4:verify
|
|
103
|
-
// 判 ok,修复从未触发,用户命令仍是旧版)。npm install -g 是安装语义,
|
|
104
|
-
// volta install 是生效语义,两者都执行;用户无需改变安装习惯。
|
|
293
|
+
// volta 安装路径:当 PATH 上的生效入口来自 volta(shim 或 packages 镜像)
|
|
294
|
+
// 时,升级只能走 `volta install`——`npm install -g` 只写 node 工具链目录,
|
|
295
|
+
// 不会更新 volta 的 packages 镜像。此函数现在是 volta 分支的唯一升级动作,
|
|
296
|
+
// 不再作为 npm 安装后的无条件补丁(双写会制造两份副本并引发漂移)。
|
|
297
|
+
// volta 不可执行时返回 false,由调用方决定如何报告。
|
|
105
298
|
export async function repairWithVolta(run, log, env = process.env) {
|
|
106
299
|
const voltaHome = env.VOLTA_HOME || join(env.HOME || '', '.volta');
|
|
107
300
|
const voltaBin = findOnPath('volta', env) || join(voltaHome, 'bin', 'volta');
|
|
@@ -124,9 +317,21 @@ export async function updatePackage(options = {}) {
|
|
|
124
317
|
const run = options.run || execa;
|
|
125
318
|
const log = options.log || console;
|
|
126
319
|
const currentVersion = options.currentVersion || CURRENT_VERSION;
|
|
320
|
+
const env = options.env || process.env;
|
|
127
321
|
// 显式传 null 跳过自检/修正(测试用);默认真实现
|
|
128
322
|
const verify = options.verify === undefined ? verifyEffectiveVersion : options.verify;
|
|
129
323
|
const repairVolta = options.repairVolta === undefined ? repairWithVolta : options.repairVolta;
|
|
324
|
+
const detectSource = options.detectSource === undefined ? detectInstallSource : options.detectSource;
|
|
325
|
+
// 非生效副本收敛:默认开启,TTY 下交互确认;--prune 跳过确认。
|
|
326
|
+
const cleanup = options.cleanup !== false;
|
|
327
|
+
const prune = options.prune === true;
|
|
328
|
+
const isTTY = options.isTTY === undefined
|
|
329
|
+
? Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
330
|
+
: options.isTTY;
|
|
331
|
+
const confirm = options.confirm || defaultConfirm;
|
|
332
|
+
const detectStale = options.detectStale || detectStaleInstalls;
|
|
333
|
+
const checkHooks = options.checkHooks || hooksReference;
|
|
334
|
+
const checkTmuxConf = options.checkTmuxConf || tmuxConfReferences;
|
|
130
335
|
|
|
131
336
|
log.log(`Current version: v${currentVersion}`);
|
|
132
337
|
|
|
@@ -145,28 +350,61 @@ export async function updatePackage(options = {}) {
|
|
|
145
350
|
}
|
|
146
351
|
|
|
147
352
|
log.log(`Updating to v${latestVersion}...`);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
353
|
+
|
|
354
|
+
// 跟随当前生效入口:PATH 上第一个 tmuxclihook 属于哪套机制,就只升级那套。
|
|
355
|
+
// volta 入口走 volta install(npm -g 写不到 packages 镜像);其余入口走 npm -g。
|
|
356
|
+
// 不再双写——那会留下两份同版本副本,配置指向一份而命令执行另一份即产生漂移。
|
|
357
|
+
const source = typeof detectSource === 'function' ? detectSource(env) : 'unknown';
|
|
358
|
+
|
|
359
|
+
if (source === 'volta') {
|
|
360
|
+
if (repairVolta) {
|
|
361
|
+
const ok = await repairVolta(run, log, env);
|
|
362
|
+
if (!ok) {
|
|
363
|
+
log.error('Failed to update via volta. Please check your volta installation.');
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else {
|
|
368
|
+
try {
|
|
369
|
+
await run('npm', ['install', '-g', `${PACKAGE_NAME}@latest`], { stdio: 'inherit' });
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
152
373
|
}
|
|
153
374
|
|
|
154
375
|
log.log(`Done. Updated to v${latestVersion}`);
|
|
155
376
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
377
|
+
if (!verify) return true;
|
|
378
|
+
|
|
379
|
+
// 事后确认(尽力而为):正常情况下生效入口应已是新版。
|
|
380
|
+
let result = await verify(latestVersion, env);
|
|
381
|
+
|
|
382
|
+
// 兜底:非 volta 来源、自检未过、且本机确有 volta 时补一次并复检。
|
|
383
|
+
// 仅在异常场景触发(例如 npm 全局 bin 不在 PATH),保持「不双写」语义。
|
|
384
|
+
if (result.status !== 'ok' && source !== 'volta' && repairVolta) {
|
|
385
|
+
const repaired = await repairVolta(run, log, env);
|
|
386
|
+
if (repaired) result = await verify(latestVersion, env);
|
|
160
387
|
}
|
|
161
388
|
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
389
|
+
if (result.status === 'ok') {
|
|
390
|
+
log.log(`Verified: tmuxclihook on your PATH now resolves to v${latestVersion}.`);
|
|
391
|
+
} else {
|
|
392
|
+
warnNotEffective(log, result);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// 升级已确认成功后再收敛历史副本;清理失败不影响升级结果,也不改变退出码。
|
|
396
|
+
if (cleanup && result.status === 'ok') {
|
|
397
|
+
try {
|
|
398
|
+
const removed = await cleanupStale({
|
|
399
|
+
source, env, run, log, prune, isTTY, confirm, checkHooks, checkTmuxConf, detectStale,
|
|
400
|
+
});
|
|
401
|
+
// 删过东西就再确认一次生效入口,避免误删导致命令不可用。
|
|
402
|
+
if (removed && verify) {
|
|
403
|
+
const after = await verify(latestVersion, env);
|
|
404
|
+
if (after.status !== 'ok') warnNotEffective(log, after);
|
|
405
|
+
}
|
|
406
|
+
} catch (err) {
|
|
407
|
+
log.error(`Cleanup skipped: ${err}`);
|
|
170
408
|
}
|
|
171
409
|
}
|
|
172
410
|
|