dsh-plugin-toolkit 0.8.1 → 0.8.2

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,14 @@
1
+ ## 0.8.2
2
+
3
+ `doctor` now refuses the failure that shipped in 0.8.0.
4
+
5
+ - New check `root export`: when a package declares `dsh.bundle`, its root module
6
+ has to be the plugin. A root that is also a `bin` target — a CLI wearing a
7
+ plugin's clothes — is reported, and so is a root module with no `apply`.
8
+ pnpm reports success either way; the row just mounts nothing.
9
+ - `test/doctor.test.mjs` covers both shapes, and a bundle with no root module
10
+ (a subpath plugin) stays legal.
11
+
1
12
  ## 0.8.1
2
13
 
3
14
  `0.8.0` installed and then did nothing: the loader starts a plugin by importing
package/lib/doctor.mjs CHANGED
@@ -138,6 +138,34 @@ export function checkFilesList(manifest) {
138
138
  return null;
139
139
  }
140
140
 
141
+ /**
142
+ * The loader starts a plugin by importing the bare package name. A package that
143
+ * ships both a CLI and a panel is one `exports` edit away from having its root
144
+ * point at the CLI — the row then mounts nothing, and pnpm reports success.
145
+ */
146
+ export function checkRootExport(dir, manifest) {
147
+ if (manifest.dsh === undefined || manifest.dsh === null) return null;
148
+ const root = typeof manifest.exports === 'object' && manifest.exports !== null ? manifest.exports['.'] : undefined;
149
+ const entry = typeof root === 'string' ? root : manifest.main;
150
+ // A bundle that inserts a subpath plugin needs no root module at all.
151
+ if (typeof entry !== 'string') return null;
152
+
153
+ const target = entry.replace(/^\.\//, '');
154
+ const bins = Object.values(manifest.bin ?? {}).filter((value) => typeof value === 'string');
155
+ if (bins.some((bin) => bin.replace(/^\.\//, '') === target) === true) {
156
+ return `exports["."] points at the CLI (${entry}), so the loader's row has no plugin to apply; point it at the module that exports apply`;
157
+ }
158
+
159
+ const file = path.resolve(dir, target);
160
+ if (fs.existsSync(file) === false) return null;
161
+ const source = fs.readFileSync(file, 'utf8');
162
+ const exported = /export\s+(?:async\s+)?function\s+apply\b|export\s+(?:const|let|var)\s+apply\b|export\s*\{[^}]*\bapply\b|\bapply\s*[:=]\s*(?:async\s*)?(?:function|\()/.test(source);
163
+ if (exported === false) {
164
+ return `the root export ${entry} does not export apply, so the loader imports the package and mounts nothing`;
165
+ }
166
+ return null;
167
+ }
168
+
141
169
  /** Runs every check and returns `{ failures, notes }`. */
142
170
  export function doctor(dir) {
143
171
  const { manifest } = readManifest(dir);
@@ -149,6 +177,7 @@ export function doctor(dir) {
149
177
  ['patch file', checkPatchFile(dir, manifest)],
150
178
  ['client entry', checkClientEntry(dir, manifest)],
151
179
  ['files list', checkFilesList(manifest)],
180
+ ['root export', checkRootExport(dir, manifest)],
152
181
  ];
153
182
  const failures = checks.filter(([, result]) => result !== null && typeof result === 'string');
154
183
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-toolkit",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Preflight, release and test DeepSeek Harness plugins: checks what the host needs, waits for npm to actually serve the version, and keeps the market index honest.",
5
5
  "type": "module",
6
6
  "bin": {