dsh-plugin-toolkit 0.9.0 → 0.9.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 CHANGED
@@ -1,3 +1,13 @@
1
+ ## 0.9.1
2
+
3
+ - `audit` separates problems from advisories. Running 0.9.0 on a real profile
4
+ said "17 of 19 installed plugin(s) have problems" — 13 of those load fine and
5
+ only lack `engines.dsh` or a README Requirements section, which the market index
6
+ needs and the loader does not. Those are now counted separately, so the plugins
7
+ that are actually broken stay visible.
8
+ - `client entry` and `dev` resolve a `./client` export written as a conditions
9
+ object (`{ default: './lib/client.cjs' }`), not only as a string.
10
+
1
11
  ## 0.9.0
2
12
 
3
13
  - `audit`: every plugin installed in a profile, in one pass — version, whether the
package/README.md CHANGED
@@ -212,6 +212,10 @@ demo-panel 0.3.0 not loaded 1 problem: root export
212
212
  1 installed plugin(s) are not in the running app; restart the app once after installing.
213
213
  ```
214
214
 
215
+ Advisories (`engines.dsh`, README Requirements) are counted apart from problems:
216
+ the market index needs them, the loader does not, so they should not bury a plugin
217
+ that is actually broken.
218
+
215
219
  Without `--host` the loaded column is `?`, because guessing would be worse than
216
220
  saying nothing. Only packages that declare a `dsh` field are audited — the rest of
217
221
  `node_modules` is ordinary dependencies, and auditing those is noise.
package/lib/audit.mjs CHANGED
@@ -48,9 +48,26 @@ export function listInstalled({ home = path.join(os.homedir(), '.dsh') } = {}) {
48
48
  return found;
49
49
  }
50
50
 
51
- function describe({ failures }) {
52
- if (failures.length === 0) return 'ok';
53
- return `${failures.length} problem${failures.length === 1 ? '' : 's'}: ${failures[0].check}`;
51
+ /**
52
+ * `engines.dsh` and the README Requirements section are what the market index
53
+ * needs. The loader does not read either, so a plugin without them still loads —
54
+ * calling that "a problem" would bury the plugins that are actually broken.
55
+ */
56
+ const ADVISORY = new Set(['engines.dsh', 'readme']);
57
+
58
+ function split(failures) {
59
+ return {
60
+ hard: failures.filter((failure) => ADVISORY.has(failure.check) === false),
61
+ advisory: failures.filter((failure) => ADVISORY.has(failure.check) === true),
62
+ };
63
+ }
64
+
65
+ function describe({ hard, advisory }) {
66
+ if (hard.length === 0 && advisory.length === 0) return 'ok';
67
+ const parts = [];
68
+ if (hard.length > 0) parts.push(`${hard.length} problem${hard.length === 1 ? '' : 's'}: ${hard[0].check}`);
69
+ if (advisory.length > 0) parts.push(`${advisory.length} advisor${advisory.length === 1 ? 'y' : 'ies'}`);
70
+ return parts.join(', ');
54
71
  }
55
72
 
56
73
  export async function audit({ home, host, io = console, fetchImpl, timeoutMs = 3000 } = {}) {
@@ -74,7 +91,7 @@ export async function audit({ home, host, io = console, fetchImpl, timeoutMs = 3
74
91
  failures = [{ check: 'manifest', message: error.message }];
75
92
  }
76
93
  const loaded = graph?.ok === true ? graph.ids.has(manifest.name) : null;
77
- return { name: manifest.name, version: manifest.version ?? '?', profile, dir, failures, loaded };
94
+ return { name: manifest.name, version: manifest.version ?? '?', profile, dir, failures, ...split(failures), loaded };
78
95
  });
79
96
 
80
97
  const width = Math.max(...rows.map((row) => row.name.length), 6);
@@ -84,12 +101,17 @@ export async function audit({ home, host, io = console, fetchImpl, timeoutMs = 3
84
101
  for (const failure of row.failures) io.log(`${' '.repeat(width + 22)}${failure.check}: ${failure.message}`);
85
102
  }
86
103
 
87
- const failed = rows.filter((row) => row.failures.length > 0);
104
+ const failed = rows.filter((row) => row.hard.length > 0);
105
+ const advised = rows.filter((row) => row.hard.length === 0 && row.advisory.length > 0);
88
106
  const notLoaded = rows.filter((row) => row.loaded === false);
89
107
  if (notLoaded.length > 0) {
90
108
  io.log('');
91
109
  io.log(`${notLoaded.length} installed plugin(s) are not in the running app; restart the app once after installing.`);
92
110
  }
111
+ if (advised.length > 0) {
112
+ io.log('');
113
+ io.log(`${advised.length} plugin(s) load but are not ready for the market index (engines.dsh, README Requirements).`);
114
+ }
93
115
  if (failed.length > 0) {
94
116
  io.log('');
95
117
  io.log(`${failed.length} of ${rows.length} installed plugin(s) have problems.`);
package/lib/dev.mjs CHANGED
@@ -12,12 +12,14 @@ import os from 'node:os';
12
12
  import path from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
14
 
15
+ import { exportTarget } from './doctor.mjs';
16
+
15
17
  const IGNORED = new Set(['node_modules', '.git', 'docs', 'test', 'tests', 'coverage', 'fixtures']);
16
18
 
17
19
  export function resolveEntry(dir, manifest) {
18
20
  const declared = manifest?.dsh?.client?.entry;
19
21
  const exported = manifest?.exports;
20
- const subpath = typeof exported === 'string' ? undefined : exported?.['./client'];
22
+ const subpath = typeof exported === 'string' ? undefined : exportTarget(exported?.['./client']);
21
23
  const candidates = [declared, subpath, 'client.js', 'lib/client.js', 'dist/client.js'].filter((value) => typeof value === 'string');
22
24
  for (const candidate of candidates) {
23
25
  const absolute = path.resolve(dir, candidate);
package/lib/doctor.mjs CHANGED
@@ -84,6 +84,17 @@ export function checkPatchFile(dir, manifest) {
84
84
  return `dsh.bundle.patch points at ${bundle.patch}, which does not exist`;
85
85
  }
86
86
 
87
+ /** `exports['./client']` is a string in some packages and a conditions object in others. */
88
+ export function exportTarget(value) {
89
+ if (typeof value === 'string') return value;
90
+ if (value !== null && typeof value === 'object') {
91
+ for (const key of ['default', 'import', 'require']) {
92
+ if (typeof value[key] === 'string') return value[key];
93
+ }
94
+ }
95
+ return undefined;
96
+ }
97
+
87
98
  export function checkClientEntry(dir, manifest) {
88
99
  const client = manifest.dsh?.client;
89
100
  if (client === undefined || client === null) return null;
@@ -91,7 +102,7 @@ export function checkClientEntry(dir, manifest) {
91
102
  // with a `dsh/` folder names its bundle), then the plain file names. A plugin
92
103
  // that ships `dsh/client.js` is not wrong for failing to also ship `client.js`.
93
104
  const exported = manifest.exports;
94
- const subpath = typeof exported === 'string' ? undefined : exported?.['./client'];
105
+ const subpath = typeof exported === 'string' ? undefined : exportTarget(exported?.['./client']);
95
106
  const entries = [client.entry, subpath, 'client.js', 'lib/client.js', 'dist/client.js']
96
107
  .filter((entry) => typeof entry === 'string' && entry.length > 0)
97
108
  .map((entry) => entry.replace(/^\.\//u, ''));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-toolkit",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
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": {