dsh-update-status 0.1.4 → 0.1.6
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 +28 -0
- package/README.md +49 -11
- package/README.zh.md +49 -11
- package/assets/sidebar-chip.png +0 -0
- package/assets/update-panel.png +0 -0
- package/cordis.patch.yml +4 -1
- package/docs/RELEASING.md +1 -1
- package/lib/client.js +139 -33
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +47 -7
- package/lib/index.js +55 -46
- package/package.json +15 -11
package/lib/index.d.ts
CHANGED
|
@@ -4,6 +4,23 @@ import { Context } from "@deepseek-ai/cordis";
|
|
|
4
4
|
declare const RELEASE_CHANNELS: readonly ['latest', 'next', 'alpha'];
|
|
5
5
|
type ReleaseChannel = (typeof RELEASE_CHANNELS)[number];
|
|
6
6
|
type ReleaseCompatibility = 'verified' | 'unverified' | 'incompatible';
|
|
7
|
+
/**
|
|
8
|
+
* Severity of `warning`, so a surface can tell "the plugin could not determine
|
|
9
|
+
* the update state" from "here is something worth knowing".
|
|
10
|
+
*
|
|
11
|
+
* - `failure`: no usable answer — a failed registry read, a channel the registry
|
|
12
|
+
* does not publish, or a version SemVer cannot compare. This is what justifies
|
|
13
|
+
* repainting the sidebar chip.
|
|
14
|
+
* - `notice`: the answer is complete and usable; the text is advisory, e.g. a
|
|
15
|
+
* preview channel whose release this bundle has not been verified against.
|
|
16
|
+
* The chip must NOT repaint for this — an operator upgrading DSH ahead of the
|
|
17
|
+
* plugin would otherwise see the chip turn red for a plugin-side bookkeeping
|
|
18
|
+
* fact.
|
|
19
|
+
*
|
|
20
|
+
* Optional on purpose: a Host older than this field leaves it `undefined`, and
|
|
21
|
+
* the client then falls back to treating any warning as a failure.
|
|
22
|
+
*/
|
|
23
|
+
type UpdateWarningKind = 'failure' | 'notice';
|
|
7
24
|
type InstallKind = 'npm-global' | 'pnpm-global' | 'source-checkout' | 'unknown';
|
|
8
25
|
interface ChannelRelease {
|
|
9
26
|
channel: ReleaseChannel;
|
|
@@ -23,6 +40,8 @@ interface UpdateStatus {
|
|
|
23
40
|
cached: boolean;
|
|
24
41
|
checkedAt: string | null;
|
|
25
42
|
warning: string | null;
|
|
43
|
+
/** Severity of `warning`; absent from a Host older than the field. */
|
|
44
|
+
warningKind?: UpdateWarningKind | null;
|
|
26
45
|
installKind: InstallKind;
|
|
27
46
|
upgradeCommand: string;
|
|
28
47
|
releaseUrl: string;
|
|
@@ -37,6 +56,32 @@ interface UpdateStatus {
|
|
|
37
56
|
canApplyInPlace: false;
|
|
38
57
|
}
|
|
39
58
|
//#endregion
|
|
59
|
+
//#region src/host/settings.d.ts
|
|
60
|
+
/** Deployment config (ordinary) plus the volatile user preferences the form edits. */
|
|
61
|
+
interface UpdateStatusConfig {
|
|
62
|
+
/** Deployment: on-demand registry cache duration, in hours. */
|
|
63
|
+
cacheTtlHours?: number;
|
|
64
|
+
/** Deployment: registry request timeout, in milliseconds. */
|
|
65
|
+
timeoutMs?: number;
|
|
66
|
+
/** Deployment: run one metadata check when the process mounts the plugin. */
|
|
67
|
+
autoCheckOnMount?: boolean;
|
|
68
|
+
/** Preference: show the sidebar version chip and its update panel. */
|
|
69
|
+
sidebarEnabled?: boolean;
|
|
70
|
+
/** Preference: release channel followed for comparison and commands. */
|
|
71
|
+
channel?: ReleaseChannel;
|
|
72
|
+
/** Preference: on-demand registry cache duration, in minutes. */
|
|
73
|
+
cacheTtlMinutes?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The plugin's Config schema; its volatile fields ARE the settings form.
|
|
77
|
+
*
|
|
78
|
+
* The explicit two-argument annotation is load-bearing: a `.volatile()` field's
|
|
79
|
+
* output is a stable reference (`Volatile<T>`) rather than the bare value the
|
|
80
|
+
* input side takes, so the inferred schema type cannot be named by the emitted
|
|
81
|
+
* `.d.ts` (TS2883) without stating the input side here.
|
|
82
|
+
*/
|
|
83
|
+
declare const Config: z<UpdateStatusConfig, Record<string, unknown>>;
|
|
84
|
+
//#endregion
|
|
40
85
|
//#region src/host/installation.d.ts
|
|
41
86
|
interface InstallationInfo {
|
|
42
87
|
currentVersion: string;
|
|
@@ -74,6 +119,7 @@ declare class UpdateStatusService {
|
|
|
74
119
|
private refreshRelease;
|
|
75
120
|
private statusAfterFailure;
|
|
76
121
|
private statusFromCache;
|
|
122
|
+
/** No remote data at all: the registry read itself failed, never a notice. */
|
|
77
123
|
private statusWithoutRemoteRelease;
|
|
78
124
|
}
|
|
79
125
|
//#endregion
|
|
@@ -81,13 +127,7 @@ declare class UpdateStatusService {
|
|
|
81
127
|
declare const name = "dsh-update-status";
|
|
82
128
|
/** Connection supplies the authenticated transport; settings remains optional. */
|
|
83
129
|
declare const inject: string[];
|
|
84
|
-
|
|
85
|
-
cacheTtlHours?: number;
|
|
86
|
-
timeoutMs?: number;
|
|
87
|
-
autoCheckOnMount?: boolean;
|
|
88
|
-
}
|
|
89
|
-
/** Deployment config only controls metadata-check timing; it never authorizes upgrades. */
|
|
90
|
-
declare const Config: z<Config>;
|
|
130
|
+
type Config = UpdateStatusConfig;
|
|
91
131
|
declare function apply(ctx: Context, config?: Config): void;
|
|
92
132
|
//#endregion
|
|
93
133
|
export { Config, type InstallKind, type UpdateStatus, UpdateStatusService, apply, inject, name };
|
package/lib/index.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import z from "@deepseek-ai/schemastery";
|
|
3
2
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
4
3
|
import { dirname, join, normalize, sep } from "node:path";
|
|
4
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
//#region src/shared/types.ts
|
|
6
|
-
/**
|
|
7
|
-
* JSON-only contract shared by the Host and Web halves.
|
|
8
|
-
*
|
|
9
|
-
* The static package uses the authenticated Connection RPC channel because
|
|
10
|
-
* `harness.handle` / `host.call` are dynamic-Cordis-only closure APIs in DSH
|
|
11
|
-
* 0.1.5-rc.1. The endpoint vocabulary remains deliberately small and private
|
|
12
|
-
* to this plugin channel.
|
|
13
|
-
*/
|
|
14
|
-
const PLUGIN_ID = "dsh-update-status";
|
|
15
6
|
const PACKAGE_NAME = "@deepseek-ai/dsh";
|
|
16
7
|
/** Shared Connection RPC channel. Custom prefixes 405 on the SPA fallback. */
|
|
17
8
|
const UPDATE_STATUS_CHANNEL = "/api";
|
|
@@ -21,7 +12,7 @@ const UPDATE_STATUS_CHANNEL = "/api";
|
|
|
21
12
|
* release the registry reports stays `unverified` — the plugin never claims a
|
|
22
13
|
* compatibility nobody checked.
|
|
23
14
|
*/
|
|
24
|
-
const VERIFIED_DSH_VERSIONS = ["0.1.
|
|
15
|
+
const VERIFIED_DSH_VERSIONS = ["0.1.7-alpha.2", "0.1.7-rc.1"];
|
|
25
16
|
const RELEASE_CHANNELS = [
|
|
26
17
|
"latest",
|
|
27
18
|
"next",
|
|
@@ -262,34 +253,6 @@ function installUpdateStatusRpc(ctx, service) {
|
|
|
262
253
|
});
|
|
263
254
|
}
|
|
264
255
|
//#endregion
|
|
265
|
-
//#region src/host/settings.ts
|
|
266
|
-
const SettingsSchema = z.object({
|
|
267
|
-
sidebarEnabled: z.boolean().default(true),
|
|
268
|
-
channel: z.union([
|
|
269
|
-
"latest",
|
|
270
|
-
"next",
|
|
271
|
-
"alpha"
|
|
272
|
-
]).default("latest").loose(),
|
|
273
|
-
cacheTtlMinutes: z.number().min(30).max(1440).default(360)
|
|
274
|
-
});
|
|
275
|
-
/** Settings are optional composition; absent providers leave the Web default on. */
|
|
276
|
-
function installSettings(ctx) {
|
|
277
|
-
ctx.inject(["settings"], (settingsCtx) => {
|
|
278
|
-
try {
|
|
279
|
-
settingsCtx.settings.register(PLUGIN_ID, SettingsSchema, {
|
|
280
|
-
base: {
|
|
281
|
-
sidebarEnabled: true,
|
|
282
|
-
channel: "latest",
|
|
283
|
-
cacheTtlMinutes: 360
|
|
284
|
-
},
|
|
285
|
-
applies: "live"
|
|
286
|
-
});
|
|
287
|
-
} catch (error) {
|
|
288
|
-
console.error("[dsh-update-status] settings namespace registration failed:", error);
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
//#endregion
|
|
293
256
|
//#region src/shared/semver.ts
|
|
294
257
|
const SEMVER = /^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
295
258
|
const NUMERIC_IDENTIFIER = /^(0|[1-9]\d*)$/;
|
|
@@ -477,13 +440,16 @@ var UpdateStatusService = class {
|
|
|
477
440
|
const missingWarning = selected.version === null ? `npm registry 未发布 ${channel} 通道。` : null;
|
|
478
441
|
const comparisonWarning = selected.version !== null && comparison === void 0 ? `无法按 SemVer 比较当前版本 ${this.installation.currentVersion} 与 ${channel} 通道版本 ${selected.version}。` : null;
|
|
479
442
|
const previewWarning = channel !== "latest" && selected.version !== null && selected.compatibility !== "verified" ? `${channel} 是预览通道,版本 ${selected.version} 尚未验证与本插件兼容。` : null;
|
|
443
|
+
const failureWarning = warningWith(warningWith(initialWarning, missingWarning), comparisonWarning);
|
|
444
|
+
const warning = warningWith(failureWarning, previewWarning);
|
|
480
445
|
return {
|
|
481
446
|
currentVersion: this.installation.currentVersion,
|
|
482
447
|
latestVersion: selected.version,
|
|
483
448
|
hasUpdate: comparison !== void 0 && comparison < 0,
|
|
484
449
|
cached,
|
|
485
450
|
checkedAt: new Date(cache.checkedAtMs).toISOString(),
|
|
486
|
-
warning
|
|
451
|
+
warning,
|
|
452
|
+
warningKind: warning === null ? null : failureWarning === null ? "notice" : "failure",
|
|
487
453
|
installKind: this.installation.installKind,
|
|
488
454
|
upgradeCommand: upgradeCommandFor(this.installation.installKind, this.installation.packageName || "@deepseek-ai/dsh", channel),
|
|
489
455
|
releaseUrl: this.releaseUrl,
|
|
@@ -495,6 +461,7 @@ var UpdateStatusService = class {
|
|
|
495
461
|
canApplyInPlace: false
|
|
496
462
|
};
|
|
497
463
|
}
|
|
464
|
+
/** No remote data at all: the registry read itself failed, never a notice. */
|
|
498
465
|
statusWithoutRemoteRelease(channel, warning) {
|
|
499
466
|
return {
|
|
500
467
|
currentVersion: this.installation.currentVersion,
|
|
@@ -503,6 +470,7 @@ var UpdateStatusService = class {
|
|
|
503
470
|
cached: false,
|
|
504
471
|
checkedAt: null,
|
|
505
472
|
warning,
|
|
473
|
+
warningKind: "failure",
|
|
506
474
|
installKind: this.installation.installKind,
|
|
507
475
|
upgradeCommand: upgradeCommandFor(this.installation.installKind, this.installation.packageName || "@deepseek-ai/dsh", channel),
|
|
508
476
|
releaseUrl: this.releaseUrl,
|
|
@@ -521,16 +489,57 @@ var UpdateStatusService = class {
|
|
|
521
489
|
}
|
|
522
490
|
};
|
|
523
491
|
//#endregion
|
|
524
|
-
//#region src/
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
492
|
+
//#region src/host/settings.ts
|
|
493
|
+
/**
|
|
494
|
+
* The plugin's Config schema; its volatile fields ARE the settings form.
|
|
495
|
+
*
|
|
496
|
+
* The explicit two-argument annotation is load-bearing: a `.volatile()` field's
|
|
497
|
+
* output is a stable reference (`Volatile<T>`) rather than the bare value the
|
|
498
|
+
* input side takes, so the inferred schema type cannot be named by the emitted
|
|
499
|
+
* `.d.ts` (TS2883) without stating the input side here.
|
|
500
|
+
*/
|
|
529
501
|
const Config = z.object({
|
|
530
502
|
cacheTtlHours: z.number().step(1).min(1).max(24).default(6),
|
|
531
503
|
timeoutMs: z.number().step(1).min(1e3).max(3e4).default(DEFAULT_TIMEOUT_MS),
|
|
532
|
-
autoCheckOnMount: z.boolean().default(true)
|
|
504
|
+
autoCheckOnMount: z.boolean().default(true),
|
|
505
|
+
sidebarEnabled: z.boolean().default(true).volatile(),
|
|
506
|
+
channel: z.union([
|
|
507
|
+
"latest",
|
|
508
|
+
"next",
|
|
509
|
+
"alpha"
|
|
510
|
+
]).default("latest").loose().volatile(),
|
|
511
|
+
cacheTtlMinutes: z.number().min(30).max(1440).default(360).volatile()
|
|
533
512
|
});
|
|
513
|
+
/**
|
|
514
|
+
* Suppress the auto-generated settings page for this entry.
|
|
515
|
+
*
|
|
516
|
+
* `autoGenerate` defaults to true, which would put a second, generic form beside
|
|
517
|
+
* the plugin's own `settings.section` page. Registering the policy is the
|
|
518
|
+
* documented move for a plugin that owns its preference UI (`ui-theme`,
|
|
519
|
+
* `ui-chat`, `ui-conversation` all do exactly this), and the entry's row stays
|
|
520
|
+
* in `settings.describe()` either way — the browser half still reads and writes
|
|
521
|
+
* it through `ctx.configForms`.
|
|
522
|
+
*
|
|
523
|
+
* Guarded twice: a deployment without the settings service never runs the inject
|
|
524
|
+
* callback (the plugin keeps working from its defaults), and a refused policy
|
|
525
|
+
* registration is contained instead of taking the plugin down.
|
|
526
|
+
*
|
|
527
|
+
* @param ctx - Host plugin context owning the entry's page policy.
|
|
528
|
+
*/
|
|
529
|
+
function installSettings(ctx) {
|
|
530
|
+
try {
|
|
531
|
+
ctx.inject(["settings"], (child) => {
|
|
532
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
533
|
+
});
|
|
534
|
+
} catch (error) {
|
|
535
|
+
console.error("[dsh-update-status] settings page policy registration failed:", error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/index.ts
|
|
540
|
+
const name = "dsh-update-status";
|
|
541
|
+
/** Connection supplies the authenticated transport; settings remains optional. */
|
|
542
|
+
const inject = ["connection"];
|
|
534
543
|
function boundedNumber(value, fallback, min, max) {
|
|
535
544
|
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
536
545
|
return Math.min(max, Math.max(min, Math.floor(value)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-update-status",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Read-only DeepSeek Harness Web plugin that shows the running DSH version, discovers latest/next/alpha releases, warns about compatibility, and generates copy-only upgrade commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"CHANGELOG.md",
|
|
25
25
|
"LICENSE",
|
|
26
26
|
"assets/update-panel.png",
|
|
27
|
+
"assets/sidebar-chip.png",
|
|
27
28
|
"docs/RELEASING.md"
|
|
28
29
|
],
|
|
29
30
|
"engines": {
|
|
@@ -41,12 +42,17 @@
|
|
|
41
42
|
"access": "public"
|
|
42
43
|
},
|
|
43
44
|
"dsh": {
|
|
45
|
+
"manifestVersion": 1,
|
|
46
|
+
"engines": {
|
|
47
|
+
"dsh": ">=0.1.7-alpha.2 <0.2.0"
|
|
48
|
+
},
|
|
44
49
|
"bundle": {
|
|
45
50
|
"patch": "./cordis.patch.yml"
|
|
46
51
|
},
|
|
47
52
|
"client": {
|
|
48
53
|
"platform": "web",
|
|
49
54
|
"inject": [
|
|
55
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
50
56
|
"@deepseek-ai/dsh-client-connection",
|
|
51
57
|
"@deepseek-ai/dsh-client-ui-layout",
|
|
52
58
|
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
@@ -56,8 +62,8 @@
|
|
|
56
62
|
},
|
|
57
63
|
"compatibility": {
|
|
58
64
|
"dshReleases": {
|
|
59
|
-
"0.1.
|
|
60
|
-
"0.1.
|
|
65
|
+
"0.1.7-alpha.2": "compatible",
|
|
66
|
+
"0.1.7-rc.1": "compatible"
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
},
|
|
@@ -70,17 +76,15 @@
|
|
|
70
76
|
"version"
|
|
71
77
|
],
|
|
72
78
|
"license": "MIT",
|
|
73
|
-
"dependencies": {
|
|
74
|
-
"@deepseek-ai/schemastery": "^3.18.2"
|
|
75
|
-
},
|
|
76
79
|
"peerDependencies": {
|
|
77
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
78
|
-
"@deepseek-ai/dsh-settings": ">=0.1.
|
|
80
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
81
|
+
"@deepseek-ai/dsh-settings": ">=0.1.7-alpha.2 <0.2.0",
|
|
82
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
79
83
|
},
|
|
80
84
|
"devDependencies": {
|
|
81
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
82
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
83
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
85
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
86
|
+
"@deepseek-ai/dsh-settings": "0.1.7-alpha.2",
|
|
87
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
84
88
|
"@types/node": "^26.2.0",
|
|
85
89
|
"@types/react": "^18.3.31",
|
|
86
90
|
"react": "^18.3.1",
|