@ssamurias/dsh-fish 0.9.0 → 0.9.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/AGENTS.md +35 -4
- package/CHANGELOG.md +46 -1
- package/README.md +11 -6
- package/RELEASE-NOTES-0.9.1.md +296 -0
- package/RELEASE.md +151 -0
- package/lib/ask.js +18 -5
- package/lib/browse.js +1 -0
- package/lib/capture.js +16 -1
- package/lib/client.js +50 -11
- package/lib/comment.js +74 -0
- package/lib/compact.js +45 -1
- package/lib/curiosity.js +6 -2
- package/lib/derive.js +11 -2
- package/lib/desk.js +3 -1
- package/lib/dreamtable.js +13 -2
- package/lib/drive.js +14 -2
- package/lib/freewake.js +4 -1
- package/lib/home.js +87 -22
- package/lib/index.js +1 -1
- package/lib/interest.js +3 -1
- package/lib/look.js +23 -3
- package/lib/observe.js +9 -0
- package/lib/owner.js +5 -2
- package/lib/persona.js +16 -2
- package/lib/proactive.js +67 -12
- package/lib/scheduler.js +99 -27
- package/lib/selfdoc.js +5 -3
- package/lib/selfupdate.js +8 -1
- package/lib/settings.js +5 -0
- package/lib/status.js +31 -3
- package/lib/store.js +14 -2
- package/lib/tenant.js +12 -0
- package/lib/turns.js +5 -1
- package/lib/upgrade.js +4 -1
- package/package.json +2 -1
- package/scripts/fix-legacy-link.mjs +197 -90
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* 把老的 `
|
|
3
|
+
* 把老的 `dsh-fish` 安装收拾到新包名(@ssamurias/dsh-fish)。
|
|
4
4
|
*
|
|
5
5
|
* 为什么需要它:0.8.10 起插件包名从 `dsh-fish` 改成 `@ssamurias/dsh-fish`,
|
|
6
6
|
* 插件自带 cordis.patch.yml 里的条目名也跟着变成完整包名;而老 profile 的
|
|
7
7
|
* node_modules 里只有裸名链接 `dsh-fish` → 条目 import 失败 → **宿主直接启动崩溃**。
|
|
8
8
|
* 升级前跑一遍这个脚本就能避免;已经崩了也能跑(它只改 profile 文件,不需要应用启动)。
|
|
9
9
|
*
|
|
10
|
+
* 0.9.1 修(issue #43):原来只认 `link:` / `file:` 两种依赖写法。可是按更新说明用
|
|
11
|
+
* `dsh plugin add "@ssamurias/dsh-fish@0.9.0"` 升级的人,profile 里留下的是**版本号**,
|
|
12
|
+
* 脚本一句「没有老依赖,跳过」就退出 0 —— 旧键 `dsh-fish` 与旧 bundles 条目原封不动,
|
|
13
|
+
* 下次 pnpm 清理时两条 fish 打架。现在三种写法都收拾:源码目录、tgz、版本号/远端。
|
|
14
|
+
*
|
|
10
15
|
* 用法(先关掉 DeepSeek Harness):
|
|
11
16
|
* node scripts/fix-legacy-link.mjs # 预览
|
|
12
17
|
* node scripts/fix-legacy-link.mjs --write # 真的改
|
|
@@ -15,31 +20,14 @@
|
|
|
15
20
|
import fs from 'node:fs'
|
|
16
21
|
import path from 'node:path'
|
|
17
22
|
import os from 'node:os'
|
|
23
|
+
import { fileURLToPath } from 'node:url'
|
|
18
24
|
|
|
19
|
-
const NEW = '@ssamurias/dsh-fish'
|
|
20
|
-
const LEGACY = 'dsh-fish'
|
|
21
|
-
const args = process.argv.slice(2)
|
|
22
|
-
const WRITE = args.includes('--write')
|
|
23
|
-
const pIdx = args.indexOf('--profile')
|
|
24
|
-
const onlyProfile = pIdx >= 0 ? args[pIdx + 1] : ''
|
|
25
|
-
|
|
26
|
-
const stamp = new Date().toISOString().slice(0, 10).replace(/-/g, '')
|
|
27
|
-
const profilesRoot = path.join(os.homedir(), '.dsh', 'profiles')
|
|
25
|
+
export const NEW = '@ssamurias/dsh-fish'
|
|
26
|
+
export const LEGACY = 'dsh-fish'
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
if (onlyProfile) return [path.resolve(onlyProfile)]
|
|
31
|
-
try {
|
|
32
|
-
return fs
|
|
33
|
-
.readdirSync(profilesRoot, { withFileTypes: true })
|
|
34
|
-
.filter((d) => d.isDirectory())
|
|
35
|
-
.map((d) => path.join(profilesRoot, d.name))
|
|
36
|
-
.filter((p) => fs.existsSync(path.join(p, 'package.json')))
|
|
37
|
-
} catch {
|
|
38
|
-
return []
|
|
39
|
-
}
|
|
40
|
-
}
|
|
28
|
+
const SPEC_FIELDS = ['dependencies', 'devDependencies', 'optionalDependencies']
|
|
41
29
|
|
|
42
|
-
function readJson(p) {
|
|
30
|
+
export function readJson(p) {
|
|
43
31
|
try {
|
|
44
32
|
return JSON.parse(fs.readFileSync(p, 'utf8'))
|
|
45
33
|
} catch {
|
|
@@ -47,101 +35,220 @@ function readJson(p) {
|
|
|
47
35
|
}
|
|
48
36
|
}
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
/** profile 里那条老依赖:字段名 + 原样的 spec。没有就 null。 */
|
|
39
|
+
export function specOf(pkg) {
|
|
40
|
+
for (const field of SPEC_FIELDS) {
|
|
52
41
|
const spec = pkg?.[field]?.[LEGACY]
|
|
53
|
-
if (typeof spec === 'string' &&
|
|
42
|
+
if (typeof spec === 'string' && spec.trim()) return { field, spec: spec.trim() }
|
|
54
43
|
}
|
|
55
44
|
return null
|
|
56
45
|
}
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
/**
|
|
48
|
+
* 这条 spec 指向什么?
|
|
49
|
+
* dir —— `link:` / `file:` 指向目录(源码目录安装),要建 junction
|
|
50
|
+
* tarball —— `file:` 指向 .tgz(0.8.x 的临时包装法),只清账、不建链接
|
|
51
|
+
* spec —— 版本号 / `^1.2.3` / `github:...`,只清账
|
|
52
|
+
*/
|
|
53
|
+
export function specKind(spec) {
|
|
54
|
+
const s = String(spec ?? '').trim()
|
|
55
|
+
if (!/^(link:|file:)/i.test(s)) return 'spec'
|
|
56
|
+
return /\.(tgz|tar\.gz)$/i.test(s) ? 'tarball' : 'dir'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function targetOf(profileDir, spec) {
|
|
60
|
+
const raw = String(spec).replace(/^(link:|file:)/i, '').trim()
|
|
60
61
|
if (!raw) return ''
|
|
61
62
|
if (/^[A-Za-z]:[\\/]/.test(raw) || path.isAbsolute(raw)) return path.resolve(raw)
|
|
62
63
|
return path.resolve(profileDir, raw)
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
function linkName(profileDir, name) {
|
|
66
|
+
export function linkName(profileDir, name) {
|
|
66
67
|
return path.join(profileDir, 'node_modules', ...name.split('/'))
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
export function bundlesOf(pkg) {
|
|
71
|
+
const prof = pkg?.dsh?.profile ?? (pkg?.dsh && Array.isArray(pkg.dsh.bundles) ? pkg.dsh : null)
|
|
72
|
+
return prof && Array.isArray(prof.bundles) ? prof.bundles : null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 把老依赖从依赖表里清掉;新包名已经在装的话顺手对齐 bundles。 */
|
|
76
|
+
export function cleanDepKey(pkg, found, newName) {
|
|
77
|
+
const done = []
|
|
78
|
+
delete pkg[found.field][LEGACY]
|
|
79
|
+
done.push(`删掉 ${found.field}["${LEGACY}"]`)
|
|
80
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }
|
|
81
|
+
if (deps[newName]) {
|
|
82
|
+
const bundles = bundlesOf(pkg)
|
|
83
|
+
if (bundles && !bundles.includes(newName)) {
|
|
84
|
+
bundles.push(newName)
|
|
85
|
+
done.push(`bundles 补上 ${newName}`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return done
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function dropLegacyBundle(pkg, newName) {
|
|
92
|
+
const bundles = bundlesOf(pkg)
|
|
93
|
+
if (!bundles) return []
|
|
94
|
+
let n = 0
|
|
95
|
+
for (let i = bundles.length - 1; i >= 0; i -= 1) {
|
|
96
|
+
if (bundles[i] === LEGACY) {
|
|
97
|
+
bundles.splice(i, 1)
|
|
98
|
+
n += 1
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const done = n ? [`bundles 删掉 ${LEGACY} ×${n}`] : []
|
|
102
|
+
if (newName && !bundles.includes(newName)) {
|
|
103
|
+
bundles.push(newName)
|
|
104
|
+
done.push(`bundles 补上 ${newName}`)
|
|
105
|
+
}
|
|
106
|
+
return done
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function retireOldLink(profileDir, tag, done = []) {
|
|
110
|
+
const oldLink = linkName(profileDir, LEGACY)
|
|
111
|
+
if (!fs.existsSync(oldLink)) return done
|
|
112
|
+
const off = `${oldLink}.disabled-${tag}`
|
|
113
|
+
try {
|
|
114
|
+
fs.renameSync(oldLink, off)
|
|
115
|
+
done.push(`node_modules/${LEGACY} 改名停用`)
|
|
116
|
+
} catch (e) {
|
|
117
|
+
done.push(`node_modules/${LEGACY} 改名失败:${e?.code ?? e?.message ?? e}`)
|
|
118
|
+
}
|
|
119
|
+
return done
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 收拾一个 profile。返回 { changed, done, notes }。
|
|
124
|
+
* `write=false` 时只算要做什么,不落盘。
|
|
125
|
+
*/
|
|
126
|
+
export function migrateOne(profileDir, { write = false, log = (s) => console.log(s), tag = '' } = {}) {
|
|
127
|
+
const pkgPath = path.join(profileDir, 'package.json')
|
|
72
128
|
const pkg = readJson(pkgPath)
|
|
73
|
-
|
|
129
|
+
const notes = []
|
|
130
|
+
const done = []
|
|
131
|
+
if (!pkg) {
|
|
132
|
+
log(`- ${profileDir}: 没有 package.json,跳过`)
|
|
133
|
+
return { changed: false, done, notes }
|
|
134
|
+
}
|
|
74
135
|
const found = specOf(pkg)
|
|
75
136
|
if (!found) {
|
|
76
|
-
|
|
77
|
-
|
|
137
|
+
log(`- ${profileDir}: 没有老的 ${LEGACY} 依赖,跳过`)
|
|
138
|
+
return { changed: false, done, notes }
|
|
78
139
|
}
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
const kind = specKind(found.spec)
|
|
141
|
+
const how = kind === 'dir' ? '源码目录' : kind === 'tarball' ? 'tgz 包' : '版本号/远端'
|
|
142
|
+
log(`\n== ${profileDir}`)
|
|
143
|
+
log(` 老依赖: ${found.field}["${LEGACY}"] = ${found.spec}(${how})`)
|
|
144
|
+
|
|
145
|
+
let newName = NEW
|
|
146
|
+
let target = ''
|
|
147
|
+
if (kind === 'dir') {
|
|
148
|
+
target = targetOf(profileDir, found.spec)
|
|
149
|
+
const srcPkg = readJson(path.join(target, 'package.json'))
|
|
150
|
+
const srcName = String(srcPkg?.name ?? '')
|
|
151
|
+
log(` 源目录: ${target}`)
|
|
152
|
+
log(` 源包名: ${srcName || '(读不到)'}`)
|
|
153
|
+
if (!srcName) notes.push('源目录读不到 package.json,这条按"只清账"处理')
|
|
154
|
+
else if (srcName !== LEGACY && srcName !== NEW) log(` ! 源包名是 ${srcName},脚本按它迁移`)
|
|
155
|
+
if (srcName && srcName !== LEGACY) newName = srcName
|
|
89
156
|
}
|
|
90
|
-
if (srcName !== NEW) console.log(` ! 源包名是 ${srcName},脚本按它迁移`)
|
|
91
|
-
const newName = srcName === LEGACY ? NEW : srcName
|
|
92
157
|
|
|
93
|
-
if (!
|
|
94
|
-
|
|
95
|
-
|
|
158
|
+
if (!write) {
|
|
159
|
+
if (kind === 'dir' && target) {
|
|
160
|
+
log(` [预览] 会:建 node_modules/${newName} → 源目录;把依赖键改成 ${newName};删掉 ${LEGACY} 键与链接`)
|
|
161
|
+
} else {
|
|
162
|
+
log(` [预览] 会:删掉 ${found.field}["${LEGACY}"];bundles 里去掉 ${LEGACY};旧 node_modules/${LEGACY} 改名停用`)
|
|
163
|
+
}
|
|
164
|
+
return { changed: true, done, notes }
|
|
96
165
|
}
|
|
97
166
|
|
|
98
|
-
|
|
99
|
-
const bak = `${pkgPath}.bak-fishrename-${stamp}`
|
|
167
|
+
const bak = `${pkgPath}.bak-fishrename-${tag || 'now'}`
|
|
100
168
|
fs.copyFileSync(pkgPath, bak)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
169
|
+
log(` 备份: ${bak}`)
|
|
170
|
+
|
|
171
|
+
if (kind === 'dir' && target) {
|
|
172
|
+
const newLink = linkName(profileDir, newName)
|
|
173
|
+
fs.mkdirSync(path.dirname(newLink), { recursive: true })
|
|
174
|
+
if (!fs.existsSync(newLink)) {
|
|
175
|
+
try {
|
|
176
|
+
fs.symlinkSync(target, newLink, 'junction')
|
|
177
|
+
done.push(`建链 node_modules/${newName} → ${target}`)
|
|
178
|
+
} catch (e) {
|
|
179
|
+
done.push(`建链失败:${e?.message ?? e}(可手动 New-Item -ItemType Junction)`)
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
done.push(`链接已存在:${newName}`)
|
|
112
183
|
}
|
|
113
|
-
} else {
|
|
114
|
-
console.log(` 链接已存在: ${newLink}`)
|
|
115
184
|
}
|
|
116
185
|
|
|
117
|
-
// 3) 改依赖键
|
|
118
186
|
delete pkg[found.field][LEGACY]
|
|
119
|
-
pkg.dependencies
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
fs.renameSync(oldLink, off)
|
|
135
|
-
console.log(` 老链接停用: ${off}`)
|
|
136
|
-
} catch (e) {
|
|
137
|
-
console.log(` ! 老链接改名失败: ${e.message}`)
|
|
187
|
+
const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) }
|
|
188
|
+
if (deps[newName]) {
|
|
189
|
+
// 新包名已经在依赖里(plugin add 装好了):只把 bundles 对齐,不动依赖表。
|
|
190
|
+
done.push(`删掉 ${found.field}["${LEGACY}"]`)
|
|
191
|
+
done.push(...dropLegacyBundle(pkg, newName))
|
|
192
|
+
} else if (kind === 'dir') {
|
|
193
|
+
// 源码目录:把依赖键改名指向同一条 spec。
|
|
194
|
+
pkg.dependencies = pkg.dependencies ?? {}
|
|
195
|
+
pkg.dependencies[newName] = found.spec
|
|
196
|
+
done.push(`依赖改为 ${found.field}["${newName}"]`)
|
|
197
|
+
const bundles = bundlesOf(pkg)
|
|
198
|
+
if (bundles) {
|
|
199
|
+
for (let i = 0; i < bundles.length; i += 1) if (bundles[i] === LEGACY) bundles[i] = newName
|
|
200
|
+
if (!bundles.includes(newName)) bundles.push(newName)
|
|
201
|
+
done.push(`bundles 对齐 ${newName}`)
|
|
138
202
|
}
|
|
203
|
+
} else {
|
|
204
|
+
done.push(`删掉 ${found.field}["${LEGACY}"]`)
|
|
205
|
+
done.push(...dropLegacyBundle(pkg, ''))
|
|
206
|
+
notes.push('新包名不在依赖里:只清账,装不装得上取决于这次 add 是否成功')
|
|
207
|
+
}
|
|
208
|
+
retireOldLink(profileDir, tag || 'now', done)
|
|
209
|
+
|
|
210
|
+
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8')
|
|
211
|
+
done.push('写入 package.json')
|
|
212
|
+
return { changed: true, done, notes }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function profileDirs(onlyProfile = '') {
|
|
216
|
+
if (onlyProfile) return [path.resolve(onlyProfile)]
|
|
217
|
+
const profilesRoot = path.join(os.homedir(), '.dsh', 'profiles')
|
|
218
|
+
try {
|
|
219
|
+
return fs
|
|
220
|
+
.readdirSync(profilesRoot, { withFileTypes: true })
|
|
221
|
+
.filter((d) => d.isDirectory())
|
|
222
|
+
.map((d) => path.join(profilesRoot, d.name))
|
|
223
|
+
.filter((p) => fs.existsSync(path.join(p, 'package.json')))
|
|
224
|
+
} catch {
|
|
225
|
+
return []
|
|
139
226
|
}
|
|
140
|
-
touched++
|
|
141
227
|
}
|
|
142
228
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
229
|
+
function main() {
|
|
230
|
+
const args = process.argv.slice(2)
|
|
231
|
+
const write = args.includes('--write')
|
|
232
|
+
const pIdx = args.indexOf('--profile')
|
|
233
|
+
const onlyProfile = pIdx >= 0 ? args[pIdx + 1] : ''
|
|
234
|
+
const tag = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14)
|
|
235
|
+
let touched = 0
|
|
236
|
+
for (const dir of profileDirs(onlyProfile)) {
|
|
237
|
+
const r = migrateOne(dir, { write, tag })
|
|
238
|
+
for (const n of r.notes) console.log(` 注: ${n}`)
|
|
239
|
+
for (const d of r.done) console.log(` · ${d}`)
|
|
240
|
+
if (write && r.changed) touched += 1
|
|
241
|
+
}
|
|
242
|
+
if (!write) console.log('\n这是预览。确认无误后加 --write 再跑一次(记得先关应用)。')
|
|
243
|
+
else console.log(`\n改完 ${touched} 个 profile。现在启动 DeepSeek Harness。`)
|
|
147
244
|
}
|
|
245
|
+
|
|
246
|
+
const invokedDirectly = (() => {
|
|
247
|
+
try {
|
|
248
|
+
return Boolean(process.argv[1]) && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
249
|
+
} catch {
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
})()
|
|
253
|
+
|
|
254
|
+
if (invokedDirectly) main()
|