dsh-plugin-shop 0.4.3 → 0.4.5
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 +13 -8
- package/lib/client.js +257 -35
- package/lib/index.js +161 -81
- package/lib/typert.host.js +87 -11
- package/lib/typert.remote-client.d.ts +5 -1
- package/lib/typert.remote-client.js +62 -8
- package/lib/types/client/ShopTab.d.ts +5 -1
- package/lib/types/client/locales.d.ts +6 -0
- package/lib/types/client/present.d.ts +7 -0
- package/lib/types/client/useUpdateSelf.d.ts +20 -0
- package/lib/types/host/index.d.ts +43 -5
- package/lib/types/host/restart.d.ts +27 -24
- package/lib/types/host/self-update.d.ts +9 -0
- package/lib/types/own-version.d.ts +7 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* §11.3.4): no render path here may ever use dangerouslySetInnerHTML. */
|
|
6
6
|
import { type ReactNode } from 'react';
|
|
7
7
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
8
|
-
import type { InstallArgs, ShopCatalogResult, ShopInstalledEntry, ShopInstallResult, ShopInstallStatusResult, ShopRestartResult, ShopSetEnabledResult, ShopUninstallResult } from '../host/index.ts';
|
|
8
|
+
import type { InstallArgs, ShopCatalogResult, ShopInstalledEntry, ShopInstallResult, ShopInstallStatusResult, ShopRestartResult, ShopSetEnabledResult, ShopUninstallResult, ShopUpdateResult, ShopVersionResult } from '../host/index.ts';
|
|
9
9
|
/** The tab's Remote face: the Host result types, already unwrapped from the
|
|
10
10
|
* wire envelope by `index.ts`; `catalog` throws on a wire error so the tab's
|
|
11
11
|
* error state renders. */
|
|
@@ -26,6 +26,10 @@ export interface ShopTabInjected {
|
|
|
26
26
|
name: string;
|
|
27
27
|
}) => Promise<ShopUninstallResult>;
|
|
28
28
|
restart: () => Promise<ShopRestartResult>;
|
|
29
|
+
version: () => Promise<ShopVersionResult>;
|
|
30
|
+
updateStart: (args: {
|
|
31
|
+
version: string;
|
|
32
|
+
}) => Promise<ShopUpdateResult>;
|
|
29
33
|
}
|
|
30
34
|
/** Full component props assembled by the Settings slot renderer. */
|
|
31
35
|
export type ShopTabProps = PropsRuntime<'settings.plugins.tab'> & PropsLocale<'settings.shop'> & InjectFace<ShopTabInjected>;
|
|
@@ -59,7 +59,10 @@ export declare const zh: {
|
|
|
59
59
|
restartBody: string;
|
|
60
60
|
restartConfirm: string;
|
|
61
61
|
restarting: string;
|
|
62
|
+
restartFailedNotice: string;
|
|
62
63
|
restartTransportFailed: string;
|
|
64
|
+
updateFailed: string;
|
|
65
|
+
updateTransportFailed: string;
|
|
63
66
|
enabledSwitch: string;
|
|
64
67
|
toggleFailed: string;
|
|
65
68
|
hotApplyNote: string;
|
|
@@ -126,7 +129,10 @@ export declare const en: {
|
|
|
126
129
|
restartBody: string;
|
|
127
130
|
restartConfirm: string;
|
|
128
131
|
restarting: string;
|
|
132
|
+
restartFailedNotice: string;
|
|
129
133
|
restartTransportFailed: string;
|
|
134
|
+
updateFailed: string;
|
|
135
|
+
updateTransportFailed: string;
|
|
130
136
|
enabledSwitch: string;
|
|
131
137
|
toggleFailed: string;
|
|
132
138
|
hotApplyNote: string;
|
|
@@ -58,6 +58,13 @@ export type InstallEvent = {
|
|
|
58
58
|
};
|
|
59
59
|
/** §7.2 once-per-second poll cadence, as a named constant. */
|
|
60
60
|
export declare const INSTALL_POLL_MS = 1000;
|
|
61
|
+
/** §8 restart handoff: the client polls the origin only after this grace
|
|
62
|
+
* period (the host's exit delay plus margin), so an origin that answers is
|
|
63
|
+
* the NEW server, never the dying old one. */
|
|
64
|
+
export declare const RESTART_GRACE_MS = 3000;
|
|
65
|
+
/** §8 restart handoff: how long the client waits for the new server to
|
|
66
|
+
* answer before reporting the manual restart command. */
|
|
67
|
+
export declare const RESTART_WAIT_MS = 30000;
|
|
61
68
|
/** How many shelf cards mount at a time. The shelf holds ~1900 entries; one
|
|
62
69
|
* commit of the whole grid is ~28k DOM nodes, so the list renders in batches
|
|
63
70
|
* behind a sentinel (§A1) and grows on scroll. 48 is ~3-4 rows. */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Self-update driving hook for the shop itself: start, poll to terminal.
|
|
2
|
+
* Shares the install view machine and poll loop; the start mapping is the
|
|
3
|
+
* uninstall one — a business refusal (a non-semver version) lands in the
|
|
4
|
+
* `failed` view with the host's detail, never in the install `rejected`
|
|
5
|
+
* codes. */
|
|
6
|
+
import type { ShopInstallStatusResult, ShopUpdateResult } from '../host/index.ts';
|
|
7
|
+
import type { InstallView } from './present.ts';
|
|
8
|
+
export interface UseUpdateSelfResult {
|
|
9
|
+
view: InstallView;
|
|
10
|
+
start: (args: {
|
|
11
|
+
version: string;
|
|
12
|
+
}) => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/** Drive one self-update: the host's business union, the shared polling
|
|
15
|
+
* loop, and terminal states. The `rejected` install state never occurs. */
|
|
16
|
+
export declare function useUpdateSelf(updateStart: (args: {
|
|
17
|
+
version: string;
|
|
18
|
+
}) => Promise<ShopUpdateResult>, installStatus: (args: {
|
|
19
|
+
installId: string;
|
|
20
|
+
}) => Promise<ShopInstallStatusResult>): UseUpdateSelfResult;
|
|
@@ -36,6 +36,13 @@ export interface ShopGatewayOptions {
|
|
|
36
36
|
/** Test-only injection: the exit the restart calls after the response is
|
|
37
37
|
* delivered. Production uses `process.exit`. */
|
|
38
38
|
exit?: (code?: number) => void;
|
|
39
|
+
/** The pid the restart helper waits on before exec'ing the new dsh;
|
|
40
|
+
* defaults to this process. Tests point it at a dead pid so the fixture
|
|
41
|
+
* runs immediately instead of waiting for the vitest worker to exit. */
|
|
42
|
+
restartParentPid?: number;
|
|
43
|
+
/** Test-only injection: the shop's latest-version lookup; production
|
|
44
|
+
* fetches the npm packument. */
|
|
45
|
+
fetchLatestVersion?: () => Promise<string | null>;
|
|
39
46
|
/** How long the gateway waits after a successful restart response before
|
|
40
47
|
* exiting the old process; test-only shortening, production uses 2s. */
|
|
41
48
|
restartExitDelayMs?: number;
|
|
@@ -72,6 +79,24 @@ export type ShopUninstallResult = {
|
|
|
72
79
|
/** `shop/restart` result (§7.3): the restarted server's URL, or a typed
|
|
73
80
|
* failure — on failure the old process is still serving. */
|
|
74
81
|
export type ShopRestartResult = RestartOutcome;
|
|
82
|
+
/** `shop/version` result (§7.3): the RUNNING shop version (from the shipped
|
|
83
|
+
* package.json, not the manifest's range), the npm latest when the check
|
|
84
|
+
* could answer (`null` = no answer — advisory, never an error), and the
|
|
85
|
+
* comparison verdict. */
|
|
86
|
+
export interface ShopVersionResult {
|
|
87
|
+
installed: string;
|
|
88
|
+
latest: string | null;
|
|
89
|
+
outdated: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** `shop/updateStart` result (§7.3): the self-update spawn, or a typed
|
|
92
|
+
* refusal (a version that is not plain semver). */
|
|
93
|
+
export type ShopUpdateResult = {
|
|
94
|
+
ok: true;
|
|
95
|
+
installId: string;
|
|
96
|
+
} | {
|
|
97
|
+
ok: false;
|
|
98
|
+
detail: string;
|
|
99
|
+
};
|
|
75
100
|
/** `shop/installed` entry (§7.3): one installed catalog plugin. `installed`
|
|
76
101
|
* is the profile manifest's dependency spec verbatim (a range, a tag, or
|
|
77
102
|
* `workspace:*`); `outdated` is the Host's verdict that the installed version
|
|
@@ -111,6 +136,8 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
111
136
|
* production, a spy in tests. */
|
|
112
137
|
private readonly exit;
|
|
113
138
|
private readonly restartExitDelayMs;
|
|
139
|
+
private readonly restartParentPid;
|
|
140
|
+
private readonly latestVersion;
|
|
114
141
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
115
142
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
116
143
|
/** Finished install records retained, so a poll sees the true terminal
|
|
@@ -184,11 +211,22 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
184
211
|
name: string;
|
|
185
212
|
}): Promise<ShopUninstallResult>;
|
|
186
213
|
/** Restart the dsh process the shop runs in (§8 amendment, 2026-08-27):
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* the exit is delayed past the RPC round-trip. */
|
|
214
|
+
* commit a two-phase handoff — a detached helper waits for this pid to
|
|
215
|
+
* exit, then re-runs this process's own command line — and exit once the
|
|
216
|
+
* response is out. The browser monitors the origin and refreshes when the
|
|
217
|
+
* new server answers. Refusals are issued before anything is torn down. */
|
|
192
218
|
restart(): Promise<ShopRestartResult>;
|
|
219
|
+
/** The shop's own version and whether npm has a newer one (§7.3). The
|
|
220
|
+
* check is advisory: a registry that cannot answer leaves `latest` null
|
|
221
|
+
* and the client shows the version alone. `installed` is the RUNNING
|
|
222
|
+
* version (own-version.ts), not the manifest's range spec. */
|
|
223
|
+
version(): Promise<ShopVersionResult>;
|
|
224
|
+
/** Update the shop itself to a published version (§7.3): the explicit pin
|
|
225
|
+
* is the only install form that bypasses pnpm's release cooldown. The
|
|
226
|
+
* version is re-validated as plain semver at the boundary — the spec
|
|
227
|
+
* `dsh-plugin-shop@<version>` is built here, never from the wire. */
|
|
228
|
+
updateStart(args: {
|
|
229
|
+
version: string;
|
|
230
|
+
}): Promise<ShopUpdateResult>;
|
|
193
231
|
}
|
|
194
232
|
export default ShopGateway;
|
|
@@ -1,35 +1,38 @@
|
|
|
1
|
-
/** Restart executor:
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
1
|
+
/** Restart executor: hand the port to a new dsh instance, two-phase.
|
|
2
|
+
*
|
|
3
|
+
* The old process cannot wait for the new one: the new one must bind the
|
|
4
|
+
* port the old one still holds, and two live processes cannot bind it at
|
|
5
|
+
* once — the first implementation spawned the child and waited for its URL,
|
|
6
|
+
* and the child crashed in boot with EADDRINUSE every time. The handoff is
|
|
7
|
+
* therefore inverted: the parent commits and exits FIRST, and a detached
|
|
8
|
+
* helper waits for the parent's pid to disappear before exec'ing the same
|
|
9
|
+
* dsh command line. The browser monitors the origin and refreshes once the
|
|
10
|
+
* new server answers; a boot that fails is diagnosed from the log file,
|
|
11
|
+
* since nobody is attached to the child's pipes. */
|
|
12
|
+
/** `shop/restart` result: committed, or a typed refusal issued BEFORE
|
|
13
|
+
* anything is torn down. Once `ok` is returned the old process WILL exit —
|
|
14
|
+
* the client monitors the new server and reports a failed boot with the
|
|
15
|
+
* manual command. */
|
|
10
16
|
export type RestartOutcome = {
|
|
11
17
|
ok: true;
|
|
12
|
-
url: string;
|
|
13
18
|
} | {
|
|
14
19
|
ok: false;
|
|
15
20
|
detail: string;
|
|
16
21
|
};
|
|
17
|
-
/** Spawn the
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* how the browser finds it.
|
|
22
|
+
/** Spawn the two-phase handoff. The helper is a POSIX shell wrapper that
|
|
23
|
+
* polls the parent pid until it is gone, then replaces itself with the dsh
|
|
24
|
+
* command — `exec "$@"` keeps the argv verbatim, so no argument quoting is
|
|
25
|
+
* involved. The child's stdout/stderr go to `logFile`, opened here in
|
|
26
|
+
* append mode; opening throws on failure, and the caller treats a throw as
|
|
27
|
+
* a refusal (the restart is never committed without its log).
|
|
24
28
|
*
|
|
25
|
-
*
|
|
26
|
-
* the
|
|
27
|
-
*
|
|
28
|
-
* error, or the timeout) the child is killed if still running and the old
|
|
29
|
-
* process is untouched. */
|
|
29
|
+
* The pid-poll has the usual tiny reuse race — if the parent's pid is
|
|
30
|
+
* recycled within the 0.2s polling gap the helper waits for the unrelated
|
|
31
|
+
* process too. Harmless: it only delays the boot. */
|
|
30
32
|
export declare function startRestart(options: {
|
|
31
33
|
dshBin: string;
|
|
32
34
|
argv: string[];
|
|
35
|
+
parentPid: number;
|
|
36
|
+
logFile: string;
|
|
33
37
|
env?: NodeJS.ProcessEnv;
|
|
34
|
-
|
|
35
|
-
}): Promise<RestartOutcome>;
|
|
38
|
+
}): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Self-update version check: the shop's latest published version, from the
|
|
2
|
+
* npm packument. Advisory by design — like the stars sidecar, a failed
|
|
3
|
+
* check degrades to `null` and never throws, never blocks a publish. The
|
|
4
|
+
* catalog cannot serve here: the shop is bootstrap-installed and is not
|
|
5
|
+
* harvested into its own catalog. */
|
|
6
|
+
/** Fetch the shop's `latest` dist-tag, or `null` when the registry cannot
|
|
7
|
+
* answer (network failure, unexpected payload — anything). The caller
|
|
8
|
+
* renders `null` as "no update check", never as a failure. */
|
|
9
|
+
export declare function fetchLatestVersion(fetchFn?: typeof fetch): Promise<string | null>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** The shop's own published version, read from the package.json that ships
|
|
2
|
+
* next to this package — the RUNNING version, not the manifest's range
|
|
3
|
+
* spec. This lives at the package root (not under src/host) on purpose:
|
|
4
|
+
* both the source tree (tests) and the bundled `lib/index.js` sit exactly
|
|
5
|
+
* one level below the package root, so the same relative URL resolves in
|
|
6
|
+
* both. */
|
|
7
|
+
export declare function ownVersion(): string;
|
package/package.json
CHANGED