dsh-config-manager 0.1.78 → 0.1.80
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/adapters/credentials.d.ts +6 -2
- package/lib/adapters/credentials.js +19 -3
- package/lib/adapters/file-collection.js +2 -1
- package/lib/adapters/mcp.d.ts +48 -2
- package/lib/adapters/mcp.js +265 -3
- package/lib/adapters/messages.d.ts +3 -0
- package/lib/adapters/messages.js +7 -0
- package/lib/adapters/plugin-files.js +2 -1
- package/lib/adapters/plugins.js +105 -20
- package/lib/adapters/pnpm-workspace.d.ts +32 -0
- package/lib/adapters/pnpm-workspace.js +113 -2
- package/lib/adapters/prompts.d.ts +6 -2
- package/lib/adapters/prompts.js +17 -2
- package/lib/adapters/providers.d.ts +19 -2
- package/lib/adapters/providers.js +59 -3
- package/lib/adapters/settings.d.ts +6 -2
- package/lib/adapters/settings.js +13 -2
- package/lib/adapters/skills.d.ts +25 -1
- package/lib/adapters/skills.js +60 -0
- package/lib/adapters/ui.d.ts +6 -2
- package/lib/adapters/ui.js +13 -2
- package/lib/adapters/units.d.ts +18 -0
- package/lib/adapters/units.js +39 -0
- package/lib/adapters/workspaces.js +2 -1
- package/lib/client.d.ts +401 -112
- package/lib/client.js +2899 -1525
- package/lib/core/backup.js +20 -0
- package/lib/core/mcp-connector-store.d.ts +159 -0
- package/lib/core/mcp-connector-store.js +445 -0
- package/lib/core/messages-core.d.ts +2 -0
- package/lib/core/messages-core.js +4 -0
- package/lib/core/messages.d.ts +20 -0
- package/lib/core/messages.js +44 -0
- package/lib/core/phase3-host.js +18 -1
- package/lib/core/plugin-cli.d.ts +22 -2
- package/lib/core/plugin-cli.js +22 -3
- package/lib/core/recovery-orchestrator.d.ts +3 -1
- package/lib/core/recovery-orchestrator.js +75 -16
- package/lib/core/rollback-activity.d.ts +89 -0
- package/lib/core/rollback-activity.js +350 -0
- package/lib/core/rollback-ledger.d.ts +0 -9
- package/lib/core/rollback-ledger.js +37 -0
- package/lib/core/rollback-merge.d.ts +22 -2
- package/lib/core/rollback-merge.js +63 -13
- package/lib/core/rollback-point.d.ts +93 -0
- package/lib/core/rollback-point.js +420 -101
- package/lib/core/rollback-undo-journal.d.ts +123 -0
- package/lib/core/rollback-undo-journal.js +517 -0
- package/lib/core/self-update.js +2 -4
- package/lib/core/text-fidelity.d.ts +89 -0
- package/lib/core/text-fidelity.js +176 -0
- package/lib/core/types.d.ts +12 -3
- package/lib/core/update-check.js +2 -4
- package/lib/core/validator.d.ts +9 -0
- package/lib/core/validator.js +12 -0
- package/lib/foreign/bundle.js +42 -3
- package/lib/foreign/claude-code.js +358 -26
- package/lib/foreign/claude-sessions.d.ts +51 -8
- package/lib/foreign/claude-sessions.js +111 -13
- package/lib/foreign/read-claude-code.d.ts +33 -4
- package/lib/foreign/read-claude-code.js +446 -65
- package/lib/foreign/read-claude-plugins.d.ts +36 -0
- package/lib/foreign/read-claude-plugins.js +403 -0
- package/lib/foreign/registry.js +25 -2
- package/lib/foreign/truth-table.js +6 -4
- package/lib/foreign/types.d.ts +170 -2
- package/lib/index.d.ts +14 -5
- package/lib/index.js +60 -16
- package/lib/market/git-file-writer.js +7 -1
- package/lib/profiles/dsh-profile-launcher.d.ts +20 -0
- package/lib/profiles/dsh-profile-launcher.js +58 -1
- package/lib/profiles/dsh-profile-shared.d.ts +8 -2
- package/lib/profiles/dsh-profile-shared.js +8 -2
- package/lib/routes/foreign.js +8 -1
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/kit.d.ts +2 -2
- package/lib/routes/sync.js +173 -9
- package/lib/schema/manifest.d.ts +6 -1
- package/lib/schema/manifest.js +55 -0
- package/lib/schema/types.d.ts +47 -2
- package/lib/sync/ancestor.d.ts +22 -0
- package/lib/sync/ancestor.js +66 -2
- package/lib/sync/autosync-config.d.ts +73 -0
- package/lib/sync/autosync-config.js +89 -0
- package/lib/sync/autosync-scheduler.d.ts +80 -6
- package/lib/sync/autosync-scheduler.js +263 -40
- package/lib/sync/gist/gist-transport.d.ts +18 -1
- package/lib/sync/gist/gist-transport.js +42 -3
- package/lib/sync/gist/messages.d.ts +2 -0
- package/lib/sync/gist/messages.js +5 -0
- package/lib/sync/git/git-transport.d.ts +42 -3
- package/lib/sync/git/git-transport.js +146 -3
- package/lib/sync/merge.d.ts +47 -14
- package/lib/sync/merge.js +242 -222
- package/lib/sync/messages.d.ts +8 -0
- package/lib/sync/messages.js +19 -0
- package/lib/sync/risk.d.ts +19 -2
- package/lib/sync/risk.js +10 -3
- package/lib/sync/s3/messages.d.ts +6 -0
- package/lib/sync/s3/messages.js +13 -0
- package/lib/sync/s3/s3-transport.d.ts +9 -1
- package/lib/sync/s3/s3-transport.js +78 -6
- package/lib/sync/s3/sigv4.js +11 -2
- package/lib/sync/sync-engine.d.ts +212 -8
- package/lib/sync/sync-engine.js +514 -58
- package/lib/sync/sync-history.d.ts +11 -0
- package/lib/sync/sync-history.js +20 -0
- package/lib/sync/sync-state.d.ts +43 -0
- package/lib/sync/sync-state.js +44 -0
- package/lib/sync/sync-watcher.d.ts +76 -0
- package/lib/sync/sync-watcher.js +202 -0
- package/lib/sync/text-merge.d.ts +85 -0
- package/lib/sync/text-merge.js +513 -0
- package/lib/sync/transport.d.ts +48 -0
- package/lib/sync/transport.js +12 -0
- package/lib/sync/watch-scope.d.ts +38 -0
- package/lib/sync/watch-scope.js +95 -0
- package/lib/sync/webdav/webdav-transport.d.ts +16 -2
- package/lib/sync/webdav/webdav-transport.js +128 -11
- package/lib/ui/foreign-view.js +10 -0
- package/lib/ui/i18n.d.ts +9 -0
- package/lib/ui/i18n.js +22 -0
- package/lib/ui/rollback-view.d.ts +12 -0
- package/lib/ui/rollback-view.js +2 -0
- package/lib/ui/sync-settings-view.d.ts +47 -0
- package/lib/ui/sync-settings-view.js +24 -0
- package/lib/ui/types.d.ts +21 -0
- package/lib/utils/env-lock.d.ts +1 -0
- package/lib/utils/env-lock.js +7 -4
- package/package.json +6 -6
- package/src/adapters/credentials.ts +22 -4
- package/src/adapters/file-collection.ts +2 -1
- package/src/adapters/mcp.test.ts +235 -1
- package/src/adapters/mcp.ts +276 -5
- package/src/adapters/messages.ts +7 -0
- package/src/adapters/plugin-files.ts +2 -1
- package/src/adapters/plugins.test.ts +176 -2
- package/src/adapters/plugins.ts +107 -21
- package/src/adapters/pnpm-workspace.test.ts +89 -1
- package/src/adapters/pnpm-workspace.ts +127 -2
- package/src/adapters/prompts.ts +20 -3
- package/src/adapters/providers.ts +62 -4
- package/src/adapters/settings.ts +16 -3
- package/src/adapters/skills.test.ts +141 -0
- package/src/adapters/skills.ts +58 -2
- package/src/adapters/ui.ts +16 -3
- package/src/adapters/units-entries.test.ts +190 -0
- package/src/adapters/units.ts +50 -0
- package/src/adapters/workspaces.ts +2 -1
- package/src/client/about/PluginUpdateDialog.tsx +4 -2
- package/src/client/about/ReleaseNotesDialog.tsx +4 -2
- package/src/client/chat/RollbackReviewDialog.tsx +3 -2
- package/src/client/common/Skeleton.tsx +32 -0
- package/src/client/common/api-base.test.ts +36 -0
- package/src/client/common/http.test.ts +12 -1
- package/src/client/common/http.ts +8 -2
- package/src/client/common/routes.ts +46 -3
- package/src/client/config-manager.module.css +321 -11
- package/src/client/home/HomePanel.tsx +14 -6
- package/src/client/import/ForeignImportView.tsx +3 -1
- package/src/client/import/import-wizard-steps.tsx +2 -1
- package/src/client/library/ArtifactRow.tsx +12 -2
- package/src/client/library/LibraryActions.tsx +6 -3
- package/src/client/library/LibraryPanel.tsx +21 -3
- package/src/client/library/RestorePlanView.tsx +4 -2
- package/src/client/locales.ts +3 -0
- package/src/client/market/market-api.test.ts +3 -2
- package/src/client/recovery/RecoveryPanel.tsx +54 -7
- package/src/client/recovery/RollbackCard.tsx +7 -2
- package/src/client/recovery/RollbackDiffPane.tsx +3 -2
- package/src/client/recovery/recovery-locales.ts +20 -2
- package/src/client/recovery/recovery-view.test.ts +39 -1
- package/src/client/recovery/recovery-view.ts +23 -1
- package/src/client/run-store.test.ts +1 -1
- package/src/client/run-store.ts +4 -0
- package/src/client/sync/AutosyncCard.tsx +49 -6
- package/src/client/sync/MergeModal.tsx +220 -0
- package/src/client/sync/MergePreviewView.tsx +306 -0
- package/src/client/sync/SyncActivityBar.tsx +31 -0
- package/src/client/sync/SyncConfirmView.tsx +2 -1
- package/src/client/sync/SyncPanel.tsx +130 -87
- package/src/client/sync/SyncProbeRow.tsx +162 -0
- package/src/client/sync/SyncSettingsView.test.ts +27 -2
- package/src/client/sync/WorkDirCard.tsx +44 -6
- package/src/client/sync/history-model.ts +4 -1
- package/src/client/sync/merge-view.test.ts +248 -0
- package/src/client/sync/merge-view.ts +398 -0
- package/src/client/sync/probe-view.test.ts +90 -0
- package/src/client/sync/probe-view.ts +63 -0
- package/src/client/sync/remote-device-view.test.ts +35 -0
- package/src/client/sync/remote-device-view.ts +25 -0
- package/src/client/sync/sync-api.test.ts +5 -3
- package/src/client/sync/sync-api.ts +212 -2
- package/src/client/sync/sync-entry.test.ts +78 -0
- package/src/client/sync/sync-entry.ts +53 -0
- package/src/client/sync/sync-locales.ts +234 -2
- package/src/client/sync/sync-view.test.ts +18 -1
- package/src/client/sync/sync-view.ts +20 -0
- package/src/core/backup.test.ts +41 -0
- package/src/core/backup.ts +20 -0
- package/src/core/mcp-connector-store.test.ts +265 -0
- package/src/core/mcp-connector-store.ts +516 -0
- package/src/core/messages-core.ts +4 -0
- package/src/core/messages.ts +46 -0
- package/src/core/phase3-host.ts +18 -1
- package/src/core/phase3-production-integration.test.ts +4 -0
- package/src/core/plugin-cli.test.ts +26 -0
- package/src/core/plugin-cli.ts +30 -4
- package/src/core/recovery-orchestrator.test.ts +104 -5
- package/src/core/recovery-orchestrator.ts +74 -17
- package/src/core/rollback-activity.test.ts +311 -0
- package/src/core/rollback-activity.ts +367 -0
- package/src/core/rollback-cross-session.test.ts +694 -0
- package/src/core/rollback-interrupted.test.ts +137 -0
- package/src/core/rollback-ledger.ts +32 -0
- package/src/core/rollback-merge.test.ts +130 -0
- package/src/core/rollback-merge.ts +78 -14
- package/src/core/rollback-point.ts +468 -91
- package/src/core/rollback-undo-journal.test.ts +508 -0
- package/src/core/rollback-undo-journal.ts +571 -0
- package/src/core/self-update.ts +2 -5
- package/src/core/text-fidelity.test.ts +189 -0
- package/src/core/text-fidelity.ts +197 -0
- package/src/core/types.ts +11 -3
- package/src/core/update-check.ts +2 -5
- package/src/core/validator.ts +14 -0
- package/src/foreign/bundle-origin.test.ts +265 -0
- package/src/foreign/bundle.ts +45 -5
- package/src/foreign/claude-code.test.ts +268 -0
- package/src/foreign/claude-code.ts +406 -25
- package/src/foreign/claude-plugins.test.ts +234 -0
- package/src/foreign/claude-sessions.test.ts +141 -0
- package/src/foreign/claude-sessions.ts +179 -23
- package/src/foreign/file-budget.test.ts +4 -1
- package/src/foreign/read-claude-code.test.ts +320 -0
- package/src/foreign/read-claude-code.ts +490 -71
- package/src/foreign/read-claude-plugins.ts +465 -0
- package/src/foreign/registry.ts +25 -2
- package/src/foreign/session-ir.test.ts +65 -0
- package/src/foreign/truth-table.test.ts +81 -0
- package/src/foreign/truth-table.ts +6 -3
- package/src/foreign/types.ts +162 -2
- package/src/index.facade.test.ts +26 -4
- package/src/index.ts +76 -19
- package/src/market/git-file-writer.test.ts +4 -0
- package/src/market/git-file-writer.ts +7 -1
- package/src/profiles/dsh-profile-launcher.test.ts +68 -3
- package/src/profiles/dsh-profile-launcher.ts +56 -1
- package/src/profiles/dsh-profile-shared.ts +8 -2
- package/src/routes/foreign.ts +8 -1
- package/src/routes/index.ts +1 -1
- package/src/routes/kit.ts +2 -2
- package/src/routes/sync.ts +176 -9
- package/src/schema/manifest-schema.test.ts +87 -2
- package/src/schema/manifest.ts +57 -1
- package/src/schema/types.ts +45 -2
- package/src/sync/ancestor.ts +76 -2
- package/src/sync/autosync-config.test.ts +73 -1
- package/src/sync/autosync-config.ts +124 -0
- package/src/sync/autosync-risk-tier.test.ts +38 -2
- package/src/sync/autosync-scheduler.test.ts +264 -12
- package/src/sync/autosync-scheduler.ts +296 -44
- package/src/sync/gist/gist-conditional-write.test.ts +203 -0
- package/src/sync/gist/gist-transport.ts +53 -5
- package/src/sync/gist/messages.ts +5 -0
- package/src/sync/git/git-probe.test.ts +80 -0
- package/src/sync/git/git-transport.test.ts +185 -0
- package/src/sync/git/git-transport.ts +178 -5
- package/src/sync/merge.test.ts +188 -55
- package/src/sync/merge.ts +282 -224
- package/src/sync/messages.ts +19 -0
- package/src/sync/risk.ts +29 -5
- package/src/sync/s3/messages.ts +13 -0
- package/src/sync/s3/s3-conditional-write.test.ts +330 -0
- package/src/sync/s3/s3-transport.test.ts +2 -1
- package/src/sync/s3/s3-transport.ts +87 -9
- package/src/sync/s3/sigv4.test.ts +4 -1
- package/src/sync/s3/sigv4.ts +11 -2
- package/src/sync/sync-device-identity.test.ts +267 -0
- package/src/sync/sync-engine.test.ts +410 -19
- package/src/sync/sync-engine.ts +602 -57
- package/src/sync/sync-history.test.ts +16 -1
- package/src/sync/sync-history.ts +26 -0
- package/src/sync/sync-state.test.ts +64 -0
- package/src/sync/sync-state.ts +79 -0
- package/src/sync/sync-watcher.test.ts +197 -0
- package/src/sync/sync-watcher.ts +246 -0
- package/src/sync/text-merge.test.ts +358 -0
- package/src/sync/text-merge.ts +554 -0
- package/src/sync/transport.ts +57 -0
- package/src/sync/watch-scope.test.ts +122 -0
- package/src/sync/watch-scope.ts +120 -0
- package/src/sync/webdav/webdav-conditional-write.test.ts +360 -0
- package/src/sync/webdav/webdav-transport.ts +137 -14
- package/src/ui/foreign-view.test.ts +34 -0
- package/src/ui/foreign-view.ts +10 -0
- package/src/ui/i18n.ts +22 -0
- package/src/ui/rollback-view.ts +14 -0
- package/src/ui/sync-settings-view.test.ts +28 -1
- package/src/ui/sync-settings-view.ts +43 -0
- package/src/ui/types.ts +22 -0
- package/src/utils/env-lock.ts +8 -5
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文本保真(text fidelity)—— 字节 ↔ 文本的**无损往返**与「形态」(行尾 / BOM)判定。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它(2026-10-18 保真缺陷):
|
|
5
|
+
* 回滚 / 跨对话三方合并是**文本**级算法,但工作区文件是**字节**。用 'Buffer.toString("utf8")' 去解一个
|
|
6
|
+
* GBK / Latin-1 文件**不会抛错**(非法字节被替换成 U+FFFD),合并结果再按 UTF-8 写回 ⇒ 原文件被
|
|
7
|
+
* **mojibake 覆盖、内容不可逆损毁**。换行符同理:把 CRLF 折成 LF 再 join('\n'),整份文件的行尾被
|
|
8
|
+
* 静默改写(Windows 上 = 全文件 diff)。
|
|
9
|
+
*
|
|
10
|
+
* 两条硬规矩:
|
|
11
|
+
* ① **无损或拒绝**:解出来的文本必须能逐字节编码回原字节(decodeTextBytes 自己证明这一点,见判据 1);
|
|
12
|
+
* 做不到就 ok:false,调用方必须**零写入** —— 绝不「差不多就行」。
|
|
13
|
+
* ② **形态不猜**:行尾只有 '\n' | '\r\n' 两种可无损表示的形式;孤立 '\r'(老 Mac 风格)无法用它们表示
|
|
14
|
+
* ⇒ 同样 ok:false,而不是悄悄把它当 LF 写掉。
|
|
15
|
+
*
|
|
16
|
+
* ── 归一化口径(**唯一的契约**,判据 1 就建立在这三条上)──
|
|
17
|
+
* · decodeTextBytes **只做一件事**:剥掉开头的 UTF-8 BOM(EF BB BF)。**不改写任何行尾** ——
|
|
18
|
+
* 返回的 text 里 CRLF / LF 原样保留,因此**混合行尾**的文件也能逐字节往返。
|
|
19
|
+
* · shape.eol 描述这份文本的**主导行尾**(完整的 CRLF 数**严格多于**纯 LF 数时为 '\r\n';
|
|
20
|
+
* 相等 / 没有整行行尾 / 空文本一律 '\n')。它供「需要新建行」的一方(三方合并)决定输出行尾,
|
|
21
|
+
* **不影响** encodeTextBytes 的字节。
|
|
22
|
+
* · encodeTextBytes(text, shape) **只做一件事**:shape.bom 为真时补回 3 字节 BOM。**不改写任何行尾**。
|
|
23
|
+
* 于是恒有 encodeTextBytes(d.text, d.shape) 逐字节等于原始 bytes(d = decodeTextBytes(bytes))。
|
|
24
|
+
* · 行尾的**归一化只发生在三方合并**(rollback-merge.ts):输出按 shape.eol 统一,并用 eolMixed /
|
|
25
|
+
* eolRewritten 如实标注,绝不假装「原样保留」。
|
|
26
|
+
*
|
|
27
|
+
* ── 已知口径(**不猜、不谎报**)──
|
|
28
|
+
* · 前 8 KiB 出现 NUL 字节 ⇒ 'binary'(与 utils/line-diff.ts、rollback-point.ts 同一口径)。
|
|
29
|
+
* · 非法 UTF-8(超长编码、孤立代理项的 CESU/WTF-8 编码、UTF-16 的 FF FE 前缀等)⇒ 'non-utf8'。
|
|
30
|
+
* · 「恰好也是合法 UTF-8 的无 BOM UTF-16LE」在**字节层面无法与 UTF-8 区分**(例:UTF-16LE 的
|
|
31
|
+
* 你好 = 60 4F 7D 59,是 4 个合法 ASCII 字节)。本模块**不猜编码**:它会被当作合法 UTF-8 读出,
|
|
32
|
+
* 但往返仍逐字节相同 ⇒ **不会写坏字节**(要「猜编码」需要额外启发式,那才是真正的越权)。
|
|
33
|
+
*/
|
|
34
|
+
/** 行尾形态:只有 LF 与 CRLF 两种能被无损表示的换行符(孤立 CR 一律拒绝,见模块头 ②)。 */
|
|
35
|
+
export type TextEol = '\n' | '\r\n';
|
|
36
|
+
/** 一份文本的「形态」:主导行尾 + 是否带 UTF-8 BOM。 */
|
|
37
|
+
export interface TextShape {
|
|
38
|
+
/** 主导行尾(严格多者胜;相等 / 无行尾 / 空文本 ⇒ '\n') */
|
|
39
|
+
eol: TextEol;
|
|
40
|
+
/** 是否带 UTF-8 BOM(EF BB BF) */
|
|
41
|
+
bom: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** 已解码文本的形态探测结果(合并侧用:只如实描述,不做任何改写)。 */
|
|
44
|
+
export interface TextForm extends TextShape {
|
|
45
|
+
/** 这份文本里**出现过**的行尾风格(同一份文件可能两种都有 ⇒ 长度为 2) */
|
|
46
|
+
eols: TextEol[];
|
|
47
|
+
/** 孤立 CR(其后不是 LF)的个数;> 0 ⇒ 行尾无法用 TextShape 无损表示 */
|
|
48
|
+
loneCr: number;
|
|
49
|
+
}
|
|
50
|
+
/** 解码结论。ok:false 时调用方必须**零写入**(绝不 mojibake 写回)。 */
|
|
51
|
+
export type DecodeTextResult = {
|
|
52
|
+
ok: true;
|
|
53
|
+
text: string;
|
|
54
|
+
shape: TextShape;
|
|
55
|
+
} | {
|
|
56
|
+
ok: false;
|
|
57
|
+
reason: 'binary' | 'non-utf8';
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* 文本能否无损编码成 UTF-8 字节。
|
|
61
|
+
* 唯一会失败的形态是**孤立代理项**(TextEncoder 会把它替换成 U+FFFD ⇒ 写回即内容损毁)。
|
|
62
|
+
*/
|
|
63
|
+
export declare function isUtf8RoundTrippable(text: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* 探测一段**已解码文本**的形态:主导行尾、BOM 前缀、出现过的行尾风格、孤立 CR 计数。
|
|
66
|
+
* 纯描述、零改写(shape.eol 只用来决定「新建行」的行尾)。
|
|
67
|
+
*/
|
|
68
|
+
export declare function probeTextForm(text: string): TextForm;
|
|
69
|
+
/**
|
|
70
|
+
* 字节 → 文本(**无损或拒绝**)。
|
|
71
|
+
*
|
|
72
|
+
* 自证(判据 1):成功时一定有 encodeTextBytes(result.text, result.shape) **逐字节等于**入参 bytes。
|
|
73
|
+
*
|
|
74
|
+
* 拒绝的两种形态(调用方**必须零写入**并按 reason 如实报告):
|
|
75
|
+
* · 'binary':前 8 KiB 含 NUL(不是文本,行 / 合并算法对它没有定义);
|
|
76
|
+
* · 'non-utf8':非法 UTF-8,或解码结果无法逐字节往返,或含孤立 CR(超出两种 EOL 的表示能力)。
|
|
77
|
+
*/
|
|
78
|
+
export declare function decodeTextBytes(bytes: Uint8Array): DecodeTextResult;
|
|
79
|
+
/**
|
|
80
|
+
* 文本 → 字节(**保真**)。
|
|
81
|
+
*
|
|
82
|
+
* 契约:text 必须是 decodeTextBytes 输出的形态(BOM 已剥)。本函数**不改写任何行尾**,只在
|
|
83
|
+
* shape.bom 为真时补回 3 字节 BOM ⇒ 与 decodeTextBytes 互逆(判据 1)。
|
|
84
|
+
*
|
|
85
|
+
* 注意:shape.eol 在这里**不参与**字节生成 —— 行尾归一化只发生在三方合并里;在这里归一化就意味着
|
|
86
|
+
* 混合行尾 / CRLF 文件无法逐字节往返。含孤立代理项的文本按 UTF-8 替换字符写出(与 TextEncoder 一致):
|
|
87
|
+
* 调用方应先在 decodeTextBytes / mergeThreeWay 处把好关。
|
|
88
|
+
*/
|
|
89
|
+
export declare function encodeTextBytes(text: string, shape: TextShape): Uint8Array;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文本保真(text fidelity)—— 字节 ↔ 文本的**无损往返**与「形态」(行尾 / BOM)判定。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它(2026-10-18 保真缺陷):
|
|
5
|
+
* 回滚 / 跨对话三方合并是**文本**级算法,但工作区文件是**字节**。用 'Buffer.toString("utf8")' 去解一个
|
|
6
|
+
* GBK / Latin-1 文件**不会抛错**(非法字节被替换成 U+FFFD),合并结果再按 UTF-8 写回 ⇒ 原文件被
|
|
7
|
+
* **mojibake 覆盖、内容不可逆损毁**。换行符同理:把 CRLF 折成 LF 再 join('\n'),整份文件的行尾被
|
|
8
|
+
* 静默改写(Windows 上 = 全文件 diff)。
|
|
9
|
+
*
|
|
10
|
+
* 两条硬规矩:
|
|
11
|
+
* ① **无损或拒绝**:解出来的文本必须能逐字节编码回原字节(decodeTextBytes 自己证明这一点,见判据 1);
|
|
12
|
+
* 做不到就 ok:false,调用方必须**零写入** —— 绝不「差不多就行」。
|
|
13
|
+
* ② **形态不猜**:行尾只有 '\n' | '\r\n' 两种可无损表示的形式;孤立 '\r'(老 Mac 风格)无法用它们表示
|
|
14
|
+
* ⇒ 同样 ok:false,而不是悄悄把它当 LF 写掉。
|
|
15
|
+
*
|
|
16
|
+
* ── 归一化口径(**唯一的契约**,判据 1 就建立在这三条上)──
|
|
17
|
+
* · decodeTextBytes **只做一件事**:剥掉开头的 UTF-8 BOM(EF BB BF)。**不改写任何行尾** ——
|
|
18
|
+
* 返回的 text 里 CRLF / LF 原样保留,因此**混合行尾**的文件也能逐字节往返。
|
|
19
|
+
* · shape.eol 描述这份文本的**主导行尾**(完整的 CRLF 数**严格多于**纯 LF 数时为 '\r\n';
|
|
20
|
+
* 相等 / 没有整行行尾 / 空文本一律 '\n')。它供「需要新建行」的一方(三方合并)决定输出行尾,
|
|
21
|
+
* **不影响** encodeTextBytes 的字节。
|
|
22
|
+
* · encodeTextBytes(text, shape) **只做一件事**:shape.bom 为真时补回 3 字节 BOM。**不改写任何行尾**。
|
|
23
|
+
* 于是恒有 encodeTextBytes(d.text, d.shape) 逐字节等于原始 bytes(d = decodeTextBytes(bytes))。
|
|
24
|
+
* · 行尾的**归一化只发生在三方合并**(rollback-merge.ts):输出按 shape.eol 统一,并用 eolMixed /
|
|
25
|
+
* eolRewritten 如实标注,绝不假装「原样保留」。
|
|
26
|
+
*
|
|
27
|
+
* ── 已知口径(**不猜、不谎报**)──
|
|
28
|
+
* · 前 8 KiB 出现 NUL 字节 ⇒ 'binary'(与 utils/line-diff.ts、rollback-point.ts 同一口径)。
|
|
29
|
+
* · 非法 UTF-8(超长编码、孤立代理项的 CESU/WTF-8 编码、UTF-16 的 FF FE 前缀等)⇒ 'non-utf8'。
|
|
30
|
+
* · 「恰好也是合法 UTF-8 的无 BOM UTF-16LE」在**字节层面无法与 UTF-8 区分**(例:UTF-16LE 的
|
|
31
|
+
* 你好 = 60 4F 7D 59,是 4 个合法 ASCII 字节)。本模块**不猜编码**:它会被当作合法 UTF-8 读出,
|
|
32
|
+
* 但往返仍逐字节相同 ⇒ **不会写坏字节**(要「猜编码」需要额外启发式,那才是真正的越权)。
|
|
33
|
+
*/
|
|
34
|
+
/** NUL 探测窗口(与 line-diff / rollback-point 同口径:只看前 8 KiB) */
|
|
35
|
+
const NUL_PROBE_BYTES = 8192;
|
|
36
|
+
/** UTF-8 BOM 字节 */
|
|
37
|
+
const UTF8_BOM = new Uint8Array([0xef, 0xbb, 0xbf]);
|
|
38
|
+
/** 解码器:fatal = 非法 UTF-8 直接抛错(不替换成 U+FFFD);ignoreBOM = 不代我们剥 BOM(剥与不剥由本模块决定)。 */
|
|
39
|
+
const UTF8_DECODER = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
40
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
41
|
+
/** 前 8 KiB 是否出现 NUL 字节(与既有 binary 判据一致) */
|
|
42
|
+
function containsNul(bytes) {
|
|
43
|
+
const head = bytes.subarray(0, NUL_PROBE_BYTES);
|
|
44
|
+
for (let i = 0; i < head.length; i += 1) {
|
|
45
|
+
if (head[i] === 0)
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
/** 是否以 UTF-8 BOM(EF BB BF)开头 */
|
|
51
|
+
function startsWithUtf8Bom(bytes) {
|
|
52
|
+
return bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf;
|
|
53
|
+
}
|
|
54
|
+
/** 逐字节相等(保真自证用) */
|
|
55
|
+
function bytesEqual(left, right) {
|
|
56
|
+
if (left.length !== right.length)
|
|
57
|
+
return false;
|
|
58
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
59
|
+
if (left[i] !== right[i])
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 文本能否无损编码成 UTF-8 字节。
|
|
66
|
+
* 唯一会失败的形态是**孤立代理项**(TextEncoder 会把它替换成 U+FFFD ⇒ 写回即内容损毁)。
|
|
67
|
+
*/
|
|
68
|
+
export function isUtf8RoundTrippable(text) {
|
|
69
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
70
|
+
const code = text.charCodeAt(i);
|
|
71
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
72
|
+
// 高代理项后面必须紧跟低代理项,否则是孤立代理项
|
|
73
|
+
const next = text.charCodeAt(i + 1);
|
|
74
|
+
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
75
|
+
return false;
|
|
76
|
+
i += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (code >= 0xdc00 && code <= 0xdfff)
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
/** 扫描行尾:完整 CRLF / 纯 LF / 孤立 CR 各计数(三者互斥) */
|
|
85
|
+
function scanEol(text) {
|
|
86
|
+
let crlf = 0;
|
|
87
|
+
let lf = 0;
|
|
88
|
+
let loneCr = 0;
|
|
89
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
90
|
+
const code = text.charCodeAt(i);
|
|
91
|
+
if (code === 13) {
|
|
92
|
+
if (text.charCodeAt(i + 1) === 10) {
|
|
93
|
+
crlf += 1;
|
|
94
|
+
i += 1;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
loneCr += 1;
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (code === 10)
|
|
102
|
+
lf += 1;
|
|
103
|
+
}
|
|
104
|
+
return { crlf, lf, loneCr };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 探测一段**已解码文本**的形态:主导行尾、BOM 前缀、出现过的行尾风格、孤立 CR 计数。
|
|
108
|
+
* 纯描述、零改写(shape.eol 只用来决定「新建行」的行尾)。
|
|
109
|
+
*/
|
|
110
|
+
export function probeTextForm(text) {
|
|
111
|
+
const bom = text.startsWith('\uFEFF');
|
|
112
|
+
const body = bom ? text.slice(1) : text;
|
|
113
|
+
const scan = scanEol(body);
|
|
114
|
+
const eols = [];
|
|
115
|
+
if (scan.crlf > 0)
|
|
116
|
+
eols.push('\r\n');
|
|
117
|
+
if (scan.lf > 0)
|
|
118
|
+
eols.push('\n');
|
|
119
|
+
return {
|
|
120
|
+
// 严格多者胜:相等 / 没有整行行尾 / 空文本 ⇒ '\n'
|
|
121
|
+
eol: scan.crlf > scan.lf ? '\r\n' : '\n',
|
|
122
|
+
bom,
|
|
123
|
+
eols,
|
|
124
|
+
loneCr: scan.loneCr,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 字节 → 文本(**无损或拒绝**)。
|
|
129
|
+
*
|
|
130
|
+
* 自证(判据 1):成功时一定有 encodeTextBytes(result.text, result.shape) **逐字节等于**入参 bytes。
|
|
131
|
+
*
|
|
132
|
+
* 拒绝的两种形态(调用方**必须零写入**并按 reason 如实报告):
|
|
133
|
+
* · 'binary':前 8 KiB 含 NUL(不是文本,行 / 合并算法对它没有定义);
|
|
134
|
+
* · 'non-utf8':非法 UTF-8,或解码结果无法逐字节往返,或含孤立 CR(超出两种 EOL 的表示能力)。
|
|
135
|
+
*/
|
|
136
|
+
export function decodeTextBytes(bytes) {
|
|
137
|
+
if (containsNul(bytes))
|
|
138
|
+
return { ok: false, reason: 'binary' };
|
|
139
|
+
const bom = startsWithUtf8Bom(bytes);
|
|
140
|
+
const body = bom ? bytes.subarray(UTF8_BOM.length) : bytes;
|
|
141
|
+
let text;
|
|
142
|
+
try {
|
|
143
|
+
text = UTF8_DECODER.decode(body);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return { ok: false, reason: 'non-utf8' };
|
|
147
|
+
}
|
|
148
|
+
// 自证:解出来的文本必须能逐字节编码回原字节(防「解码器接受了、编码器却写不回」的边角形态)
|
|
149
|
+
if (!bytesEqual(UTF8_ENCODER.encode(text), body))
|
|
150
|
+
return { ok: false, reason: 'non-utf8' };
|
|
151
|
+
const form = probeTextForm(text);
|
|
152
|
+
// 孤立 CR:TextShape 表达不了 ⇒ 宁可拒绝,也绝不悄悄把它改成 LF
|
|
153
|
+
if (form.loneCr > 0)
|
|
154
|
+
return { ok: false, reason: 'non-utf8' };
|
|
155
|
+
return { ok: true, text, shape: { eol: form.eol, bom } };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 文本 → 字节(**保真**)。
|
|
159
|
+
*
|
|
160
|
+
* 契约:text 必须是 decodeTextBytes 输出的形态(BOM 已剥)。本函数**不改写任何行尾**,只在
|
|
161
|
+
* shape.bom 为真时补回 3 字节 BOM ⇒ 与 decodeTextBytes 互逆(判据 1)。
|
|
162
|
+
*
|
|
163
|
+
* 注意:shape.eol 在这里**不参与**字节生成 —— 行尾归一化只发生在三方合并里;在这里归一化就意味着
|
|
164
|
+
* 混合行尾 / CRLF 文件无法逐字节往返。含孤立代理项的文本按 UTF-8 替换字符写出(与 TextEncoder 一致):
|
|
165
|
+
* 调用方应先在 decodeTextBytes / mergeThreeWay 处把好关。
|
|
166
|
+
*/
|
|
167
|
+
export function encodeTextBytes(text, shape) {
|
|
168
|
+
const body = UTF8_ENCODER.encode(text);
|
|
169
|
+
if (!shape.bom)
|
|
170
|
+
return body;
|
|
171
|
+
const out = new Uint8Array(body.length + UTF8_BOM.length);
|
|
172
|
+
out.set(UTF8_BOM, 0);
|
|
173
|
+
out.set(body, UTF8_BOM.length);
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=text-fidelity.js.map
|
package/lib/core/types.d.ts
CHANGED
|
@@ -253,10 +253,19 @@ export interface PluginInfo {
|
|
|
253
253
|
}
|
|
254
254
|
export interface PluginsFacade {
|
|
255
255
|
listInstalled(): Promise<PluginInfo[]>;
|
|
256
|
-
/**
|
|
257
|
-
*
|
|
258
|
-
|
|
256
|
+
/**
|
|
257
|
+
* 安装/更新插件。`target` 是**已经推导好的完整安装目标**(`name@1.2.3`、`github:user/repo`、
|
|
258
|
+
* `file:/abs/path.tgz`),由 `core/plugin-cli.ts` 的 `pluginInstallTarget()` 统一决策 ——
|
|
259
|
+
* 门面只负责执行,不再二次推导(否则「执行日志里写的」与「真正跑的命令」会分叉,issue #101)。
|
|
260
|
+
* 缺省按裸包名装 npm 最新版(旧调用方 / 旧备份)。
|
|
261
|
+
*
|
|
262
|
+
* `installedVersion`:安装后回读 `node_modules/<pkg>/package.json` 的实际版本(读不到为 null)。
|
|
263
|
+
* 调用方据此校验升级是否真的落地,而不是「子进程返回 0 就当成功」(issue #101)。
|
|
264
|
+
* signal 可选:中止时宿主应 kill 子进程并清理半装状态(恢复 package.json + 删 node_modules/<pkg>)。
|
|
265
|
+
*/
|
|
266
|
+
install(pkg: string, target?: string, signal?: AbortSignal): Promise<{
|
|
259
267
|
needsRestart: boolean;
|
|
268
|
+
installedVersion?: string | null;
|
|
260
269
|
}>;
|
|
261
270
|
}
|
|
262
271
|
export interface WorkspaceFacade {
|
package/lib/core/update-check.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* 不读代理设置(Node 的 fetch 默认不认 HTTP_PROXY/HTTPS_PROXY):公司网络下可能直接失败 ——
|
|
18
18
|
* 这时如实报「检查失败」,其余功能不受影响(本模块与插件主干零耦合)。
|
|
19
19
|
*/
|
|
20
|
-
import { compareVersionStrings } from './validator.js';
|
|
20
|
+
import { compareVersionStrings, isConcreteVersion } from './validator.js';
|
|
21
21
|
/** 被检查的 npm 包名(= package.json 的 name;单点常量,勿在别处再写一份) */
|
|
22
22
|
export const PLUGIN_NPM_PACKAGE = 'dsh-config-manager';
|
|
23
23
|
/** registry 的 latest 端点(体积约数 KB,只取版本号) */
|
|
@@ -28,8 +28,6 @@ export const UPDATE_CHECK_TIMEOUT_MS = 5000;
|
|
|
28
28
|
export const UPDATE_CHECK_CACHE_MS = 10 * 60 * 1000;
|
|
29
29
|
/** 响应体上限(字节):latest 端点实测约 3 KB,超过一律视为异常(不把 registry 的大响应读进内存) */
|
|
30
30
|
export const UPDATE_CHECK_MAX_BYTES = 64 * 1024;
|
|
31
|
-
/** semver 形态判定(允许 1.2.3 / 1.2.3-rc.1 / 1.2.3+build;不允许多余字符) */
|
|
32
|
-
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
33
31
|
/**
|
|
34
32
|
* 解析 registry 的 latest 响应体(只认 `{ version: 'x.y.z' }`)。
|
|
35
33
|
* 畸形 / 缺字段 / 版本号不是 semver 形态 → null(调用方据此报「响应异常」,不猜)。
|
|
@@ -41,7 +39,7 @@ export function parseLatestVersion(payload) {
|
|
|
41
39
|
if (typeof version !== 'string')
|
|
42
40
|
return null;
|
|
43
41
|
const trimmed = version.trim();
|
|
44
|
-
return
|
|
42
|
+
return isConcreteVersion(trimmed) ? trimmed : null;
|
|
45
43
|
}
|
|
46
44
|
/**
|
|
47
45
|
* 请求 URL 是否要求**绕过缓存**(`?force=1`)。
|
package/lib/core/validator.d.ts
CHANGED
|
@@ -11,6 +11,15 @@ export declare function validateSections(sections: ReadonlyMap<SectionId, unknow
|
|
|
11
11
|
* (例如直接字符串比大小),`1.10.0` vs `1.9.0` 这类就会判错。
|
|
12
12
|
*/
|
|
13
13
|
export declare function compareVersionStrings(a: string, b: string): -1 | 0 | 1;
|
|
14
|
+
/**
|
|
15
|
+
* semver 形态判定(`1.2.3` / `1.2.3-rc.1` / `1.2.3+build`;区间 `^1.2.3`、dist-tag 与空串一律 false)。
|
|
16
|
+
*
|
|
17
|
+
* 与上面的 `compareVersionStrings` 分工不同:那个是**容错比较**(为了让 `0.2.1` 与 `0.2.1-rc.2`
|
|
18
|
+
* 也能排出大小,解析用宽松正则),这个是**严格形态判定** —— 只有它是具体版本时才允许拿来
|
|
19
|
+
* 拼 `<pkg>@<version>` 精确安装(issue #101)。导出为唯一实现,`self-update.ts`(是否接受目标
|
|
20
|
+
* 版本)与 `update-check.ts`(registry 返回值是否认得)都必须复用它,不许各留一份正则。
|
|
21
|
+
*/
|
|
22
|
+
export declare function isConcreteVersion(v: string): boolean;
|
|
14
23
|
/**
|
|
15
24
|
* 兼容性判定的**结构化原因**(与 `computeCompatibility` 同源:评分由本函数派生)。
|
|
16
25
|
*
|
package/lib/core/validator.js
CHANGED
|
@@ -42,6 +42,18 @@ function compareVersions(a, b) {
|
|
|
42
42
|
export function compareVersionStrings(a, b) {
|
|
43
43
|
return compareVersions(parseVersion(a), parseVersion(b));
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* semver 形态判定(`1.2.3` / `1.2.3-rc.1` / `1.2.3+build`;区间 `^1.2.3`、dist-tag 与空串一律 false)。
|
|
47
|
+
*
|
|
48
|
+
* 与上面的 `compareVersionStrings` 分工不同:那个是**容错比较**(为了让 `0.2.1` 与 `0.2.1-rc.2`
|
|
49
|
+
* 也能排出大小,解析用宽松正则),这个是**严格形态判定** —— 只有它是具体版本时才允许拿来
|
|
50
|
+
* 拼 `<pkg>@<version>` 精确安装(issue #101)。导出为唯一实现,`self-update.ts`(是否接受目标
|
|
51
|
+
* 版本)与 `update-check.ts`(registry 返回值是否认得)都必须复用它,不许各留一份正则。
|
|
52
|
+
*/
|
|
53
|
+
export function isConcreteVersion(v) {
|
|
54
|
+
return SEMVER_RE.test(v.trim());
|
|
55
|
+
}
|
|
56
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
45
57
|
/**
|
|
46
58
|
* 兼容性判定的**结构化原因**(与 `computeCompatibility` 同源:评分由本函数派生)。
|
|
47
59
|
*
|
package/lib/foreign/bundle.js
CHANGED
|
@@ -6,10 +6,14 @@
|
|
|
6
6
|
* 冲突判定、导入前强制快照、dry-run)自动生效,不需要为外部来源另开一条写入通道。
|
|
7
7
|
*
|
|
8
8
|
* 两个刻意为之的决定:
|
|
9
|
-
* ① manifest
|
|
10
|
-
*
|
|
9
|
+
* ① manifest 的**来源标记**只在翻译层给出 `result.origin` 时写入 `manifest.foreign`
|
|
10
|
+
* (字段形状见 src/schema/types.ts 的 ManifestForeignOrigin;只有标识与计数,绝不含值)。
|
|
11
|
+
* origin 缺省 = **不写该键**,DSH 自产备份与旧包逐字不变(additive)。
|
|
12
|
+
* 审计结论:标准 bundle v1 原本没有任何「这包从哪来」的字段,外部来源包落盘后与自产备份
|
|
13
|
+
* 逐字同构 ⇒ 用户无法复核来源。本块就是补上这一位(读侧宽容,见 manifest.ts)。
|
|
11
14
|
* ② source.dshVersion 填**导入目标机**的版本:包内容是为「现在导入」合成的;
|
|
12
|
-
*
|
|
15
|
+
* 填外部工具的版本会让兼容性评分走上一条没有意义的分支(**已知缺陷**:这让评分被
|
|
16
|
+
* 构造性拉满 —— 真实来源事实由 `foreign` 承载,评分口径本身不在本文件范围内)。
|
|
13
17
|
*/
|
|
14
18
|
import { buildManifest, CHECKSUMS_FILE, MANIFEST_FILE } from '../schema/manifest.js';
|
|
15
19
|
import { isFileSection, SECTION_FILE_PREFIXES, SECTION_JSON_PATHS } from '../schema/config.js';
|
|
@@ -20,6 +24,37 @@ import { engineMsg } from './messages.js';
|
|
|
20
24
|
function utf8(s) {
|
|
21
25
|
return new TextEncoder().encode(s);
|
|
22
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* ForeignOrigin(翻译层的溯源块)→ ManifestForeignOrigin(manifest 字段形状)。
|
|
29
|
+
*
|
|
30
|
+
* 唯一的形状差异是 **source → agent**(对外契约用 agent 表达「来源是谁」);其余字段逐字搬运。
|
|
31
|
+
* 两个方向都只带**标识与计数**:凭据只有引用名、诊断只有机器码 —— 这里不做任何裁剪,
|
|
32
|
+
* 因为裁剪会悄悄改写来源事实;不变量由上游 ForeignOrigin 的装配侧保证(本层只搬运)。
|
|
33
|
+
*/
|
|
34
|
+
function toManifestForeignOrigin(origin) {
|
|
35
|
+
return {
|
|
36
|
+
agent: origin.source,
|
|
37
|
+
locations: [...origin.locations],
|
|
38
|
+
...(origin.sourceVersion !== undefined ? { sourceVersion: origin.sourceVersion } : {}),
|
|
39
|
+
...(origin.projects !== undefined ? { projects: [...origin.projects] } : {}),
|
|
40
|
+
...(origin.plugins !== undefined
|
|
41
|
+
? {
|
|
42
|
+
plugins: origin.plugins.map((p) => ({
|
|
43
|
+
name: p.name,
|
|
44
|
+
...(p.version !== undefined ? { version: p.version } : {}),
|
|
45
|
+
...(p.marketplace !== undefined ? { marketplace: p.marketplace } : {}),
|
|
46
|
+
...(p.repo !== undefined ? { repo: p.repo } : {}),
|
|
47
|
+
...(p.ref !== undefined ? { ref: p.ref } : {}),
|
|
48
|
+
...(p.components !== undefined ? { components: [...p.components] } : {}),
|
|
49
|
+
})),
|
|
50
|
+
}
|
|
51
|
+
: {}),
|
|
52
|
+
...(origin.skipped !== undefined
|
|
53
|
+
? { skipped: origin.skipped.map((s) => ({ code: s.code, count: s.count })) }
|
|
54
|
+
: {}),
|
|
55
|
+
...(origin.credentialRefs !== undefined ? { credentialRefs: [...origin.credentialRefs] } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
23
58
|
export async function writeForeignBundle(opts) {
|
|
24
59
|
const entries = [];
|
|
25
60
|
const flags = {};
|
|
@@ -60,8 +95,12 @@ export async function writeForeignBundle(opts) {
|
|
|
60
95
|
encrypted: false,
|
|
61
96
|
encryption: null,
|
|
62
97
|
exportedAt: opts.exportedAt,
|
|
98
|
+
// origin 缺省 = 不传 ⇒ manifest 里不产生 foreign 键(旧行为逐字不变)
|
|
99
|
+
...(opts.result.origin !== undefined ? { foreign: toManifestForeignOrigin(opts.result.origin) } : {}),
|
|
63
100
|
});
|
|
64
101
|
// checksums 覆盖除 manifest/checksums 自身以外的全部条目(与 exporter 一致)
|
|
102
|
+
// 注意:foreign 只进 manifest.json,既不是 ZIP 条目、也不参与任何条目摘要 ⇒
|
|
103
|
+
// 同一份内容在「有/无 origin」两种包里的 integrity/checksums.json **逐字相同**。
|
|
65
104
|
const contentEntries = entries.filter((e) => e.name !== MANIFEST_FILE && e.name !== CHECKSUMS_FILE);
|
|
66
105
|
entries.push({
|
|
67
106
|
name: CHECKSUMS_FILE,
|