@bushizhanghan/dsh-publish-gate 0.12.0 → 0.13.1
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 +14 -0
- package/README.md +3 -1
- package/lib/engine.js +85 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.1
|
|
4
|
+
|
|
5
|
+
- The tag check now asks `git ls-remote origin` instead of `git tag --points-at HEAD`. When the commit is pushed
|
|
6
|
+
through the GitHub API the local HEAD is not the remote one, so the local form reported `git-tag-missing` on
|
|
7
|
+
seven of my nine plugins whose tag existed all along. It now compares the tag against the shipped branch
|
|
8
|
+
head and stays silent when the remote cannot be reached — a check that cannot see the truth must not claim
|
|
9
|
+
a defect.
|
|
10
|
+
## 0.13.0
|
|
11
|
+
|
|
12
|
+
- `market-fork-of-listed` / `market-fork-undisclosed` (warn): the repo is a GitHub fork whose parent is
|
|
13
|
+
already in the catalogue, and nothing says so. A submission was blocked for exactly this: the entry
|
|
14
|
+
described the fork as if it were original work, and both plugins claim the same provider id, so installing
|
|
15
|
+
both silently shadows one of them. The check uses the `fork` and `parent.full_name` fields of the repo
|
|
16
|
+
metadata the market layer already fetches.
|
|
3
17
|
## 0.12.0
|
|
4
18
|
|
|
5
19
|
- `pack-ships-junk` (warn): the tarball carries `.bak-*`, `.orig`, `.swp`, `.log`, `~` or `.DS_Store` debris.
|
package/README.md
CHANGED
|
@@ -141,6 +141,8 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
|
|
|
141
141
|
| `market-duplicate-entry` | 这个仓库**已经在目录里**。再提一个 PR 就是同一仓库的第二条条目,reviewer 会拒。版本升级根本不需要 PR——目录条目只存 `url` |
|
|
142
142
|
| `market-npm-name-collision` | 另一个条目也在发同一个 npm 包,安装路径会互相盖 |
|
|
143
143
|
| `market-repo-too-new` | 仓库创建不满 24 小时。门禁会报 `0.0 days old` 然后**自己重跑**,PR 不会丢,但在那之前什么都不会合 |
|
|
144
|
+
| `market-fork-of-listed` | 这个仓库是某个**已经在目录里**的仓库的 fork。两条条目装的代码抢同一批 id,两个都装就有一个被静默盖掉——而且 reviewer 会把"没写"当成"隐瞒 fork" |
|
|
145
|
+
| `market-fork-undisclosed` | 是 fork,但 README 和 package.json 里都没有上游的 `owner/repo` |
|
|
144
146
|
| `market-no-repo` | `package.json` 里没有 GitHub 地址,没东西可提交 |
|
|
145
147
|
| `market-unavailable` | 目录拉不到(warn)——这几条没跑,别把它当干净 |
|
|
146
148
|
|
|
@@ -221,7 +223,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
|
|
|
221
223
|
## 开发
|
|
222
224
|
|
|
223
225
|
```sh
|
|
224
|
-
node --test #
|
|
226
|
+
node --test # 108 个引擎单测
|
|
225
227
|
node test/integration.test.mjs # 27 条集成断言(9 个真实插件作 fixture)
|
|
226
228
|
```
|
|
227
229
|
|
package/lib/engine.js
CHANGED
|
@@ -931,7 +931,7 @@ export async function checkMarketReadiness(dir, opts = {}) {
|
|
|
931
931
|
if (hours < 24) {
|
|
932
932
|
out.push(
|
|
933
933
|
F(
|
|
934
|
-
'market-repo-too-new',
|
|
934
|
+
'market-repo-too-new', 'market-fork-of-listed', 'market-fork-undisclosed',
|
|
935
935
|
'warn',
|
|
936
936
|
`the repo is ${hours.toFixed(1)}h old; the submission gate needs 24h`,
|
|
937
937
|
'The gate fails with a "repo too new" message and reruns on its own, so the PR is not lost — but nothing merges until it passes.',
|
|
@@ -941,12 +941,45 @@ export async function checkMarketReadiness(dir, opts = {}) {
|
|
|
941
941
|
}
|
|
942
942
|
}
|
|
943
943
|
}
|
|
944
|
+
// A fork of a repo that is already listed collides with its own upstream:
|
|
945
|
+
// both register the same provider or tool names, and whichever loads second
|
|
946
|
+
// wins or throws. A reviewer blocked a submission for exactly this and the
|
|
947
|
+
// description said nothing about it.
|
|
948
|
+
const parentSlug = typeof repoInfo?.parent?.full_name === 'string' ? repoInfo.parent.full_name : null
|
|
949
|
+
if (repoInfo?.fork === true && parentSlug) {
|
|
950
|
+
const parentListed = entries.some((e) => typeof e?.url === 'string' && e.url.toLowerCase().includes(parentSlug.toLowerCase()))
|
|
951
|
+
if (parentListed) {
|
|
952
|
+
out.push(
|
|
953
|
+
F(
|
|
954
|
+
'market-fork-of-listed',
|
|
955
|
+
'warn',
|
|
956
|
+
`this repo is a fork of ${parentSlug}, which is already in the catalogue`,
|
|
957
|
+
'Both entries install code that claims the same ids, so a user who installs both gets one silently shadowing the other — and a reviewer reads the missing note as an undisclosed fork.',
|
|
958
|
+
`Say in the entry description that it is a fork of ${parentSlug} and that the two must not be installed together.`,
|
|
959
|
+
),
|
|
960
|
+
)
|
|
961
|
+
}
|
|
962
|
+
const readme = safeText(join(dir, 'README.md')) || ''
|
|
963
|
+
const desc = typeof json?.description === 'string' ? json.description : ''
|
|
964
|
+
if (!readme.includes(parentSlug) && !desc.includes(parentSlug)) {
|
|
965
|
+
out.push(
|
|
966
|
+
F(
|
|
967
|
+
'market-fork-undisclosed',
|
|
968
|
+
'warn',
|
|
969
|
+
`nothing in README or package.json names the upstream ${parentSlug}`,
|
|
970
|
+
'A fork that does not say what it forked from looks like original work until someone diffs it, and the upstream author finds out from a collision report.',
|
|
971
|
+
`Mention ${parentSlug} in the README and in the market entry description.`,
|
|
972
|
+
),
|
|
973
|
+
)
|
|
974
|
+
}
|
|
975
|
+
}
|
|
944
976
|
return out
|
|
945
977
|
}
|
|
946
978
|
|
|
947
979
|
// =====================================================================
|
|
948
980
|
// Source sanity: the entry protocol
|
|
949
981
|
// =====================================================================
|
|
982
|
+
|
|
950
983
|
export function checkEntrySource(dir) {
|
|
951
984
|
const out = []
|
|
952
985
|
const path = join(dir, 'lib', 'index.js')
|
|
@@ -2270,11 +2303,43 @@ export async function checkTraceability(dir, opts = {}) {
|
|
|
2270
2303
|
}
|
|
2271
2304
|
}
|
|
2272
2305
|
|
|
2273
|
-
// 2.
|
|
2306
|
+
// 2. A tag on the bytes that actually shipped. When the commit was pushed
|
|
2307
|
+
// through the GitHub API the local HEAD is not the remote one, so a local
|
|
2308
|
+
// `git tag --points-at HEAD` reports a missing tag that exists — ask the
|
|
2309
|
+
// remote instead, and say nothing when it cannot be reached.
|
|
2310
|
+
const remote = opts.remote === undefined ? await readRemoteRefs(dir) : opts.remote
|
|
2274
2311
|
const atHead = opts.atHead === undefined ? await gitOut(dir, ['tag', '--points-at', 'HEAD']) : opts.atHead
|
|
2275
|
-
const
|
|
2312
|
+
const localTags = typeof atHead === 'string' ? atHead.split('\n').map((t) => t.trim()).filter(Boolean) : []
|
|
2276
2313
|
const wanted = [`v${version}`, version]
|
|
2277
|
-
|
|
2314
|
+
const tagOnRemote = remote ? remote.tags[`v${version}`] || remote.tags[version] || null : null
|
|
2315
|
+
const tagIsOnShipped = Boolean(remote && tagOnRemote && tagOnRemote === remote.head)
|
|
2316
|
+
const localHasIt = wanted.some((t) => localTags.includes(t))
|
|
2317
|
+
|
|
2318
|
+
if (remote) {
|
|
2319
|
+
// The remote is the truth: the tag either points at the shipped commit or not.
|
|
2320
|
+
if (tagIsOnShipped) return out
|
|
2321
|
+
const exists = Boolean(tagOnRemote) || (opts.tagExists !== undefined ? Boolean(opts.tagExists) : Boolean(await gitOut(dir, ['rev-parse', '--verify', '--quiet', `refs/tags/v${version}`])))
|
|
2322
|
+
out.push(
|
|
2323
|
+
exists
|
|
2324
|
+
? F(
|
|
2325
|
+
'git-tag-elsewhere',
|
|
2326
|
+
'warn',
|
|
2327
|
+
`tag v${version} does not point at the shipped commit`,
|
|
2328
|
+
`The tag is on ${String(tagOnRemote || 'another commit').slice(0, 12)} but the branch head is ${String(remote.head).slice(0, 12)}. A release built from that tag is not what you just published.`,
|
|
2329
|
+
`Move it: git tag -f v${version} <shipped-sha> && git push -f origin v${version}`,
|
|
2330
|
+
)
|
|
2331
|
+
: F(
|
|
2332
|
+
'git-tag-missing',
|
|
2333
|
+
'warn',
|
|
2334
|
+
`no git tag v${version} on the shipped commit`,
|
|
2335
|
+
'After publishing, the only record of these bytes is the registry. A GitHub release, a changelog entry or a market listing cannot point at a commit that was never tagged.',
|
|
2336
|
+
`git tag v${version} && git push origin v${version}`,
|
|
2337
|
+
),
|
|
2338
|
+
)
|
|
2339
|
+
return out
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
if (typeof atHead === 'string' && !localHasIt) {
|
|
2278
2343
|
const exists = opts.tagExists === undefined ? await gitOut(dir, ['rev-parse', '--verify', '--quiet', `refs/tags/v${version}`]) : opts.tagExists ? 'sha' : null
|
|
2279
2344
|
out.push(
|
|
2280
2345
|
exists
|
|
@@ -2297,6 +2362,22 @@ export async function checkTraceability(dir, opts = {}) {
|
|
|
2297
2362
|
return out
|
|
2298
2363
|
}
|
|
2299
2364
|
|
|
2365
|
+
/** `{ head, tags }` from one `git ls-remote`, or null when it cannot be read. */
|
|
2366
|
+
async function readRemoteRefs(dir) {
|
|
2367
|
+
const raw = await gitOut(dir, ['ls-remote', '--heads', '--tags', 'origin'])
|
|
2368
|
+
if (typeof raw !== 'string' || raw.length === 0) return null
|
|
2369
|
+
const tags = {}
|
|
2370
|
+
let head = null
|
|
2371
|
+
for (const line of raw.split('\n')) {
|
|
2372
|
+
const [sha, ref] = line.split('\t')
|
|
2373
|
+
if (!sha || !ref) continue
|
|
2374
|
+
if (ref === 'refs/heads/main' || ref === 'refs/heads/master') head = sha
|
|
2375
|
+
const m = /^refs\/tags\/(.+?)(?:\^\{\})?$/.exec(ref)
|
|
2376
|
+
if (m) tags[m[1]] = sha // a dereferenced annotated tag overwrites the tag object, which is what we want
|
|
2377
|
+
}
|
|
2378
|
+
return head ? { head, tags } : null
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2300
2381
|
// =====================================================================
|
|
2301
2382
|
// Orchestration
|
|
2302
2383
|
// =====================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bushizhanghan/dsh-publish-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
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",
|