dsh-plugin-toolkit 0.13.1 → 0.13.3

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 CHANGED
@@ -1,3 +1,23 @@
1
+ ## 0.13.3
2
+
3
+ - **`verify` no longer fails a correct plugin.** The check that calls a client
4
+ module's `apply()` before its services exist handed it `{}`, so
5
+ `ctx.inject(...)` — the cordis API every plugin uses — threw
6
+ `ctx.inject is not a function` and the plugin was reported as broken. The
7
+ probe now models a real context: `inject`, `on`, `effect`, `logger`, `get`,
8
+ `set` and `provide` exist; only the services are absent.
9
+ - Found by this repository's own example plugin, whose CI went red on the
10
+ previous release. That is the whole point of the example.
11
+
12
+ ## 0.13.2
13
+
14
+ - **A table of the four ways a plugin installs and still does nothing** — red
15
+ dot with no panel, a row the host drops, an app that disables every plugin
16
+ after one unguarded `apply()`, and a tab type nothing can open. Each row is a
17
+ bug this toolkit shipped and then learned to catch, and each row names the
18
+ check that catches it now.
19
+ - Both READMEs. Nothing in the code changed.
20
+
1
21
  ## 0.13.1
2
22
 
3
23
  - **A badge you can put on a plugin that passes.** `docs/badge.svg` plus the
package/README.md CHANGED
@@ -73,6 +73,19 @@ Releasing one plugin fifteen times taught me three things the ecosystem's toolin
73
73
  2. **The market index is a second release, done by hand.** Each version needs an entry carrying `publishTimestamp` from the registry and `engines.dsh` from the manifest. Forget the range and the index validator rejects the PR with `Engines mismatch` — after the tag is already public.
74
74
  3. **Reading a manifest is not the same as checking it.** A plugin can have a valid `package.json` and still be uninstallable: `files` that omit the entry the manifest points at, a bundle patch that does not exist, a README that never states the host range it declares.
75
75
 
76
+ ### Four ways a plugin installs and still does nothing
77
+
78
+ Each of these shipped in a real version of this toolkit before the check below existed. A manifest check passes on all four.
79
+
80
+ | What you see | What it was | Caught by |
81
+ | --- | --- | --- |
82
+ | The tab is a red dot and the panel never opens | `exports["."]` pointed at the CLI, so the loader's row had no plugin to apply | `root export` |
83
+ | The plugin is installed, the app never serves it | `dsh.client` declared, `exports["./client"]` missing — the host drops the row | `client declaration` |
84
+ | The app says the third-party plugin is unsupported and disables all of them | `apply()` read `ctx.webServer` without declaring it; cordis throws instead of returning `undefined` | `verify` (calls `apply` on a context that has no services) |
85
+ | The tab registers, and nothing in the UI opens it | `sidebarRightTabs.register()` registers a *type*; the sidebar lists what `tabs.guide()` returns | a testkit test, not `doctor` |
86
+
87
+ The first three are static: `doctor` and `verify` catch them without the app running. The fourth is a UI contract, so it needs the panel mounted and the registration inspected — that is what [`testkit`](#test-a-panel-without-a-browser) is for.
88
+
76
89
  ## Requirements
77
90
 
78
91
  - Node.js 18 or newer (the registry is read with `fetch`; no runtime dependencies)
package/README.zh.md CHANGED
@@ -31,6 +31,19 @@ npx dsh-plugin-toolkit@latest release --pr --merge # 打 tag、等
31
31
 
32
32
  每一条都能在发布前被机器拦住,所以每一条现在都是一项检查。这些检查对着**宿主的解析代码**写,而不是对着文件约定写——差别就是 `verify` 报 `mounted the panel` 而真实宿主什么都不加载。
33
33
 
34
+ ### 插件装上了却什么都不做的四种样子
35
+
36
+ 下面每一条都真实发布过,而且**只看 manifest 一个都发现不了**。
37
+
38
+ | 你会看到 | 真正的原因 | 现在由谁拦 |
39
+ | --- | --- | --- |
40
+ | 标签是个红点,面板永远打不开 | `exports["."]` 指向了 CLI,加载器那一行没有插件可挂 | `root export` |
41
+ | 插件装上了,应用从不加载它 | 声明了 `dsh.client`,但 `exports["./client"]` 不存在——宿主直接丢掉这一行 | `client declaration` |
42
+ | 应用提示「第三方插件不支持」,把所有插件都禁用 | `apply()` 读了没声明的 `ctx.webServer`,cordis 不是返回 `undefined` 而是抛错 | `verify`(在一个没有任何服务的 ctx 上调用 `apply`) |
43
+ | 标签注册成功,界面里却没有任何入口 | `sidebarRightTabs.register()` 注册的是**类型**,侧栏列的是 `tabs.guide()` 的返回 | testkit 的测试,不是 `doctor` |
44
+
45
+ 前三条是静态的:`doctor` 和 `verify` 不用开应用就能抓到。第四条是 UI 契约,必须把面板挂起来、检查注册出去的参数——那是 [`testkit`](#不用浏览器测面板) 的活。
46
+
34
47
  ## 安装要求
35
48
 
36
49
  - Node.js 18 或更新(CI 在 18/20/22 × ubuntu/macos/windows 上跑)。
package/lib/verify.mjs CHANGED
@@ -39,8 +39,37 @@ function packTarball(dir, destination) {
39
39
  * What cordis hands a plugin that declares no `inject`: every undeclared
40
40
  * property read throws. A plain `{}` hides the bug this is looking for.
41
41
  */
42
+ // cordis itself is present on every context — `ctx.inject`, `ctx.on`,
43
+ // `ctx.effect` and the logger. What can be absent is the *services*.
44
+ function cordisApi() {
45
+ const noop = () => () => {};
46
+ const base = {
47
+ inject: (list, callback) => {
48
+ // A declared service may still be absent when apply runs: record nothing,
49
+ // call nothing. That is exactly what the host does before a service mounts.
50
+ void list;
51
+ void callback;
52
+ return () => {};
53
+ },
54
+ on: noop,
55
+ effect: noop,
56
+ logger: () => {},
57
+ get: () => undefined,
58
+ set: () => {},
59
+ provide: noop,
60
+ reflect: { get: () => undefined, set: () => {}, provide: noop },
61
+ };
62
+ return base;
63
+ }
64
+
65
+ /** A context whose services have not mounted yet; reading one gives undefined. */
66
+ function absentContext() {
67
+ return cordisApi();
68
+ }
69
+
70
+ /** A context that also enforces cordis's rule: an undeclared read throws. */
42
71
  function undeclaredContext() {
43
- const base = { inject: undefined };
72
+ const base = cordisApi();
44
73
  return new Proxy(base, {
45
74
  get(target, key) {
46
75
  if (key in target) return target[key];
@@ -212,7 +241,7 @@ export async function verify({ dir, mount = true }) {
212
241
  faceProblems.push('the client module declares no inject list, so the loader calls apply() before its services exist');
213
242
  }
214
243
  try {
215
- face.apply({});
244
+ face.apply(absentContext());
216
245
  } catch (error) {
217
246
  faceProblems.push(`apply() throws when the services are absent, which fails the web boot: ${error.message}`);
218
247
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-toolkit",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "Preflight, release and test DeepSeek Harness plugins: check a folder or any GitHub repository, wait for npm to actually serve the version, and keep the market index honest.",
5
5
  "type": "module",
6
6
  "bin": {