dsh-plugin-update 0.1.2 → 0.3.0
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 +49 -0
- package/README.md +483 -255
- package/dist/batch-run.d.ts +80 -0
- package/dist/batch-run.js +86 -0
- package/dist/batch.d.ts +111 -0
- package/dist/batch.js +183 -0
- package/dist/changelog-io.d.ts +56 -0
- package/dist/changelog-io.js +258 -0
- package/dist/changelog.d.ts +96 -0
- package/dist/changelog.js +287 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.js +75 -1
- package/dist/commands.d.ts +59 -0
- package/dist/commands.js +60 -4
- package/dist/config.d.ts +48 -0
- package/dist/config.js +9 -1
- package/dist/diag.d.ts +125 -0
- package/dist/diag.js +243 -0
- package/dist/entry.d.ts +60 -0
- package/dist/entry.js +305 -0
- package/dist/gate.d.ts +35 -0
- package/dist/host-batch.d.ts +113 -0
- package/dist/host-batch.js +778 -0
- package/dist/host.d.ts +129 -0
- package/dist/host.js +506 -52
- package/dist/panel-batch.d.ts +151 -0
- package/dist/panel-batch.js +927 -0
- package/dist/panel.d.ts +256 -0
- package/dist/panel.js +1300 -0
- package/dist/ports.d.ts +208 -0
- package/dist/queue.d.ts +109 -0
- package/dist/queue.js +182 -0
- package/dist/reader.d.ts +84 -0
- package/dist/reader.js +65 -6
- package/dist/redaction.d.ts +120 -0
- package/dist/redaction.js +205 -0
- package/dist/service.d.ts +72 -0
- package/dist/service.js +100 -6
- package/dist/store.d.ts +229 -0
- package/dist/store.js +251 -12
- package/package.json +27 -2
package/dist/diag.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/diag.ts
|
|
2
|
+
import { DIAG_INLINE_BUDGET_BYTES, diagByteLength, enforceDiagBudget, resolveRegistryHost, sanitizeDetail } from "./redaction.js";
|
|
3
|
+
import { validReleaseVersion, validRequestId } from "./service.js";
|
|
4
|
+
const DIAG_VERSION = 1;
|
|
5
|
+
const DIAG_STAGES = [
|
|
6
|
+
"read-installed",
|
|
7
|
+
"fetch-release",
|
|
8
|
+
"validate-release",
|
|
9
|
+
"preflight",
|
|
10
|
+
"exec",
|
|
11
|
+
"verify"
|
|
12
|
+
];
|
|
13
|
+
const DIAG_ACTIONS = ["retry", "manual", "contact", "restart"];
|
|
14
|
+
const DIAG_KNOWN_TYPES = {
|
|
15
|
+
v: "number",
|
|
16
|
+
stage: "string",
|
|
17
|
+
route: "string",
|
|
18
|
+
method: "string",
|
|
19
|
+
httpStatus: "number",
|
|
20
|
+
exitCode: "number",
|
|
21
|
+
latencyMs: "number",
|
|
22
|
+
detail: "string",
|
|
23
|
+
targetPackageName: "string",
|
|
24
|
+
runningVersion: "string",
|
|
25
|
+
latestVersion: "string",
|
|
26
|
+
environmentKind: "string",
|
|
27
|
+
requestId: "string",
|
|
28
|
+
checkId: "string",
|
|
29
|
+
registryHost: "string",
|
|
30
|
+
action: "string"
|
|
31
|
+
};
|
|
32
|
+
const DIAG_KEYS = Object.keys(DIAG_KNOWN_TYPES);
|
|
33
|
+
const DIAG_STAGE_EXPECT = {
|
|
34
|
+
"read-installed": { want: [], forbid: ["httpStatus", "exitCode"] },
|
|
35
|
+
"fetch-release": { want: ["httpStatus"], forbid: ["exitCode"] },
|
|
36
|
+
"validate-release": { want: [], forbid: ["httpStatus", "exitCode"] },
|
|
37
|
+
preflight: { want: [], forbid: ["httpStatus", "exitCode"] },
|
|
38
|
+
exec: { want: [], forbid: ["httpStatus"] },
|
|
39
|
+
verify: { want: [], forbid: ["httpStatus", "exitCode"] }
|
|
40
|
+
};
|
|
41
|
+
function isStage(v) {
|
|
42
|
+
return typeof v === "string" && DIAG_STAGES.includes(v);
|
|
43
|
+
}
|
|
44
|
+
function isAction(v) {
|
|
45
|
+
return typeof v === "string" && DIAG_ACTIONS.includes(v);
|
|
46
|
+
}
|
|
47
|
+
function deriveStage(errorCode, phoneKind) {
|
|
48
|
+
const code = String(errorCode || "");
|
|
49
|
+
if (phoneKind === "update-status") return "read-installed";
|
|
50
|
+
if (phoneKind === "update-check") {
|
|
51
|
+
if (code === "check-failed" || code === "internal") return "fetch-release";
|
|
52
|
+
if (code === "invalid-release") return "validate-release";
|
|
53
|
+
return "read-installed";
|
|
54
|
+
}
|
|
55
|
+
if (code === "install-failed") return "exec";
|
|
56
|
+
if (code === "check-failed" || code === "internal") return "fetch-release";
|
|
57
|
+
if (code === "invalid-release") return "validate-release";
|
|
58
|
+
return "preflight";
|
|
59
|
+
}
|
|
60
|
+
function deriveRouteMethod(stage, environmentKind, errorCode) {
|
|
61
|
+
if (stage === "fetch-release" || stage === "validate-release") return { route: "registry", method: "https" };
|
|
62
|
+
if (stage === "read-installed" || stage === "verify") return { route: "disk", method: "fs" };
|
|
63
|
+
if (stage === "preflight") {
|
|
64
|
+
if (String(errorCode || "") === "update-busy") return { route: "queue", method: "fs" };
|
|
65
|
+
return { route: "disk", method: "fs" };
|
|
66
|
+
}
|
|
67
|
+
const kind = String(environmentKind || "");
|
|
68
|
+
if (kind === "desktop-manager") return { route: "desktop-manager", method: "phone" };
|
|
69
|
+
if (kind === "desktop") return { route: "desktop-service", method: "service" };
|
|
70
|
+
if (kind === "cli") return { route: "cli-process", method: "spawn" };
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
function deriveAction(stage, errorCode) {
|
|
74
|
+
const code = String(errorCode || "");
|
|
75
|
+
if (code === "pending-restart" || stage === "verify") return "restart";
|
|
76
|
+
if (code === "install-failed" || stage === "exec") return "contact";
|
|
77
|
+
if (code === "invalid-release" || stage === "validate-release") return "manual";
|
|
78
|
+
if (code === "check-expired") return "manual";
|
|
79
|
+
if (code === "update-busy") return "retry";
|
|
80
|
+
if (code === "check-failed" || code === "internal") return "retry";
|
|
81
|
+
if (stage === "fetch-release") return "retry";
|
|
82
|
+
return "manual";
|
|
83
|
+
}
|
|
84
|
+
function pickHttpStatus(stage, error) {
|
|
85
|
+
if (DIAG_STAGE_EXPECT[stage].forbid.includes("httpStatus")) return null;
|
|
86
|
+
const raw = error?.httpStatus;
|
|
87
|
+
if (typeof raw === "number" && Number.isInteger(raw) && raw >= 100 && raw <= 599) return raw;
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function pickExitCode(stage, error) {
|
|
91
|
+
if (DIAG_STAGE_EXPECT[stage].forbid.includes("exitCode")) return null;
|
|
92
|
+
const raw = error?.exitCode;
|
|
93
|
+
if (typeof raw === "number" && Number.isInteger(raw)) return raw;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function pickLatencyMs(raw) {
|
|
97
|
+
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) return null;
|
|
98
|
+
return Math.floor(raw);
|
|
99
|
+
}
|
|
100
|
+
function pickVersion(raw) {
|
|
101
|
+
if (typeof raw !== "string" || !raw) return null;
|
|
102
|
+
const t = raw.trim();
|
|
103
|
+
if (!t) return null;
|
|
104
|
+
return validReleaseVersion(t) ? t : null;
|
|
105
|
+
}
|
|
106
|
+
function pickPackageName(raw) {
|
|
107
|
+
if (typeof raw !== "string" || !raw) return null;
|
|
108
|
+
const t = raw.trim();
|
|
109
|
+
if (!t || t.length > 256) return null;
|
|
110
|
+
if (/^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/.test(t)) return t;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function pickEnvironmentKind(raw) {
|
|
114
|
+
return raw === "desktop" || raw === "desktop-manager" || raw === "cli" ? String(raw) : null;
|
|
115
|
+
}
|
|
116
|
+
function pickId(raw) {
|
|
117
|
+
return typeof raw === "string" && validRequestId(raw) ? raw.trim() : null;
|
|
118
|
+
}
|
|
119
|
+
function pickDetail(errorCode, error, httpStatus, phoneKind) {
|
|
120
|
+
try {
|
|
121
|
+
const rawDetail = error?.detail;
|
|
122
|
+
if (typeof rawDetail === "string" && rawDetail.trim()) {
|
|
123
|
+
const clean2 = sanitizeDetail(rawDetail);
|
|
124
|
+
return clean2 ? clean2 : null;
|
|
125
|
+
}
|
|
126
|
+
const rawMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
127
|
+
const msg = typeof rawMessage === "string" ? rawMessage.trim() : "";
|
|
128
|
+
const isBareCode = msg && /^[a-z][a-z-]*$/.test(msg) && msg.length <= 32;
|
|
129
|
+
if (msg && !isBareCode) {
|
|
130
|
+
const clean2 = sanitizeDetail(msg);
|
|
131
|
+
if (clean2) return clean2;
|
|
132
|
+
}
|
|
133
|
+
const code = String(errorCode || "");
|
|
134
|
+
const phone = String(phoneKind || "");
|
|
135
|
+
let fallback = "\u64CD\u4F5C\u5931\u8D25";
|
|
136
|
+
if (code === "check-failed" && phone === "update-status") fallback = "\u8BFB\u672C\u5730\u72B6\u6001\u6CA1\u6210\u529F";
|
|
137
|
+
else if ((code === "check-failed" || code === "internal") && phone === "update-install") fallback = "\u88C5\u524D\u91CD\u9A8C\u53D6\u6570\u6CA1\u6210\u529F";
|
|
138
|
+
else if (code === "check-failed" && httpStatus === 429) fallback = "\u6E90\u8FD4\u56DE 429\uFF0C\u8FD9\u4E00\u5206\u949F\u8BF7\u6C42\u592A\u591A";
|
|
139
|
+
else if (code === "check-failed" && typeof httpStatus === "number") fallback = `\u6E90\u8FD4\u56DE ${httpStatus}\uFF0C\u91CD\u8BD5\u4ECD\u5931\u8D25`;
|
|
140
|
+
else if (code === "check-failed" || code === "internal") fallback = "\u67E5\u65B0\u7248\u6CA1\u6210\u529F\uFF08\u8054\u7F51\u3001\u6E90\u3001\u9650\u6D41\u90FD\u53EF\u80FD\uFF09";
|
|
141
|
+
else if (code === "invalid-release") fallback = "\u6E05\u5355\u91CC\u7684\u7248\u672C\u53F7\u4E0D\u662F\u5408\u6CD5\u7248\u672C";
|
|
142
|
+
else if (code === "check-expired") fallback = "\u51ED\u8BC1\u8FC7\u671F\u4E86\uFF0C\u5B89\u88C5\u8BF7\u6C42\u88AB\u62D2";
|
|
143
|
+
else if (code === "update-busy") fallback = "\u540C\u4E00\u4F7F\u7528\u8303\u56F4\u6B63\u5728\u88C5\u53E6\u4E00\u4E2A";
|
|
144
|
+
else if (code === "install-failed") fallback = "\u5B89\u88C5\u5931\u8D25";
|
|
145
|
+
else if (code === "unknown-profile") fallback = "\u4F7F\u7528\u8303\u56F4\u6216\u63D2\u4EF6\u4F4D\u7F6E\u8BA4\u4E0D\u51FA";
|
|
146
|
+
else if (code === "source-install") fallback = "\u5F53\u524D\u662F\u4ECE\u6E90\u7801\u88C5\u7684\uFF0C\u4E0D\u662F\u6309\u7248\u672C\u53F7\u88C5\u7684";
|
|
147
|
+
else if (code === "invalid-installation") fallback = "\u5DF2\u88C5\u7684\u5305\u4E0D\u5B8C\u6574";
|
|
148
|
+
else if (code === "installation-changed") fallback = "\u5B89\u88C5\u4F4D\u7F6E\u5728\u4F7F\u7528\u4E2D\u9014\u53D8\u4E86";
|
|
149
|
+
else if (code === "pending-restart") fallback = "\u65B0\u7248\u5DF2\u88C5\u5230\u78C1\u76D8\uFF0C\u6B63\u5728\u8DD1\u7684\u8FD8\u662F\u65E7\u7248";
|
|
150
|
+
else if (code === "incompatible-node") fallback = "\u65B0\u7248\u8981\u6C42\u7684 Node \u4E0E\u5F53\u524D\u8FD0\u884C\u7684\u5BF9\u4E0D\u4E0A";
|
|
151
|
+
else if (code === "registry-conflict") fallback = "\u672C\u5730\u58F0\u660E\u7684\u7248\u672C\u4E0E\u78C1\u76D8\u5B9E\u9645\u7248\u672C\u4E92\u76F8\u77DB\u76FE";
|
|
152
|
+
else if (code === "recovery-required") fallback = "\u4E0A\u6B21\u5B89\u88C5\u88AB\u6253\u65AD\uFF0C\u7559\u4E0B\u4E00\u4E2A\u534A\u622A\u4EFB\u52A1";
|
|
153
|
+
const clean = sanitizeDetail(fallback);
|
|
154
|
+
return clean ? clean : null;
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function byteLength(text) {
|
|
160
|
+
try {
|
|
161
|
+
return diagByteLength(text);
|
|
162
|
+
} catch {
|
|
163
|
+
return text.length;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function buildDiag(input) {
|
|
167
|
+
try {
|
|
168
|
+
const errorCode = String(input?.errorCode || "");
|
|
169
|
+
if (!errorCode) return void 0;
|
|
170
|
+
const phoneKind = String(input?.phoneKind || "");
|
|
171
|
+
const stage = deriveStage(errorCode, phoneKind);
|
|
172
|
+
if (!isStage(stage)) return void 0;
|
|
173
|
+
const { route, method } = deriveRouteMethod(stage, input?.environmentKind, errorCode);
|
|
174
|
+
const action = deriveAction(stage, errorCode);
|
|
175
|
+
if (!isAction(action)) return void 0;
|
|
176
|
+
const httpStatus = pickHttpStatus(stage, input?.error);
|
|
177
|
+
const exitCode = pickExitCode(stage, input?.error);
|
|
178
|
+
const latencyMs = pickLatencyMs(input?.latencyMs);
|
|
179
|
+
const detail = pickDetail(errorCode, input?.error, httpStatus, phoneKind);
|
|
180
|
+
const targetPackageName = pickPackageName(input?.targetPackageName);
|
|
181
|
+
const runningVersion = pickVersion(input?.runningVersion);
|
|
182
|
+
const latestVersion = pickVersion(input?.latestVersion);
|
|
183
|
+
const environmentKind = pickEnvironmentKind(input?.environmentKind);
|
|
184
|
+
const args = input?.args && typeof input.args === "object" ? input.args : {};
|
|
185
|
+
const requestId = pickId(args["requestId"]);
|
|
186
|
+
const checkId = pickId(args["checkId"]);
|
|
187
|
+
let registryHost = null;
|
|
188
|
+
try {
|
|
189
|
+
registryHost = resolveRegistryHost(String(input?.registryUrl ?? ""));
|
|
190
|
+
} catch {
|
|
191
|
+
registryHost = null;
|
|
192
|
+
}
|
|
193
|
+
const diag = { v: DIAG_VERSION, stage };
|
|
194
|
+
if (route) diag.route = route;
|
|
195
|
+
if (method) diag.method = method;
|
|
196
|
+
if (httpStatus !== null) diag.httpStatus = httpStatus;
|
|
197
|
+
if (exitCode !== null) diag.exitCode = exitCode;
|
|
198
|
+
if (latencyMs !== null) diag.latencyMs = latencyMs;
|
|
199
|
+
if (detail) diag.detail = detail;
|
|
200
|
+
if (targetPackageName) diag.targetPackageName = targetPackageName;
|
|
201
|
+
if (runningVersion) diag.runningVersion = runningVersion;
|
|
202
|
+
if (latestVersion) diag.latestVersion = latestVersion;
|
|
203
|
+
if (environmentKind) diag.environmentKind = environmentKind;
|
|
204
|
+
if (requestId) diag.requestId = requestId;
|
|
205
|
+
if (checkId) diag.checkId = checkId;
|
|
206
|
+
if (registryHost) diag.registryHost = registryHost;
|
|
207
|
+
diag.action = action;
|
|
208
|
+
return enforceBudget(diag);
|
|
209
|
+
} catch {
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function enforceBudget(diag) {
|
|
214
|
+
try {
|
|
215
|
+
const out = enforceDiagBudget(diag);
|
|
216
|
+
if (!out || typeof out !== "object") return void 0;
|
|
217
|
+
if (out.truncated === true) return out;
|
|
218
|
+
const clean = { ...out };
|
|
219
|
+
delete clean["truncated"];
|
|
220
|
+
try {
|
|
221
|
+
if (diagByteLength(clean) > DIAG_INLINE_BUDGET_BYTES) return void 0;
|
|
222
|
+
} catch {
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
return clean;
|
|
226
|
+
} catch {
|
|
227
|
+
return void 0;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
void DIAG_INLINE_BUDGET_BYTES;
|
|
231
|
+
export {
|
|
232
|
+
DIAG_ACTIONS,
|
|
233
|
+
DIAG_KEYS,
|
|
234
|
+
DIAG_KNOWN_TYPES,
|
|
235
|
+
DIAG_STAGES,
|
|
236
|
+
DIAG_STAGE_EXPECT,
|
|
237
|
+
DIAG_VERSION,
|
|
238
|
+
buildDiag,
|
|
239
|
+
deriveAction,
|
|
240
|
+
deriveRouteMethod,
|
|
241
|
+
deriveStage,
|
|
242
|
+
enforceBudget
|
|
243
|
+
};
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type UpdatePanelContainer, type UpdatePanelTheme } from './panel.js';
|
|
2
|
+
import type { UpdateSnapshot } from './ports.js';
|
|
3
|
+
/** 入口件的形态。 */
|
|
4
|
+
export type EntryVariant = 'button' | 'badge' | 'inline';
|
|
5
|
+
/** 什么时候主动查一次(只读,联网一次)。 */
|
|
6
|
+
export type EntryAutoCheck = 'mount' | 'never';
|
|
7
|
+
/** 用户点下去之后做什么。 */
|
|
8
|
+
export type EntryOpenOn = 'has-update' | 'always' | 'manual';
|
|
9
|
+
/** 入口件主题(与面板同一套取值,切换即换肤)。 */
|
|
10
|
+
export type EntryTheme = UpdatePanelTheme;
|
|
11
|
+
/** 入口件的状态档:只用来算文案与 data-state,不另立状态机。 */
|
|
12
|
+
export type EntryStateKind = 'idle' | 'update' | 'busy' | 'restart' | 'failed';
|
|
13
|
+
/** 算文案要的全部输入:一份快照 + 最近一次通话的失败稳定码(没查过、没失败为 null)。 */
|
|
14
|
+
export interface UpdateEntryState {
|
|
15
|
+
snapshot: UpdateSnapshot | null;
|
|
16
|
+
error: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface UpdateEntryOptions {
|
|
19
|
+
pluginId: string;
|
|
20
|
+
/** 单插件电话前缀(与宿主侧一致)。 */
|
|
21
|
+
prefix: string;
|
|
22
|
+
/** 调宿主电话:(phoneName, args) => Promise<reply>。 */
|
|
23
|
+
call: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
24
|
+
variant?: EntryVariant;
|
|
25
|
+
theme?: 'default' | 'd5-paper';
|
|
26
|
+
/** 缺省 'mount':进页面静默查一次(只读)。'never' 则只在用户点击时查。 */
|
|
27
|
+
autoCheck?: EntryAutoCheck;
|
|
28
|
+
/** 缺省 'has-update':有新版才开面板,没有就只在原地给一句提示。 */
|
|
29
|
+
openOn?: EntryOpenOn;
|
|
30
|
+
/** 覆盖默认按钮文案(不传就用状态联动文案)。 */
|
|
31
|
+
label?: string;
|
|
32
|
+
profileName?: string;
|
|
33
|
+
pollMs?: number;
|
|
34
|
+
/** variant='badge' 或 openOn='manual' 时,点击交给接入方(自己跳自己的页面)。 */
|
|
35
|
+
onActivate?: (state: {
|
|
36
|
+
hasUpdate: boolean;
|
|
37
|
+
latestVersion: string | null;
|
|
38
|
+
}) => void;
|
|
39
|
+
}
|
|
40
|
+
export interface UpdateEntryController {
|
|
41
|
+
/** 立刻查一次并把状态刷到界面上(只读)。 */
|
|
42
|
+
refresh(): Promise<void>;
|
|
43
|
+
/** 打开更新面板(dialog 形态)。 */
|
|
44
|
+
open(): void;
|
|
45
|
+
/** 关掉更新面板。 */
|
|
46
|
+
close(): void;
|
|
47
|
+
/** 当前按钮上的状态文案(接入方做自定义排版时读它)。 */
|
|
48
|
+
label(): string;
|
|
49
|
+
setTheme(theme: 'default' | 'd5-paper'): void;
|
|
50
|
+
unmount(): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 状态档:快照六字段 + 失败码推出,顺序固定(活任务 > 待重启 > 失败 > 有新版 > 待查)。
|
|
54
|
+
* 待重启是正常终态(不是失败),所以排在失败前面;正在装的任务比一条陈旧的失败码更可信。
|
|
55
|
+
*/
|
|
56
|
+
export declare function entryStateKind(state: UpdateEntryState | null | undefined): EntryStateKind;
|
|
57
|
+
/** 入口件文案(唯一出处:测试与接入方都读它,不各写一份)。 */
|
|
58
|
+
export declare function entryLabelFor(state: UpdateEntryState | null | undefined): string;
|
|
59
|
+
export declare const UPDATE_ENTRY_CSS: string;
|
|
60
|
+
export declare function mountUpdateEntry(container: UpdatePanelContainer, options: UpdateEntryOptions): UpdateEntryController;
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/entry.ts
|
|
2
|
+
import { DEFAULT_PREFIX, MIN_PANEL_POLL_MS, buildPhoneNames } from "./config.js";
|
|
3
|
+
import {
|
|
4
|
+
failureCodeOf,
|
|
5
|
+
mountUpdatePanel
|
|
6
|
+
} from "./panel.js";
|
|
7
|
+
const LABEL_IDLE = "\u68C0\u67E5\u66F4\u65B0";
|
|
8
|
+
const LABEL_FAILED = "\u66F4\u65B0\u5931\u8D25\uFF0C\u70B9\u6B64\u67E5\u770B";
|
|
9
|
+
const LABEL_BUSY = "\u6B63\u5728\u5B89\u88C5\u2026";
|
|
10
|
+
const LABEL_RESTART = "\u5F85\u91CD\u542F";
|
|
11
|
+
function hasUpdateOf(snapshot) {
|
|
12
|
+
if (!snapshot) return false;
|
|
13
|
+
const latest = snapshot.latestVersion;
|
|
14
|
+
if (typeof latest !== "string" || !latest.trim()) return false;
|
|
15
|
+
return latest.trim() !== snapshot.runningVersion;
|
|
16
|
+
}
|
|
17
|
+
function entryStateKind(state) {
|
|
18
|
+
const snapshot = state?.snapshot ?? null;
|
|
19
|
+
const job = snapshot?.job ?? null;
|
|
20
|
+
if (job && (job.state === "installing" || job.state === "verifying")) return "busy";
|
|
21
|
+
if (snapshot && (snapshot.blockedReason === "pending-restart" || job?.state === "restart-required")) {
|
|
22
|
+
return "restart";
|
|
23
|
+
}
|
|
24
|
+
if (state && state.error || job && (job.state === "failed" || job.state === "interrupted")) {
|
|
25
|
+
return "failed";
|
|
26
|
+
}
|
|
27
|
+
if (hasUpdateOf(snapshot)) return "update";
|
|
28
|
+
return "idle";
|
|
29
|
+
}
|
|
30
|
+
function entryLabelFor(state) {
|
|
31
|
+
switch (entryStateKind(state)) {
|
|
32
|
+
case "busy":
|
|
33
|
+
return LABEL_BUSY;
|
|
34
|
+
case "restart":
|
|
35
|
+
return LABEL_RESTART;
|
|
36
|
+
case "failed":
|
|
37
|
+
return LABEL_FAILED;
|
|
38
|
+
case "update": {
|
|
39
|
+
const latest = state?.snapshot?.latestVersion;
|
|
40
|
+
return typeof latest === "string" && latest.trim() ? `\u6709\u65B0\u7248 ${latest.trim()}` : LABEL_IDLE;
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
return LABEL_IDLE;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const UPDATE_ENTRY_CSS = [
|
|
47
|
+
'.dsh-upd-entry{display:inline-flex;align-items:center;gap:8px;font:13px/1.6 system-ui,"Microsoft YaHei",sans-serif;color:var(--dsh-upd-fg,#1f2937)}',
|
|
48
|
+
".dsh-upd-entry-btn{font:inherit;border:1px solid var(--dsh-upd-line,#d1d5db);border-radius:6px;",
|
|
49
|
+
"background:var(--dsh-upd-btn,#f9fafb);color:inherit;padding:4px 12px;cursor:pointer}",
|
|
50
|
+
".dsh-upd-entry-btn:hover{border-color:var(--dsh-upd-primary,#2563eb)}",
|
|
51
|
+
".dsh-upd-entry-btn:focus-visible,.dsh-upd-entry-dot:focus-visible{outline:2px solid var(--dsh-upd-focus,#2563eb);outline-offset:1px}",
|
|
52
|
+
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-btn{border-color:var(--dsh-upd-ok-line,#059669);color:var(--dsh-upd-ok-line,#059669)}',
|
|
53
|
+
'.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-btn{border-color:var(--dsh-upd-warn-line,#d97706);color:var(--dsh-upd-warn-line,#d97706)}',
|
|
54
|
+
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-btn{border-color:var(--dsh-upd-bad-line,#dc2626);color:var(--dsh-upd-bad-line,#dc2626)}',
|
|
55
|
+
".dsh-upd-entry-dot{width:10px;height:10px;padding:0;border:0;border-radius:50%;background:var(--dsh-upd-line,#9ca3af);cursor:pointer}",
|
|
56
|
+
'.dsh-upd-entry[data-state="update"] .dsh-upd-entry-dot{background:var(--dsh-upd-ok-line,#059669)}',
|
|
57
|
+
'.dsh-upd-entry[data-state="busy"] .dsh-upd-entry-dot,.dsh-upd-entry[data-state="restart"] .dsh-upd-entry-dot{background:var(--dsh-upd-warn-line,#d97706)}',
|
|
58
|
+
'.dsh-upd-entry[data-state="failed"] .dsh-upd-entry-dot{background:var(--dsh-upd-bad-line,#dc2626)}',
|
|
59
|
+
".dsh-upd-entry-note{font-size:12.5px;opacity:.75}",
|
|
60
|
+
'.dsh-upd-entry[data-theme="d5-paper"]{font-family:Georgia,"Songti SC","STSong","SimSun",serif;color:#1a1a1a}',
|
|
61
|
+
'.dsh-upd-entry[data-theme="d5-paper"] .dsh-upd-entry-btn{border-color:#c4b896;background:transparent;border-radius:3px}',
|
|
62
|
+
'.dsh-upd-entry[data-theme="d5-paper"] .dsh-upd-entry-btn:hover{border-color:#c8402a;color:#c8402a}',
|
|
63
|
+
"@media (prefers-color-scheme: dark){.dsh-upd-entry{color:#e5e7eb}",
|
|
64
|
+
".dsh-upd-entry-btn{--dsh-upd-btn:#1f2937;--dsh-upd-line:#374151}}"
|
|
65
|
+
].join("\n");
|
|
66
|
+
const ENTRY_ATTR = "data-dsh-upd-entry";
|
|
67
|
+
const ENTRY_SELECTOR = "[data-dsh-upd-entry]";
|
|
68
|
+
function isObject(value) {
|
|
69
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
70
|
+
}
|
|
71
|
+
function asSnapshot(value) {
|
|
72
|
+
if (!isObject(value)) return null;
|
|
73
|
+
if (typeof value["runningVersion"] !== "string") return null;
|
|
74
|
+
if (typeof value["canInstall"] !== "boolean") return null;
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
function escapeHtml(value) {
|
|
78
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
79
|
+
}
|
|
80
|
+
function mountUpdateEntry(container, options) {
|
|
81
|
+
if (!container || typeof container.innerHTML !== "string") {
|
|
82
|
+
throw new Error("[dsh-plugin-update] \u6302\u66F4\u65B0\u5165\u53E3\u4EF6\u9700\u8981\u4E00\u4E2A\u6709 innerHTML \u7684\u5BB9\u5668");
|
|
83
|
+
}
|
|
84
|
+
if (!options || typeof options !== "object") {
|
|
85
|
+
throw new Error("[dsh-plugin-update] \u6302\u66F4\u65B0\u5165\u53E3\u4EF6\u7F3A\u5C11\u914D\u7F6E\uFF1A\u63D2\u4EF6\u6807\u8BC6 pluginId \u5FC5\u586B");
|
|
86
|
+
}
|
|
87
|
+
const pluginId = options.pluginId;
|
|
88
|
+
if (typeof pluginId !== "string" || !pluginId) {
|
|
89
|
+
throw new Error(`[dsh-plugin-update] \u63D2\u4EF6\u6807\u8BC6 pluginId \u5FC5\u586B\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32\uFF08\u6536\u5230 ${JSON.stringify(pluginId)}\uFF09`);
|
|
90
|
+
}
|
|
91
|
+
if (typeof options.call !== "function") {
|
|
92
|
+
throw new Error("[dsh-plugin-update] \u6302\u66F4\u65B0\u5165\u53E3\u4EF6\u9700\u8981\u4F20\u8F93\u51FD\u6570 call\uFF08\u5165\u53E3\u4EF6\u8C03\u5BBF\u4E3B\u7535\u8BDD\u7684\u552F\u4E00\u63A5\u89E6\u9762\uFF09");
|
|
93
|
+
}
|
|
94
|
+
const rawPrefix = options.prefix;
|
|
95
|
+
const prefix = rawPrefix === void 0 ? DEFAULT_PREFIX : rawPrefix;
|
|
96
|
+
const phoneNames = buildPhoneNames(prefix);
|
|
97
|
+
const variant = options.variant ?? "button";
|
|
98
|
+
if (variant !== "button" && variant !== "badge" && variant !== "inline") {
|
|
99
|
+
throw new Error(`[dsh-plugin-update] \u5165\u53E3\u4EF6\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 button / badge / inline\uFF08\u6536\u5230 ${JSON.stringify(options.variant)}\uFF09`);
|
|
100
|
+
}
|
|
101
|
+
const autoCheck = options.autoCheck ?? "mount";
|
|
102
|
+
if (autoCheck !== "mount" && autoCheck !== "never") {
|
|
103
|
+
throw new Error(`[dsh-plugin-update] \u81EA\u52A8\u68C0\u67E5\u65F6\u673A\u975E\u6CD5\uFF1A\u53EA\u6536 mount \u6216 never\uFF08\u6536\u5230 ${JSON.stringify(options.autoCheck)}\uFF09`);
|
|
104
|
+
}
|
|
105
|
+
const openOn = options.openOn ?? "has-update";
|
|
106
|
+
if (openOn !== "has-update" && openOn !== "always" && openOn !== "manual") {
|
|
107
|
+
throw new Error(`[dsh-plugin-update] \u70B9\u51FB\u53BB\u5411\u975E\u6CD5\uFF1A\u53EA\u6536 has-update / always / manual\uFF08\u6536\u5230 ${JSON.stringify(options.openOn)}\uFF09`);
|
|
108
|
+
}
|
|
109
|
+
let theme = options.theme ?? "default";
|
|
110
|
+
if (theme !== "default" && theme !== "d5-paper") {
|
|
111
|
+
throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
|
|
112
|
+
}
|
|
113
|
+
const pollMs = options.pollMs;
|
|
114
|
+
if (pollMs !== void 0 && (typeof pollMs !== "number" || !Number.isFinite(pollMs) || pollMs < MIN_PANEL_POLL_MS)) {
|
|
115
|
+
throw new Error(`[dsh-plugin-update] \u9762\u677F\u8F6E\u8BE2\u95F4\u9694\u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 ${JSON.stringify(options.pollMs)}\uFF09`);
|
|
116
|
+
}
|
|
117
|
+
const labelOverride = typeof options.label === "string" && options.label ? options.label : null;
|
|
118
|
+
const profileName = typeof options.profileName === "string" && options.profileName ? options.profileName : null;
|
|
119
|
+
const onActivate = typeof options.onActivate === "function" ? options.onActivate : null;
|
|
120
|
+
const call = options.call;
|
|
121
|
+
let snapshot = null;
|
|
122
|
+
let error = null;
|
|
123
|
+
let note = null;
|
|
124
|
+
let mounted = true;
|
|
125
|
+
let panel = null;
|
|
126
|
+
let panelMode = null;
|
|
127
|
+
const panelHost = {
|
|
128
|
+
get innerHTML() {
|
|
129
|
+
return container.innerHTML;
|
|
130
|
+
},
|
|
131
|
+
set innerHTML(value) {
|
|
132
|
+
container.innerHTML = value;
|
|
133
|
+
},
|
|
134
|
+
addEventListener(type, listener) {
|
|
135
|
+
container.addEventListener?.(type, listener);
|
|
136
|
+
},
|
|
137
|
+
removeEventListener(type, listener) {
|
|
138
|
+
container.removeEventListener?.(type, listener);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
async function panelCall(name, args) {
|
|
142
|
+
const reply = await call(name, args);
|
|
143
|
+
return isObject(reply) ? reply : {};
|
|
144
|
+
}
|
|
145
|
+
function stateOf() {
|
|
146
|
+
return { snapshot, error };
|
|
147
|
+
}
|
|
148
|
+
function currentLabel() {
|
|
149
|
+
return labelOverride ?? entryLabelFor(stateOf());
|
|
150
|
+
}
|
|
151
|
+
function hasUpdate() {
|
|
152
|
+
return hasUpdateOf(snapshot);
|
|
153
|
+
}
|
|
154
|
+
function phoneArgs() {
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
function entryHTML() {
|
|
158
|
+
const kind = entryStateKind(stateOf());
|
|
159
|
+
const text = currentLabel();
|
|
160
|
+
const themeAttr = theme === "d5-paper" ? ' data-theme="d5-paper"' : "";
|
|
161
|
+
const control = variant === "badge" ? `<button type="button" class="dsh-upd-entry-dot" ${ENTRY_ATTR}="activate" title="${escapeHtml(text)}" aria-label="${escapeHtml(text)}"></button>` : `<button type="button" class="dsh-upd-entry-btn" ${ENTRY_ATTR}="activate">${escapeHtml(text)}</button>`;
|
|
162
|
+
const noteHTML = note ? `<span class="dsh-upd-entry-note" data-dsh-upd-note="1">${escapeHtml(note)}</span>` : "";
|
|
163
|
+
return `<style>${UPDATE_ENTRY_CSS}</style>
|
|
164
|
+
<span class="dsh-upd-entry" data-variant="${variant}" data-state="${kind}"${themeAttr}>${control}${noteHTML}</span>`;
|
|
165
|
+
}
|
|
166
|
+
function render() {
|
|
167
|
+
if (!mounted) return;
|
|
168
|
+
if (panelMode !== null) return;
|
|
169
|
+
container.innerHTML = entryHTML();
|
|
170
|
+
}
|
|
171
|
+
function applyReply(reply) {
|
|
172
|
+
if (!isObject(reply) || reply["ok"] !== true) {
|
|
173
|
+
error = failureCodeOf(isObject(reply) ? reply : null, "check-failed");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const next = asSnapshot(reply["snapshot"]);
|
|
177
|
+
if (next) snapshot = next;
|
|
178
|
+
error = null;
|
|
179
|
+
}
|
|
180
|
+
async function refresh() {
|
|
181
|
+
if (!mounted) return;
|
|
182
|
+
try {
|
|
183
|
+
const reply = await call(phoneNames.updateStatus, phoneArgs());
|
|
184
|
+
if (!mounted) return;
|
|
185
|
+
applyReply(reply);
|
|
186
|
+
} catch {
|
|
187
|
+
if (!mounted) return;
|
|
188
|
+
error = "check-failed";
|
|
189
|
+
}
|
|
190
|
+
render();
|
|
191
|
+
}
|
|
192
|
+
async function checkNow() {
|
|
193
|
+
try {
|
|
194
|
+
const reply = await call(phoneNames.updateCheck, phoneArgs());
|
|
195
|
+
if (!mounted) return;
|
|
196
|
+
applyReply(reply);
|
|
197
|
+
} catch {
|
|
198
|
+
if (!mounted) return;
|
|
199
|
+
error = "check-failed";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function mountPanel(mode) {
|
|
203
|
+
if (!mounted || panelMode !== null) return;
|
|
204
|
+
panel = mountUpdatePanel(panelHost, {
|
|
205
|
+
pluginId,
|
|
206
|
+
prefix,
|
|
207
|
+
mode,
|
|
208
|
+
theme,
|
|
209
|
+
pollMs,
|
|
210
|
+
profileName,
|
|
211
|
+
call: panelCall
|
|
212
|
+
});
|
|
213
|
+
panelMode = mode;
|
|
214
|
+
}
|
|
215
|
+
function openDialog() {
|
|
216
|
+
mountPanel("dialog");
|
|
217
|
+
}
|
|
218
|
+
function open() {
|
|
219
|
+
if (!mounted || panelMode !== null) return;
|
|
220
|
+
if (variant === "inline") return;
|
|
221
|
+
openDialog();
|
|
222
|
+
}
|
|
223
|
+
function close() {
|
|
224
|
+
if (!mounted || panelMode !== "dialog" || !panel) return;
|
|
225
|
+
const opened = panel;
|
|
226
|
+
panel = null;
|
|
227
|
+
panelMode = null;
|
|
228
|
+
try {
|
|
229
|
+
opened.unmount();
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
render();
|
|
233
|
+
void refresh();
|
|
234
|
+
}
|
|
235
|
+
async function activate() {
|
|
236
|
+
if (!mounted || panelMode !== null) return;
|
|
237
|
+
note = null;
|
|
238
|
+
render();
|
|
239
|
+
await checkNow();
|
|
240
|
+
if (!mounted) return;
|
|
241
|
+
if (variant === "badge" || openOn === "manual") {
|
|
242
|
+
if (onActivate) onActivate({ hasUpdate: hasUpdate(), latestVersion: snapshot?.latestVersion ?? null });
|
|
243
|
+
else openDialog();
|
|
244
|
+
render();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (openOn === "always" || hasUpdate() || !snapshot || error) {
|
|
248
|
+
openDialog();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
note = `\u5DF2\u662F\u6700\u65B0 ${snapshot.runningVersion}`;
|
|
252
|
+
render();
|
|
253
|
+
}
|
|
254
|
+
function setTheme(next) {
|
|
255
|
+
if (next !== "default" && next !== "d5-paper") {
|
|
256
|
+
throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
|
|
257
|
+
}
|
|
258
|
+
theme = next;
|
|
259
|
+
if (panel) void panel.setTheme(next);
|
|
260
|
+
else render();
|
|
261
|
+
}
|
|
262
|
+
function onClick(ev) {
|
|
263
|
+
if (!mounted) return;
|
|
264
|
+
try {
|
|
265
|
+
const target = ev?.target;
|
|
266
|
+
const closest = target && typeof target.closest === "function" ? target.closest.bind(target) : null;
|
|
267
|
+
if (!closest) return;
|
|
268
|
+
if (closest(ENTRY_SELECTOR)) {
|
|
269
|
+
void activate();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (panelMode === "dialog" && closest('[data-action="close-view"]')) close();
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function unmount() {
|
|
277
|
+
if (!mounted) return;
|
|
278
|
+
mounted = false;
|
|
279
|
+
const opened = panel;
|
|
280
|
+
panel = null;
|
|
281
|
+
panelMode = null;
|
|
282
|
+
try {
|
|
283
|
+
opened?.unmount();
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
container.removeEventListener?.("click", onClick);
|
|
288
|
+
} catch {
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
container.addEventListener?.("click", onClick);
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
if (variant === "inline") mountPanel("embedded");
|
|
296
|
+
else render();
|
|
297
|
+
if (autoCheck === "mount") void refresh();
|
|
298
|
+
return { refresh, open, close, label: currentLabel, setTheme, unmount };
|
|
299
|
+
}
|
|
300
|
+
export {
|
|
301
|
+
UPDATE_ENTRY_CSS,
|
|
302
|
+
entryLabelFor,
|
|
303
|
+
entryStateKind,
|
|
304
|
+
mountUpdateEntry
|
|
305
|
+
};
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const EVENT_LEVELS: readonly ["error", "warn", "info", "debug"];
|
|
2
|
+
export type GateEventLevel = (typeof EVENT_LEVELS)[number];
|
|
3
|
+
export declare const EVENT_KINDS: readonly ["resident", "ondemand", "selfmon"];
|
|
4
|
+
export type GateEventKind = (typeof EVENT_KINDS)[number];
|
|
5
|
+
export interface GateEventEntry {
|
|
6
|
+
level: GateEventLevel;
|
|
7
|
+
kind: GateEventKind;
|
|
8
|
+
fields: string[];
|
|
9
|
+
codes?: string[];
|
|
10
|
+
rules?: string[];
|
|
11
|
+
guard?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface GateEventCounts {
|
|
14
|
+
resident: number;
|
|
15
|
+
ondemand: number;
|
|
16
|
+
selfmon: number;
|
|
17
|
+
}
|
|
18
|
+
export interface GateEventList {
|
|
19
|
+
version: 1;
|
|
20
|
+
pluginId: string;
|
|
21
|
+
counts: GateEventCounts;
|
|
22
|
+
events: Record<string, GateEventEntry>;
|
|
23
|
+
}
|
|
24
|
+
export declare function parseEventListManifest(value: unknown): GateEventList;
|
|
25
|
+
export interface GateFieldCheck {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
unknownEvent: boolean;
|
|
28
|
+
unknownFields: string[];
|
|
29
|
+
}
|
|
30
|
+
export declare function checkEventFields(manifest: GateEventList, eventName: string, fieldNames: string[]): GateFieldCheck;
|
|
31
|
+
export interface GateCountCheck {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
problems: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare function checkEventCounts(manifest: GateEventList): GateCountCheck;
|