dsh-plugin-shop 0.4.1 → 0.4.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/README.md +4 -3
- package/lib/client.js +435 -89
- package/lib/index.js +264 -36
- package/lib/typert.host.js +105 -20
- package/lib/typert.remote-client.d.ts +7 -3
- package/lib/typert.remote-client.js +73 -14
- package/lib/types/client/ShopTab.d.ts +6 -2
- package/lib/types/client/index.d.ts +3 -3
- package/lib/types/client/locales.d.ts +24 -0
- package/lib/types/client/useInstall.d.ts +9 -0
- package/lib/types/client/useUninstall.d.ts +21 -0
- package/lib/types/host/executor.d.ts +18 -6
- package/lib/types/host/index.d.ts +65 -4
- package/lib/types/host/restart.d.ts +35 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -256,7 +256,10 @@ function validateInstall(snapshot, args) {
|
|
|
256
256
|
}
|
|
257
257
|
//#endregion
|
|
258
258
|
//#region src/host/executor.ts
|
|
259
|
-
/**
|
|
259
|
+
/** Plugin-command executor: spawn the dsh CLI, stream its output, serialize
|
|
260
|
+
* per profile. One implementation drives both `dsh plugin add` (install) and
|
|
261
|
+
* `dsh plugin remove` (uninstall); the only differences are the verb and the
|
|
262
|
+
* post-exit manifest confirmation. */
|
|
260
263
|
const MAX_LOG_LINES = 200;
|
|
261
264
|
const MAX_LOG_BYTES = 65536;
|
|
262
265
|
const profileQueues = /* @__PURE__ */ new Map();
|
|
@@ -265,8 +268,7 @@ function chain(profile, task) {
|
|
|
265
268
|
profileQueues.set(profile, next.catch(() => {}));
|
|
266
269
|
return next;
|
|
267
270
|
}
|
|
268
|
-
/**
|
|
269
|
-
* The §7.2 step-6 confirm: after a zero exit, re-read the profile manifest and
|
|
271
|
+
/** The §7.2 step-6 confirm: after a zero exit, re-read the profile manifest and
|
|
270
272
|
* verify the bundle actually landed in `dsh.profile.bundles`. Exit 0 alone is
|
|
271
273
|
* not success — a library-that-looked-like-a-plugin, or a stale catalog,
|
|
272
274
|
* exits 0 while changing nothing (§10). The shop cannot force a client
|
|
@@ -285,18 +287,31 @@ function confirmBundleActivation(profile, home, expectedName) {
|
|
|
285
287
|
return `installed but the profile manifest could not be read (${join(profileDir, "package.json")}) — the catalog may be stale; refresh it`;
|
|
286
288
|
}
|
|
287
289
|
}
|
|
288
|
-
/**
|
|
289
|
-
*
|
|
290
|
+
/** The uninstall mirror of §7.2 step 6: after a zero exit, re-read the profile
|
|
291
|
+
* manifest and verify the bundle actually left `dsh.profile.bundles`. A zero
|
|
292
|
+
* exit that changed nothing must not read as success. */
|
|
293
|
+
function confirmBundleRemoval(profile, home, expectedName) {
|
|
294
|
+
const profileDir = resolveProfileDir(profile, home);
|
|
295
|
+
try {
|
|
296
|
+
if (!readProfileManifest("dsh-plugin-shop", profileDir).dsh?.profile?.bundles?.includes(expectedName)) return null;
|
|
297
|
+
return "removed but dsh.profile.bundles did not change — re-run the uninstall";
|
|
298
|
+
} catch {
|
|
299
|
+
return `removed but the profile manifest could not be read (${join(profileDir, "package.json")}) — re-run the uninstall`;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/** Run one `dsh plugin --profile <profile> <verb> <target>` and track it.
|
|
290
303
|
* Never rolls back; a failure surfaces stderr verbatim plus the recovery hint
|
|
291
304
|
* (§10). The shop never passes build-script flags: `allowBuilds` stays the
|
|
292
305
|
* user's explicit decision in the CLI (§7.2).
|
|
293
306
|
* The child inherits the current environment unless `env` is given — the
|
|
294
307
|
* real-install test pins DSH_HOME to a temporary directory this way.
|
|
295
|
-
* When `
|
|
296
|
-
* manifest (§7.2 step 6
|
|
297
|
-
*/
|
|
298
|
-
function
|
|
299
|
-
const { profile,
|
|
308
|
+
* When `confirm` is given, a zero exit is checked against the profile
|
|
309
|
+
* manifest before the command reports `done` (§7.2 step 6 and its uninstall
|
|
310
|
+
* mirror). */
|
|
311
|
+
function spawnPluginCli(options) {
|
|
312
|
+
const { profile, argv, dshBin, env, confirm, onStatus } = options;
|
|
313
|
+
const target = argv[1];
|
|
314
|
+
if (target === void 0 || target.startsWith("-")) throw new Error(`dsh-plugin-shop: refusing to spawn with a flag-like operand: ${target ?? "(none)"}`);
|
|
300
315
|
const installId = randomUUID();
|
|
301
316
|
const log = [];
|
|
302
317
|
let logBytes = 0;
|
|
@@ -325,8 +340,7 @@ function startInstall(options) {
|
|
|
325
340
|
"plugin",
|
|
326
341
|
"--profile",
|
|
327
342
|
profile,
|
|
328
|
-
|
|
329
|
-
spec
|
|
343
|
+
...argv
|
|
330
344
|
], {
|
|
331
345
|
stdio: [
|
|
332
346
|
"ignore",
|
|
@@ -352,8 +366,8 @@ function startInstall(options) {
|
|
|
352
366
|
if (state !== "running") return;
|
|
353
367
|
if (exitCode === 0) {
|
|
354
368
|
state = "done";
|
|
355
|
-
if (
|
|
356
|
-
const confirmDetail =
|
|
369
|
+
if (confirm !== void 0) {
|
|
370
|
+
const confirmDetail = confirm(env?.DSH_HOME);
|
|
357
371
|
if (confirmDetail !== null) {
|
|
358
372
|
state = "failed";
|
|
359
373
|
detail = confirmDetail;
|
|
@@ -370,6 +384,118 @@ function startInstall(options) {
|
|
|
370
384
|
}))
|
|
371
385
|
};
|
|
372
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Run one `dsh plugin --profile <profile> add <spec>` and track it.
|
|
389
|
+
* When `expectedName` is given, a zero exit is confirmed against the profile
|
|
390
|
+
* manifest (§7.2 step 6) before the install reports `done`.
|
|
391
|
+
*/
|
|
392
|
+
function startInstall(options) {
|
|
393
|
+
const { profile, spec, dshBin = "dsh", env, expectedName, onStatus } = options;
|
|
394
|
+
return spawnPluginCli({
|
|
395
|
+
profile,
|
|
396
|
+
argv: ["add", spec],
|
|
397
|
+
dshBin,
|
|
398
|
+
env,
|
|
399
|
+
confirm: expectedName !== void 0 ? (home) => confirmBundleActivation(profile, home, expectedName) : void 0,
|
|
400
|
+
onStatus
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Run one `dsh plugin --profile <profile> remove <name>` and track it.
|
|
405
|
+
* When `expectedName` is given, a zero exit is confirmed against the profile
|
|
406
|
+
* manifest — the bundle must actually have LEFT `dsh.profile.bundles` — before
|
|
407
|
+
* the uninstall reports `done`.
|
|
408
|
+
*/
|
|
409
|
+
function startUninstall(options) {
|
|
410
|
+
const { profile, name, dshBin = "dsh", env, expectedName, onStatus } = options;
|
|
411
|
+
return spawnPluginCli({
|
|
412
|
+
profile,
|
|
413
|
+
argv: ["remove", name],
|
|
414
|
+
dshBin,
|
|
415
|
+
env,
|
|
416
|
+
confirm: expectedName !== void 0 ? (home) => confirmBundleRemoval(profile, home, expectedName) : void 0,
|
|
417
|
+
onStatus
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region src/host/restart.ts
|
|
422
|
+
/** Restart executor: re-spawn the Host's own command line and hand the
|
|
423
|
+
* browser the new server's URL. The child is detached (its own process
|
|
424
|
+
* group), so it survives both the parent's exit and the launching
|
|
425
|
+
* terminal's. The parent exits only AFTER the child printed its
|
|
426
|
+
* `dsh web: <url>` line — a restart that fails to come up leaves the old
|
|
427
|
+
* process running untouched. */
|
|
428
|
+
/** How long to wait for the child to print its URL before declaring the
|
|
429
|
+
* restart failed and killing the child. dsh web prints the URL once the
|
|
430
|
+
* Loader tree settles; 20s is generous on slow machines. */
|
|
431
|
+
const RESTART_UP_TIMEOUT_MS = 2e4;
|
|
432
|
+
/** Spawn the restarted server and wait for it to announce its URL.
|
|
433
|
+
*
|
|
434
|
+
* The child runs the same `dsh` with the same argv the current process was
|
|
435
|
+
* launched with (`process.argv.slice(2)` — node and the CLI script path
|
|
436
|
+
* stripped), so the profile, port and flags reproduce the user's launch
|
|
437
|
+
* verbatim. `--port 0` therefore yields a NEW port, and the returned URL is
|
|
438
|
+
* how the browser finds it.
|
|
439
|
+
*
|
|
440
|
+
* On success the caller exits the old process — but only after delivering
|
|
441
|
+
* the RPC response carrying `url`, which is the caller's sequencing duty,
|
|
442
|
+
* not this module's. On failure (child exits before announcing, spawn
|
|
443
|
+
* error, or the timeout) the child is killed if still running and the old
|
|
444
|
+
* process is untouched. */
|
|
445
|
+
function startRestart(options) {
|
|
446
|
+
const { dshBin, argv, env, timeoutMs = RESTART_UP_TIMEOUT_MS } = options;
|
|
447
|
+
const stderr = [];
|
|
448
|
+
return new Promise((resolve) => {
|
|
449
|
+
const child = spawn(dshBin, argv, {
|
|
450
|
+
stdio: [
|
|
451
|
+
"ignore",
|
|
452
|
+
"pipe",
|
|
453
|
+
"pipe"
|
|
454
|
+
],
|
|
455
|
+
env: env ?? process.env,
|
|
456
|
+
detached: true
|
|
457
|
+
});
|
|
458
|
+
child.unref();
|
|
459
|
+
const timeout = setTimeout(() => {
|
|
460
|
+
child.kill();
|
|
461
|
+
resolve({
|
|
462
|
+
ok: false,
|
|
463
|
+
detail: "the restarted server did not announce its URL in time"
|
|
464
|
+
});
|
|
465
|
+
}, timeoutMs);
|
|
466
|
+
child.stdout.on("data", (chunk) => {
|
|
467
|
+
for (const line of chunk.toString().split("\n")) {
|
|
468
|
+
if (line === "") continue;
|
|
469
|
+
const match = /dsh web: (http:\/\/\S+)/.exec(line);
|
|
470
|
+
if (match?.[1] !== void 0) {
|
|
471
|
+
clearTimeout(timeout);
|
|
472
|
+
resolve({
|
|
473
|
+
ok: true,
|
|
474
|
+
url: match[1]
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
child.stderr.on("data", (chunk) => {
|
|
480
|
+
for (const line of chunk.toString().split("\n")) if (line !== "") stderr.push(line);
|
|
481
|
+
});
|
|
482
|
+
child.on("error", (error) => {
|
|
483
|
+
const code = error.code;
|
|
484
|
+
clearTimeout(timeout);
|
|
485
|
+
resolve({
|
|
486
|
+
ok: false,
|
|
487
|
+
detail: code === "ENOENT" ? "dsh not found on PATH — restart could not be launched" : `restart spawn failed: ${error.message}`
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
child.on("close", () => {
|
|
491
|
+
clearTimeout(timeout);
|
|
492
|
+
resolve({
|
|
493
|
+
ok: false,
|
|
494
|
+
detail: `the restarted server exited during boot — ${stderr[stderr.length - 1] ?? "no output"}`
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
}
|
|
373
499
|
//#endregion
|
|
374
500
|
//#region src/host/profile.ts
|
|
375
501
|
/** Profile directory discovery and user-layer writes (§8: hot enable/disable). */
|
|
@@ -494,7 +620,9 @@ let ShopGateway = (() => {
|
|
|
494
620
|
let _catalog_decorators;
|
|
495
621
|
let _install_decorators;
|
|
496
622
|
let _installStatus_decorators;
|
|
497
|
-
let
|
|
623
|
+
let _installed_decorators;
|
|
624
|
+
let _uninstall_decorators;
|
|
625
|
+
let _restart_decorators;
|
|
498
626
|
return class ShopGateway extends _classSuper {
|
|
499
627
|
static {
|
|
500
628
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
@@ -502,7 +630,9 @@ let ShopGateway = (() => {
|
|
|
502
630
|
_catalog_decorators = [Remote("catalog")];
|
|
503
631
|
_install_decorators = [Remote("installStart")];
|
|
504
632
|
_installStatus_decorators = [Remote("installStatus")];
|
|
505
|
-
|
|
633
|
+
_installed_decorators = [Remote("installed")];
|
|
634
|
+
_uninstall_decorators = [Remote("uninstallStart")];
|
|
635
|
+
_restart_decorators = [Remote("restart")];
|
|
506
636
|
__esDecorate(this, null, _setEnabled_decorators, {
|
|
507
637
|
kind: "method",
|
|
508
638
|
name: "setEnabled",
|
|
@@ -547,14 +677,36 @@ let ShopGateway = (() => {
|
|
|
547
677
|
},
|
|
548
678
|
metadata: _metadata
|
|
549
679
|
}, null, _instanceExtraInitializers);
|
|
550
|
-
__esDecorate(this, null,
|
|
680
|
+
__esDecorate(this, null, _installed_decorators, {
|
|
681
|
+
kind: "method",
|
|
682
|
+
name: "installed",
|
|
683
|
+
static: false,
|
|
684
|
+
private: false,
|
|
685
|
+
access: {
|
|
686
|
+
has: (obj) => "installed" in obj,
|
|
687
|
+
get: (obj) => obj.installed
|
|
688
|
+
},
|
|
689
|
+
metadata: _metadata
|
|
690
|
+
}, null, _instanceExtraInitializers);
|
|
691
|
+
__esDecorate(this, null, _uninstall_decorators, {
|
|
692
|
+
kind: "method",
|
|
693
|
+
name: "uninstall",
|
|
694
|
+
static: false,
|
|
695
|
+
private: false,
|
|
696
|
+
access: {
|
|
697
|
+
has: (obj) => "uninstall" in obj,
|
|
698
|
+
get: (obj) => obj.uninstall
|
|
699
|
+
},
|
|
700
|
+
metadata: _metadata
|
|
701
|
+
}, null, _instanceExtraInitializers);
|
|
702
|
+
__esDecorate(this, null, _restart_decorators, {
|
|
551
703
|
kind: "method",
|
|
552
|
-
name: "
|
|
704
|
+
name: "restart",
|
|
553
705
|
static: false,
|
|
554
706
|
private: false,
|
|
555
707
|
access: {
|
|
556
|
-
has: (obj) => "
|
|
557
|
-
get: (obj) => obj.
|
|
708
|
+
has: (obj) => "restart" in obj,
|
|
709
|
+
get: (obj) => obj.restart
|
|
558
710
|
},
|
|
559
711
|
metadata: _metadata
|
|
560
712
|
}, null, _instanceExtraInitializers);
|
|
@@ -572,11 +724,21 @@ let ShopGateway = (() => {
|
|
|
572
724
|
profileDir;
|
|
573
725
|
inventory;
|
|
574
726
|
dshBin;
|
|
727
|
+
/** The argv `shop/restart` re-spawns: the real process argv minus node and
|
|
728
|
+
* the CLI script path, or a test-provided substitute. */
|
|
729
|
+
restartArgv;
|
|
730
|
+
/** The exit the restart calls once the response is out; `process.exit` in
|
|
731
|
+
* production, a spy in tests. */
|
|
732
|
+
exit;
|
|
733
|
+
restartExitDelayMs;
|
|
575
734
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
576
735
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
577
736
|
/** Finished install records retained, so a poll sees the true terminal
|
|
578
737
|
* state (§8: done / needsRestart / failure detail). Oldest evicted on add. */
|
|
579
738
|
static MAX_FINISHED_INSTALLS = 32;
|
|
739
|
+
/** How long the gateway waits after a successful restart response before
|
|
740
|
+
* exiting the old process — the browser must receive the URL first. */
|
|
741
|
+
static RESTART_EXIT_DELAY_MS = 2e3;
|
|
580
742
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
581
743
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
582
744
|
lastSnapshot = null;
|
|
@@ -591,6 +753,9 @@ let ShopGateway = (() => {
|
|
|
591
753
|
this.profileDir = options.profileDir;
|
|
592
754
|
this.inventory = options.inventory;
|
|
593
755
|
this.dshBin = options.dshBin ?? "dsh";
|
|
756
|
+
this.restartArgv = options.restartArgv ?? process.argv.slice(2);
|
|
757
|
+
this.exit = options.exit ?? ((code) => process.exit(code));
|
|
758
|
+
this.restartExitDelayMs = options.restartExitDelayMs ?? ShopGateway.RESTART_EXIT_DELAY_MS;
|
|
594
759
|
}
|
|
595
760
|
/** The boot's Loader root directory (the active profile's `cordis.yml`
|
|
596
761
|
* directory, carried on `ctx.baseUrl`), when present. A `link:` install
|
|
@@ -727,8 +892,11 @@ let ShopGateway = (() => {
|
|
|
727
892
|
...running.status()
|
|
728
893
|
};
|
|
729
894
|
}
|
|
730
|
-
/** Installed plugins
|
|
731
|
-
|
|
895
|
+
/** Installed catalog plugins (§7.3): every entry of the snapshot the profile
|
|
896
|
+
* manifest declares as a dependency, with the Host's `outdated` verdict
|
|
897
|
+
* attached. The tab's shelf cards and its installed section both derive
|
|
898
|
+
* from this one list. */
|
|
899
|
+
async installed() {
|
|
732
900
|
if (this.lastSnapshot === null) {
|
|
733
901
|
const { catalogUrl, cacheDir } = this.rowConfig();
|
|
734
902
|
const { snapshot } = await (this.options.loadCatalog ?? loadCatalog)({
|
|
@@ -738,24 +906,84 @@ let ShopGateway = (() => {
|
|
|
738
906
|
this.lastSnapshot = snapshot;
|
|
739
907
|
}
|
|
740
908
|
const dependencies = readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {};
|
|
741
|
-
const
|
|
909
|
+
const installed = [];
|
|
742
910
|
for (const entry of this.lastSnapshot.entries) {
|
|
743
|
-
const
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
if (installed === entry.version) floor = installed;
|
|
747
|
-
else try {
|
|
748
|
-
floor = minVersion(installed)?.version ?? null;
|
|
749
|
-
} catch {
|
|
750
|
-
floor = null;
|
|
751
|
-
}
|
|
752
|
-
if (floor !== null && lt(floor, entry.version)) outdated.push({
|
|
911
|
+
const spec = dependencies[entry.name];
|
|
912
|
+
if (spec === void 0) continue;
|
|
913
|
+
installed.push({
|
|
753
914
|
name: entry.name,
|
|
754
|
-
installed,
|
|
755
|
-
latest: entry.version
|
|
915
|
+
installed: spec,
|
|
916
|
+
latest: entry.version,
|
|
917
|
+
outdated: this.isBehind(spec, entry.version)
|
|
756
918
|
});
|
|
757
919
|
}
|
|
758
|
-
return
|
|
920
|
+
return installed;
|
|
921
|
+
}
|
|
922
|
+
/** Whether an installed dependency spec sits behind the catalog's version.
|
|
923
|
+
* A spec identical to the catalog version is current by definition; a
|
|
924
|
+
* pnpm-written non-semver spec like `workspace:*` is not reportable and
|
|
925
|
+
* reads as current rather than killing the RPC. */
|
|
926
|
+
isBehind(spec, latest) {
|
|
927
|
+
if (spec === latest) return false;
|
|
928
|
+
let floor;
|
|
929
|
+
try {
|
|
930
|
+
floor = minVersion(spec)?.version ?? null;
|
|
931
|
+
} catch {
|
|
932
|
+
floor = null;
|
|
933
|
+
}
|
|
934
|
+
return floor !== null && lt(floor, latest);
|
|
935
|
+
}
|
|
936
|
+
/** Uninstall one installed catalog plugin from the profile (§7.3 follow-up
|
|
937
|
+
* amendment). Removing revokes privilege rather than granting it, so there
|
|
938
|
+
* is no acknowledgement gate. The name must be a catalog entry the profile
|
|
939
|
+
* manifest declares as a dependency — the RPC cannot remove profile
|
|
940
|
+
* dependencies the shop does not manage (the base bundle, the shop
|
|
941
|
+
* itself). The same install records/polling serve the client. */
|
|
942
|
+
async uninstall(args) {
|
|
943
|
+
if (this.lastSnapshot === null) {
|
|
944
|
+
const { catalogUrl, cacheDir } = this.rowConfig();
|
|
945
|
+
const { snapshot } = await (this.options.loadCatalog ?? loadCatalog)({
|
|
946
|
+
baseUrl: catalogUrl,
|
|
947
|
+
cacheDir
|
|
948
|
+
});
|
|
949
|
+
this.lastSnapshot = snapshot;
|
|
950
|
+
}
|
|
951
|
+
if (!this.lastSnapshot.entries.some((entry) => entry.name === args.name)) return {
|
|
952
|
+
ok: false,
|
|
953
|
+
detail: `dsh-plugin-shop: ${args.name} is not in the catalog`
|
|
954
|
+
};
|
|
955
|
+
if ((readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {})[args.name] === void 0) return {
|
|
956
|
+
ok: false,
|
|
957
|
+
detail: `dsh-plugin-shop: ${args.name} is not installed`
|
|
958
|
+
};
|
|
959
|
+
const running = startUninstall({
|
|
960
|
+
profile: this.profile,
|
|
961
|
+
name: args.name,
|
|
962
|
+
dshBin: this.dshBin,
|
|
963
|
+
expectedName: args.name
|
|
964
|
+
});
|
|
965
|
+
this.installs.set(running.installId, running);
|
|
966
|
+
this.installOrder.push(running.installId);
|
|
967
|
+
this.evictFinishedInstalls();
|
|
968
|
+
return {
|
|
969
|
+
ok: true,
|
|
970
|
+
installId: running.installId
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
/** Restart the dsh process the shop runs in (§8 amendment, 2026-08-27):
|
|
974
|
+
* re-spawn this process's own command line, return the new server's URL
|
|
975
|
+
* once it announces itself, and only then exit. A failed restart returns a
|
|
976
|
+
* typed failure and the old process keeps serving — the restart is
|
|
977
|
+
* all-or-nothing. The response must reach the browser before the exit, so
|
|
978
|
+
* the exit is delayed past the RPC round-trip. */
|
|
979
|
+
async restart() {
|
|
980
|
+
const outcome = await startRestart({
|
|
981
|
+
dshBin: this.dshBin,
|
|
982
|
+
argv: this.restartArgv,
|
|
983
|
+
env: process.env
|
|
984
|
+
});
|
|
985
|
+
if (outcome.ok) setTimeout(() => this.exit(0), this.restartExitDelayMs);
|
|
986
|
+
return outcome;
|
|
759
987
|
}
|
|
760
988
|
};
|
|
761
989
|
})();
|
package/lib/typert.host.js
CHANGED
|
@@ -38,6 +38,12 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
38
38
|
})),
|
|
39
39
|
'stars': z.record(z.string(), z.number()),
|
|
40
40
|
})
|
|
41
|
+
const dsh_plugin_shop_shop_installed_result$schema = z.array(z.object({
|
|
42
|
+
'name': z.string(),
|
|
43
|
+
'installed': z.string(),
|
|
44
|
+
'latest': z.string(),
|
|
45
|
+
'outdated': z.boolean(),
|
|
46
|
+
}))
|
|
41
47
|
const dsh_plugin_shop_shop_installStart_parameter_0$schema = z.object({
|
|
42
48
|
'name': z.string(),
|
|
43
49
|
'version': z.string(),
|
|
@@ -61,11 +67,13 @@ const dsh_plugin_shop_shop_installStatus_result$schema = z.object({
|
|
|
61
67
|
'needsRestart': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
|
|
62
68
|
'detail': z.union([z.undefined(), z.string()]).optional(),
|
|
63
69
|
})
|
|
64
|
-
const
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
const dsh_plugin_shop_shop_restart_result$schema = z.union([z.object({
|
|
71
|
+
'ok': z.literal(true),
|
|
72
|
+
'url': z.string(),
|
|
73
|
+
}), z.object({
|
|
74
|
+
'ok': z.literal(false),
|
|
75
|
+
'detail': z.string(),
|
|
76
|
+
})])
|
|
69
77
|
const dsh_plugin_shop_shop_setEnabled_parameter_0$schema = z.object({
|
|
70
78
|
'name': z.string(),
|
|
71
79
|
'enabled': z.boolean(),
|
|
@@ -74,6 +82,16 @@ const dsh_plugin_shop_shop_setEnabled_result$schema = z.object({
|
|
|
74
82
|
'ok': z.boolean(),
|
|
75
83
|
'detail': z.union([z.undefined(), z.string()]).optional(),
|
|
76
84
|
})
|
|
85
|
+
const dsh_plugin_shop_shop_uninstallStart_parameter_0$schema = z.object({
|
|
86
|
+
'name': z.string(),
|
|
87
|
+
})
|
|
88
|
+
const dsh_plugin_shop_shop_uninstallStart_result$schema = z.union([z.object({
|
|
89
|
+
'ok': z.literal(true),
|
|
90
|
+
'installId': z.string(),
|
|
91
|
+
}), z.object({
|
|
92
|
+
'ok': z.literal(false),
|
|
93
|
+
'detail': z.string(),
|
|
94
|
+
})])
|
|
77
95
|
|
|
78
96
|
export const TYPERT = {
|
|
79
97
|
package: 'dsh-plugin-shop',
|
|
@@ -105,7 +123,22 @@ export const TYPERT = {
|
|
|
105
123
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
106
124
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
107
125
|
},
|
|
108
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
126
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":218,"column":9},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'dsh-plugin-shop#shop/installed',
|
|
130
|
+
service: 'shop',
|
|
131
|
+
namespace: 'shop',
|
|
132
|
+
method: 'installed',
|
|
133
|
+
invocation: { kind: 'direct' },
|
|
134
|
+
parameters: [
|
|
135
|
+
],
|
|
136
|
+
result: {
|
|
137
|
+
mode: 'strict',
|
|
138
|
+
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
139
|
+
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
140
|
+
},
|
|
141
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":293,"column":9},
|
|
109
142
|
},
|
|
110
143
|
{
|
|
111
144
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -131,7 +164,7 @@ export const TYPERT = {
|
|
|
131
164
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
132
165
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
133
166
|
},
|
|
134
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
167
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":244,"column":9},
|
|
135
168
|
},
|
|
136
169
|
{
|
|
137
170
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -156,22 +189,22 @@ export const TYPERT = {
|
|
|
156
189
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
157
190
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
158
191
|
},
|
|
159
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
192
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":282,"column":3},
|
|
160
193
|
},
|
|
161
194
|
{
|
|
162
|
-
id: 'dsh-plugin-shop#shop/
|
|
195
|
+
id: 'dsh-plugin-shop#shop/restart',
|
|
163
196
|
service: 'shop',
|
|
164
197
|
namespace: 'shop',
|
|
165
|
-
method: '
|
|
198
|
+
method: 'restart',
|
|
166
199
|
invocation: { kind: 'direct' },
|
|
167
200
|
parameters: [
|
|
168
201
|
],
|
|
169
202
|
result: {
|
|
170
203
|
mode: 'strict',
|
|
171
|
-
typeSymbol: 'dsh-plugin-shop#
|
|
172
|
-
schema:
|
|
204
|
+
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
205
|
+
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
173
206
|
},
|
|
174
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
207
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":367,"column":9},
|
|
175
208
|
},
|
|
176
209
|
{
|
|
177
210
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -196,7 +229,33 @@ export const TYPERT = {
|
|
|
196
229
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
197
230
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
198
231
|
},
|
|
199
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
232
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":187,"column":3},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
236
|
+
service: 'shop',
|
|
237
|
+
namespace: 'shop',
|
|
238
|
+
method: 'uninstallStart',
|
|
239
|
+
implementation: 'uninstall',
|
|
240
|
+
invocation: { kind: 'direct' },
|
|
241
|
+
parameters: [
|
|
242
|
+
{
|
|
243
|
+
name: 'args',
|
|
244
|
+
wire: 'args',
|
|
245
|
+
source: 'json',
|
|
246
|
+
codec: {
|
|
247
|
+
mode: 'strict',
|
|
248
|
+
typeSymbol: 'dsh-plugin-shop#shop/uninstallStart:args',
|
|
249
|
+
schema: dsh_plugin_shop_shop_uninstallStart_parameter_0$schema,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
result: {
|
|
254
|
+
mode: 'strict',
|
|
255
|
+
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
256
|
+
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
257
|
+
},
|
|
258
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":333,"column":9},
|
|
200
259
|
},
|
|
201
260
|
],
|
|
202
261
|
model: {
|
|
@@ -245,10 +304,24 @@ export const TYPERT = {
|
|
|
245
304
|
},
|
|
246
305
|
{
|
|
247
306
|
"kind": "method",
|
|
248
|
-
"name": "
|
|
249
|
-
"signature": "@Remote('
|
|
250
|
-
"summary": "Installed plugins
|
|
251
|
-
"jsDoc": "/** Installed plugins
|
|
307
|
+
"name": "installed",
|
|
308
|
+
"signature": "@Remote('installed') async installed(): Promise<ShopInstalledEntry[]>",
|
|
309
|
+
"summary": "Installed catalog plugins (§7.3): every entry of the snapshot the profile manifest declares as a dependency, with the Host's `outdated` verdict attached.",
|
|
310
|
+
"jsDoc": "/** Installed catalog plugins (§7.3): every entry of the snapshot the profile\n * manifest declares as a dependency, with the Host's `outdated` verdict\n * attached. The tab's shelf cards and its installed section both derive\n * from this one list. */"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"kind": "method",
|
|
314
|
+
"name": "uninstall",
|
|
315
|
+
"signature": "@Remote('uninstallStart') async uninstall(args: { name: string }): Promise<ShopUninstallResult>",
|
|
316
|
+
"summary": "Uninstall one installed catalog plugin from the profile (§7.3 follow-up amendment).",
|
|
317
|
+
"jsDoc": "/** Uninstall one installed catalog plugin from the profile (§7.3 follow-up\n * amendment). Removing revokes privilege rather than granting it, so there\n * is no acknowledgement gate. The name must be a catalog entry the profile\n * manifest declares as a dependency — the RPC cannot remove profile\n * dependencies the shop does not manage (the base bundle, the shop\n * itself). The same install records/polling serve the client. */"
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"kind": "method",
|
|
321
|
+
"name": "restart",
|
|
322
|
+
"signature": "@Remote('restart') async restart(): Promise<ShopRestartResult>",
|
|
323
|
+
"summary": "Restart the dsh process the shop runs in (§8 amendment, 2026-08-27): re-spawn this process's own command line, return the new server's URL once it announces itself, and only then exit.",
|
|
324
|
+
"jsDoc": "/** Restart the dsh process the shop runs in (§8 amendment, 2026-08-27):\n * re-spawn this process's own command line, return the new server's URL\n * once it announces itself, and only then exit. A failed restart returns a\n * typed failure and the old process keeps serving — the restart is\n * all-or-nothing. The response must reach the browser before the exit, so\n * the exit is delayed past the RPC round-trip. */"
|
|
252
325
|
}
|
|
253
326
|
],
|
|
254
327
|
"types": [
|
|
@@ -284,10 +357,18 @@ export const TYPERT = {
|
|
|
284
357
|
"name": "InstallStatus",
|
|
285
358
|
"declaration": "export interface InstallStatus {\n state: InstallState;\n log: string[];\n needsRestart?: boolean;\n detail?: string;\n}"
|
|
286
359
|
},
|
|
360
|
+
{
|
|
361
|
+
"name": "RestartOutcome",
|
|
362
|
+
"declaration": "export type RestartOutcome = { ok: true; url: string; } | { ok: false; detail: string; };"
|
|
363
|
+
},
|
|
287
364
|
{
|
|
288
365
|
"name": "ShopCatalogResult",
|
|
289
366
|
"declaration": "export interface ShopCatalogResult {\n schemaVersion: number;\n builtAt: string;\n stale: boolean;\n plugins: CatalogEntry[];\n denied: DeniedEntry[];\n stars: Record<string, number>;\n}"
|
|
290
367
|
},
|
|
368
|
+
{
|
|
369
|
+
"name": "ShopInstalledEntry",
|
|
370
|
+
"declaration": "export interface ShopInstalledEntry {\n name: string;\n installed: string;\n latest: string;\n outdated: boolean;\n}"
|
|
371
|
+
},
|
|
291
372
|
{
|
|
292
373
|
"name": "ShopInstallResult",
|
|
293
374
|
"declaration": "export type ShopInstallResult = { ok: true; installId: string; } | { ok: false; code: InstallRejectionCode; detail: string; };"
|
|
@@ -297,12 +378,16 @@ export const TYPERT = {
|
|
|
297
378
|
"declaration": "export interface ShopInstallStatusResult extends InstallStatus {\n found: boolean;\n}"
|
|
298
379
|
},
|
|
299
380
|
{
|
|
300
|
-
"name": "
|
|
301
|
-
"declaration": "export
|
|
381
|
+
"name": "ShopRestartResult",
|
|
382
|
+
"declaration": "export type ShopRestartResult = RestartOutcome;"
|
|
302
383
|
},
|
|
303
384
|
{
|
|
304
385
|
"name": "ShopSetEnabledResult",
|
|
305
386
|
"declaration": "export interface ShopSetEnabledResult {\n ok: boolean;\n detail?: string;\n}"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"name": "ShopUninstallResult",
|
|
390
|
+
"declaration": "export type ShopUninstallResult = { ok: true; installId: string; } | { ok: false; detail: string; };"
|
|
306
391
|
}
|
|
307
392
|
]
|
|
308
393
|
}
|
|
@@ -3,22 +3,26 @@ import type {
|
|
|
3
3
|
RemoteResult,
|
|
4
4
|
TypertRemoteContribution,
|
|
5
5
|
} from '@deepseek-ai/dsh-typert-protocol'
|
|
6
|
-
import type { InstallArgs, ShopCatalogResult, ShopInstallResult, ShopInstallStatusResult,
|
|
6
|
+
import type { InstallArgs, ShopCatalogResult, ShopInstalledEntry, ShopInstallResult, ShopInstallStatusResult, ShopRestartResult, ShopSetEnabledResult, ShopUninstallResult } from 'dsh-plugin-shop/types'
|
|
7
7
|
|
|
8
8
|
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
9
9
|
interface TypertRemoteNamespace$73686f70 {
|
|
10
10
|
catalog: (args?: { refresh?: boolean; }) => Promise<RemoteResult<ShopCatalogResult>>
|
|
11
|
+
installed: () => Promise<RemoteResult<ShopInstalledEntry[]>>
|
|
11
12
|
installStart: (args: InstallArgs) => Promise<RemoteResult<ShopInstallResult>>
|
|
12
13
|
installStatus: (args: { installId: string; }) => Promise<RemoteResult<ShopInstallStatusResult>>
|
|
13
|
-
|
|
14
|
+
restart: () => Promise<RemoteResult<ShopRestartResult>>
|
|
14
15
|
setEnabled: (args: { name: string; enabled: boolean; }) => Promise<RemoteResult<ShopSetEnabledResult>>
|
|
16
|
+
uninstallStart: (args: { name: string; }) => Promise<RemoteResult<ShopUninstallResult>>
|
|
15
17
|
}
|
|
16
18
|
interface TypertRemoteMap {
|
|
17
19
|
'shop/catalog': (args?: { refresh?: boolean; }) => Promise<RemoteResult<ShopCatalogResult>>
|
|
20
|
+
'shop/installed': () => Promise<RemoteResult<ShopInstalledEntry[]>>
|
|
18
21
|
'shop/installStart': (args: InstallArgs) => Promise<RemoteResult<ShopInstallResult>>
|
|
19
22
|
'shop/installStatus': (args: { installId: string; }) => Promise<RemoteResult<ShopInstallStatusResult>>
|
|
20
|
-
'shop/
|
|
23
|
+
'shop/restart': () => Promise<RemoteResult<ShopRestartResult>>
|
|
21
24
|
'shop/setEnabled': (args: { name: string; enabled: boolean; }) => Promise<RemoteResult<ShopSetEnabledResult>>
|
|
25
|
+
'shop/uninstallStart': (args: { name: string; }) => Promise<RemoteResult<ShopUninstallResult>>
|
|
22
26
|
}
|
|
23
27
|
interface TypertRemoteNamespaceMap {
|
|
24
28
|
'shop': TypertRemoteNamespace$73686f70
|