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
|
@@ -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_REMOTE = {
|
|
79
97
|
package: 'dsh-plugin-shop',
|
|
@@ -102,7 +120,22 @@ export const TYPERT_REMOTE = {
|
|
|
102
120
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
103
121
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
104
122
|
},
|
|
105
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
123
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":218,"column":9},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'dsh-plugin-shop#shop/installed',
|
|
127
|
+
service: 'shop',
|
|
128
|
+
namespace: 'shop',
|
|
129
|
+
method: 'installed',
|
|
130
|
+
invocation: { kind: 'direct' },
|
|
131
|
+
parameters: [
|
|
132
|
+
],
|
|
133
|
+
result: {
|
|
134
|
+
mode: 'strict',
|
|
135
|
+
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
136
|
+
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
137
|
+
},
|
|
138
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":293,"column":9},
|
|
106
139
|
},
|
|
107
140
|
{
|
|
108
141
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -128,7 +161,7 @@ export const TYPERT_REMOTE = {
|
|
|
128
161
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
129
162
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
130
163
|
},
|
|
131
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
164
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":244,"column":9},
|
|
132
165
|
},
|
|
133
166
|
{
|
|
134
167
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -153,22 +186,22 @@ export const TYPERT_REMOTE = {
|
|
|
153
186
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
154
187
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
155
188
|
},
|
|
156
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
189
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":282,"column":3},
|
|
157
190
|
},
|
|
158
191
|
{
|
|
159
|
-
id: 'dsh-plugin-shop#shop/
|
|
192
|
+
id: 'dsh-plugin-shop#shop/restart',
|
|
160
193
|
service: 'shop',
|
|
161
194
|
namespace: 'shop',
|
|
162
|
-
method: '
|
|
195
|
+
method: 'restart',
|
|
163
196
|
invocation: { kind: 'direct' },
|
|
164
197
|
parameters: [
|
|
165
198
|
],
|
|
166
199
|
result: {
|
|
167
200
|
mode: 'strict',
|
|
168
|
-
typeSymbol: 'dsh-plugin-shop#
|
|
169
|
-
schema:
|
|
201
|
+
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
202
|
+
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
170
203
|
},
|
|
171
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
204
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":367,"column":9},
|
|
172
205
|
},
|
|
173
206
|
{
|
|
174
207
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -193,7 +226,33 @@ export const TYPERT_REMOTE = {
|
|
|
193
226
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
194
227
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
195
228
|
},
|
|
196
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
229
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":187,"column":3},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
233
|
+
service: 'shop',
|
|
234
|
+
namespace: 'shop',
|
|
235
|
+
method: 'uninstallStart',
|
|
236
|
+
implementation: 'uninstall',
|
|
237
|
+
invocation: { kind: 'direct' },
|
|
238
|
+
parameters: [
|
|
239
|
+
{
|
|
240
|
+
name: 'args',
|
|
241
|
+
wire: 'args',
|
|
242
|
+
source: 'json',
|
|
243
|
+
codec: {
|
|
244
|
+
mode: 'strict',
|
|
245
|
+
typeSymbol: 'dsh-plugin-shop#shop/uninstallStart:args',
|
|
246
|
+
schema: dsh_plugin_shop_shop_uninstallStart_parameter_0$schema,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
],
|
|
250
|
+
result: {
|
|
251
|
+
mode: 'strict',
|
|
252
|
+
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
253
|
+
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
254
|
+
},
|
|
255
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":333,"column":9},
|
|
197
256
|
},
|
|
198
257
|
],
|
|
199
258
|
}
|
|
@@ -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, ShopInstallResult, ShopInstallStatusResult,
|
|
8
|
+
import type { InstallArgs, ShopCatalogResult, ShopInstalledEntry, ShopInstallResult, ShopInstallStatusResult, ShopRestartResult, ShopSetEnabledResult, ShopUninstallResult } 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. */
|
|
@@ -21,7 +21,11 @@ export interface ShopTabInjected {
|
|
|
21
21
|
name: string;
|
|
22
22
|
enabled: boolean;
|
|
23
23
|
}) => Promise<ShopSetEnabledResult>;
|
|
24
|
-
|
|
24
|
+
installed: () => Promise<ShopInstalledEntry[]>;
|
|
25
|
+
uninstall: (args: {
|
|
26
|
+
name: string;
|
|
27
|
+
}) => Promise<ShopUninstallResult>;
|
|
28
|
+
restart: () => Promise<ShopRestartResult>;
|
|
25
29
|
}
|
|
26
30
|
/** Full component props assembled by the Settings slot renderer. */
|
|
27
31
|
export type ShopTabProps = PropsRuntime<'settings.plugins.tab'> & PropsLocale<'settings.shop'> & InjectFace<ShopTabInjected>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shop settings surface, browser half — one tab in `settings.plugins.tab`
|
|
3
3
|
* that browses the catalog, installs with acknowledgement, toggles
|
|
4
|
-
* enablement,
|
|
5
|
-
* (the assembly does not know this package)
|
|
6
|
-
* the
|
|
4
|
+
* enablement, lists installed plugins, and restarts dsh after installs.
|
|
5
|
+
* Mounts the shop Remote itself (the assembly does not know this package)
|
|
6
|
+
* and holds no privilege beyond the seven `shop/*` methods (§5.3).
|
|
7
7
|
*/
|
|
8
8
|
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
9
9
|
import type { ShopLocaleKey } from './locales.ts';
|
|
@@ -48,6 +48,18 @@ export declare const zh: {
|
|
|
48
48
|
installedVersion: string;
|
|
49
49
|
latestVersion: string;
|
|
50
50
|
update: string;
|
|
51
|
+
installed: string;
|
|
52
|
+
uninstall: string;
|
|
53
|
+
uninstalling: string;
|
|
54
|
+
uninstallFailed: string;
|
|
55
|
+
uninstallTransportFailed: string;
|
|
56
|
+
uninstalledRestartNotice: string;
|
|
57
|
+
restart: string;
|
|
58
|
+
restartTitle: string;
|
|
59
|
+
restartBody: string;
|
|
60
|
+
restartConfirm: string;
|
|
61
|
+
restarting: string;
|
|
62
|
+
restartTransportFailed: string;
|
|
51
63
|
enabledSwitch: string;
|
|
52
64
|
toggleFailed: string;
|
|
53
65
|
hotApplyNote: string;
|
|
@@ -103,6 +115,18 @@ export declare const en: {
|
|
|
103
115
|
installedVersion: string;
|
|
104
116
|
latestVersion: string;
|
|
105
117
|
update: string;
|
|
118
|
+
installed: string;
|
|
119
|
+
uninstall: string;
|
|
120
|
+
uninstalling: string;
|
|
121
|
+
uninstallFailed: string;
|
|
122
|
+
uninstallTransportFailed: string;
|
|
123
|
+
uninstalledRestartNotice: string;
|
|
124
|
+
restart: string;
|
|
125
|
+
restartTitle: string;
|
|
126
|
+
restartBody: string;
|
|
127
|
+
restartConfirm: string;
|
|
128
|
+
restarting: string;
|
|
129
|
+
restartTransportFailed: string;
|
|
106
130
|
enabledSwitch: string;
|
|
107
131
|
toggleFailed: string;
|
|
108
132
|
hotApplyNote: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Install driving hook for one entry: start, poll to terminal, reset. */
|
|
2
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
3
|
import { type InstallView } from './present.ts';
|
|
3
4
|
import type { InstallArgs, ShopInstallResult, ShopInstallStatusResult } from '../host/index.ts';
|
|
4
5
|
export interface UseInstallResult {
|
|
@@ -6,6 +7,14 @@ export interface UseInstallResult {
|
|
|
6
7
|
start: (args: InstallArgs) => Promise<void>;
|
|
7
8
|
reset: () => void;
|
|
8
9
|
}
|
|
10
|
+
/** The poll loop shared by the install and uninstall drivers: while the view
|
|
11
|
+
* is `running`, poll once per second and fold each status through the
|
|
12
|
+
* reducer. A poll failure is transient — the host retains the record, so the
|
|
13
|
+
* next tick finds it. The rejection handler must be present: an unhandled
|
|
14
|
+
* rejection here would escape the poll loop. */
|
|
15
|
+
export declare function usePollStatus(view: InstallView, setView: Dispatch<SetStateAction<InstallView>>, installStatus: (args: {
|
|
16
|
+
installId: string;
|
|
17
|
+
}) => Promise<ShopInstallStatusResult>): void;
|
|
9
18
|
/** Drive one install: rejections, the polling loop at INSTALL_POLL_MS, and
|
|
10
19
|
* terminal states. The interval is cleared on unmount and whenever the view
|
|
11
20
|
* leaves `running`, so a done/failed/rejected install never polls again. */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Uninstall driving hook for one entry: start, poll to terminal. Shares the
|
|
2
|
+
* install view machine and poll loop; the only difference is the start
|
|
3
|
+
* mapping — an uninstall business failure (not in the catalog / not
|
|
4
|
+
* installed) lands in the `failed` view with the host's published detail,
|
|
5
|
+
* never in the install `rejected` codes, which belong to the install gate
|
|
6
|
+
* (§7.2). */
|
|
7
|
+
import type { ShopInstallStatusResult, ShopUninstallResult } from '../host/index.ts';
|
|
8
|
+
import type { InstallView } from './present.ts';
|
|
9
|
+
export interface UseUninstallResult {
|
|
10
|
+
view: InstallView;
|
|
11
|
+
start: (args: {
|
|
12
|
+
name: string;
|
|
13
|
+
}) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/** Drive one uninstall: the host's business union, the shared polling loop,
|
|
16
|
+
* and terminal states. The `rejected` install state never occurs here. */
|
|
17
|
+
export declare function useUninstall(uninstall: (args: {
|
|
18
|
+
name: string;
|
|
19
|
+
}) => Promise<ShopUninstallResult>, installStatus: (args: {
|
|
20
|
+
installId: string;
|
|
21
|
+
}) => Promise<ShopInstallStatusResult>): UseUninstallResult;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Plugin-command executor: spawn the dsh CLI, stream its output, serialize
|
|
2
|
+
* per profile. One implementation drives both `dsh plugin add` (install) and
|
|
3
|
+
* `dsh plugin remove` (uninstall); the only differences are the verb and the
|
|
4
|
+
* post-exit manifest confirmation. */
|
|
2
5
|
export type InstallState = 'running' | 'done' | 'failed';
|
|
3
6
|
export interface InstallStatus {
|
|
4
7
|
state: InstallState;
|
|
@@ -13,11 +16,6 @@ interface RunningInstall {
|
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Run one `dsh plugin --profile <profile> add <spec>` and track it.
|
|
16
|
-
* Never rolls back; a failure surfaces stderr verbatim plus the recovery hint
|
|
17
|
-
* (§10). The shop never passes build-script flags: `allowBuilds` stays the
|
|
18
|
-
* user's explicit decision in the CLI (§7.2).
|
|
19
|
-
* The child inherits the current environment unless `env` is given — the
|
|
20
|
-
* real-install test pins DSH_HOME to a temporary directory this way.
|
|
21
19
|
* When `expectedName` is given, a zero exit is confirmed against the profile
|
|
22
20
|
* manifest (§7.2 step 6) before the install reports `done`.
|
|
23
21
|
*/
|
|
@@ -29,4 +27,18 @@ export declare function startInstall(options: {
|
|
|
29
27
|
expectedName?: string;
|
|
30
28
|
onStatus?: (status: InstallStatus) => void;
|
|
31
29
|
}): RunningInstall;
|
|
30
|
+
/**
|
|
31
|
+
* Run one `dsh plugin --profile <profile> remove <name>` and track it.
|
|
32
|
+
* When `expectedName` is given, a zero exit is confirmed against the profile
|
|
33
|
+
* manifest — the bundle must actually have LEFT `dsh.profile.bundles` — before
|
|
34
|
+
* the uninstall reports `done`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function startUninstall(options: {
|
|
37
|
+
profile: string;
|
|
38
|
+
name: string;
|
|
39
|
+
dshBin?: string;
|
|
40
|
+
env?: NodeJS.ProcessEnv;
|
|
41
|
+
expectedName?: string;
|
|
42
|
+
onStatus?: (status: InstallStatus) => void;
|
|
43
|
+
}): RunningInstall;
|
|
32
44
|
export {};
|
|
@@ -5,6 +5,7 @@ import { loadCatalog, type LoadCatalogOptions } from './catalog.ts';
|
|
|
5
5
|
import type { CatalogEntry, DeniedEntry } from './types.ts';
|
|
6
6
|
import { type InstallArgs, type InstallRejectionCode } from './install.ts';
|
|
7
7
|
import { type InstallStatus } from './executor.ts';
|
|
8
|
+
import { type RestartOutcome } from './restart.ts';
|
|
8
9
|
export type { InstallArgs, InstallRejectionCode } from './install.ts';
|
|
9
10
|
export type { CatalogEntry } from './types.ts';
|
|
10
11
|
/** One Loader inventory entry, structurally — the shop never depends on
|
|
@@ -29,6 +30,15 @@ export interface ShopGatewayOptions {
|
|
|
29
30
|
list(): InventoryEntry[];
|
|
30
31
|
};
|
|
31
32
|
dshBin?: string;
|
|
33
|
+
/** The dsh argv this process was launched with, for `shop/restart`;
|
|
34
|
+
* defaults to the real `process.argv` minus node and the script path. */
|
|
35
|
+
restartArgv?: string[];
|
|
36
|
+
/** Test-only injection: the exit the restart calls after the response is
|
|
37
|
+
* delivered. Production uses `process.exit`. */
|
|
38
|
+
exit?: (code?: number) => void;
|
|
39
|
+
/** How long the gateway waits after a successful restart response before
|
|
40
|
+
* exiting the old process; test-only shortening, production uses 2s. */
|
|
41
|
+
restartExitDelayMs?: number;
|
|
32
42
|
}
|
|
33
43
|
/** `shop/installStart` result (§7.3): rejections are typed wire values with an
|
|
34
44
|
* author-readable `detail`, not thrown RPC errors. */
|
|
@@ -49,11 +59,28 @@ export interface ShopSetEnabledResult {
|
|
|
49
59
|
ok: boolean;
|
|
50
60
|
detail?: string;
|
|
51
61
|
}
|
|
52
|
-
/** `shop/
|
|
53
|
-
|
|
62
|
+
/** `shop/uninstallStart` result (§7.3): a name outside the catalog or not
|
|
63
|
+
* installed is a typed wire value with an author-readable `detail`, not a
|
|
64
|
+
* thrown RPC error. */
|
|
65
|
+
export type ShopUninstallResult = {
|
|
66
|
+
ok: true;
|
|
67
|
+
installId: string;
|
|
68
|
+
} | {
|
|
69
|
+
ok: false;
|
|
70
|
+
detail: string;
|
|
71
|
+
};
|
|
72
|
+
/** `shop/restart` result (§7.3): the restarted server's URL, or a typed
|
|
73
|
+
* failure — on failure the old process is still serving. */
|
|
74
|
+
export type ShopRestartResult = RestartOutcome;
|
|
75
|
+
/** `shop/installed` entry (§7.3): one installed catalog plugin. `installed`
|
|
76
|
+
* is the profile manifest's dependency spec verbatim (a range, a tag, or
|
|
77
|
+
* `workspace:*`); `outdated` is the Host's verdict that the installed version
|
|
78
|
+
* sits behind the catalog's — the client never does version math. */
|
|
79
|
+
export interface ShopInstalledEntry {
|
|
54
80
|
name: string;
|
|
55
81
|
installed: string;
|
|
56
82
|
latest: string;
|
|
83
|
+
outdated: boolean;
|
|
57
84
|
}
|
|
58
85
|
/** `shop/catalog` result (§7.3), plus the denied list for the install gate's UI. */
|
|
59
86
|
export interface ShopCatalogResult {
|
|
@@ -77,11 +104,21 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
77
104
|
private readonly profileDir?;
|
|
78
105
|
private readonly inventory?;
|
|
79
106
|
private readonly dshBin;
|
|
107
|
+
/** The argv `shop/restart` re-spawns: the real process argv minus node and
|
|
108
|
+
* the CLI script path, or a test-provided substitute. */
|
|
109
|
+
private readonly restartArgv;
|
|
110
|
+
/** The exit the restart calls once the response is out; `process.exit` in
|
|
111
|
+
* production, a spy in tests. */
|
|
112
|
+
private readonly exit;
|
|
113
|
+
private readonly restartExitDelayMs;
|
|
80
114
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
81
115
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
82
116
|
/** Finished install records retained, so a poll sees the true terminal
|
|
83
117
|
* state (§8: done / needsRestart / failure detail). Oldest evicted on add. */
|
|
84
118
|
private static readonly MAX_FINISHED_INSTALLS;
|
|
119
|
+
/** How long the gateway waits after a successful restart response before
|
|
120
|
+
* exiting the old process — the browser must receive the URL first. */
|
|
121
|
+
private static readonly RESTART_EXIT_DELAY_MS;
|
|
85
122
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
86
123
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
87
124
|
private lastSnapshot;
|
|
@@ -127,7 +164,31 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
127
164
|
installStatus(args: {
|
|
128
165
|
installId: string;
|
|
129
166
|
}): ShopInstallStatusResult;
|
|
130
|
-
/** Installed plugins
|
|
131
|
-
|
|
167
|
+
/** Installed catalog plugins (§7.3): every entry of the snapshot the profile
|
|
168
|
+
* manifest declares as a dependency, with the Host's `outdated` verdict
|
|
169
|
+
* attached. The tab's shelf cards and its installed section both derive
|
|
170
|
+
* from this one list. */
|
|
171
|
+
installed(): Promise<ShopInstalledEntry[]>;
|
|
172
|
+
/** Whether an installed dependency spec sits behind the catalog's version.
|
|
173
|
+
* A spec identical to the catalog version is current by definition; a
|
|
174
|
+
* pnpm-written non-semver spec like `workspace:*` is not reportable and
|
|
175
|
+
* reads as current rather than killing the RPC. */
|
|
176
|
+
private isBehind;
|
|
177
|
+
/** Uninstall one installed catalog plugin from the profile (§7.3 follow-up
|
|
178
|
+
* amendment). Removing revokes privilege rather than granting it, so there
|
|
179
|
+
* is no acknowledgement gate. The name must be a catalog entry the profile
|
|
180
|
+
* manifest declares as a dependency — the RPC cannot remove profile
|
|
181
|
+
* dependencies the shop does not manage (the base bundle, the shop
|
|
182
|
+
* itself). The same install records/polling serve the client. */
|
|
183
|
+
uninstall(args: {
|
|
184
|
+
name: string;
|
|
185
|
+
}): Promise<ShopUninstallResult>;
|
|
186
|
+
/** Restart the dsh process the shop runs in (§8 amendment, 2026-08-27):
|
|
187
|
+
* re-spawn this process's own command line, return the new server's URL
|
|
188
|
+
* once it announces itself, and only then exit. A failed restart returns a
|
|
189
|
+
* typed failure and the old process keeps serving — the restart is
|
|
190
|
+
* all-or-nothing. The response must reach the browser before the exit, so
|
|
191
|
+
* the exit is delayed past the RPC round-trip. */
|
|
192
|
+
restart(): Promise<ShopRestartResult>;
|
|
132
193
|
}
|
|
133
194
|
export default ShopGateway;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Restart executor: re-spawn the Host's own command line and hand the
|
|
2
|
+
* browser the new server's URL. The child is detached (its own process
|
|
3
|
+
* group), so it survives both the parent's exit and the launching
|
|
4
|
+
* terminal's. The parent exits only AFTER the child printed its
|
|
5
|
+
* `dsh web: <url>` line — a restart that fails to come up leaves the old
|
|
6
|
+
* process running untouched. */
|
|
7
|
+
/** `shop/restart` outcome: the new server's URL, or a typed failure with an
|
|
8
|
+
* author-readable detail. A failure means the OLD process is still serving —
|
|
9
|
+
* the restart is all-or-nothing. */
|
|
10
|
+
export type RestartOutcome = {
|
|
11
|
+
ok: true;
|
|
12
|
+
url: string;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
detail: string;
|
|
16
|
+
};
|
|
17
|
+
/** Spawn the restarted server and wait for it to announce its URL.
|
|
18
|
+
*
|
|
19
|
+
* The child runs the same `dsh` with the same argv the current process was
|
|
20
|
+
* launched with (`process.argv.slice(2)` — node and the CLI script path
|
|
21
|
+
* stripped), so the profile, port and flags reproduce the user's launch
|
|
22
|
+
* verbatim. `--port 0` therefore yields a NEW port, and the returned URL is
|
|
23
|
+
* how the browser finds it.
|
|
24
|
+
*
|
|
25
|
+
* On success the caller exits the old process — but only after delivering
|
|
26
|
+
* the RPC response carrying `url`, which is the caller's sequencing duty,
|
|
27
|
+
* not this module's. On failure (child exits before announcing, spawn
|
|
28
|
+
* error, or the timeout) the child is killed if still running and the old
|
|
29
|
+
* process is untouched. */
|
|
30
|
+
export declare function startRestart(options: {
|
|
31
|
+
dshBin: string;
|
|
32
|
+
argv: string[];
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
}): Promise<RestartOutcome>;
|
package/package.json
CHANGED