@bushizhanghan/dsh-publish-gate 0.1.4 → 0.1.6

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 CHANGED
@@ -29,7 +29,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
29
29
  | `publish_gate` | 全量门禁:静态检查 + 网络检查,返回 `safeToPublish` |
30
30
  | `publish_gate_patch` | 只查 `cordis.patch.yml`——「插件装了但工具没出现」的第一个怀疑对象 |
31
31
  | `publish_gate_market` | 校验市场条目 YAML(url/name/category/description 双语) |
32
- | `publish_gate_next_version` | 当前版本、是否已被 registry 占用、建议 bump 到多少 |
32
+ | `publish_gate_next_version` | 当前版本、是否已被 registry 占用、建议 bump 到多少(本地比 npm 旧时从 npm 的 latest 往上加,不会推荐一个已被占用的版本) |
33
33
  | `publish_gate_doctor` | 扫整个目录,按 blocker 数排序,看哪个插件还不能发 |
34
34
  | `publish_gate_commands` | 按顺序列出发布命令,并标出这台机器已经走到哪一步 |
35
35
 
@@ -90,7 +90,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
90
90
  ## 开发
91
91
 
92
92
  ```sh
93
- node --test # 24 个测试(含 4 个真实插件作 fixture)
93
+ node --test # 27 个测试(含 4 个真实插件作 fixture)
94
94
  ```
95
95
 
96
96
  ## License
package/lib/engine.js CHANGED
@@ -433,6 +433,33 @@ export function bumpPatch(v) {
433
433
  return m ? `${m[1]}.${m[2]}.${Number(m[3]) + 1}` : v
434
434
  }
435
435
 
436
+ /** -1 / 0 / 1 by semver-ish comparison of the numeric part. */
437
+ export function compareVersions(a, b) {
438
+ const pa = /^(\d+)\.(\d+)\.(\d+)/.exec(String(a))?.slice(1).map(Number) ?? [0, 0, 0]
439
+ const pb = /^(\d+)\.(\d+)\.(\d+)/.exec(String(b))?.slice(1).map(Number) ?? [0, 0, 0]
440
+ for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1
441
+ return 0
442
+ }
443
+
444
+ /**
445
+ * The registry's `dist-tags.latest`, or null when it could not be read.
446
+ * next_version needs this: a local checkout that is BEHIND npm would otherwise
447
+ * be told to publish a version that is already taken.
448
+ */
449
+ export async function latestPublished(name, { registry = 'https://registry.npmjs.org', timeoutMs = 8000 } = {}) {
450
+ try {
451
+ const res = await fetch(`${registry.replace(/\/$/, '')}/${name.replace('/', '%2f')}`, {
452
+ headers: { accept: 'application/vnd.npm.install-v1+json' },
453
+ signal: AbortSignal.timeout(timeoutMs),
454
+ })
455
+ if (!res.ok) return null
456
+ const json = await res.json()
457
+ return json?.['dist-tags']?.latest ?? null
458
+ } catch {
459
+ return null
460
+ }
461
+ }
462
+
436
463
  export function checkGitState(dir) {
437
464
  const out = []
438
465
  const gitDir = join(dir, '.git')
@@ -492,7 +519,10 @@ export function checkEntrySource(dir) {
492
519
  if (!/export\s+(?:const|function)\s+apply/.test(text)) out.push(F('no-apply-export', 'error', 'lib/index.js does not export `apply`', 'cordis calls apply(ctx, config); without it nothing happens.', 'Add `export function apply(ctx, config) {}`.'))
493
520
  if (!/export\s+const\s+inject/.test(text)) out.push(F('no-inject', 'warn', 'lib/index.js does not export `inject`', 'Without an inject list the service is resolved late and `ctx.tools` may be undefined when apply runs.', 'Add `export const inject = [\'tools\']`.'))
494
521
  // require() in ESM blows up at load
495
- if (/^\s*const\s+\w+\s*=\s*require\(/m.test(text)) {
522
+ // Any call form, including destructuring and a member access: `const {a} = require(…)`,
523
+ // `x = require(…)`, `require('node:fs').readFileSync`. A bare `require(` inside a
524
+ // string is not matched because the regex requires a call with a quote argument.
525
+ if (/\brequire\s*\(\s*['"`]/.test(text)) {
496
526
  out.push(
497
527
  F(
498
528
  'require-in-esm',
package/lib/index.js CHANGED
@@ -18,6 +18,8 @@ import {
18
18
  checkPatchYml,
19
19
  checkTests,
20
20
  checkVersionTaken,
21
+ compareVersions,
22
+ latestPublished,
21
23
  renderReport,
22
24
  runStaticChecks,
23
25
  } from './engine.js'
@@ -168,23 +170,36 @@ export function apply(ctx, config = {}) {
168
170
  if (!dir) return { ok: false, error: 'no plugin directory given' }
169
171
  const json = readJson(join(dir, 'package.json'))
170
172
  if (!json?.version) return { ok: false, error: 'no version in package.json' }
171
- const taken = (await checkVersionTaken(json.name, json.version, { registry: cfg.registry })).map((f) => f.title)
172
- const [a, b, c] = /^(\d+)\.(\d+)\.(\d+)/.exec(json.version)?.slice(1).map(Number) ?? [0, 0, 0]
173
+ const taken = (await checkVersionTaken(json.name, json.version, { registry: cfg.registry, timeoutMs: cfg.networkTimeoutMs })).map((f) => f.title)
174
+ const latest = await latestPublished(json.name, { registry: cfg.registry, timeoutMs: cfg.networkTimeoutMs })
175
+ // A checkout that is behind npm must bump from npm's latest, or the
176
+ // "suggested" version is one that is already published.
177
+ const behind = latest && compareVersions(latest, json.version) > 0 ? latest : null
178
+ const base = behind ?? json.version
179
+ const [a, b, c] = /^(\d+)\.(\d+)\.(\d+)/.exec(base)?.slice(1).map(Number) ?? [0, 0, 0]
173
180
  const bump = args.bump || 'patch'
174
181
  const suggested =
175
182
  bump === 'major' ? `${a + 1}.0.0` : bump === 'minor' ? `${a}.${b + 1}.0` : `${a}.${b}.${c + 1}`
176
- return { ok: true, current: json.version, taken, suggested, suggestedFor: bump }
183
+ return {
184
+ ok: true,
185
+ current: json.version,
186
+ latest,
187
+ behind,
188
+ taken,
189
+ suggested,
190
+ suggestedFor: bump,
191
+ note: behind ? `package.json is behind the registry (${behind} is live); the suggestion bumps from there.` : undefined,
192
+ }
177
193
  },
178
194
  },
179
195
  {
180
196
  name: 'publish_gate_doctor',
181
197
  description:
182
- 'Run the gate over every plugin in a directory tree (e.g. a workspace holding several plugin checkouts) and rank them by blocker count, so you can see which one is not ready to ship before you start a publish session. Returns {ok, plugins:[{dir,blockers,warns,notes,top}], totals}.',
198
+ 'Run the gate over every plugin in a directory tree (e.g. a workspace holding several plugin checkouts) and rank them by blocker count, so you can see which one is not ready to ship before you start a publish session. Static checks only — no registry or GitHub calls, so it is fast and works offline. Returns {ok, plugins:[{dir,blockers,warns,notes,top}], totals}.',
183
199
  parameters: {
184
200
  type: 'object',
185
201
  properties: {
186
202
  root: { type: 'string', description: 'Directory containing plugin checkouts.' },
187
- skipNetwork: { type: 'boolean', description: 'Skip registry/GitHub checks (default true for a multi-plugin sweep).' },
188
203
  },
189
204
  },
190
205
  async execute(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bushizhanghan/dsh-publish-gate",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Helps DSH plugin AUTHORS publish their plugins: a one-command gate that turns every silent failure in the publish path into a deterministic check — package.json completeness, BOM, YAML/JSON parseability, junction and patch insert correctness, npm scope permissions, GitHub repo and token state, market entry format, LICENSE/README, test suite. 帮助插件作者一键发布:写完插件后跑一遍门禁,把发布链路上的每个静默坑变成确定性检查,然后 GitHub + npm + 市场一键三连。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",