@tokensapi/dsh-plugin-check 0.3.4 → 0.4.0

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.
@@ -1,206 +1,78 @@
1
- /* ============================================================
2
- * 已发布包体检(注册表版,零依赖纯函数)
3
- * ============================================================
4
- * 对 npm registry 的版本文档跑清单规则,与目录体检(manifest-check)
5
- * 同一套语义,但不需要文件系统 —— 供 Cowork 插件进程内使用。
6
- *
7
- * 两个入口:
8
- * - checkPublishedManifest 纯同步零依赖,只看清单字段;
9
- * - checkPublishedPackage 额外取 tarball 读出 cordis.patch.yml,
10
- * 补上 M4/M5 —— 只看清单会漏掉"补丁行 name 写成插件名"这类
11
- * 装上即让整棵插件树崩溃的缺陷。
12
- * 冒烟阶段无法在宿主进程执行,完整体检走 CLI 或 Plugin Check
13
- * workflow。
14
- * ============================================================ */
15
- import { checkPatchContent } from './patch-rows.mjs'
16
- import { fetchTarEntry } from './tarball.mjs'
17
- import { LIFECYCLE_SCRIPTS, lifecycleMessage } from './contract.mjs'
18
-
19
- const isIdentityCore = (name) =>
20
- name === '@deepseek-ai/cordis' || name.startsWith('@deepseek-ai/cordis-') || name.startsWith('@deepseek-ai/dsh')
21
-
22
- /* ------------------------- 迷你 SemVer ------------------------- */
23
- // 只实现体检需要的子集:比较 + 范围判定(空格=且、||=或、^、~、
24
- // >=、>、<=、<、=、裸版本),预发布参与比较;识别不了的范围返回
25
- // null 由调用方按"范围不合法"处理。
26
-
27
- function parseVersion(input) {
28
- const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(String(input).trim())
29
- if (!match) return null
30
- return {
31
- parts: [Number(match[1]), Number(match[2]), Number(match[3])],
32
- prerelease: match[4] === undefined ? [] : match[4].split('.'),
33
- }
34
- }
35
-
36
- function compareVersions(a, b) {
37
- for (let index = 0; index < 3; index += 1) {
38
- if (a.parts[index] !== b.parts[index]) return a.parts[index] < b.parts[index] ? -1 : 1
39
- }
40
- if (a.prerelease.length === 0 && b.prerelease.length === 0) return 0
41
- if (a.prerelease.length === 0) return 1
42
- if (b.prerelease.length === 0) return -1
43
- const length = Math.max(a.prerelease.length, b.prerelease.length)
44
- for (let index = 0; index < length; index += 1) {
45
- const left = a.prerelease[index]
46
- const right = b.prerelease[index]
47
- if (left === undefined) return -1
48
- if (right === undefined) return 1
49
- const leftNumeric = /^\d+$/u.test(left)
50
- const rightNumeric = /^\d+$/u.test(right)
51
- if (leftNumeric && rightNumeric) {
52
- if (Number(left) !== Number(right)) return Number(left) < Number(right) ? -1 : 1
53
- } else if (leftNumeric !== rightNumeric) {
54
- return leftNumeric ? -1 : 1
55
- } else if (left !== right) {
56
- return left < right ? -1 : 1
57
- }
58
- }
59
- return 0
60
- }
61
-
62
- function comparatorHolds(version, operator, boundary) {
63
- const cmp = compareVersions(version, boundary)
64
- if (operator === '>') return cmp > 0
65
- if (operator === '>=') return cmp >= 0
66
- if (operator === '<') return cmp < 0
67
- if (operator === '<=') return cmp <= 0
68
- return cmp === 0
69
- }
70
-
71
- export function versionSatisfies(versionInput, rangeInput) {
72
- const version = parseVersion(versionInput)
73
- if (version === null) return null
74
- const alternatives = String(rangeInput).split('||')
75
- let recognized = false
76
- for (const alternative of alternatives) {
77
- const comparators = alternative.trim().split(/\s+/u).filter(Boolean)
78
- if (comparators.length === 0) continue
79
- let holds = true
80
- let valid = true
81
- for (const comparator of comparators) {
82
- if (comparator === '*' || comparator === 'x') continue
83
- const match = /^(>=|<=|>|<|=|\^|~)?(.+)$/u.exec(comparator)
84
- const boundary = parseVersion(match[2])
85
- if (boundary === null) { valid = false; break }
86
- const operator = match[1] ?? '='
87
- if (operator === '^') {
88
- const upper = boundary.parts[0] > 0
89
- ? { parts: [boundary.parts[0] + 1, 0, 0], prerelease: ['0'] }
90
- : boundary.parts[1] > 0
91
- ? { parts: [0, boundary.parts[1] + 1, 0], prerelease: ['0'] }
92
- : { parts: [0, 0, boundary.parts[2] + 1], prerelease: ['0'] }
93
- holds = holds && comparatorHolds(version, '>=', boundary) && comparatorHolds(version, '<', upper)
94
- } else if (operator === '~') {
95
- const upper = { parts: [boundary.parts[0], boundary.parts[1] + 1, 0], prerelease: ['0'] }
96
- holds = holds && comparatorHolds(version, '>=', boundary) && comparatorHolds(version, '<', upper)
97
- } else {
98
- holds = holds && comparatorHolds(version, operator, boundary)
99
- }
100
- if (!holds) break
101
- }
102
- if (!valid) continue
103
- recognized = true
104
- if (holds) return true
105
- }
106
- return recognized ? false : null
107
- }
108
-
109
- /* --------------------------- 规则 --------------------------- */
110
-
111
- /**
112
- * 对一份 npm 版本清单跑体检规则。
113
- * @param {object} manifest - registry 版本文档(scripts/dependencies/
114
- * peerDependencies/dsh/engines/license/version)。
115
- * @param {string} runtime - 目标 DSH 运行时版本。
116
- * @returns {Array<{level: 'error'|'warning', rule: string, message: string}>}
117
- */
118
- export function checkPublishedManifest(manifest, runtime) {
119
- const findings = []
120
- const error = (rule, message) => findings.push({ level: 'error', rule, message })
121
- const warning = (rule, message) => findings.push({ level: 'warning', rule, message })
122
-
123
- const scripts = manifest.scripts ?? {}
124
- for (const script of LIFECYCLE_SCRIPTS) {
125
- if (typeof scripts[script] === 'string') warning('M2-lifecycle', lifecycleMessage(script))
126
- }
127
-
128
- for (const name of Object.keys(manifest.dependencies ?? {})) {
129
- if (isIdentityCore(name)) {
130
- error('M3-core-peer', `${name} 出现在 dependencies;核心运行时必须是 peerDependencies,否则双内核实例会让 Cowork 启动失败`)
131
- }
132
- }
133
-
134
- const bundle = manifest.dsh?.bundle ?? {}
135
- if (typeof bundle.patch !== 'string' || bundle.patch === '') {
136
- error('M4-bundle', 'dsh.bundle.patch 缺失;宿主靠它把插件挂进加载树')
137
- }
138
-
139
- const engine = typeof manifest.dsh?.engine === 'string' ? manifest.dsh.engine : undefined
140
- if (engine === undefined) {
141
- warning('M6-engine', 'dsh.engine 未声明(目标运行时 SemVer 范围);'
142
- + '上游目前不强制读取该字段,但声明之后体检才能判断你与目标运行时是否相容')
143
- } else {
144
- const satisfied = versionSatisfies(runtime, engine)
145
- if (satisfied === null) error('M6-engine', `dsh.engine 不是可识别的 SemVer 范围: ${engine}`)
146
- else if (!satisfied) error('M6-engine', `dsh.engine "${engine}" 不覆盖当前运行时 ${runtime}`)
147
- }
148
-
149
- for (const [name, range] of Object.entries(manifest.peerDependencies ?? {})) {
150
- if (!name.startsWith('@deepseek-ai/dsh')) continue
151
- const satisfied = versionSatisfies(runtime, String(range))
152
- if (satisfied === null) error('M7-peer-range', `peer ${name} 的范围不可识别: ${range}`)
153
- else if (!satisfied) warning('M7-peer-range', `peer ${name}@"${range}" 不含当前运行时 ${runtime};宿主升级后可能运行时断裂`)
154
- }
155
-
156
- if (manifest.engines?.node === undefined) warning('M8-node-engines', 'engines.node 未声明;宿主运行在 Node 22+')
157
- if (manifest.license === undefined) warning('M9-license', 'license 未声明')
158
-
159
- const parsed = parseVersion(typeof manifest.version === 'string' ? manifest.version : '')
160
- if (parsed !== null && parsed.prerelease.length > 0) {
161
- warning('M1-manifest', `version ${manifest.version} 是预发布版;`
162
- + `市场受控安装只认稳定的精确版(上游 stableExactVersion),npm latest 停在预发布版时`
163
- + `用户端只会看到手动安装命令。预发布请发在 next 之类的 dist-tag 上`)
164
- }
165
-
166
- return findings
167
- }
168
-
169
- /**
170
- * 已发布包的完整清单侧体检:清单规则 + 包内补丁行检查。
171
- * 取不到 tarball(离线、超时、体积过大)时降级为 warning 并如实
172
- * 标注未检查,绝不因网络问题把插件判成不合格。
173
- * @param {object} manifest - registry 版本文档(需含 dist.tarball)。
174
- * @param {string} runtime - 目标 DSH 运行时版本。
175
- * @param {{ maxBytes?: number, timeoutMs?: number }} [options]
176
- * @returns {Promise<{ findings: object[], inspectedPatch: boolean }>}
177
- */
178
- export async function checkPublishedPackage(manifest, runtime, options = {}) {
179
- const findings = checkPublishedManifest(manifest, runtime)
180
- const patch = typeof manifest.dsh?.bundle?.patch === 'string' ? manifest.dsh.bundle.patch : ''
181
- const tarball = typeof manifest.dist?.tarball === 'string' ? manifest.dist.tarball : ''
182
- // 补丁未声明时 M4 已在清单阶段报错,无需再取包。
183
- if (patch === '' || tarball === '') return { findings, inspectedPatch: false }
184
-
185
- const relative = patch.replace(/^\.?\//u, '')
186
- const result = await fetchTarEntry(tarball, entry => entry === relative, options)
187
- if (result.error !== undefined) {
188
- findings.push({
189
- level: 'warning',
190
- rule: 'M4-bundle',
191
- message: `未能读取包内 ${relative}(${result.error});本次跳过补丁行检查,完整体检请走 CLI`,
192
- })
193
- return { findings, inspectedPatch: false }
194
- }
195
- if (result.content === undefined) {
196
- findings.push({
197
- level: 'error',
198
- rule: 'M4-bundle',
199
- message: `dsh.bundle.patch 指向的文件不在发布内容里: ${patch};宿主取不到补丁就挂不上插件(检查 files 白名单)`,
200
- })
201
- return { findings, inspectedPatch: true }
202
- }
203
- const packageName = typeof manifest.name === 'string' ? manifest.name : ''
204
- findings.push(...checkPatchContent(packageName, result.content).findings)
205
- return { findings, inspectedPatch: true }
206
- }
1
+ /* ============================================================
2
+ * 已发布包体检(注册表版)
3
+ * ============================================================
4
+ * 对 npm registry 的版本文档跑清单规则,与目录体检(manifest-check)
5
+ * **同一份实现** —— 不是"同一套语义"而已:清单侧规则全部来自
6
+ * checkManifestFields,两条路径不可能再对同一个包给出不同结论。
7
+ *
8
+ * 这里曾经自带一套手写的迷你 SemVer,理由是"会话内要零依赖";
9
+ * 那个前提本来就不成立(semver 是本包的 dependencies,宿主进程里
10
+ * 早就通过 cowork-plugin manifest-check 加载了),代价却是把
11
+ * ">=0.1 <0.2"、"1.x"、"1.2.0 - 2.0.0" 这些完全合法的 npm 范围判成
12
+ * "不可识别" error,合格插件被判不合格。已删除。
13
+ *
14
+ * 两个入口:
15
+ * - checkPublishedManifest 只看清单字段;
16
+ * - checkPublishedPackage 额外取 tarball 读出 cordis.patch.yml,
17
+ * 补上 M4/M5/M11 —— 只看清单会漏掉"补丁行 name 写成插件名"
18
+ * 这类装上即让整棵插件树崩溃的缺陷。
19
+ * 冒烟阶段无法在宿主进程执行,完整体检走 CLI 或 Plugin Check
20
+ * workflow。
21
+ * ============================================================ */
22
+ import { checkPatchContent } from './patch-rows.mjs'
23
+ import { fetchTarEntry } from './tarball.mjs'
24
+ import { checkManifestFields } from './manifest-check.mjs'
25
+
26
+ export { versionSatisfies } from './semver-rules.mjs'
27
+
28
+ /**
29
+ * 对一份 npm 版本清单跑体检规则。
30
+ * @param {object} manifest - registry 版本文档(scripts/dependencies/
31
+ * peerDependencies/dsh/engines/license/version)
32
+ * @param {string} [runtime] - 目标 DSH 运行时版本;省略时只判范围合法性,
33
+ * 不判是否覆盖(以前这是必填位置参数且无 guard,传 undefined 会产生
34
+ * 一批凭空的 M6/M7 error)。
35
+ * @returns {Array<{level: 'error'|'warning', rule: string, message: string}>}
36
+ */
37
+ export function checkPublishedManifest(manifest, runtime) {
38
+ return checkManifestFields(manifest, runtime)
39
+ }
40
+
41
+ /**
42
+ * 已发布包的完整清单侧体检:清单规则 + 包内补丁行检查。
43
+ * 取不到 tarball(离线、超时、体积过大)时降级为 warning 并如实
44
+ * 标注未检查,绝不因网络问题把插件判成不合格。
45
+ * @param {object} manifest - registry 版本文档(需含 dist.tarball)。
46
+ * @param {string} [runtime] - 目标 DSH 运行时版本。
47
+ * @param {{ maxBytes?: number, timeoutMs?: number }} [options]
48
+ * @returns {Promise<{ findings: object[], inspectedPatch: boolean }>}
49
+ */
50
+ export async function checkPublishedPackage(manifest, runtime, options = {}) {
51
+ const findings = checkPublishedManifest(manifest, runtime)
52
+ const patch = typeof manifest.dsh?.bundle?.patch === 'string' ? manifest.dsh.bundle.patch : ''
53
+ const tarball = typeof manifest.dist?.tarball === 'string' ? manifest.dist.tarball : ''
54
+ // 补丁未声明时 M4 已在清单阶段报错,无需再取包。
55
+ if (patch === '' || tarball === '') return { findings, inspectedPatch: false }
56
+
57
+ const relative = patch.replace(/^\.?\//u, '')
58
+ const result = await fetchTarEntry(tarball, entry => entry === relative, options)
59
+ if (result.error !== undefined) {
60
+ findings.push({
61
+ level: 'warning',
62
+ rule: 'M4-bundle',
63
+ message: `未能读取包内 ${relative}(${result.error});本次跳过补丁行检查,完整体检请走 CLI`,
64
+ })
65
+ return { findings, inspectedPatch: false }
66
+ }
67
+ if (result.content === undefined) {
68
+ findings.push({
69
+ level: 'error',
70
+ rule: 'M4-bundle',
71
+ message: `dsh.bundle.patch 指向的文件不在发布内容里: ${patch};宿主取不到补丁就挂不上插件(检查 files 白名单)`,
72
+ })
73
+ return { findings, inspectedPatch: true }
74
+ }
75
+ const packageName = typeof manifest.name === 'string' ? manifest.name : ''
76
+ findings.push(...checkPatchContent(packageName, result.content).findings)
77
+ return { findings, inspectedPatch: true }
78
+ }
@@ -0,0 +1,129 @@
1
+ /* ============================================================
2
+ * 版本相关规则(M1 / M6 / M7)的唯一实现
3
+ * ============================================================
4
+ * 这三条规则以前在两处各写一遍:CLI 走 manifest-check.mjs(用真
5
+ * semver),会话内 package= 走 registry-check.mjs(用一套手写的迷你
6
+ * SemVer)。迷你版只认完整三段版本号,把 ">=0.1 <0.2"、"1.x"、
7
+ * "1.2.0 - 2.0.0" 这些完全合法的 npm 范围判成"不可识别"→ error,
8
+ * 于是同一个包在两条路径上会拿到相反的结论,而且合格插件被判不合格。
9
+ *
10
+ * 现在统一到真 semver,并且只此一份:两条路径调同一个函数,再想漂
11
+ * 也漂不了(tests/cross-path.test.mjs 是这条约束的闸门)。
12
+ *
13
+ * 判定口径:一律带 includePrerelease —— 目标运行时本身就长期是
14
+ * 0.1.3-alpha.1 这种预发布版,不带这个选项 semver 会把它排除在
15
+ * 任何范围之外,得出"谁都不兼容"的荒谬结论。
16
+ * ============================================================ */
17
+ import semver from 'semver'
18
+ import { isIdentityCore, isRuntimeVersioned } from './contract.mjs'
19
+
20
+ const OPTIONS = { includePrerelease: true }
21
+
22
+ /**
23
+ * 版本是否落在范围内。
24
+ * @param {string} version - 具体版本。
25
+ * @param {string} range - SemVer 范围。
26
+ * @returns {boolean | null} 范围不合法时返回 null(保持既有对外契约)。
27
+ */
28
+ export function versionSatisfies(version, range) {
29
+ if (semver.valid(version) === null) return null
30
+ if (semver.validRange(range, OPTIONS) === null) return null
31
+ return semver.satisfies(version, range, OPTIONS)
32
+ }
33
+
34
+ /**
35
+ * M1:name 与 version 的基本合法性。
36
+ * @param {object} manifest - package.json / registry 版本文档。
37
+ * @returns {Array<{level: string, rule: string, message: string}>}
38
+ */
39
+ export function checkIdentity(manifest) {
40
+ const findings = []
41
+ if (typeof manifest.name !== 'string' || manifest.name === '') {
42
+ findings.push({ level: 'error', rule: 'M1-manifest', message: 'name 缺失' })
43
+ }
44
+ if (typeof manifest.version !== 'string' || semver.valid(manifest.version) === null) {
45
+ findings.push({ level: 'error', rule: 'M1-manifest', message: 'version 不是合法 SemVer' })
46
+ } else if (semver.prerelease(manifest.version) !== null) {
47
+ findings.push({
48
+ level: 'warning',
49
+ rule: 'M1-manifest',
50
+ message: `version ${manifest.version} 是预发布版;`
51
+ + `市场受控安装只认稳定的精确版(上游 stableExactVersion),npm latest 若停在预发布版,`
52
+ + `用户端拿不到一键安装按钮,只会看到手动命令。预发布请发在 next 之类的 dist-tag 上`,
53
+ })
54
+ }
55
+ return findings
56
+ }
57
+
58
+ /**
59
+ * M6:dsh.engine 声明的目标运行时范围。
60
+ * @param {object} manifest
61
+ * @param {string | undefined} runtime - 目标 DSH 运行时;未给时只判范围合法性。
62
+ * @returns {Array<{level: string, rule: string, message: string}>}
63
+ */
64
+ export function checkEngine(manifest, runtime) {
65
+ const engine = typeof manifest.dsh?.engine === 'string' ? manifest.dsh.engine : undefined
66
+ if (engine === undefined) {
67
+ return [{
68
+ level: 'warning',
69
+ rule: 'M6-engine',
70
+ message: 'dsh.engine 未声明(目标 DSH 运行时 SemVer 范围,如 ">=0.1.3-alpha.1 <0.2");'
71
+ + '上游目前不强制读取该字段,但声明之后体检才能判断你与目标运行时是否相容',
72
+ }]
73
+ }
74
+ if (semver.validRange(engine, OPTIONS) === null) {
75
+ return [{ level: 'error', rule: 'M6-engine', message: `dsh.engine 不是合法 SemVer 范围: ${engine}` }]
76
+ }
77
+ if (runtime !== undefined && runtime !== '' && !semver.satisfies(runtime, engine, OPTIONS)) {
78
+ return [{ level: 'error', rule: 'M6-engine', message: `dsh.engine "${engine}" 不覆盖目标运行时 ${runtime}` }]
79
+ }
80
+ return []
81
+ }
82
+
83
+ /**
84
+ * M7:核心 peer 的范围。
85
+ *
86
+ * 两层判定的口径**故意不同**:
87
+ * - 范围合法性:全部 isIdentityCore 的包。以前只看 @deepseek-ai/dsh
88
+ * 前缀,@deepseek-ai/cordis 的范围写坏了完全不报,偏偏它正是 S2 冒烟
89
+ * 真正要装的那个包,范围写坏下一步必然装不出来。
90
+ * - 是否含目标运行时:只对 isRuntimeVersioned 的包。cordis 自成 4.x
91
+ * 版本线,拿它跟运行时 0.1.3-alpha.1 比是范畴错误(详见 contract.mjs)。
92
+ * @param {object} manifest
93
+ * @param {string | undefined} runtime
94
+ * @returns {Array<{level: string, rule: string, message: string}>}
95
+ */
96
+ export function checkPeerRanges(manifest, runtime) {
97
+ const findings = []
98
+ for (const [name, range] of Object.entries(manifest.peerDependencies ?? {})) {
99
+ if (!isIdentityCore(name)) continue
100
+ if (semver.validRange(String(range), OPTIONS) === null) {
101
+ findings.push({ level: 'error', rule: 'M7-peer-range', message: `peer ${name} 的范围不合法: ${range}` })
102
+ continue
103
+ }
104
+ if (!isRuntimeVersioned(name)) continue
105
+ if (runtime === undefined || runtime === '') continue
106
+ if (!semver.satisfies(runtime, String(range), OPTIONS)) {
107
+ findings.push({
108
+ level: 'warning',
109
+ rule: 'M7-peer-range',
110
+ message: `peer ${name}@"${range}" 不含目标运行时 ${runtime};宿主升级后此插件可能在运行时断裂(接口错配类故障)`,
111
+ })
112
+ }
113
+ }
114
+ return findings
115
+ }
116
+
117
+ /**
118
+ * 三条一起跑,供两条执行路径直接复用。
119
+ * @param {object} manifest
120
+ * @param {string | undefined} runtime
121
+ * @returns {Array<{level: string, rule: string, message: string}>}
122
+ */
123
+ export function checkVersionRules(manifest, runtime) {
124
+ return [
125
+ ...checkIdentity(manifest),
126
+ ...checkEngine(manifest, runtime),
127
+ ...checkPeerRanges(manifest, runtime),
128
+ ]
129
+ }
@@ -193,7 +193,19 @@ export function checkSmoke(pluginDir, manifest, rows, options = {}) {
193
193
  error('S4-apply', `冒烟子进程异常: ${reason}`)
194
194
  return { findings, workspace: options.keepWorkspace ? workspace : undefined }
195
195
  }
196
- const results = JSON.parse(readFileSync(resultPath, 'utf8'))
196
+ // 子进程写出的结果文件理论上总是合法 JSON,但它可能被入口代码
197
+ // 的 stdout 污染、被磁盘写满截断、或者被入口自己覆盖掉。那种时候
198
+ // 崩掉整个 CLI 等于把"这个插件冒烟失败"变成"体检工具坏了",
199
+ // 作者拿不到任何可用结论 —— 按 S4-apply error 如实报出来。
200
+ let results
201
+ try {
202
+ results = JSON.parse(readFileSync(resultPath, 'utf8'))
203
+ if (!Array.isArray(results)) throw new TypeError('结果不是数组')
204
+ } catch (cause) {
205
+ const reason = cause instanceof Error ? cause.message : String(cause)
206
+ error('S4-apply', `冒烟结果文件不可解析: ${reason}(多半是插件入口往 stdout/结果文件里写了东西)`)
207
+ return { findings, workspace: options.keepWorkspace ? workspace : undefined }
208
+ }
197
209
  const passed = results.filter(result => result.status === 'ok').length
198
210
  findings.push({ level: 'info', rule: 'S4-apply', message: `隔离冒烟: ${passed}/${results.length} 行通过(解析→import→应用)` })
199
211
  for (const result of results) {
package/lib/tarball.mjs CHANGED
@@ -88,7 +88,11 @@ export async function fetchTarEntry(url, match, options = {}) {
88
88
  if (declared > maxBytes) return { error: `tarball 超过 ${Math.round(maxBytes / 1024 / 1024)}MB,跳过包内检查` }
89
89
  const raw = Buffer.from(await response.arrayBuffer())
90
90
  if (raw.length > maxBytes) return { error: `tarball 超过 ${Math.round(maxBytes / 1024 / 1024)}MB,跳过包内检查` }
91
- return { content: readTarEntry(gunzipSync(raw), match) }
91
+ // 上面两道闸只管住了**压缩后**的字节数,解压后的体积不受它约束
92
+ // —— 一个几百 KB 的 gzip 可以解成几 GB。maxOutputLength 让 zlib
93
+ // 在超限时抛错(ERR_BUFFER_TOO_LARGE)而不是把内存吃干。
94
+ const tar = gunzipSync(raw, { maxOutputLength: maxBytes })
95
+ return { content: readTarEntry(tar, match) }
92
96
  } catch (cause) {
93
97
  const reason = cause instanceof Error ? cause.message : String(cause)
94
98
  return { error: cause?.name === 'AbortError' ? '下载 tarball 超时' : `读取 tarball 失败: ${reason}` }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokensapi/dsh-plugin-check",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "TokensCowork/DSH 插件合格性体检:CLI 一条命令判定插件能否安全装入宿主;装入 Cowork 后提供会话内 plugin_check 工具。",
5
5
  "type": "module",
6
6
  "bin": {