dshmarket 1.73.2 → 1.73.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.
@@ -24,7 +24,7 @@ import { randomUUID } from 'node:crypto';
24
24
  import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
25
25
  import { dirname, join } from 'node:path';
26
26
  import { logEvent } from './log.js';
27
- import { readInstalledVersion, readManifestDeps, readProfileBundles } from './profile.js';
27
+ import { readInstalledManifest, readInstalledVersion, readManifestDeps, readProfileBundles } from './profile.js';
28
28
  const STATE_VERSION = 1;
29
29
  /**
30
30
  * The per-profile journal file.
@@ -175,14 +175,29 @@ function managerSucceeded(result) {
175
175
  || result.application === 'overridden';
176
176
  }
177
177
  /**
178
- * Whether this profile still wants the package: declared in its manifest, or
179
- * listed as a bundle. `readInstalledVersion` only looks at node_modules, and
180
- * the half-removed state #928 describes is exactly "still on disk, no longer
181
- * declared" — treating that as settled cleared the record while the plugin
182
- * was not composed at all (#949 review).
178
+ * Whether this profile is actually composed with the package.
179
+ *
180
+ * `readInstalledVersion` only looks at node_modules, and the half-removed
181
+ * state #928 describes is exactly "still on disk, no longer declared" —
182
+ * treating that as settled cleared the record while the plugin was not
183
+ * composed at all (#949 review). Being declared is necessary but not
184
+ * sufficient: a package that loads through the bundle layer is composed only
185
+ * when the profile lists it in `dsh.profile.bundles`, and the desktop manager
186
+ * can fail at its application stage AFTER writing the build and the
187
+ * dependency but BEFORE that row (#928) — the reporter's restart restored
188
+ * files and dependencies, left `dsh.profile.bundles` without the name, and
189
+ * the record said "recorded as finished".
190
+ *
191
+ * The row is required only of a package that declares `dsh.bundle`: a
192
+ * client-only package has no bundle layer to be listed in, and demanding the
193
+ * row of one would reinstall it on every start.
183
194
  */
184
- function profileStillWants(profile, profileDirectory, name) {
185
- if (Object.hasOwn(readManifestDeps(profile, profileDirectory), name))
195
+ function composed(profile, profileDirectory, name) {
196
+ if (!Object.hasOwn(readManifestDeps(profile, profileDirectory), name))
197
+ return false;
198
+ const manifest = readInstalledManifest(profile, name, profileDirectory);
199
+ const dsh = manifest?.dsh;
200
+ if (dsh?.bundle === undefined)
186
201
  return true;
187
202
  return readProfileBundles(profileDirectory).includes(name);
188
203
  }
@@ -242,16 +257,17 @@ export async function repairInterruptedUpdates(options) {
242
257
  const pending = journal.list().filter(entry => entry.profile === options.profile);
243
258
  for (const entry of pending) {
244
259
  const installed = readInstalledVersion(options.profile, entry.name, options.profileDirectory);
245
- const wanted = profileStillWants(options.profile, options.profileDirectory, entry.name);
246
- // Settled means BOTH halves agree: the build is on disk AND the profile
247
- // still declares it. "On disk but no longer declared" is the interrupted
248
- // removal itself, so it falls through to the reinstall.
249
- if (installed !== null && wanted && (entry.to === '' || installed === entry.to || installed === entry.from)) {
260
+ const settled = composed(options.profile, options.profileDirectory, entry.name);
261
+ // Settled means every half agrees: the build is on disk, the profile
262
+ // declares it, and a bundle-layer package is listed as one. "On disk but
263
+ // no longer composed" is the interrupted removal itself, so it falls
264
+ // through to the reinstall.
265
+ if (installed !== null && settled && (entry.to === '' || installed === entry.to || installed === entry.from)) {
250
266
  journal.finish(entry.id);
251
267
  report.finished.push(entry.name);
252
268
  continue;
253
269
  }
254
- if (installed !== null && wanted) {
270
+ if (installed !== null && settled) {
255
271
  journal.bump(entry.id);
256
272
  report.failed.push({
257
273
  name: entry.name,
@@ -275,6 +291,13 @@ export async function repairInterruptedUpdates(options) {
275
291
  if (after === null || (entry.to !== '' && after !== entry.to)) {
276
292
  throw new Error(`the desktop app refused the reinstall (${result.application})`);
277
293
  }
294
+ // The files landing is not the same answer as the plugin loading:
295
+ // this manager failed at the application stage, which is the step
296
+ // that writes `dsh.profile.bundles` (#928). Finishing here cleared
297
+ // the record for a package the host still would not compose.
298
+ if (!composed(options.profile, options.profileDirectory, entry.name)) {
299
+ throw new Error(`the desktop app wrote ${after} but did not compose it — no dsh.profile.bundles row`);
300
+ }
278
301
  journal.finish(entry.id);
279
302
  report.reinstalled.push(entry.name);
280
303
  logEvent('warn', 'update-repair', `${entry.name}: the interrupted update had already written ${after}; recorded as finished`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dshmarket",
3
3
  "description": "Visual plugin market inside DeepSeek Harness — browse, search, and one-click install community plugins. · DSH 可视化插件市场:逛一逛,点一下,装好。",
4
- "version": "1.73.2",
4
+ "version": "1.73.3",
5
5
  "icon": "assets/logo.svg",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",