dsh-plugin-update 0.1.2 → 0.2.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/README.md +29 -5
- package/dist/host.js +29 -13
- package/dist/reader.js +59 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
给 DSH 插件加「检查更新 / 安装更新」能力的 npm 包。宿主侧一段接线,面板侧构建期派生取值,装不上时给用户一条可复制的手工命令。
|
|
4
4
|
|
|
5
|
-
要求 Node 22 或更高,零运行时依赖。当前版本 `0.
|
|
5
|
+
要求 Node 22 或更高,零运行时依赖。当前版本 `0.2.0`。
|
|
6
6
|
|
|
7
7
|
装上它你会拿到三样东西:
|
|
8
8
|
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
npm install dsh-plugin-update
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
把本包装成**你自己插件的依赖**即可(推荐);老式的「把本包 vendor 进插件目录」也照旧可用。
|
|
20
|
+
两种形态下,包都按**包名**在自己的 `node_modules` 链上找你的插件包——找到它,才能推出「插件装在哪、正在跑的是哪一版」。
|
|
21
|
+
|
|
19
22
|
包还没公开发布时先用本地路径代替(例如 `npm install ../dsh-plugin-update`)。
|
|
20
23
|
|
|
21
24
|
## 2. 三步接入
|
|
@@ -51,6 +54,16 @@ for (const [name, handler] of Object.entries(update.handlers)) {
|
|
|
51
54
|
`pluginId` 必填(非空字符串,不含路径分隔符)。单例复用键强制含插件标识,多插件不串内存状态与锁。
|
|
52
55
|
除 `pluginId` 之外的配置都可选并带默认值,不传即走默认(见第 3 节)。
|
|
53
56
|
|
|
57
|
+
接线的第二个参数除 `{ ctx, logCtx }` 外还能给 `pluginManager`(显式交宿主管理器实例)与 `readerOverrides`。
|
|
58
|
+
平时不用管;只有自动解析对不上目标包时(hoisted、多副本、开发态链接)才用得上它的 `targetPackageDir`:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
createHostUpdate(
|
|
62
|
+
{ ctx, logCtx, readerOverrides: { targetPackageDir: '/abs/path/to/node_modules/my-notes-plugin' } },
|
|
63
|
+
{ pluginId: 'my-notes-plugin', prefix: 'notes', targetPackageName: 'my-notes-plugin' }
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
54
67
|
### 第 3 步:面板侧接线(构建期派生)
|
|
55
68
|
|
|
56
69
|
目标只有一句:**面板里不要写死电话名与轮询间隔**。构建时用包内工具生成一个小文件:
|
|
@@ -82,13 +95,16 @@ setInterval(readStatus, UPD_POLL) // 轮询
|
|
|
82
95
|
|
|
83
96
|
宿主种类与安装出口都由本包自己探测和选择,**升级依赖即可,宿主侧与面板侧都不用改代码**:
|
|
84
97
|
|
|
85
|
-
1. 依赖版本提到 `^0.
|
|
98
|
+
1. 依赖版本提到 `^0.2.0`。
|
|
86
99
|
2. 重新装 / 发一版你自己的插件,让新依赖进当前使用范围(运行时用的是 `node_modules` 里那份)。
|
|
87
100
|
3. 如果你自己接过一版宿主安装出口,把它删掉——它会挡在本包的路由前面。
|
|
88
101
|
|
|
89
102
|
两处例外必须动代码(它们把自动探测挡住了):显式传了 `readerOverrides.environmentKind`;显式传了与真实使用范围不符的 `profileDir` / `profileName`。
|
|
90
103
|
|
|
91
|
-
`targetPackageName` 建议就是你自己的包名:使用范围目录按「装好的包住在 `<范围>/node_modules
|
|
104
|
+
`targetPackageName` 建议就是你自己的包名:使用范围目录按「装好的包住在 `<范围>/node_modules/<目标包名>`」反推。
|
|
105
|
+
|
|
106
|
+
`0.2.0` 起目标包**按包名解析**(清单直解 → 入口反查 → `node_modules` 步行 → 自锚定兜底),`exports` 没导出 `.` 与 `./package.json` 的包也能命中。
|
|
107
|
+
`0.1.x` 只能在本包被 vendor 进插件目录时找到目标包;以依赖形态安装时一键升级会永远不可用(面板显示假的 `installation-changed`)。
|
|
92
108
|
|
|
93
109
|
## 3. 配置
|
|
94
110
|
|
|
@@ -160,7 +176,7 @@ const update = createHostUpdate(
|
|
|
160
176
|
|
|
161
177
|
| 原因 | 中文含义 | 用户该做什么 |
|
|
162
178
|
|---|---|---|
|
|
163
|
-
| `unknown-profile` |
|
|
179
|
+
| `unknown-profile` | 使用范围或目标包认不出(名字非法、目录不存在、按包名找不到你的插件包) | 检查使用范围名与目录是否还在;插件以依赖形态接入却在别的 `node_modules` 树里(hoisted、多副本、开发态链接)时,让集成方传 `readerOverrides.targetPackageDir`;这种情形不给手工命令 |
|
|
164
180
|
| `source-install` | 当前是从源码装的,不是按版本号装的 | 这种情形不给手工命令;想走更新先按版本号重装一次 |
|
|
165
181
|
| `invalid-installation` | 已装的包不完整(名字对不上、版本非法、入口文件缺失) | 重装当前版本,修好已装目录再查更新 |
|
|
166
182
|
| `installation-changed` | 安装位置在使用中途变了(换了目录或换了包) | 重新打开宿主再查一次;还出现就重装 |
|
|
@@ -213,6 +229,8 @@ dsh plugin --profile my-web add --save-exact my-notes-plugin@1.2.3 --registry=ht
|
|
|
213
229
|
9. 第三方 Desktop 自动装失败但命令能装:检查桌面当前激活的使用范围是不是插件所在的那一个;对不上时自动装一定诚实失败,复制第 5.3 节的命令手工执行。
|
|
214
230
|
10. 官方桌面版(使用范围名 `desktop`)装不上:看任务里的 `message`,形如 `install-failed: <宿主原话>`,里面带着宿主的错误码(例如 `operation-error`)。`message` 可能带详情,**匹配请匹配前缀错误码,不要整串相等**;日志里这条路的路由是 `desktop-manager`。
|
|
215
231
|
11. 还定位不到:打开调试日志,按插件标识过滤 `host.call`、`host.call.fail`、`update.install.exec` 三个事件,看 `pluginId` 与 `route`、`exitCode` 字段;日志里不记命令与路径原文。
|
|
232
|
+
12. 以依赖形态接入后一直显示「安装位置在使用中途变了」(`installation-changed`):`0.1.x` 的已知缺陷(拿「本包自己住在哪」推断目标包位置,依赖形态下恒失败);升到 `0.2.0` 即修复。
|
|
233
|
+
13. 报 `unknown-profile` 但使用范围名与目录都没问题:本包按包名找不到你的插件包。让集成方传 `readerOverrides.targetPackageDir`,或把本包装成插件包的依赖(别装到别的 `node_modules` 树里)。
|
|
216
234
|
|
|
217
235
|
## 7. 接入自检清单
|
|
218
236
|
|
|
@@ -248,8 +266,14 @@ checkEventCounts(manifest) // 计数与
|
|
|
248
266
|
`kind` 分三类只为计数检查服务:`resident` 常驻、`ondemand` 按需、`selfmon` 自监控。空模板见包内的 `event-list.template.json`。
|
|
249
267
|
清单要以**对象**传入:字符串或数组会被拦下并给出中文说明,不静默修补。
|
|
250
268
|
|
|
269
|
+
**宿主侧工具**:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
resolveTargetPackage(name, { targetPackageDir }) // 按包名解析你的插件包;找不到返回 null(自己搭读取器或写测试时用)
|
|
273
|
+
```
|
|
274
|
+
|
|
251
275
|
## 9. 兼容与稳定性
|
|
252
276
|
|
|
253
277
|
这些形状稳定,可以放心依赖:三个电话名与入参回参、快照六字段、任务公开形状、配置只经函数入参注入、安装配方五键、日志事件字段基线(三个事件各带必填 `pluginId`)、历史落盘路径。
|
|
254
278
|
|
|
255
|
-
|
|
279
|
+
向后兼容的扩展:新增可选配置键、新增可选 `readerOverrides`(如 `targetPackageDir`)、新增宿主种类与路由取值。调用方不认新取值时按普通宿主处理即可,不会因此报错。
|
package/dist/host.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/host.ts
|
|
2
2
|
import { realpath } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import {
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { sep } from "node:path";
|
|
6
5
|
import {
|
|
7
6
|
DEFAULT_CONFIRMATION_TTL_MS,
|
|
8
7
|
DEFAULT_CHECK_TIMEOUT_MS,
|
|
@@ -13,10 +12,10 @@ import {
|
|
|
13
12
|
import { manualCommand } from "./commands.js";
|
|
14
13
|
import { validVersion } from "./service.js";
|
|
15
14
|
import { createUpdateDiskPorts, createUpdateExecutor } from "./store.js";
|
|
16
|
-
import {
|
|
15
|
+
import { createUpdateReader, defaultHomeDir, resolveProfileName, resolveTargetPackage } from "./reader.js";
|
|
17
16
|
import { buildPhoneName, buildPhoneNames as buildPhoneNames2, resolveUpdateConfig as resolveUpdateConfig2 } from "./config.js";
|
|
18
17
|
import { parseEventListManifest, checkEventFields, checkEventCounts } from "./gate.js";
|
|
19
|
-
import { containingPackage
|
|
18
|
+
import { containingPackage, defaultHomeDir as defaultHomeDir2, profileNameValid, registrySpec, resolveTargetPackage as resolveTargetPackage2 } from "./reader.js";
|
|
20
19
|
import { createUpdateDiskPorts as createUpdateDiskPorts2, createUpdateExecutor as createUpdateExecutor2, pathsForUpdate, resolveCliEntry } from "./store.js";
|
|
21
20
|
let sharedReader = null;
|
|
22
21
|
let sharedReaderKey = "";
|
|
@@ -71,10 +70,11 @@ function hash8(s) {
|
|
|
71
70
|
return "00000000";
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
|
-
async function
|
|
73
|
+
async function tryInferProfileDir(targetDir, targetPackageName) {
|
|
75
74
|
try {
|
|
76
|
-
const dir =
|
|
77
|
-
|
|
75
|
+
const dir = String(targetDir || "");
|
|
76
|
+
if (!dir) return null;
|
|
77
|
+
const marker = `${sep}node_modules${sep}${String(targetPackageName || "").split("/").join(sep)}`;
|
|
78
78
|
const at = dir.lastIndexOf(marker);
|
|
79
79
|
if (at > 0) {
|
|
80
80
|
const candidate = dir.slice(0, at);
|
|
@@ -86,24 +86,38 @@ async function inferProfileDir(loaded, homeDirDefault, targetPackageName) {
|
|
|
86
86
|
}
|
|
87
87
|
} catch {
|
|
88
88
|
}
|
|
89
|
-
return
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function unknownProfile() {
|
|
92
|
+
return Object.assign(new Error("unknown-profile"), { code: "unknown-profile" });
|
|
90
93
|
}
|
|
91
94
|
async function getSharedReader(pluginId, config, overrides = {}) {
|
|
92
95
|
const env = overrides.env ?? process.env;
|
|
93
96
|
const osHome = overrides.osHome ?? homedir();
|
|
94
97
|
const homeDirDefault = defaultHomeDir(env, osHome);
|
|
95
98
|
const targetPackageName = overrides.targetPackageName ?? config.targetPackageName;
|
|
96
|
-
const
|
|
99
|
+
const explicitTargetDir = typeof overrides.targetPackageDir === "string" && overrides.targetPackageDir ? String(overrides.targetPackageDir) : "";
|
|
100
|
+
const loaded = await resolveTargetPackage(targetPackageName, explicitTargetDir ? { targetPackageDir: explicitTargetDir } : {}).catch(
|
|
101
|
+
() => null
|
|
102
|
+
);
|
|
97
103
|
const runningVersion = overrides.runningVersion ?? (loaded && validVersion(loaded.manifest.version) ? String(loaded.manifest.version) : null);
|
|
98
|
-
if (!runningVersion) throw
|
|
99
|
-
|
|
104
|
+
if (!runningVersion) throw unknownProfile();
|
|
105
|
+
let profileDirInput;
|
|
106
|
+
if (overrides.profileDir) {
|
|
107
|
+
profileDirInput = String(overrides.profileDir);
|
|
108
|
+
} else {
|
|
109
|
+
const anchorDir = (loaded && loaded.directory ? String(loaded.directory) : "") || explicitTargetDir;
|
|
110
|
+
const inferred = anchorDir ? await tryInferProfileDir(anchorDir, targetPackageName) : null;
|
|
111
|
+
if (!inferred) throw unknownProfile();
|
|
112
|
+
profileDirInput = inferred;
|
|
113
|
+
}
|
|
100
114
|
const homeDirInput = overrides.homeDir ?? config.homeDir ?? homeDirDefault;
|
|
101
115
|
const environmentKind = overrides.environmentKind ?? detectEnvironmentKind(overrides.ctx, resolveProfileName(overrides.profileName, profileDirInput));
|
|
102
116
|
const checkTimeoutMs = overrides.checkTimeoutMs ?? config.checkTimeoutMs;
|
|
103
117
|
const confirmationTtlMs = overrides.confirmationTtlMs ?? config.confirmationTtlMs;
|
|
104
118
|
const installTimeoutMs = overrides.installTimeoutMs ?? config.installTimeoutMs;
|
|
105
119
|
const registryUrl = overrides.registryUrl ?? config.registryUrl;
|
|
106
|
-
const key = `${pluginId}\0${config.prefix}\0${runningVersion}\0${profileDirInput}\0${overrides.profileName ?? ""}\0${homeDirInput}\0${overrides.runInstall ? "exec" : ""}\0${environmentKind}\0${targetPackageName}`;
|
|
120
|
+
const key = `${pluginId}\0${config.prefix}\0${runningVersion}\0${profileDirInput}\0${overrides.profileName ?? ""}\0${homeDirInput}\0${overrides.runInstall ? "exec" : ""}\0${environmentKind}\0${targetPackageName}\0${explicitTargetDir}`;
|
|
107
121
|
if (sharedReader && sharedReaderKey === key) return sharedReader;
|
|
108
122
|
const disk = overrides.readJob && overrides.writeJob ? null : createUpdateDiskPorts(homeDirInput, pluginId, profileDirInput);
|
|
109
123
|
const defaultRun = createUpdateExecutor({
|
|
@@ -129,6 +143,7 @@ async function getSharedReader(pluginId, config, overrides = {}) {
|
|
|
129
143
|
pluginId,
|
|
130
144
|
profileName: overrides.profileName ?? void 0,
|
|
131
145
|
homeDir: overrides.homeDir,
|
|
146
|
+
targetPackageDir: explicitTargetDir || void 0,
|
|
132
147
|
env,
|
|
133
148
|
osHome,
|
|
134
149
|
fetchImpl: overrides.fetchImpl,
|
|
@@ -278,7 +293,7 @@ export {
|
|
|
278
293
|
buildPhoneNames2 as buildPhoneNames,
|
|
279
294
|
checkEventCounts,
|
|
280
295
|
checkEventFields,
|
|
281
|
-
|
|
296
|
+
containingPackage,
|
|
282
297
|
createHostUpdate,
|
|
283
298
|
createUpdateDiskPorts2 as createUpdateDiskPorts,
|
|
284
299
|
createUpdateExecutor2 as createUpdateExecutor,
|
|
@@ -289,5 +304,6 @@ export {
|
|
|
289
304
|
profileNameValid,
|
|
290
305
|
registrySpec,
|
|
291
306
|
resolveCliEntry,
|
|
307
|
+
resolveTargetPackage2 as resolveTargetPackage,
|
|
292
308
|
resolveUpdateConfig2 as resolveUpdateConfig
|
|
293
309
|
};
|
package/dist/reader.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/reader.ts
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -37,6 +38,56 @@ async function containingPackage(filename, name) {
|
|
|
37
38
|
directory = parent;
|
|
38
39
|
}
|
|
39
40
|
}
|
|
41
|
+
async function packageAtIfNamed(directory, name) {
|
|
42
|
+
try {
|
|
43
|
+
const pkg = await packageAt(directory);
|
|
44
|
+
if (pkg.manifest && pkg.manifest.name === name) return pkg;
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
async function resolveTargetPackage(targetName, opts = {}) {
|
|
50
|
+
const name = String(targetName || "");
|
|
51
|
+
if (!name) return null;
|
|
52
|
+
if (opts.targetPackageDir) return packageAtIfNamed(resolve(String(opts.targetPackageDir)), name);
|
|
53
|
+
const base = import.meta.url;
|
|
54
|
+
let req = null;
|
|
55
|
+
try {
|
|
56
|
+
req = createRequire(base);
|
|
57
|
+
} catch {
|
|
58
|
+
req = null;
|
|
59
|
+
}
|
|
60
|
+
if (req) {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = await packageAtIfNamed(dirname(req.resolve(`${name}/package.json`)), name);
|
|
63
|
+
if (pkg) return pkg;
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const entry = req.resolve(name);
|
|
68
|
+
const pkg = await containingPackage(entry, name);
|
|
69
|
+
if (pkg) return pkg;
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
let dir = dirname(fileURLToPath(base));
|
|
75
|
+
while (true) {
|
|
76
|
+
const pkg = await packageAtIfNamed(join(dir, "node_modules", name), name);
|
|
77
|
+
if (pkg) return pkg;
|
|
78
|
+
const parent = dirname(dir);
|
|
79
|
+
if (parent === dir) break;
|
|
80
|
+
dir = parent;
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const pkg = await containingPackage(fileURLToPath(base), name);
|
|
86
|
+
if (pkg) return pkg;
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
40
91
|
function resolveProfileName(profileName, profileDir) {
|
|
41
92
|
return profileName ?? basename(profileDir);
|
|
42
93
|
}
|
|
@@ -102,7 +153,10 @@ function createUpdateReader(options) {
|
|
|
102
153
|
const environmentKind = options.environmentKind ?? "cli";
|
|
103
154
|
const targetPackageName = options.targetPackageName ?? "dsh-mattpocock-skills-deck";
|
|
104
155
|
const registryUrl = options.registryUrl ?? "https://registry.npmjs.org/";
|
|
105
|
-
const loadedPackage =
|
|
156
|
+
const loadedPackage = resolveTargetPackage(
|
|
157
|
+
targetPackageName,
|
|
158
|
+
options.targetPackageDir ? { targetPackageDir: options.targetPackageDir } : {}
|
|
159
|
+
).catch(() => null);
|
|
106
160
|
let boundIdentity;
|
|
107
161
|
async function readInstalledReal() {
|
|
108
162
|
const result = {
|
|
@@ -148,6 +202,7 @@ function createUpdateReader(options) {
|
|
|
148
202
|
result.installedVersion = typeof installed.manifest.version === "string" ? installed.manifest.version : null;
|
|
149
203
|
result.packageValid = await validPackage(installed, targetPackageName);
|
|
150
204
|
const loaded = await loadedPackage;
|
|
205
|
+
const loadedUnresolvable = loaded === null;
|
|
151
206
|
const identity = `${homeDir}\0${profileDir}\0${profileName}\0${pluginId}`;
|
|
152
207
|
const sameLoadedPackage = loaded?.directory === installed.directory && loaded?.manifest.version === installed.manifest.version;
|
|
153
208
|
if (boundIdentity === void 0 && sameLoadedPackage && result.packageValid) boundIdentity = identity;
|
|
@@ -158,6 +213,7 @@ function createUpdateReader(options) {
|
|
|
158
213
|
result.installationKey = null;
|
|
159
214
|
}
|
|
160
215
|
if (boundIdentity !== void 0 && boundIdentity !== identity) result.blockedReason = "installation-changed";
|
|
216
|
+
else if (loadedUnresolvable) result.blockedReason = "unknown-profile";
|
|
161
217
|
else if (!sameLoadedPackage && boundIdentity === void 0) result.blockedReason = "installation-changed";
|
|
162
218
|
else if (!result.packageValid) result.blockedReason = "invalid-installation";
|
|
163
219
|
else if (result.sourceInstall) result.blockedReason = "source-install";
|
|
@@ -193,5 +249,6 @@ export {
|
|
|
193
249
|
defaultHomeDir,
|
|
194
250
|
profileNameValid,
|
|
195
251
|
registrySpec,
|
|
196
|
-
resolveProfileName
|
|
252
|
+
resolveProfileName,
|
|
253
|
+
resolveTargetPackage
|
|
197
254
|
};
|