dsh-life-pack 0.2.0 → 0.2.2

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.
Files changed (41) hide show
  1. package/dist/client.js +820 -13
  2. package/dist/client.js.map +1 -1
  3. package/dist/dsh-ctx.d.ts +18 -2
  4. package/dist/dsh-ctx.d.ts.map +1 -1
  5. package/dist/index.d.ts +4 -8
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +63 -7
  8. package/dist/index.js.map +1 -1
  9. package/dist/nav.d.ts +34 -1
  10. package/dist/nav.d.ts.map +1 -1
  11. package/dist/nav.js +43 -1
  12. package/dist/nav.js.map +1 -1
  13. package/dist/update-client.d.ts +40 -0
  14. package/dist/update-client.d.ts.map +1 -0
  15. package/dist/update-client.js +121 -0
  16. package/dist/update-client.js.map +1 -0
  17. package/dist/update-contract.d.ts +47 -0
  18. package/dist/update-contract.d.ts.map +1 -0
  19. package/dist/update-contract.js +69 -0
  20. package/dist/update-contract.js.map +1 -0
  21. package/dist/update-env.d.ts +36 -0
  22. package/dist/update-env.d.ts.map +1 -0
  23. package/dist/update-env.js +242 -0
  24. package/dist/update-env.js.map +1 -0
  25. package/dist/update-host.d.ts +18 -0
  26. package/dist/update-host.d.ts.map +1 -0
  27. package/dist/update-host.js +161 -0
  28. package/dist/update-host.js.map +1 -0
  29. package/dist/update-panel.d.ts +63 -0
  30. package/dist/update-panel.d.ts.map +1 -0
  31. package/dist/update-panel.js +231 -0
  32. package/dist/update-panel.js.map +1 -0
  33. package/dist/update-targets.d.ts +17 -0
  34. package/dist/update-targets.d.ts.map +1 -0
  35. package/dist/update-targets.js +29 -0
  36. package/dist/update-targets.js.map +1 -0
  37. package/dist/update-view.d.ts +55 -0
  38. package/dist/update-view.d.ts.map +1 -0
  39. package/dist/update-view.js +65 -0
  40. package/dist/update-view.js.map +1 -0
  41. package/package.json +5 -2
@@ -0,0 +1,121 @@
1
+ /** 面板到宿主的三条流程(纯异步函数:无 DOM、无 react;传输口由调用方注入,便于单测)。
2
+ *
3
+ * 三条流程与更新包 README 的对应关系:
4
+ * - 检查更新:`updateCheck`(第 3 节第 3 步的 `host.call(UPD_CHECK, {})`);
5
+ * - 装上更新:`updateCheck` 拿凭证 → `updateInstall` 提交 → **按间隔轮询 `updateStatus`**
6
+ * 直到任务离开 `installing`/`verifying`(第 3 节第 3 步与第 9 节:安装是后台跑,
7
+ * 调用方不干等;凭证过期 `check-expired` 是正常错误码,重查一次再提交,见第 11 节);
8
+ * - 装上缺席的包:走总管自有的 `ilife-manager.install`(更新包的三个电话只管已装的包)。
9
+ */
10
+ import { MANAGER_ACTIONS, MANAGER_RPC, reasonText } from './update-contract.js';
11
+ /** 轮询上限:1 秒一次、最多 900 次(覆盖更新包 15 分钟的安装时限)。 */
12
+ const MAX_POLL_TRIES = 900;
13
+ /** 轮询默认间隔(只有在宿主没给 `pollMs` 时才用;正常取值由宿主转交更新包的 panelPollMs)。 */
14
+ const FALLBACK_POLL_MS = 1000;
15
+ let requestSeq = 0;
16
+ function failure(code, details = {}, manual = null) {
17
+ return { ok: false, code, message: reasonText(code), details, manual };
18
+ }
19
+ /** 取数间隔:settle 即清 timer。定时器有界重试的例外依据(p10 静态门三件套):
20
+ * 这里有清理(clearTimeout)、有次数上限(MAX_POLL_TRIES)、间隔取自宿主转交的更新包
21
+ * `panelPollMs`(不写死);用途是更新包 README 第 3 节要求的状态轮询(安装是后台跑,
22
+ * 面板每秒问一次进度),不是无界重试。 */
23
+ function sleep(ms) {
24
+ return new Promise((resolve) => {
25
+ const timer = setTimeout(() => {
26
+ clearTimeout(timer);
27
+ resolve();
28
+ }, ms);
29
+ });
30
+ }
31
+ async function callManager(call, method, payload) {
32
+ if (typeof call !== 'function')
33
+ return failure('internal', { detail: '宿主连接缺席:connection.rpc.call 不可用' });
34
+ let result;
35
+ try {
36
+ result = await call(MANAGER_RPC.channel, MANAGER_RPC.endpoint, { method, payload });
37
+ }
38
+ catch (error) {
39
+ return failure('check-failed', { detail: String(error?.message ?? error) });
40
+ }
41
+ if (!result || typeof result !== 'object' || typeof result.ok !== 'boolean') {
42
+ return failure('internal', { detail: '回执信封异常(非 ok 信封)' });
43
+ }
44
+ if (result.ok)
45
+ return { ok: true, value: result.value };
46
+ const error = result.error;
47
+ const code = typeof error?.code === 'string' ? error.code : 'internal';
48
+ const details = error && typeof error.details === 'object' && error.details !== null ? error.details : {};
49
+ return failure(code, details);
50
+ }
51
+ /** 取七个更新目标的表(宿主转交更新包的三个电话名与版本行,面板不写死任何电话名)。 */
52
+ export async function loadTargets(call) {
53
+ const out = await callManager(call, MANAGER_ACTIONS.targets, {});
54
+ if (!out.ok)
55
+ return out;
56
+ const targets = Array.isArray(out.value?.targets) ? out.value.targets : [];
57
+ const pollMs = typeof out.value?.pollMs === 'number' && out.value.pollMs > 0 ? out.value.pollMs : FALLBACK_POLL_MS;
58
+ return { ok: true, value: { targets, pollMs } };
59
+ }
60
+ /** 查一家:默认查新版(`updateCheck`,顺带回最新版本与凭证);给 `phoneName` 时查那个电话。 */
61
+ export async function checkTarget(call, target, phoneName) {
62
+ const name = phoneName ?? target.phones?.check;
63
+ if (!target.phones || !name)
64
+ return failure('bad-request', { target: target.key });
65
+ return callManager(call, name, {});
66
+ }
67
+ /** 装上缺席的包:版本取自查新版拿到的「最新版本」(宿主用更新包的执行器真装)。 */
68
+ export async function installAbsent(call, target, version) {
69
+ return callManager(call, MANAGER_ACTIONS.install, { packageName: target.packageName, version });
70
+ }
71
+ /** 装上更新:查新版 → 提交安装 → 有界轮询查状态到任务收尾;凭证过期重查一次再提交。 */
72
+ export async function updateInstalled(call, target, pollMs) {
73
+ if (!target.phones)
74
+ return failure('bad-request', { target: target.key });
75
+ const first = await checkTarget(call, target);
76
+ if (!first.ok)
77
+ return first;
78
+ let checked = first.value;
79
+ if (!checked.receipt) {
80
+ const code = checked.snapshot.blockedReason ?? 'check-failed';
81
+ return failure(code, { target: target.key }, checked.manual);
82
+ }
83
+ requestSeq += 1;
84
+ const requestId = 'ilife-manager-' + Date.now().toString(36) + '-' + String(requestSeq);
85
+ let submitted = await callManager(call, target.phones.install, {
86
+ checkId: checked.receipt.checkId,
87
+ requestId,
88
+ });
89
+ if (!submitted.ok && submitted.code === 'check-expired') {
90
+ const again = await checkTarget(call, target);
91
+ if (!again.ok)
92
+ return again;
93
+ checked = again.value;
94
+ if (!checked.receipt) {
95
+ return failure(checked.snapshot.blockedReason ?? 'check-failed', { target: target.key }, checked.manual);
96
+ }
97
+ submitted = await callManager(call, target.phones.install, {
98
+ checkId: checked.receipt.checkId,
99
+ requestId: requestId + 'r',
100
+ });
101
+ }
102
+ if (!submitted.ok)
103
+ return { ...submitted, manual: submitted.manual ?? checked.manual };
104
+ // 安装是后台跑(`service.ts:434-436`:落盘后后台跑,调用方不干等),故按间隔轮询到任务收尾。
105
+ let latest = submitted.value;
106
+ const interval = pollMs > 0 ? pollMs : FALLBACK_POLL_MS;
107
+ for (let tries = 0;; tries += 1) {
108
+ const job = latest.snapshot.job;
109
+ if (job === null || (job.state !== 'installing' && job.state !== 'verifying'))
110
+ break;
111
+ if (tries >= MAX_POLL_TRIES)
112
+ break;
113
+ await sleep(interval);
114
+ const status = await checkTarget(call, target, target.phones.status);
115
+ if (!status.ok)
116
+ return status;
117
+ latest = status.value;
118
+ }
119
+ return { ok: true, value: latest };
120
+ }
121
+ //# sourceMappingURL=update-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-client.js","sourceRoot":"","sources":["../src/update-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /** 总管与面板之间的定案形状:载体、电话名、原因人话表、手工兜底命令。
2
+ *
3
+ * 分工:本文件只管「两侧都认的常量与文案」;宿主半见 `update-host.ts`,面板见 `client.ts`。
4
+ * 电话名分两类:
5
+ * - 七个**更新电话组**(每组三个 `updateStatus` / `updateCheck` / `updateInstall`)由更新包
6
+ * 自己拼(`update.phoneNames`),面板侧取值见构建期派生的 `update-phones.ts`;
7
+ * - 两个**总管自有电话**(装上缺席包、取版本行数据)是本包定义的,见 `MANAGER_ACTIONS`。
8
+ *
9
+ * 原因码的人话文案逐条抄更新包 README 第 8 节「用户该做什么」列与第 12 节排错表;
10
+ * 手工命令形状抄第 9 节(`commands.ts:104-122` 的 `manualCommand`)。
11
+ */
12
+ import type { BlockedReason } from 'dsh-plugin-update';
13
+ /** 载体:宿主注册 `connection.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */
14
+ export declare const MANAGER_RPC: {
15
+ /** 单段通道名,须 match `/^\/[A-Za-z0-9._~-]+$/`(cookbook §6)。 */
16
+ readonly channel: "/ilife-manager";
17
+ /** 面板侧第二段参数(等于注册路径去掉 `/api` 前缀后那一段)。 */
18
+ readonly endpoint: "ilife-manager";
19
+ /** 宿主侧注册的完整路径(`/api` 是 DSH 公开载体)。 */
20
+ readonly path: "/api/ilife-manager";
21
+ };
22
+ /** 总管自有电话名:更新包的三个动作名是冻结集合(`config.ts:14`),这里另起名字不撞车。 */
23
+ export declare const MANAGER_ACTIONS: {
24
+ /** 装上缺席的目标包:入参 `{packageName, version}`,回包 `{packageName, version}`。 */
25
+ readonly install: "ilife-manager.install";
26
+ /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, skill}]}`。
27
+ *
28
+ * 电话名为什么由宿主转交而不是面板侧写死:更新包的三个电话名只有它自己知道
29
+ * (`update.phoneNames`,`host.ts:335`);面板侧既不能 import 更新包(浏览器产物纯度门)
30
+ * 也不该把名字再写一份(写两份必然走散)。宿主把名字表连同版本行一起交出去,
31
+ * 面板一行字面量都不留。 */
32
+ readonly targets: "ilife-manager.targets";
33
+ };
34
+ /** 更新包默认官方源(`config.ts:20`,总管侧拼手工命令时用同一个值)。 */
35
+ export declare const DEFAULT_REGISTRY: "https://registry.npmjs.org/";
36
+ /** 原因码 → 人话(认不出的码不猜、原样回码并说明这是未知原因)。 */
37
+ export declare function reasonText(code: string): string;
38
+ /** 手工兜底命令(更新包 README 第 9 节形状):使用范围名含特殊字符时加引号。 */
39
+ export declare function manualInstallCommand(input: {
40
+ profileName: string | null;
41
+ packageName: string;
42
+ version: string;
43
+ registryUrl?: string;
44
+ }): string | null;
45
+ /** 八种装不了原因码(给门禁与测试对表用;值就是 `BlockedReason` 全集)。 */
46
+ export declare const BLOCKED_REASONS: readonly BlockedReason[];
47
+ //# sourceMappingURL=update-contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-contract.d.ts","sourceRoot":"","sources":["../src/update-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,gFAAgF;AAChF,eAAO,MAAM,WAAW;IACtB,2DAA2D;;IAE3D,wCAAwC;;IAExC,qCAAqC;;CAE7B,CAAC;AAEX,wDAAwD;AACxD,eAAO,MAAM,eAAe;IAC1B,wEAAwE;;IAExE;;;;;qBAKiB;;CAET,CAAC;AAEX,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB,EAAG,6BAAsC,CAAC;AAqBvE,uCAAuC;AACvC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,iDAAiD;AACjD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,GAAG,IAAI,CAQhB;AAED,kDAAkD;AAClD,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EASnD,CAAC"}
@@ -0,0 +1,69 @@
1
+ /** 载体:宿主注册 `connection.fetch.register` 的路径 / 面板 `connection.rpc.call` 的两段参数。 */
2
+ export const MANAGER_RPC = {
3
+ /** 单段通道名,须 match `/^\/[A-Za-z0-9._~-]+$/`(cookbook §6)。 */
4
+ channel: '/ilife-manager',
5
+ /** 面板侧第二段参数(等于注册路径去掉 `/api` 前缀后那一段)。 */
6
+ endpoint: 'ilife-manager',
7
+ /** 宿主侧注册的完整路径(`/api` 是 DSH 公开载体)。 */
8
+ path: '/api/ilife-manager',
9
+ };
10
+ /** 总管自有电话名:更新包的三个动作名是冻结集合(`config.ts:14`),这里另起名字不撞车。 */
11
+ export const MANAGER_ACTIONS = {
12
+ /** 装上缺席的目标包:入参 `{packageName, version}`,回包 `{packageName, version}`。 */
13
+ install: 'ilife-manager.install',
14
+ /** 七个更新目标的表:入参 `{}`,回包 `{targets: [{key, title, packageName, phones, runningVersion, installedVersion, skill}]}`。
15
+ *
16
+ * 电话名为什么由宿主转交而不是面板侧写死:更新包的三个电话名只有它自己知道
17
+ * (`update.phoneNames`,`host.ts:335`);面板侧既不能 import 更新包(浏览器产物纯度门)
18
+ * 也不该把名字再写一份(写两份必然走散)。宿主把名字表连同版本行一起交出去,
19
+ * 面板一行字面量都不留。 */
20
+ targets: 'ilife-manager.targets',
21
+ };
22
+ /** 更新包默认官方源(`config.ts:20`,总管侧拼手工命令时用同一个值)。 */
23
+ export const DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
24
+ /** 装不了 / 装不成的人话原因表:八种 `blockedReason` + 过程错误码(更新包 README 第 8、12 节)。 */
25
+ const REASON_TEXT = {
26
+ 'unknown-profile': '使用范围认不出(名字非法或目录不存在):先检查使用范围名与目录,这种情形不给手工命令。',
27
+ 'source-install': '当前是从源码装的,不是按版本号装的:想走更新,先按版本号重装一次。',
28
+ 'invalid-installation': '已装的包不完整(名字对不上、版本非法、入口文件缺失):重装当前版本,修好已装目录再查。',
29
+ 'installation-changed': '安装位置在使用中途变了:重新打开宿主再查一次;还出现就重装。',
30
+ 'pending-restart': '新版已装到磁盘,正在跑的还是旧版:重启宿主后生效。',
31
+ 'registry-conflict': '本地声明的版本与磁盘实际版本互相矛盾:打开使用范围的清单,把目标包名那行改成版本号再试。',
32
+ 'incompatible-node': '新版要求的 Node 与当前运行的 Node 对不上:先把 Node 升到 22 或更高,再查。',
33
+ 'recovery-required': '上次安装被打断,留下一个半截任务:重新点一次安装;一直出现就查宿主日志。',
34
+ 'check-failed': '查新版失败(联网不通或官方源不可达):检查网络与官方源地址,再查一次。',
35
+ 'invalid-release': '远端发行信息不合规(名字对不上、版本非法、包地址或完整性校验不过):换个版本再查。',
36
+ 'check-expired': '上次查新版的凭证过期了:重新查一次再点安装(不要重试旧编号)。',
37
+ 'update-busy': '同一个使用范围同时只装一个:等当前任务结束再点。',
38
+ 'install-failed': '安装没跑成:按下面那条手工命令在终端执行一次。',
39
+ 'bad-request': '回执异常(请求形状不对):把宿主日志里的事件名报给作者。',
40
+ internal: '回执异常(总管内部错误):把宿主日志里的事件名报给作者。',
41
+ };
42
+ /** 原因码 → 人话(认不出的码不猜、原样回码并说明这是未知原因)。 */
43
+ export function reasonText(code) {
44
+ return REASON_TEXT[code] ?? '未知原因(' + code + '):把宿主日志里的事件名报给作者。';
45
+ }
46
+ /** 手工兜底命令(更新包 README 第 9 节形状):使用范围名含特殊字符时加引号。 */
47
+ export function manualInstallCommand(input) {
48
+ const name = typeof input.profileName === 'string' ? input.profileName.trim() : '';
49
+ if (!name || name.length > 255 || name.startsWith('-') || ['.', '..', 'node_modules'].includes(name))
50
+ return null;
51
+ const version = typeof input.version === 'string' && /^\d+\.\d+\.\d+$/.test(input.version) ? input.version : '';
52
+ if (!version)
53
+ return null;
54
+ const registry = input.registryUrl ?? DEFAULT_REGISTRY;
55
+ const profileArg = /^[A-Za-z0-9_.-]+$/.test(name) ? name : JSON.stringify(name);
56
+ return 'dsh plugin --profile ' + profileArg + ' add --save-exact ' + input.packageName + '@' + version + ' --registry=' + registry;
57
+ }
58
+ /** 八种装不了原因码(给门禁与测试对表用;值就是 `BlockedReason` 全集)。 */
59
+ export const BLOCKED_REASONS = [
60
+ 'unknown-profile',
61
+ 'source-install',
62
+ 'invalid-installation',
63
+ 'installation-changed',
64
+ 'pending-restart',
65
+ 'registry-conflict',
66
+ 'incompatible-node',
67
+ 'recovery-required',
68
+ ];
69
+ //# sourceMappingURL=update-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-contract.js","sourceRoot":"","sources":["../src/update-contract.ts"],"names":[],"mappings":"AAaA,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,2DAA2D;IAC3D,OAAO,EAAE,gBAAgB;IACzB,wCAAwC;IACxC,QAAQ,EAAE,eAAe;IACzB,qCAAqC;IACrC,IAAI,EAAE,oBAAoB;CAClB,CAAC;AAEX,wDAAwD;AACxD,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,wEAAwE;IACxE,OAAO,EAAE,uBAAuB;IAChC;;;;;qBAKiB;IACjB,OAAO,EAAE,uBAAuB;CACxB,CAAC;AAEX,+CAA+C;AAC/C,MAAM,CAAC,MAAM,gBAAgB,GAAG,6BAAsC,CAAC;AAEvE,uEAAuE;AACvE,MAAM,WAAW,GAA2B;IAC1C,iBAAiB,EAAE,6CAA6C;IAChE,gBAAgB,EAAE,mCAAmC;IACrD,sBAAsB,EAAE,6CAA6C;IACrE,sBAAsB,EAAE,gCAAgC;IACxD,iBAAiB,EAAE,2BAA2B;IAC9C,mBAAmB,EAAE,8CAA8C;IACnE,mBAAmB,EAAE,kDAAkD;IACvE,mBAAmB,EAAE,sCAAsC;IAC3D,cAAc,EAAE,qCAAqC;IACrD,iBAAiB,EAAE,2CAA2C;IAC9D,eAAe,EAAE,iCAAiC;IAClD,aAAa,EAAE,0BAA0B;IACzC,gBAAgB,EAAE,yBAAyB;IAC3C,aAAa,EAAE,8BAA8B;IAC7C,QAAQ,EAAE,8BAA8B;CACzC,CAAC;AAEF,uCAAuC;AACvC,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,OAAO,GAAG,IAAI,GAAG,mBAAmB,CAAC;AACnE,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,oBAAoB,CAAC,KAKpC;IACC,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACnF,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClH,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAChH,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,IAAI,gBAAgB,CAAC;IACvD,MAAM,UAAU,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAChF,OAAO,uBAAuB,GAAG,UAAU,GAAG,oBAAoB,GAAG,KAAK,CAAC,WAAW,GAAG,GAAG,GAAG,OAAO,GAAG,cAAc,GAAG,QAAQ,CAAC;AACrI,CAAC;AAED,kDAAkD;AAClD,MAAM,CAAC,MAAM,eAAe,GAA6B;IACvD,iBAAiB;IACjB,gBAAgB;IAChB,sBAAsB;IACtB,sBAAsB;IACtB,iBAAiB;IACjB,mBAAmB;IACnB,mBAAmB;IACnB,mBAAmB;CACpB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { EnvironmentKind, EnvironmentView } from 'dsh-plugin-update';
2
+ /** 缺席包给读取器的占位运行版本:这家读数是「没装」,不显示、也不参与能不能装(`eligible` 已为假)。 */
3
+ export declare const MISSING_RUNNING_VERSION: "0.0.0";
4
+ export interface TargetEnvOptions {
5
+ /** 使用范围目录(绝对路径)。 */
6
+ readonly profileDir: string;
7
+ /** 使用范围名(不传即取目录名)。 */
8
+ readonly profileName?: string;
9
+ /** 这家包的**运行版本**:宿主启动那一刻磁盘上的版本(见 `captureRunningVersion`)。 */
10
+ readonly runningVersion: string;
11
+ readonly environmentKind: EnvironmentKind;
12
+ /** 本家的插件标识(指纹绑定用,七家互不相同)。 */
13
+ readonly pluginId: string;
14
+ /** 目录根:不传即走更新包同一套推导(`DSH_HOME` 优先,否则家目录下 `.dsh`)。 */
15
+ readonly homeDir?: string;
16
+ }
17
+ /** 读一家的环境(更新包 `readInstalled` 接缝的实现)。 */
18
+ export declare function readTargetEnvironment(packageName: string, options: TargetEnvOptions): Promise<EnvironmentView>;
19
+ /** 启动期捕获一家的运行版本:加载那一刻磁盘上的版本,读不到即 null(这家还没装)。 */
20
+ export declare function captureRunningVersion(packageName: string, profileDir: string): Promise<string | null>;
21
+ /** 版本行要的「技能包随插件」事实:插件包 manifest 里那条 `skill-*` 依赖(精确 pin)。 */
22
+ export declare function readSkillRide(packageName: string, profileDir: string): Promise<{
23
+ packageName: string;
24
+ version: string;
25
+ } | null>;
26
+ /** 本包(总管)装在哪个使用范围:从自己的文件位置向上找自己的包目录,取它上面那层 `node_modules` 的父目录。
27
+ *
28
+ * 与更新包 `host.ts:102-121` 同一手法,只改一处:取**第一次**出现的 `/node_modules/<包名>`
29
+ * (更新包取的是最后一次)——pnpm 的 `.pnpm/<包>@<版本>/node_modules/<包>` 布局下,
30
+ * 最后一次会把使用范围认成虚拟目录,第一次才是真正的使用范围根。
31
+ */
32
+ export declare function resolveProfileDir(): Promise<{
33
+ dir: string;
34
+ name: string;
35
+ }>;
36
+ //# sourceMappingURL=update-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-env.d.ts","sourceRoot":"","sources":["../src/update-env.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAE1E,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,EAAG,OAAgB,CAAC;AA6DxD,MAAM,WAAW,gBAAgB;IAC/B,oBAAoB;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sBAAsB;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,yCAAyC;AACzC,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA+EpH;AAED,iDAAiD;AACjD,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ3G;AAED,6DAA6D;AAC7D,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAY1D;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAkChF"}
@@ -0,0 +1,242 @@
1
+ /** 环境读数:把本机的真实情况翻成更新核心要的 `EnvironmentView`。
2
+ *
3
+ * 为什么总管要自己读环境:更新包默认的读取器(`reader.ts:172-236`)从**它自己的文件位置**
4
+ * 向上找同名包(`containingPackage(import.meta.url, 目标包名)`),所以它只能服务「把它装在
5
+ * 自己内部」的那一个包。实测(2026-09-18,票 #678,复现命令与读数见实施记录):
6
+ *
7
+ * node 探针 import 已装的 dsh-plugin-update@0.1.1 →
8
+ * 以 targetPackageName 为 dsh-calorie 调 updateStatus 回
9
+ * {"ok":false,"error":"unknown-profile","errorKind":"unknown-profile"}
10
+ *
11
+ * 六个单品与总管是**兄弟包**(都在 `<使用范围>/node_modules/` 下),向上找不到同名包 ⇒
12
+ * 默认读数必抛 `unknown-profile`;即便硬给运行版本号,`sameLoadedPackage` 也永远为假 ⇒
13
+ * 一律落进 `installation-changed`(`reader.ts:218-232`)。所以总管按更新包公开的接缝
14
+ * `readerOverrides.readInstalled`(`host.ts:141`、`:211`)**自备这一家的环境读数**:
15
+ * 事实(谁装了、装到哪、版本多少、是不是按版本号装的)逐条照读,原因码阶梯照更新包的语义
16
+ * 写,只把「找得到自己包」这一步换成本包已知的安装位置。
17
+ *
18
+ * 纪律:本文件的每一步都只做「读事实」,不决定能不能装(能不能装是更新核心的事)。
19
+ */
20
+ import { createHash } from 'node:crypto';
21
+ import { readFile, realpath, stat } from 'node:fs/promises';
22
+ import { homedir } from 'node:os';
23
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+ import { defaultHomeDir, profileNameValid, registrySpec } from 'dsh-plugin-update';
26
+ /** 缺席包给读取器的占位运行版本:这家读数是「没装」,不显示、也不参与能不能装(`eligible` 已为假)。 */
27
+ export const MISSING_RUNNING_VERSION = '0.0.0';
28
+ /** 使用范围的落盘清单与锁文件(与更新包读的四处同名,`reader.ts:20`)。 */
29
+ const PROFILE_STATE_FILES = ['pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package-lock.json'];
30
+ /** 指纹绑定:第一次读到自洽的环境就把指纹记下来,之后再变即 `installation-changed`(同更新包语义)。 */
31
+ const boundKeys = new Map();
32
+ async function packageAt(directory) {
33
+ const contents = await readFile(join(directory, 'package.json'), 'utf8');
34
+ const manifest = JSON.parse(contents);
35
+ return { directory: await realpath(directory), manifest, contents };
36
+ }
37
+ async function readOptional(filename) {
38
+ try {
39
+ return await readFile(filename, 'utf8');
40
+ }
41
+ catch (error) {
42
+ if (error && error.code === 'ENOENT')
43
+ return '';
44
+ throw error;
45
+ }
46
+ }
47
+ function inside(directory, filename) {
48
+ const suffix = relative(directory, filename);
49
+ return suffix !== '..' && !suffix.startsWith('..' + sep) && !isAbsolute(suffix);
50
+ }
51
+ /** 包是否完整:名字对得上、三个入口(main / 客户端出口 / 装配行)都在包内且真存在。
52
+ * 规则照更新包 `reader.ts:84-109`;它没有把这条对外出口,只能在这里照写一遍。 */
53
+ async function packageComplete(pkg, packageName) {
54
+ if (pkg.manifest.name !== packageName || typeof pkg.manifest.version !== 'string')
55
+ return false;
56
+ const exportsField = pkg.manifest.exports;
57
+ const dshField = pkg.manifest.dsh;
58
+ const bundle = dshField?.bundle;
59
+ for (const entry of [pkg.manifest.main, exportsField?.['./client'], bundle?.patch]) {
60
+ if (typeof entry !== 'string' || !entry || isAbsolute(entry) || entry.includes('\0'))
61
+ return false;
62
+ const filename = resolve(pkg.directory, entry);
63
+ if (!inside(pkg.directory, filename))
64
+ return false;
65
+ let target = filename;
66
+ try {
67
+ target = await realpath(filename);
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ if (!inside(pkg.directory, target))
73
+ return false;
74
+ try {
75
+ if (!(await stat(target)).isFile())
76
+ return false;
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ }
82
+ return true;
83
+ }
84
+ /** 读一家的环境(更新包 `readInstalled` 接缝的实现)。 */
85
+ export async function readTargetEnvironment(packageName, options) {
86
+ const view = {
87
+ profileName: null,
88
+ environmentKind: options.environmentKind,
89
+ homeDir: null,
90
+ profileDir: null,
91
+ installedVersion: null,
92
+ packageValid: false,
93
+ sourceInstall: false,
94
+ blockedReason: null,
95
+ installationKey: null,
96
+ eligible: false,
97
+ };
98
+ const profileName = options.profileName ?? basename(options.profileDir);
99
+ view.profileName = profileName;
100
+ if (!profileNameValid(profileName)) {
101
+ view.blockedReason = 'unknown-profile';
102
+ return view;
103
+ }
104
+ const homeDirInput = options.homeDir ?? defaultHomeDir(process.env, homedir());
105
+ let homeDir;
106
+ let profileDir;
107
+ try {
108
+ homeDir = await realpath(homeDirInput);
109
+ profileDir = await realpath(options.profileDir);
110
+ }
111
+ catch {
112
+ view.blockedReason = 'unknown-profile';
113
+ return view;
114
+ }
115
+ view.homeDir = homeDir;
116
+ view.profileDir = profileDir;
117
+ let profile;
118
+ let installed;
119
+ try {
120
+ profile = await packageAt(profileDir);
121
+ }
122
+ catch {
123
+ view.blockedReason = 'invalid-installation';
124
+ return view;
125
+ }
126
+ try {
127
+ installed = await packageAt(join(profileDir, 'node_modules', packageName));
128
+ }
129
+ catch {
130
+ // 包还没装:与更新包同码(`reader.ts:204-210` 读不到已装位置时报同一个原因)。
131
+ view.blockedReason = 'invalid-installation';
132
+ return view;
133
+ }
134
+ const dependencies = (profile.manifest.dependencies ?? {});
135
+ view.installedVersion = typeof installed.manifest.version === 'string' ? installed.manifest.version : null;
136
+ view.packageValid = await packageComplete(installed, packageName);
137
+ view.sourceInstall = !registrySpec(dependencies[packageName]) || !inside(join(profileDir, 'node_modules'), installed.directory);
138
+ const stateFiles = await Promise.all(PROFILE_STATE_FILES.map((name) => readOptional(join(profileDir, name))));
139
+ view.installationKey = createHash('sha256')
140
+ .update(JSON.stringify([
141
+ homeDir,
142
+ profileDir,
143
+ profileName,
144
+ options.pluginId,
145
+ profile.contents,
146
+ installed.directory,
147
+ installed.contents,
148
+ ...stateFiles,
149
+ ]))
150
+ .digest('hex');
151
+ const bindKey = options.pluginId + '\0' + packageName;
152
+ const bound = boundKeys.get(bindKey);
153
+ if (bound === undefined) {
154
+ if (view.packageValid && !view.sourceInstall)
155
+ boundKeys.set(bindKey, view.installationKey);
156
+ }
157
+ else if (bound !== view.installationKey && view.packageValid && !view.sourceInstall) {
158
+ view.blockedReason = 'installation-changed';
159
+ }
160
+ if (view.blockedReason === null) {
161
+ if (!view.packageValid)
162
+ view.blockedReason = 'invalid-installation';
163
+ else if (view.sourceInstall)
164
+ view.blockedReason = 'source-install';
165
+ else if (view.installedVersion !== options.runningVersion)
166
+ view.blockedReason = 'pending-restart';
167
+ }
168
+ view.eligible = view.blockedReason === null;
169
+ return view;
170
+ }
171
+ /** 启动期捕获一家的运行版本:加载那一刻磁盘上的版本,读不到即 null(这家还没装)。 */
172
+ export async function captureRunningVersion(packageName, profileDir) {
173
+ try {
174
+ const raw = await readFile(join(profileDir, 'node_modules', packageName, 'package.json'), 'utf8');
175
+ const manifest = JSON.parse(raw);
176
+ return typeof manifest.version === 'string' && manifest.version.length > 0 ? manifest.version : null;
177
+ }
178
+ catch {
179
+ return null;
180
+ }
181
+ }
182
+ /** 版本行要的「技能包随插件」事实:插件包 manifest 里那条 `skill-*` 依赖(精确 pin)。 */
183
+ export async function readSkillRide(packageName, profileDir) {
184
+ try {
185
+ const raw = await readFile(join(profileDir, 'node_modules', packageName, 'package.json'), 'utf8');
186
+ const manifest = JSON.parse(raw);
187
+ const dependencies = manifest.dependencies ?? {};
188
+ const key = Object.keys(dependencies).find((name) => name.startsWith('skill-'));
189
+ if (!key)
190
+ return null;
191
+ const version = dependencies[key];
192
+ return typeof version === 'string' ? { packageName: key, version } : null;
193
+ }
194
+ catch {
195
+ return null;
196
+ }
197
+ }
198
+ /** 本包(总管)装在哪个使用范围:从自己的文件位置向上找自己的包目录,取它上面那层 `node_modules` 的父目录。
199
+ *
200
+ * 与更新包 `host.ts:102-121` 同一手法,只改一处:取**第一次**出现的 `/node_modules/<包名>`
201
+ * (更新包取的是最后一次)——pnpm 的 `.pnpm/<包>@<版本>/node_modules/<包>` 布局下,
202
+ * 最后一次会把使用范围认成虚拟目录,第一次才是真正的使用范围根。
203
+ */
204
+ export async function resolveProfileDir() {
205
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
206
+ let directory = moduleDir;
207
+ let found = null;
208
+ for (let depth = 0; depth < 12; depth += 1) {
209
+ const parent = dirname(directory);
210
+ if (parent === directory)
211
+ break;
212
+ directory = parent;
213
+ try {
214
+ const raw = await readFile(join(directory, 'package.json'), 'utf8');
215
+ const manifest = JSON.parse(raw);
216
+ if (manifest.name === 'dsh-life-pack') {
217
+ found = directory;
218
+ break;
219
+ }
220
+ }
221
+ catch {
222
+ // 这个目录没有 package.json:继续向上
223
+ }
224
+ }
225
+ if (found) {
226
+ const marker = sep + 'node_modules' + sep + 'dsh-life-pack';
227
+ const at = found.indexOf(marker);
228
+ if (at > 0) {
229
+ const dir = found.slice(0, at);
230
+ try {
231
+ const real = await realpath(dir);
232
+ return { dir: real, name: basename(real) };
233
+ }
234
+ catch {
235
+ return { dir, name: basename(dir) };
236
+ }
237
+ }
238
+ }
239
+ const fallback = join(defaultHomeDir(process.env, homedir()), 'profiles', 'web');
240
+ return { dir: fallback, name: basename(fallback) };
241
+ }
242
+ //# sourceMappingURL=update-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-env.js","sourceRoot":"","sources":["../src/update-env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /** 面板侧认的回执信封(与 DSH 载体的 `RpcCallResult` 同形,见 cookbook §6)。 */
2
+ export type ManagerReply = {
3
+ readonly ok: true;
4
+ readonly value: unknown;
5
+ } | {
6
+ readonly ok: false;
7
+ readonly error: {
8
+ readonly code: string;
9
+ readonly message: string;
10
+ readonly details: Record<string, unknown>;
11
+ };
12
+ };
13
+ export interface UpdatePhoneTable {
14
+ call(method: string, args: Record<string, unknown>): Promise<ManagerReply>;
15
+ }
16
+ /** 建电话表(懒建:首次调用时才读磁盘)。 */
17
+ export declare function buildUpdatePhoneTable(ctx: unknown): UpdatePhoneTable;
18
+ //# sourceMappingURL=update-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update-host.d.ts","sourceRoot":"","sources":["../src/update-host.ts"],"names":[],"mappings":"AAiBA,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC9C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAA;CAAE,CAAC;AAE3I,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CAC5E;AAyJD,0BAA0B;AAC1B,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAQpE"}