omnicrawl-cli 0.2.2 → 0.2.5
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 +12 -6
- package/bin/update.mjs +288 -290
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ OmniCrawl 启动器:按 `platform-arch` 选平台分包,默认启动**完整
|
|
|
14
14
|
| `@omnicrawl/cli-win32-ia32` | 仅内核(32 位 Windows 缺运行期轮子) |
|
|
15
15
|
| `@omnicrawl/cli-linux-arm-musl` | 仅内核(armv7 嵌入式目标) |
|
|
16
16
|
|
|
17
|
-
宿主载荷是
|
|
17
|
+
宿主载荷是 Rust 二进制(`host/`):`omnicrawl-host` / `omnicrawl-tui` / `omnicrawl-api` / `omnicrawl-mcp-server` 与内核同架构,`host-meta.json` 里 `hostKind: "rust"`;
|
|
18
18
|
用户机器不需要 Python 或 pip。没有宿主载荷的平台由启动器退回内核直连,并打印这一边界。
|
|
19
19
|
|
|
20
20
|
**平台分包、`os`/`cpu` 与平台依赖只出现在发布产物里**(`dist/npm/`)。仓库内的 `packages/cli*/package.json`
|
|
@@ -60,11 +60,17 @@ node packages/cli/scripts/install-smoke.mjs # 安装冒烟:干净前缀里
|
|
|
60
60
|
npm test -w omnicrawl-cli # e2e:真二进制 + 协议 v1
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
宿主载荷是纯 Rust
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
宿主载荷是纯 Rust 产物,按构建机的目标构建即可(`omnicrawl-tts` 的 ONNX 运行时已改成可选
|
|
64
|
+
feature,所以 musl 静态宿主载荷在 Windows 上交叉编译也能过,本机实测见
|
|
65
|
+
`rust/docs/python-free-build.md` 第 7 节):Windows 侧用 `--target` 指到与内核相同的三元组目录,
|
|
66
|
+
Linux 侧目前仍按 runner 原生 gnu 目标构建(aarch64 musl 宿主在 arm runner 上还没验证过)。
|
|
67
|
+
若目标无法在构建机上构建(或宿主没接线),启动器会退回协议直连内核——内核可以单独交叉编译成
|
|
68
|
+
musl 静态。
|
|
69
|
+
|
|
70
|
+
平台包里的内核二进制声明成 `omnicrawl-kernel`,**不能**叫 `omnicrawl`:启动器自己也声明了
|
|
71
|
+
`omnicrawl`,而平台包在安装树里同样会生成 `node_modules/.bin` 条目,撞名会让平台包抢走
|
|
72
|
+
`.bin/omnicrawl`,本地安装与 `npx` 下 `omnicrawl` 就变成直连内核(只打印 `omnicrawl 0.0.1`)。
|
|
73
|
+
安装冒烟里有断言守住 `.bin/omnicrawl` 必须指向启动器。
|
|
68
74
|
|
|
69
75
|
两个逃生口:`OMNICRAWL_HOST` 直接指定宿主、`OMNICRAWL_BINARY` 直接指定内核(此时启动器是纯透传)。
|
|
70
76
|
|
package/bin/update.mjs
CHANGED
|
@@ -1,290 +1,288 @@
|
|
|
1
|
-
// 启动自动更新:检测 npm registry 上的 `omnicrawl-cli` 最新版,落后时全局升级并重启。
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// -
|
|
18
|
-
// - `
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export const
|
|
29
|
-
/**
|
|
30
|
-
export const
|
|
31
|
-
|
|
32
|
-
export const
|
|
33
|
-
export const
|
|
34
|
-
export const
|
|
35
|
-
export const
|
|
36
|
-
export const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
if (!
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (leftNumber !== null
|
|
73
|
-
|
|
74
|
-
} else if (
|
|
75
|
-
return
|
|
76
|
-
} else if (
|
|
77
|
-
return 1
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
if (!
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (env
|
|
242
|
-
if (
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return code
|
|
290
|
-
}
|
|
1
|
+
// 启动自动更新:检测 npm registry 上的 `omnicrawl-cli` 最新版,落后时全局升级并重启。
|
|
2
|
+
//
|
|
3
|
+
// 版本源是 npm registry 的 `latest`,升级方式是 `npm install -g`。
|
|
4
|
+
//
|
|
5
|
+
// 流程(仅默认工作台路径,非逃生口):
|
|
6
|
+
//
|
|
7
|
+
// 1. 读取 `[update].enabled`(缺省启用),并检查环境变量跳过开关;
|
|
8
|
+
// 2. 读 24h 本地缓存,过期或缺失才请求 registry(3s 超时);
|
|
9
|
+
// 3. 本地版本落后时打印升级说明,执行 `npm install -g omnicrawl-cli@<latest>`;
|
|
10
|
+
// 4. 升级成功后重新拉起启动器(`node <launcher> <原参数>`),把子进程退出码作为本次退出码;
|
|
11
|
+
// 5. 任何失败(离线、npm 不可用、安装报错)都只打印原因与手动升级命令,继续用当前版本启动,
|
|
12
|
+
// 绝不让更新流程阻塞工具可用性。
|
|
13
|
+
//
|
|
14
|
+
// 安全护栏:
|
|
15
|
+
// - 源码检出 / 开发环境(祖先目录里出现 `.git`)直接跳过;
|
|
16
|
+
// - `OMNICRAWL_SKIP_AUTO_UPDATE=1` 可彻底关闭;
|
|
17
|
+
// - 逃生口 `OMNICRAWL_HOST` / `OMNICRAWL_BINARY`(协议测试与开发)不走更新检查;
|
|
18
|
+
// - 执行过一次升级尝试后写入 `OMNICRAWL_AUTO_UPDATE_ATTEMPTED=1`,重启后的子进程继承该标记
|
|
19
|
+
// 不再重复尝试,避免升级失败/版本探测异常时无限重启循环。
|
|
20
|
+
import { spawn } from 'node:child_process'
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
22
|
+
import { homedir } from 'node:os'
|
|
23
|
+
import { dirname, join, resolve } from 'node:path'
|
|
24
|
+
import process from 'node:process'
|
|
25
|
+
|
|
26
|
+
export const PACKAGE_NAME = 'omnicrawl-cli'
|
|
27
|
+
/** 彻底关闭自动更新(用户 / CI / 故障排查用)。 */
|
|
28
|
+
export const SKIP_AUTO_UPDATE_ENV = 'OMNICRAWL_SKIP_AUTO_UPDATE'
|
|
29
|
+
/** 标记「本次升级已经尝试过」,重启后的子进程据此不再重复尝试。 */
|
|
30
|
+
export const AUTO_UPDATE_ATTEMPTED_ENV = 'OMNICRAWL_AUTO_UPDATE_ATTEMPTED'
|
|
31
|
+
export const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`
|
|
32
|
+
export const VERSION_CHECK_TIMEOUT_MS = 3000
|
|
33
|
+
export const VERSION_CHECK_CACHE_TTL_SECONDS = 24 * 60 * 60
|
|
34
|
+
export const VERSION_CHECK_CACHE_FILENAME = 'npm-version-check.json'
|
|
35
|
+
export const USER_CONFIG_DIRNAME = '.OmniCrawl'
|
|
36
|
+
export const CONFIG_FILENAME = 'config.toml'
|
|
37
|
+
|
|
38
|
+
/** 已识别的子命令:这些路径不做启动更新(工作台才是自动更新的入口)。 */
|
|
39
|
+
const DISPATCH_COMMANDS = new Set(['api', 'kernel', 'plugin'])
|
|
40
|
+
/** `npm install -g` 的附加参数(减少噪音与交互)。 */
|
|
41
|
+
const NPM_QUIET_ARGS = ['--no-fund', '--no-audit']
|
|
42
|
+
|
|
43
|
+
const VERSION_PATTERN =
|
|
44
|
+
/^v?(?<release>\d+(?:\.\d+)*)(?:-(?<pre>[0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
|
|
45
|
+
|
|
46
|
+
/** 解析常见 semver(`1.2.3`、`1.2.3-rc.1`、`v1.2.3+build`);非法值返回 null。 */
|
|
47
|
+
export function parseVersion(value) {
|
|
48
|
+
const text = String(value ?? '').trim()
|
|
49
|
+
const match = VERSION_PATTERN.exec(text)
|
|
50
|
+
if (!match) return null
|
|
51
|
+
const release = match.groups.release.split('.').map((part) => Number.parseInt(part, 10))
|
|
52
|
+
if (release.some((part) => !Number.isFinite(part))) return null
|
|
53
|
+
const pre = match.groups.pre
|
|
54
|
+
return { release, pre: pre ? pre.split('.') : null }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** semver 预发布标识比较:数字段按数值,字母段按字典序,数字段低于字母段。 */
|
|
58
|
+
function comparePre(a, b) {
|
|
59
|
+
if (a === null && b === null) return 0
|
|
60
|
+
if (a === null) return 1 // 无预发布高于有预发布
|
|
61
|
+
if (b === null) return -1
|
|
62
|
+
const width = Math.max(a.length, b.length)
|
|
63
|
+
for (let index = 0; index < width; index += 1) {
|
|
64
|
+
const left = a[index]
|
|
65
|
+
const right = b[index]
|
|
66
|
+
if (left === undefined) return -1
|
|
67
|
+
if (right === undefined) return 1
|
|
68
|
+
const leftNumber = /^\d+$/.test(left) ? Number.parseInt(left, 10) : null
|
|
69
|
+
const rightNumber = /^\d+$/.test(right) ? Number.parseInt(right, 10) : null
|
|
70
|
+
if (leftNumber !== null && rightNumber !== null) {
|
|
71
|
+
if (leftNumber !== rightNumber) return leftNumber < rightNumber ? -1 : 1
|
|
72
|
+
} else if (leftNumber !== null) {
|
|
73
|
+
return -1
|
|
74
|
+
} else if (rightNumber !== null) {
|
|
75
|
+
return 1
|
|
76
|
+
} else if (left !== right) {
|
|
77
|
+
return left < right ? -1 : 1
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return 0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** 比较版本;任一侧非法(异常值)都按「不可升级」处理(Python `is_newer_version` 同口径)。 */
|
|
84
|
+
export function isNewerVersion(candidate, current) {
|
|
85
|
+
const candidateKey = parseVersion(candidate)
|
|
86
|
+
const currentKey = parseVersion(current)
|
|
87
|
+
if (!candidateKey || !currentKey) return false
|
|
88
|
+
|
|
89
|
+
const width = Math.max(candidateKey.release.length, currentKey.release.length)
|
|
90
|
+
for (let index = 0; index < width; index += 1) {
|
|
91
|
+
const left = candidateKey.release[index] ?? 0
|
|
92
|
+
const right = currentKey.release[index] ?? 0
|
|
93
|
+
if (left !== right) return left > right
|
|
94
|
+
}
|
|
95
|
+
return comparePre(candidateKey.pre, currentKey.pre) > 0
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** 从 registry 的 `latest` 响应体解析版本号;解析失败抛错(调用方降级)。 */
|
|
99
|
+
export function parseRegistryVersion(payload) {
|
|
100
|
+
const body = typeof payload === 'string' ? JSON.parse(payload) : payload
|
|
101
|
+
const version = String(body?.version ?? '').trim()
|
|
102
|
+
if (!version || !parseVersion(version)) {
|
|
103
|
+
throw new Error('npm registry 的 latest 响应里没有有效版本。')
|
|
104
|
+
}
|
|
105
|
+
return version
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 默认取版本方式:直连 npm registry,带 3s 超时,失败抛错。 */
|
|
109
|
+
export async function fetchRegistryLatest(url = REGISTRY_URL) {
|
|
110
|
+
const response = await fetch(url, {
|
|
111
|
+
headers: { Accept: 'application/json', 'User-Agent': `${PACKAGE_NAME} version-check` },
|
|
112
|
+
signal: AbortSignal.timeout(VERSION_CHECK_TIMEOUT_MS),
|
|
113
|
+
})
|
|
114
|
+
if (!response.ok) throw new Error(`npm registry 返回 ${response.status}`)
|
|
115
|
+
return parseRegistryVersion(await response.json())
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function cachePath(home) {
|
|
119
|
+
return join(home, USER_CONFIG_DIRNAME, VERSION_CHECK_CACHE_FILENAME)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readFreshCache(home, now) {
|
|
123
|
+
try {
|
|
124
|
+
const data = JSON.parse(readFileSync(cachePath(home), 'utf8'))
|
|
125
|
+
const checkedAt = Number(data.checked_at)
|
|
126
|
+
const latest = String(data.latest_version ?? '').trim()
|
|
127
|
+
const age = Math.max(0, now / 1000 - checkedAt)
|
|
128
|
+
if (age < VERSION_CHECK_CACHE_TTL_SECONDS && parseVersion(latest)) return latest
|
|
129
|
+
} catch {
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function writeCache(home, now, latestVersion) {
|
|
136
|
+
const target = cachePath(home)
|
|
137
|
+
const temp = `${target}.${process.pid}.tmp`
|
|
138
|
+
try {
|
|
139
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
140
|
+
writeFileSync(
|
|
141
|
+
temp,
|
|
142
|
+
JSON.stringify({ checked_at: now / 1000, latest_version: latestVersion }),
|
|
143
|
+
'utf8',
|
|
144
|
+
)
|
|
145
|
+
renameSync(temp, target)
|
|
146
|
+
} catch {
|
|
147
|
+
// 缓存失败不应抹掉已经取得的在线检查结果。
|
|
148
|
+
} finally {
|
|
149
|
+
rmSync(temp, { force: true })
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 读取 `[update].enabled`;缺省开启,配置异常也按开启处理(保底默认)。 */
|
|
154
|
+
export function loadUpdateEnabled(home = homedir()) {
|
|
155
|
+
try {
|
|
156
|
+
const text = readFileSync(join(home, USER_CONFIG_DIRNAME, CONFIG_FILENAME), 'utf8')
|
|
157
|
+
const section = text.match(/(?:^|\n)\[update\]\s*\n(?<body>[\s\S]*?)(?:\n\[|$)/)
|
|
158
|
+
if (!section) return true
|
|
159
|
+
const match = section.groups.body.match(/^\s*enabled\s*=\s*(true|false)\s*$/m)
|
|
160
|
+
if (!match) return true
|
|
161
|
+
return match[1] === 'true'
|
|
162
|
+
} catch {
|
|
163
|
+
return true // 更新功能不能因配置问题影响启动
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 判断是否从源码检出 / 开发环境运行(此类环境不自动更新)。
|
|
169
|
+
*
|
|
170
|
+
* npm 全局安装的启动器会落在 `node_modules/@omnicrawl/cli/bin` 这类目录里,祖先里没有 `.git`;
|
|
171
|
+
* 仓库内开发时祖先里有 `.git`。
|
|
172
|
+
*/
|
|
173
|
+
export function isSourceCheckout(launcherPath, maxDepth = 8) {
|
|
174
|
+
let current = dirname(resolve(launcherPath))
|
|
175
|
+
for (let depth = 0; depth < maxDepth; depth += 1) {
|
|
176
|
+
if (existsSync(join(current, '.git'))) return true
|
|
177
|
+
const parent = dirname(current)
|
|
178
|
+
if (parent === current) break
|
|
179
|
+
current = parent
|
|
180
|
+
}
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 是否需要为这组参数做启动更新:只认工作台路径,已知子命令与逃生口都跳过。 */
|
|
185
|
+
export function shouldCheckUpdate(args, env = process.env) {
|
|
186
|
+
if (env.OMNICRAWL_HOST || env.OMNICRAWL_BINARY) return false
|
|
187
|
+
const first = args[0]
|
|
188
|
+
if (first !== undefined && DISPATCH_COMMANDS.has(first)) return false
|
|
189
|
+
return true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildInstallCommand(latestVersion) {
|
|
193
|
+
const command = process.env.npm_execpath
|
|
194
|
+
return command
|
|
195
|
+
? [process.execPath, command, 'install', '-g', ...NPM_QUIET_ARGS, `${PACKAGE_NAME}@${latestVersion}`]
|
|
196
|
+
: ['npm', 'install', '-g', ...NPM_QUIET_ARGS, `${PACKAGE_NAME}@${latestVersion}`]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function runDefault(command) {
|
|
200
|
+
const child = spawn(command[0], command.slice(1), { stdio: 'inherit', shell: false })
|
|
201
|
+
return new Promise((resolveExit) => {
|
|
202
|
+
child.on('error', () => resolveExit(1))
|
|
203
|
+
child.on('exit', (code) => resolveExit(code ?? 1))
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function relaunchDefault(launcherPath, args) {
|
|
208
|
+
const child = spawn(process.execPath, [launcherPath, ...args], { stdio: 'inherit' })
|
|
209
|
+
return new Promise((resolveExit) => {
|
|
210
|
+
child.on('error', () => resolveExit(null))
|
|
211
|
+
child.on('exit', (code) => resolveExit(code ?? 0))
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function manualInstallHint(latestVersion) {
|
|
216
|
+
return `可手动执行升级:npm install -g ${PACKAGE_NAME}@${latestVersion}`
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 启动阶段自动更新入口。
|
|
221
|
+
*
|
|
222
|
+
* @returns `null`:无需更新 / 已跳过 / 更新失败按用户策略继续用当前版本启动;
|
|
223
|
+
* 其他整数:已升级并重启,返回新进程的退出码,调用方应直接退出。
|
|
224
|
+
*/
|
|
225
|
+
export async function runStartupUpdateIfDue({
|
|
226
|
+
argv = [],
|
|
227
|
+
currentVersion,
|
|
228
|
+
launcherPath = new URL(import.meta.url).pathname,
|
|
229
|
+
env = process.env,
|
|
230
|
+
home = homedir(),
|
|
231
|
+
print = (line) => console.error(line),
|
|
232
|
+
now = Date.now,
|
|
233
|
+
latestChecker = null,
|
|
234
|
+
installer = null,
|
|
235
|
+
relauncher = null,
|
|
236
|
+
sourceCheckout = null,
|
|
237
|
+
configEnabled = null,
|
|
238
|
+
} = {}) {
|
|
239
|
+
if (env[SKIP_AUTO_UPDATE_ENV] === '1') return null
|
|
240
|
+
if (env[AUTO_UPDATE_ATTEMPTED_ENV] === '1') return null
|
|
241
|
+
if (env.OMNICRAWL_HOST || env.OMNICRAWL_BINARY) return null
|
|
242
|
+
if ((sourceCheckout ?? isSourceCheckout)(launcherPath)) return null
|
|
243
|
+
if (!(configEnabled ?? loadUpdateEnabled)(home)) return null
|
|
244
|
+
|
|
245
|
+
const installed = String(currentVersion ?? '').trim()
|
|
246
|
+
let latest = null
|
|
247
|
+
try {
|
|
248
|
+
latest = readFreshCache(home, now())
|
|
249
|
+
if (latest === null) {
|
|
250
|
+
latest = latestChecker
|
|
251
|
+
? await latestChecker()
|
|
252
|
+
: await fetchRegistryLatest()
|
|
253
|
+
if (latest) writeCache(home, now(), latest)
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
// 版本检查不能改变应用可用性;离线、代理和缓存损坏均只隐藏更新提示。
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
if (!latest || !isNewerVersion(latest, installed)) return null
|
|
260
|
+
|
|
261
|
+
// 先标记尝试,避免子进程(继承环境变量)在升级后因探测不一致再次升级。
|
|
262
|
+
env[AUTO_UPDATE_ATTEMPTED_ENV] = '1'
|
|
263
|
+
print(`发现新版本 ${latest}(当前 ${installed}),正在自动升级…`)
|
|
264
|
+
|
|
265
|
+
let installedOk = false
|
|
266
|
+
try {
|
|
267
|
+
installedOk = (await (installer
|
|
268
|
+
? installer(latest)
|
|
269
|
+
: runDefault(buildInstallCommand(latest)))) === 0
|
|
270
|
+
} catch {
|
|
271
|
+
installedOk = false
|
|
272
|
+
}
|
|
273
|
+
if (!installedOk) {
|
|
274
|
+
print(`自动升级失败,本次继续使用当前版本 ${installed} 启动。`)
|
|
275
|
+
print(manualInstallHint(latest))
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
print(`升级完成(${installed} → ${latest}),正在重新启动工作台…`)
|
|
280
|
+
const code = await (relauncher
|
|
281
|
+
? relauncher(launcherPath, argv)
|
|
282
|
+
: relaunchDefault(launcherPath, argv))
|
|
283
|
+
if (code === null) {
|
|
284
|
+
print('重新启动工作台失败,请手动执行 omnicrawl 重新进入。')
|
|
285
|
+
return 0
|
|
286
|
+
}
|
|
287
|
+
return code
|
|
288
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnicrawl-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "OmniCrawl 启动器:按平台启动完整宿主(工作台 / api / 插件)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
},
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"optionalDependencies": {
|
|
28
|
-
"@omnicrawl/cli-win32-x64": "0.2.
|
|
29
|
-
"@omnicrawl/cli-win32-ia32": "0.2.
|
|
30
|
-
"@omnicrawl/cli-linux-x64": "0.2.
|
|
31
|
-
"@omnicrawl/cli-linux-arm-musl": "0.2.
|
|
32
|
-
"@omnicrawl/cli-linux-arm64-musl": "0.2.
|
|
28
|
+
"@omnicrawl/cli-win32-x64": "0.2.5",
|
|
29
|
+
"@omnicrawl/cli-win32-ia32": "0.2.5",
|
|
30
|
+
"@omnicrawl/cli-linux-x64": "0.2.5",
|
|
31
|
+
"@omnicrawl/cli-linux-arm-musl": "0.2.5",
|
|
32
|
+
"@omnicrawl/cli-linux-arm64-musl": "0.2.5"
|
|
33
33
|
}
|
|
34
34
|
}
|