dsh-config-manager 0.1.66 → 0.1.68
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 +13 -0
- package/README.zh-CN.md +13 -0
- package/icon.svg +11 -0
- package/lib/adapters/file-collection.d.ts +31 -1
- package/lib/adapters/file-collection.js +82 -14
- package/lib/adapters/plugins.js +6 -1
- package/lib/adapters/test-helpers.d.ts +7 -0
- package/lib/adapters/test-helpers.js +10 -0
- package/lib/adapters/units.d.ts +12 -2
- package/lib/adapters/units.js +13 -6
- package/lib/client.d.ts +2 -0
- package/lib/client.js +438 -440
- package/lib/core/analyzer.d.ts +9 -0
- package/lib/core/analyzer.js +83 -5
- package/lib/core/backup-verify.js +1 -3
- package/lib/core/crash-report.js +1 -4
- package/lib/core/journal.js +1 -10
- package/lib/core/migration-history.js +3 -10
- package/lib/core/plugin-cli.d.ts +29 -7
- package/lib/core/plugin-cli.js +49 -3
- package/lib/core/session-meta.d.ts +6 -1
- package/lib/core/session-meta.js +49 -1
- package/lib/core/types.d.ts +36 -0
- package/lib/index.d.ts +26 -1
- package/lib/index.js +85 -33
- package/lib/market/git-file-writer.js +1 -4
- package/lib/market/types.d.ts +0 -5
- package/lib/profiles/dsh-profile-io.d.ts +8 -0
- package/lib/profiles/dsh-profile-io.js +31 -0
- package/lib/profiles/dsh-profile-launcher.d.ts +2 -0
- package/lib/profiles/dsh-profile-launcher.js +26 -22
- package/lib/profiles/dsh-profile-manager.d.ts +3 -0
- package/lib/profiles/dsh-profile-manager.js +13 -14
- package/lib/profiles/dsh-profile-runtime.js +4 -14
- package/lib/profiles/dsh-profile-shared.d.ts +17 -1
- package/lib/profiles/dsh-profile-shared.js +17 -1
- package/lib/schema/config.d.ts +1 -11
- package/lib/schema/config.js +0 -10
- package/lib/security/integrity.d.ts +0 -1
- package/lib/security/integrity.js +0 -1
- package/lib/security/redaction.d.ts +2 -0
- package/lib/security/redaction.js +15 -0
- package/lib/sync/git/git-transport.js +1 -4
- package/lib/ui/dsh-profiles-view.d.ts +11 -2
- package/lib/ui/dsh-profiles-view.js +12 -4
- package/lib/ui/export-flow.d.ts +0 -7
- package/lib/ui/runs-view.d.ts +0 -5
- package/lib/ui/runs-view.js +0 -7
- package/lib/ui/types.d.ts +1 -14
- package/lib/ui/types.js +0 -4
- package/lib/utils/atomic-write.js +1 -3
- package/lib/utils/desktop-carrier.d.ts +54 -0
- package/lib/utils/desktop-carrier.js +60 -0
- package/lib/utils/env-lock.d.ts +0 -4
- package/lib/utils/env-lock.js +11 -16
- package/lib/utils/git-quote.d.ts +8 -0
- package/lib/utils/git-quote.js +11 -0
- package/lib/utils/guards.d.ts +12 -0
- package/lib/utils/guards.js +17 -0
- package/lib/utils/hashing.d.ts +0 -1
- package/lib/utils/hashing.js +0 -5
- package/lib/utils/logger.d.ts +0 -3
- package/lib/utils/logger.js +1 -6
- package/lib/utils/paths.d.ts +0 -4
- package/lib/utils/paths.js +0 -10
- package/lib/utils/zip.d.ts +15 -0
- package/lib/utils/zip.js +69 -14
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +7 -2
- package/src/adapters/file-collection.test.ts +97 -1
- package/src/adapters/file-collection.ts +100 -16
- package/src/adapters/plugins.ts +6 -1
- package/src/adapters/test-helpers.ts +10 -0
- package/src/adapters/units.ts +22 -7
- package/src/client/common/ErrorBanner.tsx +1 -5
- package/src/client/common/Icon.tsx +0 -4
- package/src/client/common/morph-icons.ts +0 -2
- package/src/client/config-manager.module.css +7 -343
- package/src/client/history/history-locales.ts +0 -20
- package/src/client/locales.ts +4 -0
- package/src/client/profiles/ProfilesPanel.tsx +21 -11
- package/src/client/recovery/recovery-view.ts +0 -8
- package/src/client/run-store.ts +1 -8
- package/src/core/analyzer-cache.test.ts +154 -0
- package/src/core/analyzer.ts +85 -5
- package/src/core/backup-verify.ts +1 -4
- package/src/core/crash-report.ts +1 -5
- package/src/core/journal.ts +1 -13
- package/src/core/migration-history.ts +3 -13
- package/src/core/plugin-cli.test.ts +53 -1
- package/src/core/plugin-cli.ts +60 -3
- package/src/core/session-meta.test.ts +88 -2
- package/src/core/session-meta.ts +48 -1
- package/src/core/types.ts +38 -0
- package/src/index.facade.test.ts +32 -1
- package/src/index.ts +82 -27
- package/src/market/git-file-writer.ts +1 -5
- package/src/market/types.ts +0 -6
- package/src/profiles/dsh-profile-io.ts +31 -0
- package/src/profiles/dsh-profile-launcher.test.ts +19 -0
- package/src/profiles/dsh-profile-launcher.ts +28 -26
- package/src/profiles/dsh-profile-manager.test.ts +33 -0
- package/src/profiles/dsh-profile-manager.ts +11 -15
- package/src/profiles/dsh-profile-runtime.ts +4 -14
- package/src/profiles/dsh-profile-shared.ts +21 -1
- package/src/schema/config.ts +1 -15
- package/src/security/integrity.ts +0 -1
- package/src/security/redaction.ts +19 -0
- package/src/sync/git/git-transport.ts +1 -5
- package/src/ui/dsh-profiles-view.test.ts +15 -4
- package/src/ui/dsh-profiles-view.ts +18 -4
- package/src/ui/export-flow.ts +0 -8
- package/src/ui/runs-view.ts +0 -7
- package/src/ui/types.ts +2 -22
- package/src/utils/atomic-write.ts +1 -4
- package/src/utils/desktop-carrier.test.ts +55 -0
- package/src/utils/desktop-carrier.ts +82 -0
- package/src/utils/env-lock.test.ts +32 -4
- package/src/utils/env-lock.ts +11 -18
- package/src/utils/git-quote.ts +10 -0
- package/src/utils/guards.ts +17 -0
- package/src/utils/hashing.ts +0 -6
- package/src/utils/logger.ts +1 -7
- package/src/utils/paths.ts +0 -12
- package/src/utils/zip.ts +65 -15
package/README.md
CHANGED
|
@@ -197,6 +197,19 @@ dsh plugin --profile web add dsh-config-manager@latest
|
|
|
197
197
|
> Write-Output "Already present, nothing to do"
|
|
198
198
|
> }
|
|
199
199
|
> ```
|
|
200
|
+
>
|
|
201
|
+
> 🐛 **Install rejected for a version you never installed?** Symptom: pnpm logs `+ dsh-config-manager ^0.1.66`, yet DSH reports `Plugin dsh-config-manager@0.1.44 is incompatible with dsh …` and rolls the install back.
|
|
202
|
+
>
|
|
203
|
+
> The cause is neither pnpm nor version resolution. After installing, DSH runs a compatibility check that reads this plugin's `cordis.patch.yml` mount row (`name: 'dsh-config-manager'`) and **resolves that package again** — and Node's resolution chain honours `NODE_PATH`. If your global npm root (`npm root -g`) still holds an **old copy of the same package** (for example a `npm i -g dsh-config-manager` of 0.1.44 from earlier), the check compares **that** copy's `peerDependencies` and reports a version pnpm never installed.
|
|
204
|
+
>
|
|
205
|
+
> Confirm and fix:
|
|
206
|
+
>
|
|
207
|
+
> ```powershell
|
|
208
|
+
> npm ls -g dsh-config-manager # output means a global copy exists
|
|
209
|
+
> npm i -g dsh-config-manager@latest # upgrade it (or npm uninstall -g dsh-config-manager to remove it)
|
|
210
|
+
> ```
|
|
211
|
+
>
|
|
212
|
+
> Then retry the install. As long as that global copy exists and is incompatible with your DSH, every profile (`web` / `desktop` / custom) is rejected the same way.
|
|
200
213
|
|
|
201
214
|
---
|
|
202
215
|
|
package/README.zh-CN.md
CHANGED
|
@@ -200,6 +200,19 @@ dsh plugin --profile web add dsh-config-manager@latest
|
|
|
200
200
|
> Write-Output "已存在,无需修改"
|
|
201
201
|
> }
|
|
202
202
|
> ```
|
|
203
|
+
>
|
|
204
|
+
> 🐛 **安装被拒,但报错的版本你从没装过?** 症状:日志里 pnpm 明明写了 `+ dsh-config-manager ^0.1.66`,DSH 却报 `Plugin dsh-config-manager@0.1.44 is incompatible with dsh …` 并把安装回滚了。
|
|
205
|
+
>
|
|
206
|
+
> 原因既不在 pnpm 也不在版本解析:DSH 安装后还会做一次兼容校验,其中一步会读本插件 `cordis.patch.yml` 里的挂载行(`name: 'dsh-config-manager'`)并**再次解析这个包**,而 Node 的解析链会经过 `NODE_PATH`。若你的全局 npm 目录(`npm root -g`)里留着一份**同名旧版**(例如以前 `npm i -g dsh-config-manager` 装的 0.1.44),校验就会拿**那一份**的 `peerDependencies` 去比对 —— 于是报出一个 pnpm 根本没装的版本号。
|
|
207
|
+
>
|
|
208
|
+
> 确认与修复:
|
|
209
|
+
>
|
|
210
|
+
> ```powershell
|
|
211
|
+
> npm ls -g dsh-config-manager # 有输出 = 全局确实存在一份
|
|
212
|
+
> npm i -g dsh-config-manager@latest # 升到兼容版(或 npm uninstall -g dsh-config-manager 直接删掉)
|
|
213
|
+
> ```
|
|
214
|
+
>
|
|
215
|
+
> 之后重试安装即可。只要全局那份还在且与当前 DSH 不兼容,装哪个 profile(`web` / `desktop` / 自建)都会被同样拒绝。
|
|
203
216
|
|
|
204
217
|
---
|
|
205
218
|
|
package/icon.svg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="DSH Config Manager">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="tile" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#5B7CFA"/>
|
|
5
|
+
<stop offset="1" stop-color="#3B3AD1"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="512" height="512" rx="112" fill="url(#tile)"/>
|
|
9
|
+
<path fill="#FFFFFF" d="M196 142 254 226H224V364H168V226H138Z"/>
|
|
10
|
+
<path fill="#FFFFFF" d="M316 370 258 286H288V148H344V286H374Z"/>
|
|
11
|
+
</svg>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MsgFunc } from '../core/messages.ts';
|
|
2
2
|
import type { FilesSection, SectionId } from '../schema/types.ts';
|
|
3
|
-
import type { ApplyResult, ConfigAdapter, ExportOptions, ExportSection, ExportUnit, HostContext, ImportContext, PlanItem, Portability, ValidationResult } from '../core/types.ts';
|
|
3
|
+
import type { ApplyResult, ConfigAdapter, ExportOptions, ExportSection, ExportUnit, HostContext, ImportContext, PlanItem, Portability, SectionPreview, ValidationResult } from '../core/types.ts';
|
|
4
4
|
/**
|
|
5
5
|
* 文件类分区导出的**单分区累计字节闸门**:分区内文件内容合计放不下的单元整块剔除(见 export())。
|
|
6
6
|
*
|
|
@@ -66,6 +66,36 @@ export declare abstract class FileCollectionAdapter implements ConfigAdapter<Fil
|
|
|
66
66
|
warnings: string[];
|
|
67
67
|
} | null>;
|
|
68
68
|
export(ctx: HostContext, options: ExportOptions): Promise<ExportSection<FilesSection>>;
|
|
69
|
+
/**
|
|
70
|
+
* 只读预览(`/export-preview`):与 `export()` **共用同一个内核**(`collect`),
|
|
71
|
+
* 只把「读文件内容 + SHA-256」换成 `ctx.fs.statSize`。
|
|
72
|
+
*
|
|
73
|
+
* 为什么必须共用:预览说的「会带哪些单元、各多大」就是用户勾选的依据,一旦与真实导出
|
|
74
|
+
* 分叉,用户会按预览勾选却拿到不同的包(`file-collection.test.ts` 逐项断言两者相等)。
|
|
75
|
+
*/
|
|
76
|
+
preview(ctx: HostContext, options: ExportOptions): Promise<SectionPreview<FilesSection>>;
|
|
77
|
+
/**
|
|
78
|
+
* 分区收集内核(`export` / `preview` 共用)。
|
|
79
|
+
*
|
|
80
|
+
* - `mode === 'content'`:读文件内容 + 算 SHA-256(真实导出);
|
|
81
|
+
* - `mode === 'size'`:只取字节数(`ctx.fs.statSize`),不解码、不哈希(预览)。
|
|
82
|
+
*
|
|
83
|
+
* 两种模式共用**同一套**单元分组、条目级白名单、单分区字节闸门与告警生成 ——
|
|
84
|
+
* 因此「预览的单元 / 体积 / 告警」与「导出的单元 / 体积 / 告警」逐项相等。
|
|
85
|
+
*
|
|
86
|
+
* 返回值里的 `entries` **只含** `relativePath`/`data`/`contentHash` 三个字段:
|
|
87
|
+
* `FilesSection` 是备份格式的一部分,多带内部字段会被序列化出去;体积走旁路 `sizes`。
|
|
88
|
+
*/
|
|
89
|
+
private collect;
|
|
90
|
+
/**
|
|
91
|
+
* 读一个成员文件。
|
|
92
|
+
*
|
|
93
|
+
* - `content`:读内容并按需算 SHA-256(真实导出);
|
|
94
|
+
* - `size`:优先 `ctx.fs.statSize`(只 stat,不读内容);门面未实现或返回 null(文件消失 /
|
|
95
|
+
* 读不到)时**退回 readFile** —— 与改造前把「读不到」暴露成同一个错误,绝不把未知体积
|
|
96
|
+
* 当成 0(0 会让字节闸门少算,把本该剔除的单元放进预览,预览与导出就分叉了)。
|
|
97
|
+
*/
|
|
98
|
+
private readMember;
|
|
69
99
|
/** 单元清单(零 I/O:直接由 export 产物归并)。 */
|
|
70
100
|
listUnits(section: ExportSection<FilesSection>): ExportUnit[];
|
|
71
101
|
analyzeImport(data: FilesSection, ctx: ImportContext): Promise<PlanItem[]>;
|
|
@@ -16,7 +16,7 @@ import { sha256Hex } from '../utils/hashing.js';
|
|
|
16
16
|
import { isReservedInternalRel, normalizePath } from '../utils/paths.js';
|
|
17
17
|
import { msgOf, zhMsg } from '../core/messages.js';
|
|
18
18
|
import { linkWarnings, listFilesDetailed } from './link-report.js';
|
|
19
|
-
import { defaultUnitId, unitAllowed, unitsFromFiles } from './units.js';
|
|
19
|
+
import { defaultUnitId, unitAllowed, unitsFromEntries, unitsFromFiles } from './units.js';
|
|
20
20
|
import { validateJsonSection } from './json-section.js';
|
|
21
21
|
/**
|
|
22
22
|
* 文件类分区导出的**单分区累计字节闸门**:分区内文件内容合计放不下的单元整块剔除(见 export())。
|
|
@@ -40,6 +40,14 @@ import { validateJsonSection } from './json-section.js';
|
|
|
40
40
|
export const MAX_FILE_SECTION_BYTES = 256 * 1024 * 1024;
|
|
41
41
|
/** 字节闸门告警里最多列出的单元数(其余折叠成 "(+ N)",避免大分区把报告撑爆;与 link-report 同口径) */
|
|
42
42
|
const MAX_UNITS_PER_GATE_WARNING = 5;
|
|
43
|
+
/**
|
|
44
|
+
* 只读预览下文件条目的占位内容(长度 0)。
|
|
45
|
+
*
|
|
46
|
+
* 为什么可用空内容:预览产出的 `FilesSection` 只在内存里流转(选择器只需要 `relativePath`
|
|
47
|
+
* 与体积),**绝不**进 ZIP / 快照 / 导入路径 —— 空内容一旦被误当导出产物,写出去的会是空文件。
|
|
48
|
+
* 因此 `preview()` 的返回值在类型注释里明确标注「不是格式的一部分」(见 types.ts 的 SectionPreview)。
|
|
49
|
+
*/
|
|
50
|
+
const EMPTY_DATA = new Uint8Array(0);
|
|
43
51
|
/** 告警文案里的体积(人类可读;纯数值格式化,不含用户可见散文) */
|
|
44
52
|
function formatBytes(bytes) {
|
|
45
53
|
if (bytes < 1024)
|
|
@@ -95,7 +103,49 @@ export class FileCollectionAdapter {
|
|
|
95
103
|
return null;
|
|
96
104
|
}
|
|
97
105
|
async export(ctx, options) {
|
|
106
|
+
const collected = await this.collect(ctx, options, 'content');
|
|
107
|
+
return {
|
|
108
|
+
sectionId: this.id,
|
|
109
|
+
data: { version: 1, files: collected.entries },
|
|
110
|
+
counts: { files: collected.entries.length },
|
|
111
|
+
warnings: collected.warnings,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 只读预览(`/export-preview`):与 `export()` **共用同一个内核**(`collect`),
|
|
116
|
+
* 只把「读文件内容 + SHA-256」换成 `ctx.fs.statSize`。
|
|
117
|
+
*
|
|
118
|
+
* 为什么必须共用:预览说的「会带哪些单元、各多大」就是用户勾选的依据,一旦与真实导出
|
|
119
|
+
* 分叉,用户会按预览勾选却拿到不同的包(`file-collection.test.ts` 逐项断言两者相等)。
|
|
120
|
+
*/
|
|
121
|
+
async preview(ctx, options) {
|
|
122
|
+
const collected = await this.collect(ctx, options, 'size');
|
|
123
|
+
return {
|
|
124
|
+
section: {
|
|
125
|
+
sectionId: this.id,
|
|
126
|
+
data: { version: 1, files: collected.entries },
|
|
127
|
+
counts: { files: collected.entries.length },
|
|
128
|
+
warnings: collected.warnings,
|
|
129
|
+
},
|
|
130
|
+
sizeBytes: collected.totalBytes,
|
|
131
|
+
items: unitsFromEntries(this.id, collected.sizes, (rel) => this.unitIdOf(rel)),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 分区收集内核(`export` / `preview` 共用)。
|
|
136
|
+
*
|
|
137
|
+
* - `mode === 'content'`:读文件内容 + 算 SHA-256(真实导出);
|
|
138
|
+
* - `mode === 'size'`:只取字节数(`ctx.fs.statSize`),不解码、不哈希(预览)。
|
|
139
|
+
*
|
|
140
|
+
* 两种模式共用**同一套**单元分组、条目级白名单、单分区字节闸门与告警生成 ——
|
|
141
|
+
* 因此「预览的单元 / 体积 / 告警」与「导出的单元 / 体积 / 告警」逐项相等。
|
|
142
|
+
*
|
|
143
|
+
* 返回值里的 `entries` **只含** `relativePath`/`data`/`contentHash` 三个字段:
|
|
144
|
+
* `FilesSection` 是备份格式的一部分,多带内部字段会被序列化出去;体积走旁路 `sizes`。
|
|
145
|
+
*/
|
|
146
|
+
async collect(ctx, options, mode) {
|
|
98
147
|
const files = [];
|
|
148
|
+
const sizes = [];
|
|
99
149
|
const warnings = [];
|
|
100
150
|
const allow = options.includeItems?.[this.id];
|
|
101
151
|
// issue #37:用「跟随 junction/符号链接」的遍历,并把跟随/跳过的链接写进告警——
|
|
@@ -148,11 +198,15 @@ export class FileCollectionAdapter {
|
|
|
148
198
|
const pending = [];
|
|
149
199
|
let unitBytes = 0;
|
|
150
200
|
for (const member of unitEntries.get(unitId) ?? []) {
|
|
151
|
-
const
|
|
152
|
-
unitBytes +=
|
|
201
|
+
const read = await this.readMember(ctx, member.rel, mode);
|
|
202
|
+
unitBytes += read.sizeBytes;
|
|
153
203
|
if (unitBytes > room)
|
|
154
204
|
break;
|
|
155
|
-
pending.push({
|
|
205
|
+
pending.push({
|
|
206
|
+
rel: member.rel,
|
|
207
|
+
sizeBytes: read.sizeBytes,
|
|
208
|
+
entry: { relativePath: member.relPath, data: read.data, contentHash: read.contentHash },
|
|
209
|
+
});
|
|
156
210
|
}
|
|
157
211
|
if (unitBytes > room) {
|
|
158
212
|
droppedUnits.push(unitId);
|
|
@@ -160,13 +214,15 @@ export class FileCollectionAdapter {
|
|
|
160
214
|
}
|
|
161
215
|
usedBytes += unitBytes;
|
|
162
216
|
for (const p of pending)
|
|
163
|
-
keptByRel.set(p.rel, p.
|
|
217
|
+
keptByRel.set(p.rel, { entry: p.entry, sizeBytes: p.sizeBytes });
|
|
164
218
|
}
|
|
165
219
|
// ③ 按 ① 的顺序落盘(未触发闸门时与改造前的文件顺序逐项一致)
|
|
166
220
|
for (const rel of considered) {
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
|
|
221
|
+
const kept = keptByRel.get(rel);
|
|
222
|
+
if (kept === undefined)
|
|
223
|
+
continue;
|
|
224
|
+
files.push(kept.entry);
|
|
225
|
+
sizes.push({ relativePath: kept.entry.relativePath, sizeBytes: kept.sizeBytes });
|
|
170
226
|
}
|
|
171
227
|
// 绝不静默:被整块剔除的单元写进告警(导出报告可见),并给出上限与实际保留量
|
|
172
228
|
if (droppedUnits.length > 0) {
|
|
@@ -183,12 +239,24 @@ export class FileCollectionAdapter {
|
|
|
183
239
|
if (rels.length === 0)
|
|
184
240
|
warnings.push(msgOf(ctx)('adapter.dirEmpty', { type: this.displayName }));
|
|
185
241
|
warnings.push(...linkWarnings(msgOf(ctx), this.displayName, listing));
|
|
186
|
-
return {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
242
|
+
return { entries: files, sizes, totalBytes: usedBytes, warnings };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* 读一个成员文件。
|
|
246
|
+
*
|
|
247
|
+
* - `content`:读内容并按需算 SHA-256(真实导出);
|
|
248
|
+
* - `size`:优先 `ctx.fs.statSize`(只 stat,不读内容);门面未实现或返回 null(文件消失 /
|
|
249
|
+
* 读不到)时**退回 readFile** —— 与改造前把「读不到」暴露成同一个错误,绝不把未知体积
|
|
250
|
+
* 当成 0(0 会让字节闸门少算,把本该剔除的单元放进预览,预览与导出就分叉了)。
|
|
251
|
+
*/
|
|
252
|
+
async readMember(ctx, rel, mode) {
|
|
253
|
+
if (mode === 'size') {
|
|
254
|
+
const size = (await ctx.fs.statSize?.(rel)) ?? null;
|
|
255
|
+
if (size !== null)
|
|
256
|
+
return { data: EMPTY_DATA, sizeBytes: size, contentHash: '' };
|
|
257
|
+
}
|
|
258
|
+
const data = await ctx.fs.readFile(rel);
|
|
259
|
+
return { data, sizeBytes: data.byteLength, contentHash: mode === 'content' ? sha256Hex(data) : '' };
|
|
192
260
|
}
|
|
193
261
|
/** 单元清单(零 I/O:直接由 export 产物归并)。 */
|
|
194
262
|
listUnits(section) {
|
package/lib/adapters/plugins.js
CHANGED
|
@@ -609,12 +609,17 @@ export class PluginsAdapter {
|
|
|
609
609
|
if (ctx.signal?.aborted)
|
|
610
610
|
throw err;
|
|
611
611
|
const reason = err instanceof Error ? err.message : String(err);
|
|
612
|
+
// 提示里的 profile 必须与**实际安装目标**同源(ctx.target.profile = 宿主解析出的
|
|
613
|
+
// config.profile → profileContext → --profile → env → web)。此前这里单独调
|
|
614
|
+
// resolveProcessProfileName()(只认 argv/env):Desktop 外壳不传 --profile,
|
|
615
|
+
// 真机于是出现「安装用的是 desktop、提示却写 dsh plugin --profile web add …」的自相矛盾。
|
|
616
|
+
const hintProfile = ctx.target.profile ?? resolveProcessProfileName();
|
|
612
617
|
return {
|
|
613
618
|
ok: false,
|
|
614
619
|
warning: true,
|
|
615
620
|
// 保留 warning(§34.17 非致命):一个装不上的插件不得拖垮已成功导入的其余配置;
|
|
616
621
|
// message 附可复制的手动安装命令(profile 解析与 M1 宿主一致)。
|
|
617
|
-
message: (item.kind === 'Update' ? msg('adapter.pluginUpdateFailed', { name, msg: reason, profile:
|
|
622
|
+
message: (item.kind === 'Update' ? msg('adapter.pluginUpdateFailed', { name, msg: reason, profile: hintProfile }) : msg('adapter.pluginInstallFailed', { name, msg: reason, profile: hintProfile })),
|
|
618
623
|
};
|
|
619
624
|
}
|
|
620
625
|
}
|
|
@@ -26,6 +26,13 @@ export declare class MemFs implements FileSystemFacade {
|
|
|
26
26
|
/** 测试助手:登记某文件的 mtime(null = 恢复为「读不到」) */
|
|
27
27
|
setMtime(relPath: string, ms: number | null): void;
|
|
28
28
|
mtimeMs(relPath: string): Promise<number | null>;
|
|
29
|
+
/**
|
|
30
|
+
* 文件字节数(只读预览用)。
|
|
31
|
+
*
|
|
32
|
+
* 语义与真实门面一致:**读不到 → null**(不返回 0)—— 0 会让字节闸门少算,
|
|
33
|
+
* 让预览把本该剔除的单元也列出来,与真实导出分叉。
|
|
34
|
+
*/
|
|
35
|
+
statSize(relPath: string): Promise<number | null>;
|
|
29
36
|
listRecursive(dir: string): Promise<string[]>;
|
|
30
37
|
mkdir(): Promise<void>;
|
|
31
38
|
}
|
|
@@ -57,6 +57,16 @@ export class MemFs {
|
|
|
57
57
|
async mtimeMs(relPath) {
|
|
58
58
|
return this.mtimes.get(this.key(relPath)) ?? null;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* 文件字节数(只读预览用)。
|
|
62
|
+
*
|
|
63
|
+
* 语义与真实门面一致:**读不到 → null**(不返回 0)—— 0 会让字节闸门少算,
|
|
64
|
+
* 让预览把本该剔除的单元也列出来,与真实导出分叉。
|
|
65
|
+
*/
|
|
66
|
+
async statSize(relPath) {
|
|
67
|
+
const v = this.files.get(this.key(relPath));
|
|
68
|
+
return v === undefined ? null : v.byteLength;
|
|
69
|
+
}
|
|
60
70
|
async listRecursive(dir) {
|
|
61
71
|
const base = normalizePath(this.homeDir);
|
|
62
72
|
const prefix = normalizePath(dir) === '' ? base : `${base}/${normalizePath(dir)}`;
|
package/lib/adapters/units.d.ts
CHANGED
|
@@ -23,10 +23,20 @@ export declare function defaultUnitId(rel: string): string;
|
|
|
23
23
|
* 空数组 = 什么都不允许(调用方应把该分区整体视为未勾选,见 Exporter.export)。
|
|
24
24
|
*/
|
|
25
25
|
export declare function unitAllowed(allow: readonly string[] | undefined, id: string): boolean;
|
|
26
|
-
/** 白名单是否让该分区整体为空(= 用户取消了整个分区,不是「导出空分区」)。 */
|
|
27
|
-
export declare function allowListEmptiesSection(allow: readonly string[] | undefined): boolean;
|
|
28
26
|
/** 由「成员文件」归并出单元清单(体积 = 成员文件字节合计;零 I/O)。 */
|
|
29
27
|
export declare function unitsFromFiles(sectionId: string, files: readonly {
|
|
30
28
|
relativePath: string;
|
|
31
29
|
data: Uint8Array;
|
|
32
30
|
}[], unitIdOf: (rel: string) => string): ExportUnit[];
|
|
31
|
+
/**
|
|
32
|
+
* 由「路径 + 已知体积」归并出单元清单(零 I/O)。
|
|
33
|
+
*
|
|
34
|
+
* 为什么要有这一份:只读预览(`/export-preview`)拿不到文件内容(也不该读),体积来自
|
|
35
|
+
* `ctx.fs.statSize`。若预览改用「data 为空的 FilesSection」喂 `unitsFromFiles`,
|
|
36
|
+
* 每个单元的体积会变成 0,选择器显示的体积全错。两份实现必须**同一套聚合规则**,
|
|
37
|
+
* 否则预览与导出会算出不同的单元体积(`file-collection.test.ts` 钉住两者逐项相等)。
|
|
38
|
+
*/
|
|
39
|
+
export declare function unitsFromEntries(sectionId: string, entries: readonly {
|
|
40
|
+
relativePath: string;
|
|
41
|
+
sizeBytes: number;
|
|
42
|
+
}[], unitIdOf: (rel: string) => string): ExportUnit[];
|
package/lib/adapters/units.js
CHANGED
|
@@ -15,18 +15,25 @@ export function defaultUnitId(rel) {
|
|
|
15
15
|
export function unitAllowed(allow, id) {
|
|
16
16
|
return allow === undefined || allow.includes(id);
|
|
17
17
|
}
|
|
18
|
-
/** 白名单是否让该分区整体为空(= 用户取消了整个分区,不是「导出空分区」)。 */
|
|
19
|
-
export function allowListEmptiesSection(allow) {
|
|
20
|
-
return allow !== undefined && allow.length === 0;
|
|
21
|
-
}
|
|
22
18
|
/** 由「成员文件」归并出单元清单(体积 = 成员文件字节合计;零 I/O)。 */
|
|
23
19
|
export function unitsFromFiles(sectionId, files, unitIdOf) {
|
|
20
|
+
return unitsFromEntries(sectionId, files.map((f) => ({ relativePath: f.relativePath, sizeBytes: f.data.byteLength })), unitIdOf);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 由「路径 + 已知体积」归并出单元清单(零 I/O)。
|
|
24
|
+
*
|
|
25
|
+
* 为什么要有这一份:只读预览(`/export-preview`)拿不到文件内容(也不该读),体积来自
|
|
26
|
+
* `ctx.fs.statSize`。若预览改用「data 为空的 FilesSection」喂 `unitsFromFiles`,
|
|
27
|
+
* 每个单元的体积会变成 0,选择器显示的体积全错。两份实现必须**同一套聚合规则**,
|
|
28
|
+
* 否则预览与导出会算出不同的单元体积(`file-collection.test.ts` 钉住两者逐项相等)。
|
|
29
|
+
*/
|
|
30
|
+
export function unitsFromEntries(sectionId, entries, unitIdOf) {
|
|
24
31
|
const byUnit = new Map();
|
|
25
|
-
for (const f of
|
|
32
|
+
for (const f of entries) {
|
|
26
33
|
const unit = unitIdOf(f.relativePath);
|
|
27
34
|
const cur = byUnit.get(unit) ?? { count: 0, size: 0 };
|
|
28
35
|
cur.count += 1;
|
|
29
|
-
cur.size += f.
|
|
36
|
+
cur.size += f.sizeBytes;
|
|
30
37
|
byUnit.set(unit, cur);
|
|
31
38
|
}
|
|
32
39
|
return [...byUnit.entries()].map(([unit, agg]) => ({
|
package/lib/client.d.ts
CHANGED
|
@@ -3224,6 +3224,7 @@ declare const zh$3: {
|
|
|
3224
3224
|
readonly 'profiles.launching': '启动中…';
|
|
3225
3225
|
readonly 'profiles.launch.hint': '用该档案另起一个独立 DSH 实例(自动挑空闲端口、自动打开浏览器);当前实例不受影响。只支持 Web 形态档案。';
|
|
3226
3226
|
readonly 'profiles.launch.notWeb': '「{name}」不是 Web 形态(bundles 里没有 @deepseek-ai/dsh-web-app),没有浏览器界面,无法作为独立实例启动。请在终端运行:';
|
|
3227
|
+
readonly 'profiles.launch.managed': '「{name}」是 DeepSeek Harness Desktop 应用独占管理的保留档案:普通 dsh 命令行对它一律报错(profile "{name}" is managed exclusively by the Electron application),也无法作为独立实例启动。请直接在桌面端应用里打开它并安装插件。';
|
|
3227
3228
|
readonly 'profiles.launch.started': '已启动档案「{name}」的独立实例(端口 {port})';
|
|
3228
3229
|
readonly 'profiles.launch.pending': '档案「{name}」的进程已启动(端口 {port}),但 {seconds} 秒内未确认就绪 —— 请看日志确认:';
|
|
3229
3230
|
readonly 'profiles.launch.open': '打开新实例';
|
|
@@ -3301,6 +3302,7 @@ declare const zh$3: {
|
|
|
3301
3302
|
readonly 'profiles.error.reservedName': 'DSH 保留名不能手动创建';
|
|
3302
3303
|
readonly 'profiles.error.unknownTemplate': '未知的起步模板';
|
|
3303
3304
|
readonly 'profiles.error.notLaunchable': '该档案不是 Web 形态,没有浏览器界面(请用终端运行 dsh --profile <名>)';
|
|
3305
|
+
readonly 'profiles.error.managedProfile': '「desktop」档案由 DeepSeek Harness Desktop 应用独占管理:删掉或改名会让桌面端下次启动时按 web 模板重建一个空档案(已装插件全部消失)。请在桌面端应用里管理它。';
|
|
3304
3306
|
readonly 'profiles.error.launcherUnavailable': '定位不到 dsh 启动器(非标准启动方式),请手动运行 dsh --profile <名>';
|
|
3305
3307
|
readonly 'profiles.error.launchFailed': '档案实例启动失败';
|
|
3306
3308
|
readonly 'profiles.error.alreadyRunning': '该档案的实例已经在运行(先停止再启动)';
|