dsh-plugin-toolkit 0.9.2 → 0.9.4

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,29 @@
1
+ ## 0.9.4
2
+
3
+ - The client module now declares `inject: ['slots', 'locale']` and guards every
4
+ service it reads. Without the list the loader calls `apply()` before those
5
+ services exist; the throw fails the **whole web boot**, and the app then reports
6
+ that third-party plugins are unsupported and starts with them disabled. That is
7
+ what one unguarded `ctx.locale.register` did to a real profile.
8
+ - `verify` now calls the client module's `apply()` with a bare context and refuses
9
+ a face without an `inject` list, so this class of failure is caught before
10
+ install instead of at boot.
11
+ - `init` scaffolds the same guards, for the same reason.
12
+
13
+ ## 0.9.3
14
+
15
+ - The panel needed one more thing than `dsh.client`: the host resolves the bundle
16
+ through `exports["./client"]` (`clientExportOf`), and drops the row when that
17
+ subpath is missing. 0.9.2 declared `dsh.client` but its `exports` map had no
18
+ `./client`, so the tab was still absent — this release exports `./client` and
19
+ `doctor` now checks the same thing the host does.
20
+ - `init` scaffolds `exports: { '.': './index.js', './client': './client.js' }`.
21
+ Every plugin generated before this release was missing it, so every generated
22
+ panel was invisible for the same reason.
23
+ - `doctor` reports a `dsh.client` whose `exports["./client"]` file does not exist,
24
+ and a `dsh.client` with no `./client` export at all. The `files`-list gap is now
25
+ caught at the manifest stage instead of at pack time.
26
+
1
27
  ## 0.9.2
2
28
 
3
29
  - `doctor` reports a shipped client bundle that no `dsh.client` declares. The host
package/client.js CHANGED
@@ -137,25 +137,43 @@ window.__ModuleLoader__.load({
137
137
  }
138
138
 
139
139
  function apply(ctx) {
140
- injectStyles(document);
141
- ctx.locale.register(NS, COPY);
142
- ctx.inject(['sidebarRightTabs', 'sidebarRight'], (tabCtx) => {
143
- if (tabCtx.slots === undefined || tabCtx.sidebarRightTabs === undefined) return;
144
- tabCtx.effect(() => tabCtx.slots.inject('sidebar.right.pane.tab', () => tabCtx.slots.register({
145
- name: 'sidebar.right.pane.tab',
146
- key: NS,
147
- locale: NS,
148
- }, Panel)));
149
- tabCtx.effect(() => tabCtx.sidebarRightTabs.register({
150
- id: NS,
151
- kind: KIND,
152
- priority: 'extension',
153
- title: () => 'Plugin doctor',
154
- }));
155
- });
140
+ // A panel that cannot find its services must degrade, not fail the boot:
141
+ // an exception here fails the whole web boot and takes every other plugin
142
+ // down with it. Every service below is optional for that reason.
143
+ try {
144
+ injectStyles(typeof document === 'undefined' ? undefined : document);
145
+ } catch {
146
+ // No document: the styles are cosmetic.
147
+ }
148
+ try {
149
+ if (ctx.locale !== undefined && typeof ctx.locale.register === 'function') ctx.locale.register(NS, COPY);
150
+ } catch {
151
+ // The panel keeps its English strings.
152
+ }
153
+ try {
154
+ if (typeof ctx.inject !== 'function') return;
155
+ ctx.inject(['sidebarRightTabs', 'sidebarRight'], (tabCtx) => {
156
+ if (tabCtx === undefined || tabCtx.slots === undefined || tabCtx.sidebarRightTabs === undefined) return;
157
+ tabCtx.effect(() => tabCtx.slots.inject('sidebar.right.pane.tab', () => tabCtx.slots.register({
158
+ name: 'sidebar.right.pane.tab',
159
+ key: NS,
160
+ locale: NS,
161
+ }, Panel)));
162
+ tabCtx.effect(() => tabCtx.sidebarRightTabs.register({
163
+ id: NS,
164
+ kind: KIND,
165
+ priority: 'extension',
166
+ title: () => 'Plugin doctor',
167
+ }));
168
+ });
169
+ } catch {
170
+ // No sidebar: the plugin still loads, it just has no place to show.
171
+ }
156
172
  }
157
173
 
158
- module.exports = { apply, name: 'dsh-plugin-toolkit' };
174
+ // The services the tab body reads. Declaring them lets the loader wait for
175
+ // them instead of calling apply() before they exist.
176
+ module.exports = { inject: ['slots', 'locale'], apply, name: 'dsh-plugin-toolkit' };
159
177
  return module.exports;
160
178
  },
161
179
  });
package/lib/doctor.mjs CHANGED
@@ -122,8 +122,20 @@ export function checkClientDeclaration(dir, manifest) {
122
122
  const client = manifest.dsh?.client;
123
123
  if (client !== undefined && client !== null) {
124
124
  const platform = client.platform;
125
- if (platform === undefined || platform === 'web') return null;
126
- return `dsh.client.platform is ${JSON.stringify(platform)}, but the harness serves client bundles on web`;
125
+ if (platform !== undefined && platform !== 'web') {
126
+ return `dsh.client.platform is ${JSON.stringify(platform)}, but the harness serves client bundles on web`;
127
+ }
128
+ // The host reads the bundle path from exports["./client"] only — a
129
+ // declaration without that subpath makes the host drop the row.
130
+ const exported = manifest.exports;
131
+ const subpath = typeof exported === 'string' ? undefined : exportTarget(exported?.['./client']);
132
+ if (typeof subpath !== 'string') {
133
+ return 'dsh.client is declared but exports has no "./client" bundle, so the host drops the row and the panel cannot appear';
134
+ }
135
+ const file = subpath.replace(/^\.\//u, '');
136
+ return fs.existsSync(path.join(dir, file))
137
+ ? null
138
+ : `exports["./client"] points at ${subpath}, which does not exist`;
127
139
  }
128
140
  const exported = manifest.exports;
129
141
  const subpath = typeof exported === 'string' ? undefined : exportTarget(exported?.['./client']);
package/lib/init.mjs CHANGED
@@ -33,6 +33,7 @@ function manifest({ name, title, version }) {
33
33
  type: 'module',
34
34
  engines: { node: '>=18', dsh: '>=0.2.0' },
35
35
  main: 'index.js',
36
+ exports: { '.': './index.js', './client': './client.js' },
36
37
  files: ['index.js', 'client.js', 'cordis.patch.yml', 'README.md'],
37
38
  scripts: { test: 'node --test', doctor: 'dsh-plugin doctor .' },
38
39
  devDependencies: {
@@ -85,10 +86,18 @@ function client({ name, title }) {
85
86
  }
86
87
 
87
88
  return {
89
+ // The services this panel reads. Declaring them makes the loader wait,
90
+ // and guarding them keeps a missing service from failing the whole boot.
91
+ inject: ['slots', 'locale'],
88
92
  apply(ctx) {
89
- ctx.locale.register(NS, COPY);
93
+ try {
94
+ if (ctx.locale !== undefined && typeof ctx.locale.register === 'function') ctx.locale.register(NS, COPY);
95
+ } catch {
96
+ // keep the English strings
97
+ }
98
+ if (typeof ctx.inject !== 'function') return undefined;
90
99
  const dispose = ctx.inject(['sidebarRightTabs', 'sidebarRight'], (tabCtx) => {
91
- if (tabCtx.slots === undefined) return;
100
+ if (tabCtx === undefined || tabCtx.slots === undefined) return;
92
101
  const tr = tabCtx.locale.bind(NS);
93
102
  const offType = tabCtx.sidebarRightTabs.register({
94
103
  id: NS,
package/lib/verify.mjs CHANGED
@@ -165,7 +165,30 @@ export async function verify({ dir, mount = true }) {
165
165
  // running out of npx's cache, whose dependencies are not the panel's.
166
166
  const panel = await mountClient({ file: entryFile, resolveFrom: dir });
167
167
  const text = panel.text();
168
+ // The web boot calls the module's apply() before every service exists. An
169
+ // exception there fails the whole boot and takes the other plugins down
170
+ // with it, so check the face and the bare-context call here.
171
+ const { loadBundle } = await import('./testkit/index.mjs');
172
+ const face = loadBundle(entryFile);
173
+ const faceProblems = [];
174
+ if (Array.isArray(face.inject) === false) {
175
+ faceProblems.push('the client module declares no inject list, so the loader calls apply() before its services exist');
176
+ }
177
+ try {
178
+ face.apply({});
179
+ } catch (error) {
180
+ faceProblems.push(`apply() throws when the services are absent, which fails the web boot: ${error.message}`);
181
+ }
168
182
  await panel.unmount();
183
+ if (faceProblems.length > 0) {
184
+ return {
185
+ ok: false,
186
+ stage: 'mount',
187
+ failures: faceProblems.map((message) => ({ check: 'boot', message })),
188
+ files: packed.files,
189
+ installed,
190
+ };
191
+ }
169
192
  return { ok: true, stage: 'mount', failures: [], files: packed.files, size: packed.size, installed, text };
170
193
  } catch (error) {
171
194
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-toolkit",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
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": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "exports": {
10
10
  ".": "./index.js",
11
+ "./client": "./client.js",
11
12
  "./cli": "./lib/cli.mjs",
12
13
  "./testkit": "./lib/testkit/index.mjs"
13
14
  },