dsh-syn-config 0.8.2 → 0.8.4
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/lib/core/doctor.js +57 -4
- package/lib/core/pack.js +10 -2
- package/package.json +1 -1
package/lib/core/doctor.js
CHANGED
|
@@ -41,6 +41,59 @@ function section(id, title, items, extra = {}) {
|
|
|
41
41
|
return { id, title, status, items, ...extra }
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Semantic fingerprint of a plugin manifest.
|
|
46
|
+
*
|
|
47
|
+
* `link:` specs point at the same checkout from different bases on different
|
|
48
|
+
* machines (`~/code/x` vs `../../../x`), so only the final path segment is
|
|
49
|
+
* meaningful. Everything else compares by value.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} text
|
|
52
|
+
* @returns {{ deps: Record<string, string>, bundles: string[] }}
|
|
53
|
+
*/
|
|
54
|
+
function manifestFingerprint(text) {
|
|
55
|
+
const parsed = JSON.parse(text)
|
|
56
|
+
const normalized = {}
|
|
57
|
+
for (const [name, spec] of Object.entries(parsed.dependencies ?? {})) {
|
|
58
|
+
const value = typeof spec === 'string' && spec.startsWith('link:')
|
|
59
|
+
? `link:${spec.split(/[\\/]/u).filter((segment) => segment !== '').pop() ?? ''}`
|
|
60
|
+
: String(spec)
|
|
61
|
+
normalized[name] = value
|
|
62
|
+
}
|
|
63
|
+
const bundles = [...(parsed.dsh?.profile?.bundles ?? [])].map(String).sort()
|
|
64
|
+
return { deps: normalized, bundles }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Names of manifest entries that really differ between live and repository.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} liveText
|
|
71
|
+
* @param {string} repoText
|
|
72
|
+
* @returns {string[]} human-readable differences, empty when equivalent
|
|
73
|
+
*/
|
|
74
|
+
export function manifestDrift(liveText, repoText) {
|
|
75
|
+
let live
|
|
76
|
+
let repo
|
|
77
|
+
try {
|
|
78
|
+
live = manifestFingerprint(liveText)
|
|
79
|
+
repo = manifestFingerprint(repoText)
|
|
80
|
+
} catch {
|
|
81
|
+
return ['清单无法解析']
|
|
82
|
+
}
|
|
83
|
+
const drift = []
|
|
84
|
+
for (const name of new Set([...Object.keys(live.deps), ...Object.keys(repo.deps)])) {
|
|
85
|
+
if (live.deps[name] !== repo.deps[name]) {
|
|
86
|
+
drift.push(`${name} ${live.deps[name] ?? '(缺)'} ↔ ${repo.deps[name] ?? '(缺)'}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (live.bundles.join(',') !== repo.bundles.join(',')) {
|
|
90
|
+
const onlyLive = live.bundles.filter((x) => !repo.bundles.includes(x))
|
|
91
|
+
const onlyRepo = repo.bundles.filter((x) => !live.bundles.includes(x))
|
|
92
|
+
drift.push(`bundle ${[...onlyLive.map((x) => `+${x}`), ...onlyRepo.map((x) => `-${x}`)].join(' ') || '顺序不同'}`)
|
|
93
|
+
}
|
|
94
|
+
return drift
|
|
95
|
+
}
|
|
96
|
+
|
|
44
97
|
const sleep = (ms) => new Promise((done) => setTimeout(done, ms))
|
|
45
98
|
|
|
46
99
|
/**
|
|
@@ -325,10 +378,10 @@ export async function runDoctor(options) {
|
|
|
325
378
|
const liveManifestText = readTextFile(liveManifest)
|
|
326
379
|
const repoManifestText = readTextFile(repoManifest)
|
|
327
380
|
if (liveManifestText !== null && repoManifestText !== null) {
|
|
328
|
-
const
|
|
329
|
-
items.push(
|
|
330
|
-
? item('ok', 'package.json
|
|
331
|
-
: item('warn',
|
|
381
|
+
const drift = manifestDrift(liveManifestText, repoManifestText)
|
|
382
|
+
items.push(drift.length === 0
|
|
383
|
+
? item('ok', 'package.json(插件清单)与仓库一致(link: 的绝对/相对写法不参与比较)')
|
|
384
|
+
: item('warn', `package.json(插件清单)与仓库不同:${drift.slice(0, 5).join(',')}(装/卸了插件:sync 推送 / update 拉取)`))
|
|
332
385
|
try {
|
|
333
386
|
const parsed = JSON.parse(liveManifestText)
|
|
334
387
|
const dependencies = Object.keys(parsed.dependencies ?? {})
|
package/lib/core/pack.js
CHANGED
|
@@ -30,6 +30,7 @@ import { resolveAgentsHome, resolveDshHome } from './env.js'
|
|
|
30
30
|
import { isFile, readTextFile, writeFileAtomic } from './files.js'
|
|
31
31
|
import { mirrorDir } from './mirror.js'
|
|
32
32
|
import { adoptReasoningEffort } from './patch.js'
|
|
33
|
+
import { loadSecrets, redact } from './secrets.js'
|
|
33
34
|
|
|
34
35
|
/** Files mirrored from the profile directory; everything else stays local. */
|
|
35
36
|
const PROFILE_FILES = ['package.json', 'cordis.patch.yml', 'cordis.yml', 'pnpm-lock.yaml', 'pnpm-workspace.yaml']
|
|
@@ -154,6 +155,12 @@ export function snapshot(options) {
|
|
|
154
155
|
}
|
|
155
156
|
|
|
156
157
|
// --- profile --------------------------------------------------------------
|
|
158
|
+
// A real value that the repository already stores as a placeholder is not a
|
|
159
|
+
// change to publish. Projecting redaction *before* the comparison keeps the
|
|
160
|
+
// plan honest — and means the plaintext never reaches the repository tree.
|
|
161
|
+
const secretEntries = loadSecrets(join(dshHome, 'secrets.env'))
|
|
162
|
+
/** Repository-form text: real secret values replaced by their placeholders. */
|
|
163
|
+
const forRepo = (text) => redact(text, secretEntries).text
|
|
157
164
|
const profileWrites = []
|
|
158
165
|
if (includeProfile && existsSync(profileDir)) {
|
|
159
166
|
const previousPatch = readTextFile(join(repoProfile, 'cordis.patch.yml'))
|
|
@@ -175,9 +182,10 @@ export function snapshot(options) {
|
|
|
175
182
|
// local machine state and must never travel.
|
|
176
183
|
next = adoptReasoningEffort(next, previousPatch)
|
|
177
184
|
}
|
|
185
|
+
const projected = forRepo(next)
|
|
178
186
|
const current = readTextFile(join(repoProfile, name))
|
|
179
|
-
if (current !==
|
|
180
|
-
profileWrites.push({ name, text:
|
|
187
|
+
if (current !== projected) {
|
|
188
|
+
profileWrites.push({ name, text: projected })
|
|
181
189
|
changes.updated.push(`profiles/${profile}/${name}`)
|
|
182
190
|
changes.files.push(`profiles/${profile}/${name}`)
|
|
183
191
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-syn-config",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Cross-platform (macOS + Windows) DeepSeek Harness plugin that syncs DSH config — skills, profile settings, AGENTS.md — with a private dsh-config repository.",
|
|
6
6
|
"type": "module",
|