@whyshu/argent 0.1.10 → 0.1.12
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 +6 -3
- package/bin/argent.mjs +26 -9
- package/lib/index.mjs +70 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,9 +47,12 @@ npx -y @whyshu/argent@latest update check --registry https://registry.npmmirror.
|
|
|
47
47
|
1. 读取 npm `@whyshu/argent` 的 `latest` 版本;
|
|
48
48
|
2. 备份 profile 的 `package.json` / patch / lock;
|
|
49
49
|
3. 用目标版本启动器执行 `runtime install`;
|
|
50
|
-
4.
|
|
51
|
-
5. 执行 `
|
|
52
|
-
6.
|
|
50
|
+
4. **原地更新现有 profile 的插件版本**(不重建 profile、不丢 bundles);
|
|
51
|
+
5. 用客户机**真实 dsh** 执行 `dsh plugin --profile argent install`;
|
|
52
|
+
6. 用真实 dsh 执行 `health --json` 验收;
|
|
53
|
+
7. 返回 `restartRequired: true`,由空闲窗口重启宿主。
|
|
54
|
+
|
|
55
|
+
⛔ dsh 版本不等于启动器 exact pin 时,`update run` 会在改任何文件前停止;先升级宿主。
|
|
53
56
|
|
|
54
57
|
失败不会静默:保留更新前 profile 清单备份,并打印分步重试命令。当前不做自动重启。
|
|
55
58
|
|
package/bin/argent.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
DSH_EXACT_VERSION, PROFILE_NAME, PROD_BUNDLES, buildLaunchdPlist, buildProfile, dshHomeOf, runnerScriptPath,
|
|
19
19
|
depsInstalled, health, installDeps, loginWithPhone, pairRunner, probeDshVersion, readAuthToken, repoRoot, runnerConfigPath, sendLoginCode, versionMatches,
|
|
20
20
|
engineRoot, materializeRuntime, checkForUpdate, compareVersions, launcherVersion,
|
|
21
|
+
PUBLISHED_BUNDLE_VERSIONS, resolveDshBin, updateProfilePluginVersions,
|
|
21
22
|
} from '../lib/index.mjs';
|
|
22
23
|
|
|
23
24
|
const argv = process.argv.slice(2);
|
|
@@ -42,6 +43,7 @@ const opts = {
|
|
|
42
43
|
target: val('--target', join(homedir(), '.argent-dsh')),
|
|
43
44
|
skillsTarget: has('--no-skills') ? '' : val('--skills-target', join(dshHomeOf(val('--dsh-home')), 'skills')),
|
|
44
45
|
registry: val('--registry', process.env.ARGENT_NPM_REGISTRY || 'https://registry.npmjs.org'),
|
|
46
|
+
dshBin: val('--dsh-bin', resolveDshBin()),
|
|
45
47
|
extraBundles: (() => { const raw = val('--bundles', '').split(',').map((x) => x.trim()).filter(Boolean); return raw.flatMap((x) => (x === 'prod' ? PROD_BUNDLES : [x])); })(),
|
|
46
48
|
};
|
|
47
49
|
|
|
@@ -131,7 +133,7 @@ async function webCommand() {
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
async function healthCommand() {
|
|
134
|
-
const h = health({ dshHome: opts.dshHome, profile: opts.profile, repo: opts.repo, port: opts.port });
|
|
136
|
+
const h = health({ dshHome: opts.dshHome, profile: opts.profile, repo: opts.repo, port: opts.port, dshBin: opts.dshBin });
|
|
135
137
|
if (JSON_OUT) console.log(JSON.stringify(h, null, 2));
|
|
136
138
|
else {
|
|
137
139
|
console.log(`dsh: ${h.dsh.ok ? h.dsh.version : '(未找到)'} ${h.dsh.matchesExact ? '✅ exact' : `❌ 期望 ${h.expectedDsh}`}`);
|
|
@@ -223,7 +225,7 @@ async function updateCommand(sub) {
|
|
|
223
225
|
}
|
|
224
226
|
const current = launcherVersion()
|
|
225
227
|
let remote
|
|
226
|
-
try { remote = await checkForUpdate({ target: opts.target, registry: opts.registry }) }
|
|
228
|
+
try { remote = await checkForUpdate({ target: opts.target, registry: opts.registry, dshHome: opts.dshHome, profile: opts.profile }) }
|
|
227
229
|
catch (e) { console.error(`❌ 检查更新失败:${e.message}`); process.exit(2) }
|
|
228
230
|
if (!remote.ok) {
|
|
229
231
|
if (JSON_OUT) console.log(JSON.stringify(remote, null, 2)); else console.error(`❌ ${remote.message}`)
|
|
@@ -237,28 +239,40 @@ async function updateCommand(sub) {
|
|
|
237
239
|
console.log(`当前启动器: ${current || '(未知)'}`)
|
|
238
240
|
console.log(`npm latest: ${remote.latest}`)
|
|
239
241
|
console.log(remote.updateAvailable ? '→ 有可用更新' : '→ 已是最新')
|
|
242
|
+
if (remote.pluginUpdates?.length) console.log('插件可更新:' + remote.pluginUpdates.map((x) => `${x.name} ${x.current}→${x.latest}`).join(';'))
|
|
240
243
|
console.log(`runtime: ${remote.runtime || '(未物化)'} @ ${remote.runtimeTarget}`)
|
|
241
244
|
console.log(`来源: ${remote.registry}`)
|
|
242
245
|
}
|
|
243
246
|
return
|
|
244
247
|
}
|
|
245
|
-
if (
|
|
246
|
-
console.log(`已是最新(${current}),无需更新。`)
|
|
248
|
+
if (!remote.updateAvailable && !has('--force')) {
|
|
249
|
+
console.log(`已是最新(${current}${remote.pluginUpdates?.length ? ',插件也无更新' : ''}),无需更新。`)
|
|
247
250
|
return
|
|
248
251
|
}
|
|
249
252
|
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'
|
|
250
253
|
const common = ['--yes', `@whyshu/argent@${targetVersion}`]
|
|
251
254
|
const installArgs = [...common, 'runtime', 'install', '--target', opts.target, '--skills-target', opts.skillsTarget]
|
|
252
|
-
const
|
|
253
|
-
const healthArgs = [...common, 'health', '--dsh-home', opts.dshHome, '--json']
|
|
255
|
+
const healthArgs = [...common, 'health', '--dsh-home', opts.dshHome, '--json', '--dsh-bin', opts.dshBin]
|
|
254
256
|
if (DRY_RUN) {
|
|
255
|
-
print({ ok: true, dryRun: true, current, targetVersion,
|
|
257
|
+
print({ ok: true, dryRun: true, current, targetVersion, dshBin: opts.dshBin, commands: [
|
|
258
|
+
[npx, ...installArgs].join(' '),
|
|
259
|
+
`update profile plugin versions (${Object.keys(PUBLISHED_BUNDLE_VERSIONS).join(', ')})`,
|
|
260
|
+
`${opts.dshBin} plugin --profile ${opts.profile} install # DSH_HOME=${opts.dshHome}`,
|
|
261
|
+
[npx, ...healthArgs].join(' '),
|
|
262
|
+
] })
|
|
256
263
|
return
|
|
257
264
|
}
|
|
258
265
|
if (!has('--yes')) {
|
|
259
266
|
console.error('⛔ 更新会改 runtime / profile 插件,必须显式加 --yes。先看计划:argent update run --dry-run')
|
|
260
267
|
process.exit(2)
|
|
261
268
|
}
|
|
269
|
+
// 宿主版本不匹配时绝不能先改 profile(新插件可能要求新 dsh;改完再 health 失败会留下半升级)。
|
|
270
|
+
const dshProbe = probeDshVersion({ dshBin: opts.dshBin })
|
|
271
|
+
if (!dshProbe.ok || !versionMatches(dshProbe.version)) {
|
|
272
|
+
console.error(`⛔ 宿主 dsh 版本不匹配:当前 ${dshProbe.ok ? dshProbe.version : '(未找到)'},启动器要求 ${DSH_EXACT_VERSION}。`)
|
|
273
|
+
console.error(' 请先按客户机 dsh 升级流程升级宿主,再重跑 update run;本次未改任何文件。')
|
|
274
|
+
process.exit(2)
|
|
275
|
+
}
|
|
262
276
|
const stamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
263
277
|
const profileDir = join(opts.dshHome, 'profiles', opts.profile)
|
|
264
278
|
const backupDir = join(opts.dshHome, 'argent', 'backup', `update-${stamp}`)
|
|
@@ -270,12 +284,15 @@ async function updateCommand(sub) {
|
|
|
270
284
|
const env = { NPM_CONFIG_REGISTRY: opts.registry }
|
|
271
285
|
try {
|
|
272
286
|
runCli(npx, installArgs, { env })
|
|
273
|
-
|
|
287
|
+
// 不调用 setup:setup 会重建 profile,可能丢掉客户现有 bundles/依赖结构(2026-10-22 实测)。
|
|
288
|
+
const prof = updateProfilePluginVersions({ dshHome: opts.dshHome, profile: opts.profile, versions: PUBLISHED_BUNDLE_VERSIONS })
|
|
289
|
+
if (!prof.ok) throw new Error(`更新 profile 插件版本失败:${prof.message}`)
|
|
290
|
+
runCli(opts.dshBin, ['plugin', '--profile', opts.profile, 'install'], { env: { ...env, DSH_HOME: opts.dshHome }, cwd: opts.dshHome })
|
|
274
291
|
runCli(npx, healthArgs, { env })
|
|
275
292
|
} catch (e) {
|
|
276
293
|
console.error(`❌ 更新失败:${e.message}`)
|
|
277
294
|
console.error(` 已保留更新前 profile 清单备份:${backupDir}`)
|
|
278
|
-
console.error(' 可分步重试:npx -y @whyshu/argent@' + targetVersion + ' runtime install
|
|
295
|
+
console.error(' 可分步重试:npx -y @whyshu/argent@' + targetVersion + ' runtime install,再按 dsh plugin install 重试;必要时用备份恢复 package.json/patch。')
|
|
279
296
|
process.exit(2)
|
|
280
297
|
}
|
|
281
298
|
print({ ok: true, from: current, to: targetVersion, runtimeTarget: opts.target, profile: profileDir, backupDir, restartRequired: true, next: '空闲窗口重启 dsh web(service install/launchd 或现有重启脚本)' })
|
package/lib/index.mjs
CHANGED
|
@@ -403,18 +403,83 @@ export function readRuntimeVersion(target = join(homedir(), '.argent-dsh')) {
|
|
|
403
403
|
} catch { return '' }
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/** 读 profile 当前插件版本(用于判断“runtime 已是最新、但插件还是旧版”)。 */
|
|
407
|
+
export function readProfilePluginVersions({ dshHome = dshHomeOf(), profile = PROFILE_NAME } = {}) {
|
|
408
|
+
try {
|
|
409
|
+
const pkg = JSON.parse(readFileSync(join(profileDir(dshHome, profile), 'package.json'), 'utf8'))
|
|
410
|
+
const deps = pkg && typeof pkg.dependencies === 'object' ? pkg.dependencies : {}
|
|
411
|
+
const out = {}
|
|
412
|
+
for (const [name, ver] of Object.entries(deps)) {
|
|
413
|
+
if (!name.startsWith('@whyshu/argent-')) continue
|
|
414
|
+
const v = String(ver || '')
|
|
415
|
+
out[name] = v.startsWith('file:') ? '' : v
|
|
416
|
+
}
|
|
417
|
+
return out
|
|
418
|
+
} catch { return {} }
|
|
419
|
+
}
|
|
420
|
+
|
|
406
421
|
/** 检查更新(只读;不下载、不安装)。 */
|
|
407
422
|
export async function checkForUpdate({
|
|
408
|
-
launcherLib,
|
|
423
|
+
launcherLib, dshHome = dshHomeOf(), profile = PROFILE_NAME,
|
|
424
|
+
target = join(homedir(), '.argent-dsh'), registry = process.env.ARGENT_NPM_REGISTRY || DEFAULT_NPM_REGISTRY,
|
|
409
425
|
fetchImpl = globalThis.fetch,
|
|
410
426
|
} = {}) {
|
|
411
427
|
const current = launcherVersion(launcherLib)
|
|
412
428
|
const remote = await latestLauncherVersion({ registry, fetchImpl })
|
|
413
429
|
const runtime = readRuntimeVersion(target)
|
|
414
|
-
|
|
415
|
-
|
|
430
|
+
const installedPlugins = readProfilePluginVersions({ dshHome, profile })
|
|
431
|
+
if (!remote.ok) return { ok: false, code: remote.code, message: remote.message, current, runtime, installedPlugins, registry }
|
|
432
|
+
const pluginUpdates = []
|
|
433
|
+
for (const [name, want] of Object.entries(PUBLISHED_BUNDLE_VERSIONS)) {
|
|
434
|
+
const have = installedPlugins[name]
|
|
435
|
+
// 本地 file: 装配(开发机)不算可更新客户机;只比较明确版本号。
|
|
436
|
+
if (!have || !want) continue
|
|
437
|
+
if (compareVersions(want, have) > 0) pluginUpdates.push({ name, current: have, latest: want })
|
|
438
|
+
}
|
|
439
|
+
const launcherUpdate = current ? compareVersions(remote.version, current) > 0 : true
|
|
416
440
|
return {
|
|
417
|
-
ok: true, current, latest: remote.version,
|
|
418
|
-
|
|
441
|
+
ok: true, current, latest: remote.version,
|
|
442
|
+
updateAvailable: Boolean(launcherUpdate || pluginUpdates.length),
|
|
443
|
+
launcherUpdate, pluginUpdates, installedPlugins,
|
|
444
|
+
registry: remote.registry, integrity: remote.integrity, tarball: remote.tarball,
|
|
445
|
+
runtime, runtimeTarget: target,
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
/** 解析客户机**真实的** dsh(npx 启动器的依赖里也带一个 dsh,PATH 会被 npx 改写,不能直接用裸 `dsh`)。 */
|
|
451
|
+
export function resolveDshBin({ env = process.env, home = homedir() } = {}) {
|
|
452
|
+
const explicit = String(env.ARGENT_DSH_BIN || env.DSH_BIN || '').trim()
|
|
453
|
+
if (explicit && existsSync(explicit)) return explicit
|
|
454
|
+
const pathEntries = String(env.PATH || '').split(':').filter(Boolean)
|
|
455
|
+
.filter((p) => !p.includes('_npx') && !p.replace(/\/+$/, '').endsWith('node_modules/.bin'))
|
|
456
|
+
for (const dir of pathEntries) {
|
|
457
|
+
const p = join(dir, process.platform === 'win32' ? 'dsh.cmd' : 'dsh')
|
|
458
|
+
if (existsSync(p)) return p
|
|
459
|
+
}
|
|
460
|
+
for (const p of [
|
|
461
|
+
join(home, '.local', 'node', 'bin', 'dsh'),
|
|
462
|
+
'/opt/homebrew/bin/dsh',
|
|
463
|
+
'/usr/local/bin/dsh',
|
|
464
|
+
]) if (existsSync(p)) return p
|
|
465
|
+
return 'dsh'
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/** 更新 profile 里已存在的插件版本;不重建 profile、不丢 bundles/依赖结构。 */
|
|
469
|
+
export function updateProfilePluginVersions({
|
|
470
|
+
dshHome = dshHomeOf(), profile = PROFILE_NAME, versions = PUBLISHED_BUNDLE_VERSIONS,
|
|
471
|
+
} = {}) {
|
|
472
|
+
const file = join(profileDir(dshHome, profile), 'package.json')
|
|
473
|
+
let pkg = null
|
|
474
|
+
try { pkg = JSON.parse(readFileSync(file, 'utf8')) } catch { return { ok: false, code: 'profile_missing', message: `profile 不存在:${file}` } }
|
|
475
|
+
if (!pkg.dependencies || typeof pkg.dependencies !== 'object') return { ok: false, code: 'profile_shape', message: 'profile package.json 缺 dependencies' }
|
|
476
|
+
const changed = []
|
|
477
|
+
for (const [name, want] of Object.entries(versions || {})) {
|
|
478
|
+
const have = String(pkg.dependencies[name] || '')
|
|
479
|
+
if (!have || have.startsWith('file:') || have === want) continue
|
|
480
|
+
pkg.dependencies[name] = want
|
|
481
|
+
changed.push({ name, from: have, to: want })
|
|
419
482
|
}
|
|
483
|
+
if (changed.length) writeFileSync(file, JSON.stringify(pkg, null, 2) + '\n')
|
|
484
|
+
return { ok: true, file, changed }
|
|
420
485
|
}
|