dsh-boot-suite 2.13.21 → 2.15.16

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +9 -2
  3. package/lib/client.js +445 -23
  4. package/lib/host-startup.js +196 -3
  5. package/lib/host-wallpaper.js +32 -11
  6. package/lib/splash.js +270 -0
  7. package/package.json +2 -2
  8. package/templates/division-bigfish-codex/app.js +269 -0
  9. package/templates/division-bigfish-codex/assets/egg-bg.png +0 -0
  10. package/templates/division-bigfish-codex/assets/voice-full-plus.wav +0 -0
  11. package/templates/division-bigfish-codex/assets/voice-line1.wav +0 -0
  12. package/templates/division-bigfish-codex/assets/voice-line2.wav +0 -0
  13. package/templates/division-bigfish-codex/assets/voice-line3.wav +0 -0
  14. package/templates/division-bigfish-codex/assets/voice-line4.wav +0 -0
  15. package/templates/division-bigfish-codex/assets/voice-orig1.wav +0 -0
  16. package/templates/division-bigfish-codex/assets/voice-orig3.wav +0 -0
  17. package/templates/division-bigfish-codex/assets/voice-reconnect.wav +0 -0
  18. package/templates/division-bigfish-codex/assets/whale-badge-line.png +0 -0
  19. package/templates/division-bigfish-codex/config.js +24 -0
  20. package/templates/division-bigfish-codex/index.html +1 -0
  21. package/templates/division-bigfish-codex/motion.js +30 -0
  22. package/templates/division-bigfish-codex/preview-easteregg.mp4 +0 -0
  23. package/templates/division-bigfish-codex/preview-normal.mp4 +0 -0
  24. package/templates/division-bigfish-codex/render.html +1 -0
  25. package/templates/division-bigfish-codex/style.css +18 -0
  26. package/templates/division-bigfish-codex/template.json +10 -0
  27. package/templates/division-bigfish-codex/voice-env.js +1 -0
  28. package/templates/division-bigfish-codex/voice.js +10 -0
  29. package/templates/division-bigfish-codex//344/272/244/346/216/245-Codex/347/211/210/346/216/245/345/205/245/346/226/271/346/241/210.md +207 -0
  30. package/templates/division-bigfish-codex//346/217/222/344/273/266/346/216/245/345/205/245/345/245/221/347/272/246.md +177 -0
  31. package/test/smoke-client.mjs +4 -4
  32. package/test/smoke-host.mjs +23 -1
  33. package/test/smoke-package.mjs +92 -1
  34. package/test/smoke-splash.mjs +2 -0
  35. package/test/smoke-wallpaper-client.mjs +1 -1
  36. package/test/smoke-wallpaper-host.mjs +7 -1
  37. package/tools/README.md +1 -1
  38. package/tools/install.ps1 +7 -4
  39. package/tools/make-upgrade-helper.mjs +20 -0
  40. package/tools/stage-local.mjs +158 -0
package/tools/install.ps1 CHANGED
@@ -16,7 +16,7 @@
16
16
  7. 自检并告诉你下一步
17
17
  #>
18
18
  [CmdletBinding()]
19
- param([switch]$CheckOnly, [string[]]$Profiles)
19
+ param([switch]$CheckOnly, [switch]$Force, [string[]]$Profiles)
20
20
 
21
21
  $ErrorActionPreference = 'Stop'
22
22
  function Say($m) { Write-Host $m }
@@ -121,10 +121,13 @@ Say ('要装的 profile:' + ($targets -join '、'))
121
121
  # ── 3. 桌面端必须先完全退出 ─────────────────────────────────────────────────
122
122
  if ($targets -contains 'desktop') {
123
123
  $running = @(Get-Process -Name 'DeepSeek Harness' -ErrorAction SilentlyContinue)
124
- if ($running.Count -gt 0 -and -not $CheckOnly) {
125
- Die ('桌面端还在运行(' + $running.Count + ' 个进程)。请完全退出 DeepSeek Harness Desktop 后再双击安装。')
124
+ if ($running.Count -gt 0 -and -not $CheckOnly -and -not $Force) {
125
+ Die ('桌面端还在运行(' + $running.Count + ' 个进程)。请先完全退出 DeepSeek Harness Desktop 再双击安装;确实要热装可加 -Force(不推荐,装完手动重启)。')
126
126
  }
127
- if ($running.Count -gt 0) { Say '(检查模式)桌面端正在运行 —— 真正安装前需要先完全退出' }
127
+ if ($running.Count -gt 0 -and $Force) {
128
+ Say ('[!] 桌面端还在运行(' + $running.Count + ' 个进程):-Force 热装继续;装完请手动重启 DSH 让新代码生效。')
129
+ }
130
+ if ($running.Count -gt 0 -and $CheckOnly) { Say '(检查模式)桌面端正在运行 —— 真正安装前建议先完全退出' }
128
131
  }
129
132
 
130
133
  # ── 3.5 更新前的紧急回滚 checkpoint(v2.13.3)──────────────────────────────
@@ -33,6 +33,26 @@ const pkgInstall = `%USERPROFILE%\\.dsh\\boot-suite-updates\\${version}\\pkg\\to
33
33
  const helperName = `升级到 ${version}(先完全退出 DSH 再双击).cmd`
34
34
  const helperPath = path.join(desktop, helperName)
35
35
 
36
+ /* 自检 0(2026-11-02 事故):暂存包必须真的在 <updates>\<版本>\pkg\ 下,
37
+ 否则生成的助手双击后必然报 “install.ps1 不存在”。生成前先确认,绝不给用户一个空指路牌。 */
38
+ const stagedPkg = path.join(os.homedir(), '.dsh', 'boot-suite-updates', version, 'pkg')
39
+ const stagedInstall = path.join(stagedPkg, 'tools', 'install.ps1')
40
+ if (!fs.existsSync(stagedInstall)) {
41
+ console.error(`[x] 生成失败:暂存包不完整,缺少 ${stagedInstall}`)
42
+ console.error(`[x] 先把发布包解压到 ${stagedPkg}(注意是 pkg 子目录),再来生成桌面助手。`)
43
+ process.exit(1)
44
+ }
45
+ try {
46
+ const stagedVersion = JSON.parse(fs.readFileSync(path.join(stagedPkg, 'package.json'), 'utf8')).version
47
+ if (stagedVersion !== version) {
48
+ console.error(`[x] 生成失败:暂存包版本是 ${stagedVersion},与要求的 ${version} 不一致`)
49
+ process.exit(1)
50
+ }
51
+ } catch (err) {
52
+ console.error(`[x] 生成失败:读不到暂存包 package.json(${err.message})`)
53
+ process.exit(1)
54
+ }
55
+
36
56
  /* 整段模板:一行一个元素,绝不把版本号单独拼成数组元素 */
37
57
  const lines = [
38
58
  '@echo off',
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 安全暂存本机升级包(2026-11-02 事故后的流程固化)。
4
+ *
5
+ * 事故复盘:
6
+ * v2.15.13 曾把 zip 直接解压到 ~/.dsh/boot-suite-updates/2.15.13/,
7
+ * 文件铺在 2.15.13/ 下而不是 2.15.13/pkg/ 下;桌面助手固定调用
8
+ * ...\2.15.13\pkg\tools\install.ps1,于是报「路径不存在」。
9
+ * 更糟的是当时在 DSH 运行时删掉了旧 pkg,profile 的 link: 指向了
10
+ * 不存在的目录,DSH 直接打不开,只能回滚。
11
+ *
12
+ * 本脚本的顺序(任何一步失败都保留旧版本,绝不让 profile 悬空):
13
+ * 1. 确认目标 pkg 没有被任何 profile 以 link: 正在使用(在跑就拒绝);
14
+ * 2. 解压到 updates 目录内的临时目录,校验 pkg/package.json 版本、
15
+ * pkg/tools/install.ps1、文件数;
16
+ * 3. 旧 pkg 改名备份(不删除),新 pkg 原子 rename 到位;
17
+ * 4. 再校验一遍新 pkg;失败则把旧 pkg 改回来;
18
+ * 5. 复制 zip 到 <版本>/<版本>.zip,最后打印摘要。
19
+ *
20
+ * 用法:
21
+ * node tools/stage-local.mjs 2.15.13
22
+ * node tools/stage-local.mjs 2.15.13 --zip "F:/.../03-发布包/dsh-boot-suite-win.zip"
23
+ */
24
+ import fs from 'node:fs'
25
+ import os from 'node:os'
26
+ import path from 'node:path'
27
+ import { spawnSync } from 'node:child_process'
28
+ import { fileURLToPath } from 'node:url'
29
+
30
+ const here = path.dirname(fileURLToPath(import.meta.url))
31
+ const repoRoot = path.dirname(here)
32
+
33
+ const args = process.argv.slice(2)
34
+ const version = args[0]
35
+ const versionRe = /^\d+\.\d+\.\d+$/
36
+ if (!version || !versionRe.test(version)) {
37
+ console.error('[x] 用法:node tools/stage-local.mjs <版本号> [--zip <发布包 zip 路径>]')
38
+ process.exit(1)
39
+ }
40
+
41
+ const zi = args.indexOf('--zip')
42
+ let zipPath = zi >= 0 && args[zi + 1]
43
+ ? path.resolve(args[zi + 1])
44
+ : path.resolve(repoRoot, '..', '03-发布包', 'dsh-boot-suite-win.zip')
45
+
46
+ if (!fs.existsSync(zipPath)) {
47
+ console.error(`[x] 找不到发布包 zip:${zipPath}`)
48
+ process.exit(1)
49
+ }
50
+
51
+ const updatesRoot = path.join(os.homedir(), '.dsh', 'boot-suite-updates')
52
+ const versionDir = path.join(updatesRoot, version)
53
+ const targetPkg = path.join(versionDir, 'pkg')
54
+ const stamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14)
55
+ const tmpRoot = path.join(updatesRoot, `.stage-${version}-${stamp}`)
56
+ const tmpPkg = path.join(tmpRoot, 'pkg')
57
+
58
+ const fail = (msg) => {
59
+ console.error(`[x] ${msg}`)
60
+ console.error(`[x] 旧的 ${targetPkg} 未被删除(如果已备份,会在下面提示),profile 未改动。`)
61
+ process.exit(1)
62
+ }
63
+
64
+ /* 1. 目标 pkg 正被 profile 以 link: 使用时拒绝替换 */
65
+ const profilesRoot = path.join(os.homedir(), '.dsh', 'profiles')
66
+ const live = []
67
+ if (fs.existsSync(profilesRoot)) {
68
+ for (const name of fs.readdirSync(profilesRoot)) {
69
+ const pj = path.join(profilesRoot, name, 'package.json')
70
+ if (!fs.existsSync(pj)) continue
71
+ try {
72
+ const json = JSON.parse(fs.readFileSync(pj, 'utf8'))
73
+ for (const dep of Object.values(json.dependencies || {})) {
74
+ if (typeof dep !== 'string' || !dep.startsWith('link:')) continue
75
+ const resolved = path.resolve(dep.slice(5).replace(/\//g, path.sep)).toLowerCase()
76
+ if (resolved === targetPkg.toLowerCase() || resolved.startsWith(targetPkg.toLowerCase() + path.sep.toLowerCase())) {
77
+ live.push(`${name}: ${dep}`)
78
+ }
79
+ }
80
+ } catch { /* 单个 profile 读不动就跳过,不影响主流程 */ }
81
+ }
82
+ }
83
+ if (live.length) {
84
+ console.error(`[x] 目标 pkg 正被 profile 使用,拒绝替换:\n ${live.join('\n ')}`)
85
+ console.error('[x] 先让 DSH 用旧版本启动(或回滚)并完全退出,再暂存新版本。')
86
+ process.exit(1)
87
+ }
88
+
89
+ /* 2. 解压到临时目录并校验 */
90
+ fs.rmSync(tmpRoot, { recursive: true, force: true })
91
+ fs.mkdirSync(tmpPkg, { recursive: true })
92
+ console.log(`[1/5] 解压 ${zipPath}`)
93
+ const tar = spawnSync('tar', ['-xf', zipPath, '-C', tmpPkg], { stdio: 'inherit' })
94
+ if (tar.status !== 0) fail(`解压失败(tar exit ${tar.status})`)
95
+
96
+ let srcPkg = tmpPkg
97
+ if (!fs.existsSync(path.join(srcPkg, 'package.json')) && fs.existsSync(path.join(srcPkg, 'pkg', 'package.json'))) {
98
+ srcPkg = path.join(srcPkg, 'pkg') // 兼容 zip 内自带 pkg/ 层的情况
99
+ }
100
+ const countFiles = (dir) => {
101
+ let n = 0
102
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
103
+ const p = path.join(dir, e.name)
104
+ if (e.isDirectory()) n += countFiles(p)
105
+ else n++
106
+ }
107
+ return n
108
+ }
109
+ const pkgJsonPath = path.join(srcPkg, 'package.json')
110
+ const installPath = path.join(srcPkg, 'tools', 'install.ps1')
111
+ if (!fs.existsSync(pkgJsonPath)) fail('解压结果里没有 package.json')
112
+ if (!fs.existsSync(installPath)) fail('解压结果里没有 tools/install.ps1(桌面助手会找不到安装器)')
113
+ const pkgVersion = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')).version
114
+ if (pkgVersion !== version) fail(`包内版本 ${pkgVersion} 与要暂存的 ${version} 不一致`)
115
+ const fileCount = countFiles(srcPkg)
116
+ if (fileCount < 50) fail(`包内文件数异常(${fileCount}),拒绝暂存`)
117
+ console.log(`[2/5] 校验通过:version=${pkgVersion},files=${fileCount}`)
118
+
119
+ /* 3. 旧 pkg 备份 → 新 pkg 原子替换 */
120
+ fs.mkdirSync(versionDir, { recursive: true })
121
+ let backupDir = ''
122
+ if (fs.existsSync(targetPkg)) {
123
+ backupDir = path.join(versionDir, `pkg.prev-${stamp}`)
124
+ fs.renameSync(targetPkg, backupDir)
125
+ console.log(`[3/5] 旧 pkg 已备份到 ${backupDir}`)
126
+ } else {
127
+ console.log('[3/5] 没有旧 pkg,直接放入新 pkg')
128
+ }
129
+ try {
130
+ fs.renameSync(srcPkg, targetPkg)
131
+ } catch (err) {
132
+ if (backupDir) fs.renameSync(backupDir, targetPkg)
133
+ fail(`新 pkg 移入失败(${err.message}),已恢复旧 pkg`)
134
+ }
135
+
136
+ /* 4. 对最终位置再校验一遍,失败就回滚 */
137
+ let ok = false
138
+ try {
139
+ const v = JSON.parse(fs.readFileSync(path.join(targetPkg, 'package.json'), 'utf8')).version
140
+ ok = v === version && fs.existsSync(path.join(targetPkg, 'tools', 'install.ps1'))
141
+ } catch { ok = false }
142
+ if (!ok) {
143
+ fs.rmSync(targetPkg, { recursive: true, force: true })
144
+ if (backupDir) fs.renameSync(backupDir, targetPkg)
145
+ fail('新 pkg 落位后校验失败,已恢复旧 pkg')
146
+ }
147
+ const zipCopy = path.join(versionDir, `${version}.zip`)
148
+ fs.copyFileSync(zipPath, zipCopy)
149
+ console.log(`[4/5] 新 pkg 就位:${targetPkg}`)
150
+
151
+ /* 5. 打印摘要 */
152
+ const size = fs.statSync(zipPath).size
153
+ console.log('[5/5] 完成')
154
+ console.log(` version : ${version}`)
155
+ console.log(` pkg : ${targetPkg}`)
156
+ console.log(` files : ${fileCount}`)
157
+ console.log(` pkg.prev : ${backupDir || '(无)'}`)
158
+ console.log(` zip copy : ${zipCopy} (${size} bytes)`)