@bushizhanghan/dsh-publish-gate 0.5.0 → 0.7.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,83 @@
1
+ # Changelog
2
+
3
+ ## 0.7.0
4
+
5
+ - `tool-output-invalid` (error): a tool result its own `output.schema` rejects. The host validates every
6
+ result and throws `ToolOutputError`, so the tool is unusable from the first call. Only checked when the
7
+ caller names the tool in `call`, because calling a tool can launch applications or write files.
8
+ - `npm-not-logged-in` (error) / `npm-scope-mismatch` (warn): ask npm who it is before spending a publish.
9
+ ENEEDAUTH and the 403 that names neither the token nor the scope both become a message instead of a loss.
10
+ - `readme-wrong-package` (error): the README install line names a different package — a README copied from
11
+ another plugin. Whoever follows it installs something else, and nothing fails because that package exists.
12
+ ## 0.6.0
13
+
14
+ - New rule `tool-undeclared-param`: a tool whose `execute` reads `args.x` that
15
+ `parameters.properties` never declares. The host renders `parameters` into the
16
+ prompt and passes the model nothing else, so that value is undefined on every
17
+ real call — the code path exists and never runs, and nothing throws.
18
+ Covers `args.x`, `args['x']` and `const { x } = args`.
19
+
20
+ ## 0.5.0
21
+
22
+ - New layer: traceability. `git-uncommitted` (files that ship in the tarball are
23
+ not committed), `git-tag-missing` (no `v<version>` tag on HEAD),
24
+ `git-tag-elsewhere` (the tag exists but points at another commit).
25
+ - The incident behind it: a 1.2.7 went to npm while the GitHub release still
26
+ said 1.2.6 and the market entry still pointed at the old tarball.
27
+
28
+ ## 0.4.0
29
+
30
+ - New layer: dependency truth. `undeclared-import` (lib/ imports a package
31
+ package.json never declares), `dep-in-dev-only` (only devDependencies satisfy
32
+ it — npm never installs those for the consumer), `unused-dependency` (info).
33
+ - New rule `version-behind-latest`: publishing under the live `latest` is
34
+ accepted by npm and reaches nobody.
35
+
36
+ ## 0.3.4
37
+
38
+ - `tool-call-slow` split from `tool-call-throws`: a timeout is not a defect.
39
+
40
+ ## 0.3.3
41
+
42
+ - `publish_gate_tools` accepts `call: ["name"]` to really invoke a tool, opt-in
43
+ because some tools open applications or write files.
44
+
45
+ ## 0.3.2
46
+
47
+ - New rule `node-too-old`: `engines.node` says one thing, `lib/` uses an API from
48
+ a newer Node. npm only warns on `engines`, so the plugin installs and then
49
+ throws at import.
50
+
51
+ ## 0.3.1
52
+
53
+ - `publish_gate_commands` emits commands that actually run on this machine
54
+ (`cmd /c "npm publish --access public"` on Windows) and pairs the publish step
55
+ with a `publish_gate_verify` shasum check.
56
+
57
+ ## 0.3.0
58
+
59
+ - New layer: the browser half. `checkClientHalf` / `checkClientRuntime` load the
60
+ bundle against a fake `window.__ModuleLoader__` and report a bundle that never
61
+ loads, never returns `apply`, throws, registers nothing, or uses a service its
62
+ `inject` array does not declare.
63
+ - New layer: market readiness — the repo is already listed, another entry
64
+ publishes the same npm name, or the repo is under 24 hours old (the gate fails
65
+ and reruns itself).
66
+ - New rules: `client-without-platform`, `pack-omits-client`, `pack-ships-secret`,
67
+ `private-field-set`, `publish-config-registry`, `tool-bad-parameter-type`,
68
+ `entry-path-missing`, `bundle-patch-not-declared`, `inject-unused-service`.
69
+ - New tool `publish_gate_client` (9 tools).
70
+
71
+ ## 0.2.0
72
+
73
+ - New layers: artifact (`npm pack --dry-run` — what npm will really ship) and
74
+ runtime (import the entry in an isolated child process, call `apply`, report a
75
+ throwing import, a throwing apply, missing execute, duplicate tool names, a
76
+ throwing `presentCall`, and card kinds the host does not render).
77
+ - New tools `publish_gate_tools` and `publish_gate_verify`.
78
+ - Fixed: `bad-repo-url` never fired for the object form of `repository`.
79
+
80
+ ## 0.1.0
81
+
82
+ - First release: manifest rules, `cordis.patch.yml` insert checks, README and
83
+ test presence, npm name and registry state.
package/README.md CHANGED
@@ -68,9 +68,11 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
68
68
  | `version-taken` | 这个版本已经在 registry 上,再发是 403 |
69
69
  | `version-behind-latest` | 本地版本比 registry 上的 `latest` **旧**。npm 会照收,但 `latest` 继续指向新版本——包上传了,按名字装的人永远拿不到,且没有任何报错 |
70
70
  | `registry-unreachable` | registry 回了 500/429 —— 版本检查**没跑**,所以别把它当干净 |
71
+ | `npm-not-logged-in` | 这台机器上 npm 没有可用登录(`npm whoami` 报 ENEEDAUTH)。`npm publish` 会在 tarball 打好之后才失败 |
72
+ | `readme-wrong-package` | README 的安装命令写的是**另一个包名**(从别的插件抄过来没改)。照着装的人装到别的插件上,而且不会报错——那个包真的存在 |
71
73
 
72
74
  ### 值得在发布前知道的(warn / info)
73
- `unscoped-name`(granular token 的 403)、`repo-missing`、`patch-name-unresolvable`(junction 放错层级)、`inject-unused-service`(`inject` 里写了个拼错的服务名,cordis 一直等它,插件静默不激活)、`no-publish-config-access`(scoped 包没写 `publishConfig.access`,只是提醒,因为命令里本来就带 `--access public`)、`publish-config-registry`(`publishConfig.registry` 指到内网 registry,版本发到别人装不到的地方)、`no-readme` / `readme-no-install` / `readme-no-profile`、`license-mismatch`、`no-tests` / `no-test-files` / `no-test-script`、`no-engines`、`bad-repo-url`、`no-git` / `no-gitignore`、`readme-tool-undocumented`(README 没提这个工具)/ `readme-test-count`(README 写的测试数和真实数不一致),以及市场条目的 `market-*` 全系列。
75
+ `npm-scope-mismatch`(登录的账号不是包 scope 的所有者,PUT 会 403,而错误里既不说 token 也不说 scope——我为一个 403 花了一小时)、`unscoped-name`(granular token 的 403)、`repo-missing`、`patch-name-unresolvable`(junction 放错层级)、`inject-unused-service`(`inject` 里写了个拼错的服务名,cordis 一直等它,插件静默不激活)、`no-publish-config-access`(scoped 包没写 `publishConfig.access`,只是提醒,因为命令里本来就带 `--access public`)、`publish-config-registry`(`publishConfig.registry` 指到内网 registry,版本发到别人装不到的地方)、`no-readme` / `readme-no-install` / `readme-no-profile`、`license-mismatch`、`no-tests` / `no-test-files` / `no-test-script`、`no-engines`、`bad-repo-url`、`no-git` / `no-gitignore`、`readme-tool-undocumented`(README 没提这个工具)/ `readme-test-count`(README 写的测试数和真实数不一致),以及市场条目的 `market-*` 全系列。
74
76
 
75
77
  ### 产物层:npm 到底会发出什么
76
78
 
@@ -102,6 +104,8 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
102
104
  | `tool-present-call-throws` | `presentCall` 抛——调用记录渲染时炸 |
103
105
  | `tool-bad-card-kind` | `card: 'generic'` 的 `kind` 不在宿主认识的那一组里(`other` / `execute` / `search` / `read` / `edit` / `delete` / `fetch`)。注册不报错,卡片渲染坏掉——我五个插件一起踩过 |
104
106
  | `tool-bad-parameter-type` | `parameters` 里某个 `type` 写错(`"strin"`)。宿主把 `parameters` 渲染进 prompt、但**从不校验**调用值,所以这个 typo 只是悄悄把类型提示弄丢 |
107
+ | `tool-undeclared-param` | `execute` 读了 `args.x`,但 `parameters.properties` 里没有 `x`。模型只能传 schema 声明过的参数,所以这个值在真实调用里永远是 `undefined`——代码在,永远不跑,也不报错。`args.x`、`args['x']`、`const { x } = args` 三种写法都认 |
108
+ | `tool-output-invalid` | 工具返回的东西**自己的 output schema 不收**。宿主每个结果都拿 schema 校验,不匹配就抛 `ToolOutputError`——工具从第一次调用起就不可用,而报错只给一个 JSON 路径,不给是哪行代码返回的。只有点名 `call` 真的调了才检查。这个插件自己踩过:schema 收紧成 `additionalProperties: false`,但有一条错误分支还在返回 `{ ok, error }` |
105
109
 
106
110
  ### 浏览器半边:侧栏面板到底会不会出现
107
111
 
@@ -210,7 +214,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
210
214
  ## 开发
211
215
 
212
216
  ```sh
213
- node --test # 80 个引擎单测
217
+ node --test # 88 个引擎单测
214
218
  node test/integration.test.mjs # 26 条集成断言(9 个真实插件作 fixture)
215
219
  ```
216
220
 
package/lib/engine.js CHANGED
@@ -517,6 +517,33 @@ export function checkDocs(dir) {
517
517
  if (text && !/dsh plugin .*add .*--profile/.test(text) && !/dsh plugin --profile \S+ add/.test(text)) {
518
518
  out.push(F('readme-no-profile', 'info', 'README install line omits --profile', 'Omitting it installs into the default profile, which is not what every user wants.', 'Show `dsh plugin --profile desktop add <pkg>`.'))
519
519
  }
520
+ // A README copied from another plugin keeps the old package name, and the
521
+ // install line then installs something else entirely — with no error, since
522
+ // that package really does exist.
523
+ const pkg = readPkgJson(dir)
524
+ if (text && pkg?.name) {
525
+ const specs = [...text.matchAll(/dsh plugin\b[^\n]*?\badd\s+(?:--profile\s+\S+\s+)?(\S+)/g)]
526
+ .map((m) => m[1].replace(/[`'"]/g, '').trim())
527
+ .filter((s) => s && !s.startsWith('-'))
528
+ const wrong = [...new Set(specs)].filter((s) => {
529
+ if (s === pkg.name || s.startsWith('github:') || s.includes('://')) return false
530
+ // A different install FORM is not a copy-paste slip: an unscoped name in
531
+ // a scoped package's README is usually just shorthand for a dependency.
532
+ if (s.startsWith('@') !== pkg.name.startsWith('@')) return false
533
+ return /^@[\w.-]+\/[\w.-]+$|^[\w.-]+$/.test(s)
534
+ })
535
+ for (const spec of wrong) {
536
+ out.push(
537
+ F(
538
+ 'readme-wrong-package',
539
+ 'error',
540
+ `README install line says "${spec}" but the package is "${pkg.name}"`,
541
+ 'A README copied from another plugin keeps the old name. Whoever follows it installs a different package — and nothing fails, because that package exists too.',
542
+ `Change the install line to ${pkg.name}.`,
543
+ ),
544
+ )
545
+ }
546
+ }
520
547
  }
521
548
  if (!existsSync(join(dir, 'LICENSE'))) {
522
549
  out.push(F('no-license', 'error', 'LICENSE is missing', 'npm warns on publish and the market listing shows an unknown license.', 'Add a LICENSE file (MIT is the ecosystem norm).'))
@@ -533,6 +560,66 @@ export function checkDocs(dir) {
533
560
  // npm / GitHub / market state
534
561
  // =====================================================================
535
562
 
563
+ /**
564
+ * Who npm thinks you are on this machine.
565
+ *
566
+ * A publish fails at the very last step with ENEEDAUTH or a 403 that names
567
+ * neither the token nor the scope. Asking first turns that into a message
568
+ * instead of a spent version number.
569
+ */
570
+ export async function checkPublishIdentity(dir, { timeoutMs = 15000, whoami } = {}) {
571
+ const out = []
572
+ let who = whoami
573
+ if (who === undefined) {
574
+ try {
575
+ const isWin = process.platform === 'win32'
576
+ const cmd = isWin ? process.env.ComSpec || 'cmd.exe' : 'npm'
577
+ const args = isWin ? ['/d', '/s', '/c', 'npm whoami'] : ['whoami']
578
+ const { stdout } = await runFile(cmd, args, { timeout: timeoutMs, windowsHide: true })
579
+ who = stdout.trim() || null
580
+ } catch (error) {
581
+ const text = String(error?.stderr || error?.message || error)
582
+ if (/ENEEDAUTH|E401|not logged in|need auth/i.test(text)) {
583
+ out.push(
584
+ F(
585
+ 'npm-not-logged-in',
586
+ 'error',
587
+ 'npm has no usable login on this machine',
588
+ '`npm publish` will fail with ENEEDAUTH after the tarball has been built. A granular token that lacks publish rights fails later still, with a 403 that names neither the token nor the scope.',
589
+ 'Run `npm login`, or put a publish-capable token in ~/.npmrc.',
590
+ ),
591
+ )
592
+ } else {
593
+ out.push(
594
+ F(
595
+ 'npm-whoami-unavailable',
596
+ 'info',
597
+ 'could not ask npm who is logged in',
598
+ 'The publish-identity check did not run, so nothing is claimed about it.',
599
+ 'Make sure npm is on PATH and re-run.',
600
+ ),
601
+ )
602
+ }
603
+ return out
604
+ }
605
+ }
606
+
607
+ const pkg = readPkgJson(dir)
608
+ const scope = /^@([^/]+)\//.exec(pkg?.name || '')?.[1]
609
+ if (who && scope && who !== scope) {
610
+ out.push(
611
+ F(
612
+ 'npm-scope-mismatch',
613
+ 'warn',
614
+ `logged in as "${who}" but the package is under @${scope}`,
615
+ `Publishing under a scope needs a token that owns that scope. A granular token scoped to another owner answers 403 on the PUT — the error names neither the token nor the scope, which is why this takes an hour to diagnose.`,
616
+ `Log in as ${scope}, or mint a token with publish rights for @${scope}.`,
617
+ ),
618
+ )
619
+ }
620
+ return out
621
+ }
622
+
536
623
  export function checkNpmState(dir) {
537
624
  const out = []
538
625
  const json = readPkgJson(dir)
@@ -1057,6 +1144,31 @@ const walkParams = (schema, path) => {
1057
1144
  }
1058
1145
  if (schema.items) walkParams(schema.items, path + '[]')
1059
1146
  }
1147
+ // The model only ever sends the arguments \`parameters\` declares — the host
1148
+ // renders that schema into the prompt and passes nothing else. So an execute
1149
+ // that reads args.dir while the schema never mentions dir is unreachable code:
1150
+ // every call sees undefined, and the tool looks broken with no error anywhere.
1151
+ const ARG_DOT = /\\bargs\\s*\\??\\.\\s*([A-Za-z_$][\\w$]*)/g
1152
+ const ARG_INDEX = /\\bargs\\s*\\[\\s*['"]([^'"]+)['"]\\s*\\]/g
1153
+ const ARG_DESTRUCTURE = /\\{\\s*([^{}]+?)\\s*\\}\\s*=\\s*args\\b/g
1154
+ const undeclaredArgs = (def) => {
1155
+ const props = def?.parameters?.properties
1156
+ if (!props || typeof props !== 'object') return []
1157
+ const declared = Object.keys(props)
1158
+ if (typeof def.execute !== 'function') return []
1159
+ let src = ''
1160
+ try { src = def.execute.toString() } catch { return [] }
1161
+ const reads = []
1162
+ for (const m of src.matchAll(ARG_DOT)) reads.push(m[1])
1163
+ for (const m of src.matchAll(ARG_INDEX)) reads.push(m[1])
1164
+ for (const m of src.matchAll(ARG_DESTRUCTURE)) {
1165
+ for (const part of m[1].split(',')) {
1166
+ const key = part.split(':')[0].split('=')[0].replace(/\\.\\.\\./, '').trim()
1167
+ if (/^[A-Za-z_$][\\w$]*$/.test(key)) reads.push(key)
1168
+ }
1169
+ }
1170
+ return [...new Set(reads)].filter((r) => !declared.includes(r))
1171
+ }
1060
1172
  const noopService = () => new Proxy(function () {}, {
1061
1173
  get: (target, prop) => {
1062
1174
  // Own properties win, or ctx.tools would resolve to another no-op proxy
@@ -1085,12 +1197,58 @@ ctx.tools = {
1085
1197
  name: typeof def?.name === 'string' ? def.name : null,
1086
1198
  hasExecute: typeof def?.execute === 'function',
1087
1199
  hasOutputSchema: Boolean(def && def.output && def.output.schema),
1200
+ schema: (def && def.output && def.output.schema) || null,
1088
1201
  cardKind: card,
1202
+ undeclaredParams: undeclaredArgs(def),
1089
1203
  run: typeof def?.execute === 'function' ? def.execute : null,
1090
1204
  })
1091
1205
  walkParams(def?.parameters, typeof def?.name === 'string' ? def.name : '(unnamed)')
1092
1206
  },
1093
1207
  }
1208
+ // The host validates every tool result against output.schema and throws
1209
+ // ToolOutputError when it does not match — the tool is then unusable, and the
1210
+ // failure names a JSON path rather than the code that produced it. This gate
1211
+ // shipped exactly that bug: a permissive schema was tightened to
1212
+ // additionalProperties:false while one error path still returned { ok, error }.
1213
+ const validateValue = (schema, value, path) => {
1214
+ if (!schema || typeof schema !== 'object') return null
1215
+ const at = path || '$'
1216
+ if (typeof schema.type === 'string' && schema.type !== 'json') {
1217
+ const t = schema.type
1218
+ const ok = t === 'array' ? Array.isArray(value)
1219
+ : t === 'null' ? value === null
1220
+ : t === 'integer' ? Number.isInteger(value)
1221
+ : t === 'object' ? (value !== null && typeof value === 'object' && !Array.isArray(value))
1222
+ : typeof value === t
1223
+ if (!ok) return at + ' must be ' + t + ', got ' + (value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value)
1224
+ }
1225
+ if (Array.isArray(schema.enum) && !schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))) {
1226
+ return at + ' is not one of ' + JSON.stringify(schema.enum)
1227
+ }
1228
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
1229
+ for (const key of Array.isArray(schema.required) ? schema.required : []) {
1230
+ if (!(key in value)) return at + '.' + key + ' is required but missing'
1231
+ }
1232
+ if (schema.additionalProperties === false && schema.properties && typeof schema.properties === 'object') {
1233
+ const extra = Object.keys(value).filter((k) => !(k in schema.properties))
1234
+ if (extra.length) return at + ' has key(s) the schema does not declare: ' + extra.join(', ')
1235
+ }
1236
+ if (schema.properties && typeof schema.properties === 'object') {
1237
+ for (const [key, sub] of Object.entries(schema.properties)) {
1238
+ if (!(key in value)) continue
1239
+ const bad = validateValue(sub, value[key], at + '.' + key)
1240
+ if (bad) return bad
1241
+ }
1242
+ }
1243
+ }
1244
+ if (Array.isArray(value) && schema.items) {
1245
+ for (let i = 0; i < value.length; i++) {
1246
+ const bad = validateValue(schema.items, value[i], at + '[' + i + ']')
1247
+ if (bad) return bad
1248
+ }
1249
+ }
1250
+ return null
1251
+ }
1094
1252
  const emit = (payload) => process.stdout.write('\\n@@PG@@' + JSON.stringify(payload) + '\\n')
1095
1253
  let stage = 'import'
1096
1254
  try {
@@ -1108,11 +1266,11 @@ try {
1108
1266
  for (const entry of registered) {
1109
1267
  if (!wanted.has(entry.name) || !entry.run) continue
1110
1268
  try {
1111
- await Promise.race([
1269
+ const value = await Promise.race([
1112
1270
  Promise.resolve(entry.run({}, { signal: undefined })),
1113
1271
  new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), budget)),
1114
1272
  ])
1115
- calls.push({ name: entry.name, ok: true })
1273
+ calls.push({ name: entry.name, ok: true, invalid: validateValue(entry.schema, value) })
1116
1274
  } catch (error) {
1117
1275
  const message = String((error && error.message) || error)
1118
1276
  // A slow tool is not a broken tool: of_env shells out to WSL and takes
@@ -1185,7 +1343,20 @@ export async function checkToolRegistrations(dir, opts = {}) {
1185
1343
 
1186
1344
  const seen = new Set()
1187
1345
  for (const c of Array.isArray(payload.calls) ? payload.calls : []) {
1188
- if (c.ok) continue
1346
+ if (c.ok) {
1347
+ if (c.invalid) {
1348
+ out.push(
1349
+ F(
1350
+ 'tool-output-invalid',
1351
+ 'error',
1352
+ `tool "${c.name}" returns a value its own output schema rejects: ${c.invalid}`,
1353
+ 'The host validates every result against output.schema and throws ToolOutputError on a mismatch. The tool is unusable from the first call, and the error names a JSON path rather than the line that produced it.',
1354
+ 'Either widen the schema or change what execute returns so the two agree.',
1355
+ ),
1356
+ )
1357
+ }
1358
+ continue
1359
+ }
1189
1360
  if (c.timedOut) {
1190
1361
  out.push(
1191
1362
  F(
@@ -1229,6 +1400,21 @@ export async function checkToolRegistrations(dir, opts = {}) {
1229
1400
  ),
1230
1401
  )
1231
1402
  }
1403
+
1404
+ // The model only sends what `parameters` declares, so a read of anything
1405
+ // else is unreachable: args.dir is undefined on every real call, and the
1406
+ // tool looks broken while nothing throws.
1407
+ for (const name of Array.isArray(t.undeclaredParams) ? t.undeclaredParams : []) {
1408
+ out.push(
1409
+ F(
1410
+ 'tool-undeclared-param',
1411
+ 'warn',
1412
+ `tool "${label}" reads args.${name}, which parameters never declares`,
1413
+ 'The host renders parameters into the prompt and passes the model nothing else, so this value is always undefined on a real call. The code path exists and never runs.',
1414
+ `Add "${name}" to parameters.properties, or stop reading it.`,
1415
+ ),
1416
+ )
1417
+ }
1232
1418
  }
1233
1419
  const badParams = Array.isArray(payload.badParamTypes) ? payload.badParamTypes : []
1234
1420
  for (const where of badParams) {
@@ -1918,14 +2104,14 @@ const RULE_ORDER = [
1918
2104
  'inject-missing-tools', 'inject-missing-web', 'inject-missing-webServer', 'inject-missing-timer',
1919
2105
  'inject-unused-service',
1920
2106
  'tool-missing-name', 'tool-duplicate-name', 'tool-missing-execute', 'tool-present-call-throws',
1921
- 'tool-bad-card-kind', 'tool-missing-output-schema', 'tool-none-registered', 'tool-bad-parameter-type', 'tool-call-throws', 'tool-call-slow',
2107
+ 'tool-bad-card-kind', 'tool-missing-output-schema', 'tool-none-registered', 'tool-bad-parameter-type', 'tool-undeclared-param', 'tool-call-throws', 'tool-output-invalid', 'tool-call-slow',
1922
2108
  'lib-debugger', 'lib-console-log', 'lib-hardcoded-path', 'lib-url-pathname', 'node-too-old', 'stale-build', 'hand-edited-build',
1923
2109
  'undeclared-import', 'dep-in-dev-only', 'unused-dependency',
1924
2110
  'pack-omits-lib', 'pack-omits-patch', 'pack-omits-client', 'pack-oversize', 'pack-omits-readme', 'pack-omits-license', 'pack-ships-secret',
1925
2111
  'pack-unexpected-dir', 'pack-script-build', 'pack-unavailable',
1926
- 'no-license', 'license-mismatch', 'version-taken', 'version-behind-latest', 'registry-unreachable', 'publish-not-served',
2112
+ 'no-license', 'license-mismatch', 'version-taken', 'version-behind-latest', 'npm-not-logged-in', 'npm-scope-mismatch', 'npm-whoami-unavailable', 'registry-unreachable', 'publish-not-served',
1927
2113
  'publish-served-mismatch', 'publish-served', 'market-empty', 'market-no-url', 'market-url-not-github',
1928
- 'market-no-name', 'market-name-shape', 'market-no-category', 'market-bad-category', 'market-no-desc', 'market-no-en', 'market-no-zh', 'unscoped-name', 'no-git', 'no-readme', 'readme-no-install', 'readme-no-profile',
2114
+ 'market-no-name', 'market-name-shape', 'market-no-category', 'market-bad-category', 'market-no-desc', 'market-no-en', 'market-no-zh', 'unscoped-name', 'no-git', 'no-readme', 'readme-no-install', 'readme-no-profile', 'readme-wrong-package',
1929
2115
  'market-no-repo', 'market-unavailable', 'market-duplicate-entry', 'market-npm-name-collision', 'market-repo-too-new',
1930
2116
  'git-uncommitted', 'git-tag-missing', 'git-tag-elsewhere',
1931
2117
  'readme-tool-undocumented', 'readme-test-count',
package/lib/index.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  checkTests,
23
23
  checkToolRegistrations,
24
24
  checkClientRuntime,
25
+ checkPublishIdentity,
25
26
  checkVersionTaken,
26
27
  compareVersions,
27
28
  latestPublished,
@@ -64,6 +65,7 @@ export function apply(ctx, config = {}) {
64
65
  if (pkgJson?.name && pkgJson?.version) {
65
66
  findings.push(...(await checkVersionTaken(pkgJson.name, pkgJson.version, { registry: cfg.registry })))
66
67
  }
68
+ findings.push(...(await checkPublishIdentity(dir, { timeoutMs: cfg.networkTimeoutMs })))
67
69
  // is the GitHub repo there?
68
70
  if (pkgJson?.repository) {
69
71
  const url = repoUrl(pkgJson.repository)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bushizhanghan/dsh-publish-gate",
3
- "version": "0.5.0",
4
- "description": "Pre-publish gate for DSH plugins in five layers: manifest, dependencies, artifact, runtime, browser half — plus traceability (can you point at the bytes you shipped?). 发布前门禁:清单、依赖、产物、运行时、浏览器半边,外加可追溯性。",
3
+ "version": "0.7.0",
4
+ "description": "Pre-publish gate for DSH plugins: manifest, dependencies, artifact, runtime, browser half, traceability and market readiness. 发布前的七层门禁。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
@@ -13,7 +13,8 @@
13
13
  "lib",
14
14
  "cordis.patch.yml",
15
15
  "README.md",
16
- "LICENSE"
16
+ "LICENSE",
17
+ "CHANGELOG.md"
17
18
  ],
18
19
  "keywords": [
19
20
  "dsh",