@bushizhanghan/dsh-publish-gate 0.8.0 → 0.8.2
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/CHANGELOG.md +13 -0
- package/README.md +2 -1
- package/lib/engine.js +29 -0
- package/lib/index.js +15 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.2
|
|
4
|
+
|
|
5
|
+
- `publish_gate_verify` now returns `pending: true` when the wait runs out with the version still invisible.
|
|
6
|
+
Measured on 0.8.1 itself: the version appeared **four minutes** after `npm publish`, so one call cannot
|
|
7
|
+
outwait it — the cap is 90 s because the host kills a tool call at 120 s. `pending` says "run it again",
|
|
8
|
+
which is a different answer from "the registry holds different bytes".
|
|
9
|
+
## 0.8.1
|
|
10
|
+
|
|
11
|
+
- `publish_gate_verify` gained `waitMs` (capped at 90 s) and reports `waitedMs`. npm answers 202 and keeps
|
|
12
|
+
the version invisible for 60–180 s after a publish, so the single-shot check reported "not on the registry"
|
|
13
|
+
for releases that had already succeeded — it happened on this plugin own 0.8.0 publish, which stayed
|
|
14
|
+
invisible for over three minutes. `waitForPublished()` polls until the shasum appears; `waitMs: 0` keeps
|
|
15
|
+
the old single-shot behaviour, and a real mismatch stops the poll immediately.
|
|
3
16
|
## 0.8.0
|
|
4
17
|
|
|
5
18
|
- `tool-input-throws` (error): `publish_gate_tools` gained `fuzz` and `skip`. With `fuzz: true` it drives
|
package/README.md
CHANGED
|
@@ -207,6 +207,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
|
|
|
207
207
|
- **产物层用 `npm pack --dry-run`,不用自己推 `files` 规则**:npm 的入包规则(`main` / `exports` 强制入包、README/LICENSE 强制入包、`.npmignore` 优先级)比自己实现的准。唯一的代价是它需要这台机器上有 npm,所以跑不起来时降级成 `pack-unavailable` 而不是报错。
|
|
208
208
|
- **运行时层真的执行插件代码**:这是唯一能回答「装上能不能用」的办法。代价是 `apply()` 会跑,所以:假 ctx 是 Proxy 不落盘、20 秒硬超时、`skipTools` 一键跳过。
|
|
209
209
|
- **`--ignore-scripts`**:dry run 不跑 `prepack`,否则检查会触发一次构建。代价是清单可能和真实产物不同,所以有 `pack-script-build` 把这件事说出来。
|
|
210
|
+
- **发完那几十秒不算失败**:npm 收下包会先回 202,然后六十秒到几分钟里这个版本在 registry 上根本不存在。这个插件自己的 0.8.1 实测是**四分钟**才出现。所以 `publish_gate_verify` 加了 `waitMs` 轮询;一轮等不到就返回 `pending: true`——那是「还没轮到」,不是「没发成功」。单次最多等 90 秒,因为宿主 120 秒就掐掉工具调用,剩下的只能再跑一次。
|
|
210
211
|
- **`publish_gate_verify` 比 shasum,不比版本号**:版本号相同而字节不同(发了又改、或本地根本没发)是最常见的自欺。`npm pack --dry-run` 的 shasum 和 registry 的 `dist.shasum` 是同一套算法,所以这个比较是精确的。
|
|
211
212
|
- **只读**:修不修由你定,每条 finding 都带 `fix` 文案。门禁的职责是把话说清楚,不是替你做决定——所以没有 `--fix`。
|
|
212
213
|
- **确定性**:全部规则是文件内容、子进程或 registry/GitHub API 检查,没有 LLM 调用——一条规则答错,你可以直接看规则。
|
|
@@ -215,7 +216,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
|
|
|
215
216
|
## 开发
|
|
216
217
|
|
|
217
218
|
```sh
|
|
218
|
-
node --test #
|
|
219
|
+
node --test # 93 个引擎单测
|
|
219
220
|
node test/integration.test.mjs # 27 条集成断言(9 个真实插件作 fixture)
|
|
220
221
|
```
|
|
221
222
|
|
package/lib/engine.js
CHANGED
|
@@ -1916,6 +1916,35 @@ export async function checkPublishedMatches(name, version, localPack, { registry
|
|
|
1916
1916
|
return out
|
|
1917
1917
|
}
|
|
1918
1918
|
|
|
1919
|
+
/**
|
|
1920
|
+
* Poll the registry until the published bytes show up.
|
|
1921
|
+
*
|
|
1922
|
+
* npm answers 202 and keeps the version invisible for 60–180 s, so a single
|
|
1923
|
+
* shot right after `npm publish` reports "not on the registry" for a release
|
|
1924
|
+
* that already succeeded — the most common false alarm this plugin produces,
|
|
1925
|
+
* including on its own publishes. `probe` is injectable so the loop tests
|
|
1926
|
+
* without the network.
|
|
1927
|
+
*/
|
|
1928
|
+
export async function waitForPublished(name, version, localPack, opts = {}) {
|
|
1929
|
+
const waitMs = Math.max(0, Number(opts.waitMs) || 0)
|
|
1930
|
+
const intervalMs = Math.max(1000, Number(opts.intervalMs) || 10_000)
|
|
1931
|
+
const probe = opts.probe || ((o) => checkPublishedMatches(name, version, localPack, o))
|
|
1932
|
+
const net = { registry: opts.registry, timeoutMs: opts.timeoutMs }
|
|
1933
|
+
const started = Date.now()
|
|
1934
|
+
let findings = await probe(net)
|
|
1935
|
+
while (!findings.some((f) => f.rule === 'publish-served') && Date.now() - started < waitMs) {
|
|
1936
|
+
await new Promise((r) => setTimeout(r, Math.min(intervalMs, waitMs - (Date.now() - started))))
|
|
1937
|
+
findings = await probe(net)
|
|
1938
|
+
}
|
|
1939
|
+
const served = findings.some((f) => f.rule === 'publish-served')
|
|
1940
|
+
const mismatch = findings.some((f) => f.rule === 'publish-served-mismatch')
|
|
1941
|
+
// `pending` means the wait ran out with the version still invisible — the
|
|
1942
|
+
// normal outcome for a publish npm has not finished processing, NOT a
|
|
1943
|
+
// failure. Measured on this plugin's own 0.8.1: the version appeared four
|
|
1944
|
+
// minutes after `npm publish`, longer than one call can wait.
|
|
1945
|
+
return { findings, waitedMs: Date.now() - started, pending: !served && !mismatch }
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1919
1948
|
/** The async half of the gate: everything that needs a subprocess or the network. */
|
|
1920
1949
|
export async function runArtifactChecks(dir, opts = {}) {
|
|
1921
1950
|
const packResult = opts.skipPack ? { findings: [], pack: null } : await checkPack(dir, opts)
|
package/lib/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
checkClientRuntime,
|
|
25
25
|
checkPublishIdentity,
|
|
26
26
|
checkVersionTaken,
|
|
27
|
+
waitForPublished,
|
|
27
28
|
compareVersions,
|
|
28
29
|
latestPublished,
|
|
29
30
|
renderReport,
|
|
@@ -189,12 +190,17 @@ export function apply(ctx, config = {}) {
|
|
|
189
190
|
{
|
|
190
191
|
name: 'publish_gate_verify',
|
|
191
192
|
description:
|
|
192
|
-
'Check that the tarball on the npm registry is the one in this directory. Runs `npm pack --dry-run` and compares its shasum with the registry dist.shasum — an exact comparison, not a version-number guess, so it catches "I bumped and forgot to publish" and "I published, then edited lib/". Reports publish-served (match), publish-served-mismatch (the version is spent and the registry holds different bytes), or publish-not-served (not published, or still inside the registry\'s
|
|
193
|
+
'Check that the tarball on the npm registry is the one in this directory. Runs `npm pack --dry-run` and compares its shasum with the registry dist.shasum — an exact comparison, not a version-number guess, so it catches "I bumped and forgot to publish" and "I published, then edited lib/". Reports publish-served (match), publish-served-mismatch (the version is spent and the registry holds different bytes), or publish-not-served (not published, or still inside the registry\'s 202-then-404 window — pass waitMs to poll through that window instead of concluding too early). Run it right after `npm publish`. Returns {ok, name, version, published, shasumMatch, localShasum, unpackedSize, files, waitedMs, findings, report}.',
|
|
193
194
|
parameters: {
|
|
194
195
|
type: 'object',
|
|
195
196
|
properties: {
|
|
196
197
|
dir: { type: 'string', description: 'Plugin directory.' },
|
|
197
198
|
version: { type: 'string', description: 'Version to verify (defaults to package.json version).' },
|
|
199
|
+
waitMs: {
|
|
200
|
+
type: 'number',
|
|
201
|
+
description:
|
|
202
|
+
'Poll until the registry serves the bytes, up to this many milliseconds (capped at 90000 — the host kills a tool call at 120000). npm answers 202 and keeps the version invisible for 60-180 s after a publish, measured up to 4 minutes, so a single shot reports "not published" on a release that already succeeded. When the wait runs out with `pending: true`, run it again; that is not a failure.',
|
|
203
|
+
},
|
|
198
204
|
},
|
|
199
205
|
},
|
|
200
206
|
async execute(args) {
|
|
@@ -205,7 +211,12 @@ export function apply(ctx, config = {}) {
|
|
|
205
211
|
const version = args.version || json.version
|
|
206
212
|
const { pack, findings: packFindings } = await checkPack(dir)
|
|
207
213
|
if (!pack) return { ok: false, error: `npm pack --dry-run did not run, so there is nothing to compare: ${packFindings.map((f) => f.title).join('; ')}` }
|
|
208
|
-
const
|
|
214
|
+
const waitMs = Math.min(Math.max(0, Number(args.waitMs) || 0), 90_000)
|
|
215
|
+
const { findings, waitedMs, pending } = await waitForPublished(json.name, version, pack, {
|
|
216
|
+
registry: cfg.registry,
|
|
217
|
+
timeoutMs: cfg.networkTimeoutMs,
|
|
218
|
+
waitMs,
|
|
219
|
+
})
|
|
209
220
|
const served = findings.some((f) => f.rule === 'publish-served')
|
|
210
221
|
return {
|
|
211
222
|
ok: true,
|
|
@@ -213,9 +224,11 @@ export function apply(ctx, config = {}) {
|
|
|
213
224
|
version,
|
|
214
225
|
published: served || findings.some((f) => f.rule === 'publish-served-mismatch'),
|
|
215
226
|
shasumMatch: served,
|
|
227
|
+
pending,
|
|
216
228
|
localShasum: pack.shasum,
|
|
217
229
|
unpackedSize: pack.unpackedSize,
|
|
218
230
|
files: (pack.files || []).length,
|
|
231
|
+
waitedMs,
|
|
219
232
|
findings,
|
|
220
233
|
report: renderReport(findings, { title: `publish verify · ${json.name}@${version}` }),
|
|
221
234
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bushizhanghan/dsh-publish-gate",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Pre-publish gate for DSH plugins: manifest, dependencies, artifact, runtime, browser half, traceability and market readiness — plus an input fuzzer that drives every declared parameter with the wrong type. 发布前的七层门禁 + 入参模糊测试。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|