dsh-paoding 0.3.6 → 0.3.8
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 +7 -6
- package/README_en.md +7 -6
- package/docs/architecture.md +1 -1
- package/docs/architecture_en.md +1 -1
- package/docs/configuration.md +36 -2
- package/docs/configuration_en.md +36 -2
- package/docs/installation.md +7 -6
- package/docs/installation_en.md +7 -6
- package/docs/orchestration.md +20 -1
- package/docs/orchestration_en.md +20 -1
- package/package.json +1 -1
- package/plugins/paoding-config-ui/api-core.mjs +62 -31
- package/plugins/paoding-config-ui/index.mjs +29 -5
- package/plugins/paoding-config-ui/lib/client.js +139 -13
- package/plugins/paoding-config-ui/package.json +1 -1
- package/presets/orchestrator/agent.cordis.yml +11 -2
- package/tools/lib/alloc.mjs +16 -8
- package/tools/lib/compose.mjs +152 -21
- package/tools/lib/config.mjs +37 -7
- package/tools/lib/preset-system.mjs +61 -14
- package/tools/lib/state.mjs +10 -2
- package/tools/lib/upgrade.mjs +73 -26
package/tools/lib/upgrade.mjs
CHANGED
|
@@ -1,27 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 面板一键升级执行器 ——
|
|
3
|
-
*
|
|
2
|
+
* 面板一键升级执行器 —— 按 profile 分流的原地换版本(Web 面板「升级」按钮
|
|
3
|
+
* 专用;CLI 不走这里)。
|
|
4
4
|
*
|
|
5
5
|
* 唯一官方安装通道是 DSH 插件通道:包由 pnpm 装进 profile
|
|
6
|
-
* ($DSH_HOME/profiles/<name>/node_modules/dsh-paoding
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* ($DSH_HOME/profiles/<name>/node_modules/dsh-paoding)。升级子进程按
|
|
7
|
+
* profile 分流:
|
|
8
|
+
* - 常规 profile:spawn `dsh plugin --profile <profile> update dsh-paoding`,
|
|
9
|
+
* 让 dsh 的插件转发器对该包做 pnpm update + bundle reconcile;
|
|
10
|
+
* - desktop profile:dsh CLI 在启动器里硬拒 desktop profile(报
|
|
11
|
+
* `profile "desktop" is managed exclusively by the Electron application`,
|
|
12
|
+
* 只有桌面 Electron App 内部 CLI 豁免),转发器这条路必炸——改为在
|
|
13
|
+
* profile 目录里直跑 `pnpm add dsh-paoding@<目标版本>`:本包已在 profile
|
|
14
|
+
* package.json 的 dsh.profile.bundles 清单里,无需 bundle reconcile;用
|
|
15
|
+
* 精确版本而非 update,避开 pnpm 新鲜度防护 / 镜像 packument 滞后造成的
|
|
16
|
+
* 静默空转。
|
|
17
|
+
*
|
|
18
|
+
* 本模块只负责「拿目标版本 → 探测自身 profile → 跑子进程」这一段编排;
|
|
19
|
+
* profile 名从本文件运行时真实路径反推(registry 安装形态必然位于
|
|
20
|
+
* <...>/profiles/<name>/node_modules/ 之下),开发 link: 直连形态的真实路径
|
|
21
|
+
* 在仓库里、匹配不到,如实报「无法就地升级」。
|
|
11
22
|
*
|
|
12
23
|
* 零依赖纯 Node(>=18):目标版本复用 version.mjs 的 fetchLatestFromRegistry
|
|
13
24
|
* / fetchLatestFromMirror(官方源失败再走 npmmirror 镜像——点「升级」的用户
|
|
14
25
|
* 网络本来就在出状况,多一路快路能实打实救回一次升级),子进程、时钟全部
|
|
15
26
|
* 可注入便于单测。升级后不做磁盘后验
|
|
16
|
-
*
|
|
27
|
+
* ——本进程代码不随升级变化,无从对账;成功与否以升级子进程退出码为准,
|
|
17
28
|
* 新版本在重启 DSH 后生效(preset 由新包的启动自愈钩子自动重生成)。
|
|
18
29
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
30
|
+
* 失败路径全部诚实暴露(不静默):拿不到版本、开发形态、profile 未初始化、
|
|
31
|
+
* 超时、退出码非 0,都原样带回 error + 子进程输出尾部,调用方(面板路由)
|
|
32
|
+
* 转成 500 醒目展示——升级是用户主动点的高级操作,失败被吞掉比失败本身更糟。
|
|
22
33
|
*/
|
|
23
34
|
import os from 'node:os'
|
|
35
|
+
import path from 'node:path'
|
|
24
36
|
import { spawn } from 'node:child_process'
|
|
37
|
+
import { existsSync } from 'node:fs'
|
|
25
38
|
import { realpath } from 'node:fs/promises'
|
|
26
39
|
import { fileURLToPath } from 'node:url'
|
|
27
40
|
import { currentVersion, compareVersions, fetchLatestFromMirror, fetchLatestFromRegistry } from './version.mjs'
|
|
@@ -44,8 +57,10 @@ function tailOutput(text) {
|
|
|
44
57
|
* 缺省子进程执行器(可被 spawnImpl 注入替换,测试用):spawn 任意命令,合并
|
|
45
58
|
* 收集 stdout+stderr,timeoutMs 到点 kill 并按超时收敛。永不 reject,统一收敛
|
|
46
59
|
* 为 { code, output, error? }(error 仅 spawn 本身失败(如 ENOENT)时存在)。
|
|
60
|
+
* timeoutLabel 只用于超时文案标明哪条通道超时(dsh 转发器 / pnpm 直装),
|
|
61
|
+
* 不参与子进程本身。
|
|
47
62
|
*/
|
|
48
|
-
function defaultSpawn(command, args, options, timeoutMs) {
|
|
63
|
+
function defaultSpawn(command, args, options, timeoutMs, timeoutLabel) {
|
|
49
64
|
return new Promise((resolve) => {
|
|
50
65
|
const child = spawn(command, args, options)
|
|
51
66
|
let output = ''
|
|
@@ -67,7 +82,7 @@ function defaultSpawn(command, args, options, timeoutMs) {
|
|
|
67
82
|
child.on('close', (code) => {
|
|
68
83
|
clearTimeout(timer)
|
|
69
84
|
resolve(timedOut
|
|
70
|
-
? { code, output: tailOutput(output), error:
|
|
85
|
+
? { code, output: tailOutput(output), error: `${timeoutLabel} 升级超时(${Math.round(timeoutMs / 1000)}s)` }
|
|
71
86
|
: { code, output: tailOutput(output) })
|
|
72
87
|
})
|
|
73
88
|
})
|
|
@@ -91,6 +106,18 @@ export async function detectProfileName() {
|
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
|
|
109
|
+
/**
|
|
110
|
+
* desktop profile 目录:$DSH_HOME(空串视为未设)否则 ~/.dsh,再拼
|
|
111
|
+
* profiles/desktop,与 dsh 启动器的 profile 落位规则一致。desktop 分支的
|
|
112
|
+
* pnpm 子进程以此为 cwd——pnpm 的操作对象就是这份 profile,脱离目录跑不
|
|
113
|
+
* 到点子上。
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
function desktopProfileDir() {
|
|
117
|
+
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
|
118
|
+
return path.join(home, 'profiles', 'desktop')
|
|
119
|
+
}
|
|
120
|
+
|
|
94
121
|
/**
|
|
95
122
|
* 执行一键升级。
|
|
96
123
|
* @param {{profile?: string, fetchImpl?: Function, spawnImpl?: Function,
|
|
@@ -99,9 +126,10 @@ export async function detectProfileName() {
|
|
|
99
126
|
* 缺省经 detectProfileName() 从运行时真实路径反推。
|
|
100
127
|
* - fetchImpl / spawnImpl / now 可注入(单测用);缺省用 globalThis.fetch、
|
|
101
128
|
* node:child_process 的 spawn、Date.now()。
|
|
102
|
-
* - timeoutMs
|
|
103
|
-
* - spawnImpl 契约:(command, args, options, timeoutMs) →
|
|
104
|
-
* { code, output, error? }(同 defaultSpawn
|
|
129
|
+
* - timeoutMs:升级子进程超时(含 pnpm 下载安装),默认 120s。
|
|
130
|
+
* - spawnImpl 契约:(command, args, options, timeoutMs, timeoutLabel) →
|
|
131
|
+
* Promise resolving { code, output, error? }(同 defaultSpawn 的收敛形状;
|
|
132
|
+
* timeoutLabel 仅缺省实现用于超时文案,注入桩可忽略)。
|
|
105
133
|
* @returns {Promise<object>} 恒不抛错,收敛为:
|
|
106
134
|
* - { ok: true, upToDate: true, current } 本地已不落后 registry
|
|
107
135
|
* - { ok: true, version, profile, output, elapsedMs } 升级成功(退出码 0)
|
|
@@ -149,20 +177,33 @@ async function doRunUpgrade({ profile, fetchImpl, spawnImpl, timeoutMs, now }) {
|
|
|
149
177
|
}
|
|
150
178
|
}
|
|
151
179
|
|
|
152
|
-
// ③ spawn
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
180
|
+
// ③ spawn 升级子进程,按 profile 分流(守卫背景见头注):
|
|
181
|
+
// - desktop:dsh CLI 硬拒 desktop profile(Electron 专属管理),直跑
|
|
182
|
+
// pnpm add 精确版本。cwd 必须是 profile 目录(pnpm 的操作对象就是它);
|
|
183
|
+
// 目录没有 package.json 说明 profile 未初始化,诚实失败、不硬跑。
|
|
184
|
+
// - 其余 profile:照走 dsh 插件转发器(update = pnpm update + bundle
|
|
185
|
+
// reconcile),cwd 用系统临时目录——子进程不该在仓库或 $DSH_HOME 里跑;
|
|
186
|
+
// DSH_HOME 经环境继承,由子进程的 dsh 自行解析。
|
|
187
|
+
// win32 的 dsh / pnpm 都是 .cmd shim,必须借 shell 拉起(Node 直接 spawn
|
|
188
|
+
// .cmd 会 EINVAL,见 CVE-2024-27980 加固),与 bin/dsh-paoding.mjs 同一
|
|
189
|
+
// 方案;参数保持数组形式,转义交给 Node。
|
|
190
|
+
const isDesktop = resolved.toLowerCase() === 'desktop'
|
|
191
|
+
const desktopDir = isDesktop ? desktopProfileDir() : null
|
|
192
|
+
if (isDesktop && !existsSync(path.join(desktopDir, 'package.json'))) {
|
|
193
|
+
return { ok: false, error: `desktop profile 目录不存在或未初始化: ${desktopDir}` }
|
|
194
|
+
}
|
|
157
195
|
const useShell = process.platform === 'win32'
|
|
158
|
-
const command = useShell ? 'dsh.cmd' : 'dsh'
|
|
196
|
+
const command = isDesktop ? (useShell ? 'pnpm.cmd' : 'pnpm') : useShell ? 'dsh.cmd' : 'dsh'
|
|
197
|
+
const args = isDesktop
|
|
198
|
+
? ['add', `dsh-paoding@${targetVersion}`]
|
|
199
|
+
: ['plugin', '--profile', resolved, 'update', 'dsh-paoding']
|
|
159
200
|
const run = typeof spawnImpl === 'function' ? spawnImpl : defaultSpawn
|
|
160
|
-
const proc = await run(command,
|
|
161
|
-
cwd: os.tmpdir(),
|
|
201
|
+
const proc = await run(command, args, {
|
|
202
|
+
cwd: isDesktop ? desktopDir : os.tmpdir(),
|
|
162
203
|
env: process.env,
|
|
163
204
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
164
205
|
shell: useShell,
|
|
165
|
-
}, timeoutMs)
|
|
206
|
+
}, timeoutMs, isDesktop ? 'pnpm add' : 'dsh plugin update')
|
|
166
207
|
const output = tailOutput(proc?.output)
|
|
167
208
|
|
|
168
209
|
// ④ 无磁盘后验(旧 npx 时代读 $DSH_HOME/dsh-paoding 对账的方案已随该目录
|
|
@@ -170,7 +211,13 @@ async function doRunUpgrade({ profile, fetchImpl, spawnImpl, timeoutMs, now }) {
|
|
|
170
211
|
// 新版本重启 DSH 后生效(preset 由新版启动钩子自动重生成)。
|
|
171
212
|
if (proc?.error) return { ok: false, error: proc.error, output, elapsedMs: elapsedMs() }
|
|
172
213
|
if (proc?.code !== 0) {
|
|
173
|
-
|
|
214
|
+
// 报错文案点明实际通道:desktop 是 pnpm 直装,其余是 dsh 插件转发器。
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: isDesktop ? `pnpm add 退出码 ${proc?.code}` : `dsh plugin update 退出码 ${proc?.code}`,
|
|
218
|
+
output,
|
|
219
|
+
elapsedMs: elapsedMs(),
|
|
220
|
+
}
|
|
174
221
|
}
|
|
175
222
|
return { ok: true, version: targetVersion, profile: resolved, output, elapsedMs: elapsedMs() }
|
|
176
223
|
}
|