dsh-plugin-guide 0.3.8 → 0.3.9

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.
@@ -93,11 +93,28 @@ Publish once, mirror everywhere, make the mirror idempotent:
93
93
  | Surface | Mechanism | Failure mode to watch |
94
94
  |---|---|---|
95
95
  | npm | tag-triggered workflow | dist-tag lands on the wrong line |
96
- | GitHub Release | workflow or manual | release notes drift from CHANGELOG |
96
+ | GitHub Release | the same tag-triggered workflow (idempotent) | release notes drift from CHANGELOG |
97
97
  | Gitee (mirror) | scheduled sync workflow | mirror HEAD behind upstream after a force-move |
98
98
 
99
99
  Verify all three before calling a release done. A release that exists on npm but not in the mirror is a half release, and mirror users will report a bug you already fixed.
100
100
 
101
+ The GitHub Release is created by the **same tag workflow** that publishes to npm - never by hand. Manual creation is exactly how release notes drift from `CHANGELOG.md`, and the gap is invisible from the registry: a version that exists on npm but has no Release page looks complete to `npm view` and incomplete to anyone reading the repository. Keep the step idempotent, so a re-run, a backfilled tag, or an already-existing Release page cannot fail the job:
102
+
103
+ ```sh
104
+ if gh release view "$TAG" >/dev/null 2>&1; then
105
+ echo "release $TAG already exists; skipping"
106
+ exit 0
107
+ fi
108
+ node scripts/changelog-section.mjs "$VERSION" > release-notes.md || true
109
+ if [ -s release-notes.md ]; then
110
+ gh release create "$TAG" --title "$TAG" --notes-file release-notes.md
111
+ else
112
+ gh release create "$TAG" --generate-notes
113
+ fi
114
+ ```
115
+
116
+ Three details carry the weight. The job needs `permissions: contents: write` (the publish job's `contents: read` is not enough, and job-level permissions override the workflow's). It belongs in a **separate job that `needs:` the publish job**, so a tag whose `CHANGELOG.md` lacks that section cannot turn a successful npm publish into a red release. And the `|| true` plus the `-s` test is what makes the missing-section case degrade to generated notes instead of failing - a release page with generated notes beats no release page at all.
117
+
101
118
  ---
102
119
 
103
120
  ## 5. README parity is part of the build
@@ -135,6 +152,7 @@ Before every tag push:
135
152
  - [ ] package invariants: the `files` whitelist contains the built entry, and the entry exists
136
153
  - [ ] README language parity plus encoding audit (no BOM, no mojibake, no replacement characters)
137
154
  - [ ] `CHANGELOG.md` has a section for the version you are about to tag
155
+ - [ ] the tag workflow creates the GitHub Release itself, idempotently, from that section (section 4)
138
156
  - [ ] the version bump matches intent (patch = wave, minor = feature, major = breaking)
139
157
  - [ ] the tag does not already exist on the remote
140
158
 
@@ -158,7 +176,18 @@ Document these once so nobody debugs them twice:
158
176
 
159
177
  ---
160
178
 
161
- ## 9. What this buys you
179
+ ## 9. Local tooling that fakes a result
180
+
181
+ Each of these returns a confident wrong answer rather than an error, and each one cost a real debugging cycle. Written down so the next person recognises the shape:
182
+
183
+ - **`npm view <pkg>@<ver> A B --json` invents absences.** Asking for two fields at once can report a field that exists as empty, which reads as "the published package lost its `peerDependencies`". Query one field per call; when it matters, unpack the published tarball and read its `package.json` - that is the ground truth. An audit that flagged eight packages this way was wrong about all eight.
184
+ - **A content search that honours `.gitignore` returns false negatives.** Where the workspace root ignores everything (`*` plus a single negation), a search across it finds nothing, and "nothing" reads as proof of absence. Before concluding a pattern is absent, confirm it with a scan that does not consult ignore rules.
185
+ - **Windows PowerShell 5.1's `Set-Content -Encoding utf8` writes a BOM.** A JSON request body beginning `EF BB BF` fails to parse, and the error blames the payload's contents rather than its encoding. Write machine-read files through an explicit encoder: `[System.IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))`.
186
+ - **On Windows, `rd /s /q` cannot remove a tree containing a reserved device name** (`NUL`, `CON`, `AUX`, `PRN`, `COM1`-`COM9`, `LPT1`-`LPT9`). It reports success and leaves the entire ancestor chain in place. Delete that entry through a `\\?\`-prefixed long path, then assert the directory is actually gone - a zero exit code is not evidence that the tree disappeared.
187
+
188
+ ---
189
+
190
+ ## 10. What this buys you
162
191
 
163
192
  A portfolio maintained this way behaves like a product: users on any harness line can install any plugin, every version is attested, mirrors agree, and a breaking upstream change costs one wave instead of an outage. It is also the strongest form of ecosystem contribution - the official project asks for exactly this, and explicitly does not rank official packages above community ones.
164
193
 
@@ -93,11 +93,28 @@ jobs:
93
93
  | 面 | 机制 | 要盯的失败模式 |
94
94
  |---|---|---|
95
95
  | npm | tag 触发 workflow | dist-tag 落错线 |
96
- | GitHub Release | workflow 或手工 | release notes 与 CHANGELOG 漂移 |
96
+ | GitHub Release | 同一个 tag 触发的 workflow(幂等) | release notes 与 CHANGELOG 漂移 |
97
97
  | Gitee(镜像) | 定时同步 workflow | 强制移动 tag 后镜像 HEAD 落后于上游 |
98
98
 
99
99
  三面都验过,才算发布完成。只上了 npm 而镜像没有,是半个发布——镜像上的用户会来报一个你早已修好的 bug。
100
100
 
101
+ GitHub Release 由**发布 npm 的同一个 tag workflow** 创建,**绝不手工建**。手工建正是 release notes 与 `CHANGELOG.md` 漂移的来源,而且这个缺口在 registry 侧看不见:一个上了 npm 却没有 Release 页的版本,对 `npm view` 看起来是完整的,对任何读仓库的人却是不完整的。这一步必须幂等,这样重跑、补 tag、或 Release 页已存在都不会让 job 失败:
102
+
103
+ ```sh
104
+ if gh release view "$TAG" >/dev/null 2>&1; then
105
+ echo "release $TAG already exists; skipping"
106
+ exit 0
107
+ fi
108
+ node scripts/changelog-section.mjs "$VERSION" > release-notes.md || true
109
+ if [ -s release-notes.md ]; then
110
+ gh release create "$TAG" --title "$TAG" --notes-file release-notes.md
111
+ else
112
+ gh release create "$TAG" --generate-notes
113
+ fi
114
+ ```
115
+
116
+ 三个细节是关键。该 job 需要 `permissions: contents: write`(publish job 的 `contents: read` 不够,且 job 级权限会覆盖 workflow 级)。它应当是**一个 `needs:` 发布 job 的独立 job**,这样一个 `CHANGELOG.md` 缺少该小节的 tag 就不会把已经成功的 npm 发布变成红灯。而 `|| true` 加 `-s` 判断,正是让「缺小节」这一情形退化为生成式 notes 而不是失败的原因——有生成式 notes 的 Release 页,胜过没有 Release 页。
117
+
101
118
  ---
102
119
 
103
120
  ## 5. README 一致性属于构建的一部分
@@ -135,6 +152,7 @@ jobs:
135
152
  - [ ] 包不变量:`files` 白名单确实包含构建产物,且该产物存在
136
153
  - [ ] 多语 README 一致 + 编码审计(无 BOM、无乱码、无替换字符)
137
154
  - [ ] `CHANGELOG.md` 有即将打的这个版本的小节
155
+ - [ ] tag workflow 自己创建 GitHub Release,且幂等,正文取自该版本小节(见第 4 节)
138
156
  - [ ] 版本号与意图一致(patch = 波,minor = 功能,major = 破坏性)
139
157
  - [ ] 远端还没有这个 tag
140
158
 
@@ -158,7 +176,18 @@ jobs:
158
176
 
159
177
  ---
160
178
 
161
- ## 9. 这样做换来什么
179
+ ## 9. 会伪造结论的本地工具
180
+
181
+ 下面每一条都返回一个自信的错误答案,而不是报错;每一条都真实吃掉过一个排查周期。写下来,好让下一个人认得出这个形状:
182
+
183
+ - **`npm view <pkg>@<ver> A B --json` 会凭空造出「缺失」。** 一次问两个字段时,它可能把一个实际存在的字段报成空值,读起来就像「已发布的包丢了 `peerDependencies`」。每次只查一个字段;当真要下结论时,解开已发布的 tarball 读它的 `package.json`——那才是地面真相。曾有一次审计据此标出八个仓,八个全错。
184
+ - **遵守 `.gitignore` 的内容检索会返回假阴性。** 当工作区根目录忽略一切(`*` 加一条例外)时,在它上面做检索会一无所获,而「一无所获」会被读成「不存在」的证据。在断言某个模式不存在之前,先用一个不查忽略规则的扫描确认一遍。
185
+ - **Windows PowerShell 5.1 的 `Set-Content -Encoding utf8` 会写 BOM。** 以 `EF BB BF` 开头的 JSON 请求体会解析失败,而报错指向的是载荷内容而不是编码。机器要读的文件一律走显式编码器:`[System.IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))`。
186
+ - **在 Windows 上,`rd /s /q` 删不掉含保留设备名的目录树**(`NUL`、`CON`、`AUX`、`PRN`、`COM1`-`COM9`、`LPT1`-`LPT9`)。它会报成功,却把整条祖先链原样留下。用 `\\?\` 前缀的长路径删除该条目,然后断言目录确实消失——退出码为 0 不能作为「树已消失」的证据。
187
+
188
+ ---
189
+
190
+ ## 10. 这样做换来什么
162
191
 
163
192
  按这套方式维护的组合,表现得像一个产品:任何宿主线上的用户都能装到任何插件,每个版本都有凭证,镜像一致,上游的破坏性变更只花掉一波而不是一次事故。这同时也是最强的生态贡献形式——官方要的正是这个,并且明确否认"官方仓的包比社区的包更重要"。
164
193
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-guide",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "The dsh-plugin-guide knowledge base plus the dsh-plugin-dev CLI toolchain: official docs, Cordis primer, community deep-dives, and battle-tested pitfalls registered as an on-demand agent skill, with a scaffolder, static checker, and pack verifier for building DSH plugins.",
5
5
  "type": "module",
6
6
  "main": "index.js",