@yunzai-ng/core 0.1.1 → 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/dist/http/client.d.ts.map +1 -1
- package/dist/http/client.js +39 -3
- package/dist/http/client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/platform/paths.d.ts +10 -0
- package/dist/platform/paths.d.ts.map +1 -1
- package/dist/platform/paths.js +50 -7
- package/dist/platform/paths.js.map +1 -1
- package/dist/plugin/context.d.ts +3 -0
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/context.js +18 -2
- package/dist/plugin/context.js.map +1 -1
- package/dist/plugin/market.d.ts +119 -8
- package/dist/plugin/market.d.ts.map +1 -1
- package/dist/plugin/market.js +183 -22
- package/dist/plugin/market.js.map +1 -1
- package/dist/plugin/pm.d.ts +82 -0
- package/dist/plugin/pm.d.ts.map +1 -0
- package/dist/plugin/pm.js +129 -0
- package/dist/plugin/pm.js.map +1 -0
- package/dist/server/api.d.ts.map +1 -1
- package/dist/server/api.js +54 -5
- package/dist/server/api.js.map +1 -1
- package/dist/server/browse.d.ts.map +1 -1
- package/dist/server/browse.js +10 -2
- package/dist/server/browse.js.map +1 -1
- package/package.json +2 -2
- package/src/http/client.test.ts +75 -1
- package/src/http/client.ts +40 -3
- package/src/index.ts +1 -0
- package/src/platform/paths.test.ts +166 -0
- package/src/platform/paths.ts +52 -7
- package/src/plugin/context.test.ts +29 -1
- package/src/plugin/context.ts +18 -2
- package/src/plugin/market.test.ts +375 -2
- package/src/plugin/market.ts +278 -25
- package/src/plugin/pm.test.ts +73 -0
- package/src/plugin/pm.ts +163 -0
- package/src/server/api.test.ts +72 -2
- package/src/server/api.ts +65 -5
- package/src/server/browse.test.ts +18 -10
- package/src/server/browse.ts +11 -2
package/dist/plugin/market.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HttpClient, Logger } from "@yunzai-ng/types";
|
|
2
|
+
import { type PmRunner } from "./pm.js";
|
|
2
3
|
/** 安装来源 */
|
|
3
4
|
export interface MarketInstallSpec {
|
|
4
5
|
/** 取源方式 */
|
|
@@ -8,6 +9,33 @@ export interface MarketInstallSpec {
|
|
|
8
9
|
/** git 分支,缺省由远端决定 */
|
|
9
10
|
readonly branch?: string;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* 装后步骤:装完依赖之后还要跑哪些 npm script
|
|
14
|
+
*
|
|
15
|
+
* 由索引声明而非读插件的 package.json:`scripts` 里有的是开发用的(`test`、`lint`),
|
|
16
|
+
* 有的是幂等的(`build`),有的会下载上百兆(`install:browser`),从名字分不出该跑哪些。
|
|
17
|
+
* 索引是**经审核的**那一份事实,故把「该跑什么」放在这里。
|
|
18
|
+
*
|
|
19
|
+
* 名字要拼进命令行,逐个过 `pm.ts` 的白名单;不合法的整条 `setup` 丢弃 —— 只丢那一个名字
|
|
20
|
+
* 会得到一份「跑了一半」的装后步骤,而使用者无从知道少跑了哪个。
|
|
21
|
+
*/
|
|
22
|
+
export interface MarketSetupSpec {
|
|
23
|
+
/**
|
|
24
|
+
* 依次要跑的 script 名
|
|
25
|
+
*
|
|
26
|
+
* 顺序即依赖关系(先 `build` 再 `install:browser`),故按序执行、**一个失败即停** ——
|
|
27
|
+
* 后一个多半建立在前一个的产物上,继续跑只会得到第二条更难懂的错误。
|
|
28
|
+
*/
|
|
29
|
+
readonly scripts: readonly string[];
|
|
30
|
+
/**
|
|
31
|
+
* 装依赖时是否连 devDependencies 一起装
|
|
32
|
+
*
|
|
33
|
+
* 从源码装、要靠 `build` 出产物的插件必须为真:编译器在 devDependencies 里,
|
|
34
|
+
* `--prod` 装出来的目录跑 `build` 会报「找不到 tsc」,离真实原因很远。
|
|
35
|
+
* 缺省为真 —— 声明了 `setup` 就意味着有装后步骤要跑,而那多半需要开发依赖。
|
|
36
|
+
*/
|
|
37
|
+
readonly dev: boolean;
|
|
38
|
+
}
|
|
11
39
|
/** 索引中的一个插件条目 */
|
|
12
40
|
export interface MarketEntry {
|
|
13
41
|
/** 插件名,同时是安装目录名与配置文件名 */
|
|
@@ -30,6 +58,13 @@ export interface MarketEntry {
|
|
|
30
58
|
readonly minCore?: string;
|
|
31
59
|
/** 安装来源 */
|
|
32
60
|
readonly install: MarketInstallSpec;
|
|
61
|
+
/**
|
|
62
|
+
* 装后步骤,缺省即「装完依赖就算完」
|
|
63
|
+
*
|
|
64
|
+
* 声明在索引里而非插件的 package.json 里:装后步骤要在**取到内容之前**就能让面板
|
|
65
|
+
* 说清「这次会跑什么」,而 package.json 要等下载完才读得到。
|
|
66
|
+
*/
|
|
67
|
+
readonly setup?: MarketSetupSpec;
|
|
33
68
|
/** 该条目来自哪个索引地址 */
|
|
34
69
|
readonly source: string;
|
|
35
70
|
}
|
|
@@ -68,8 +103,53 @@ export interface MarketSnapshot {
|
|
|
68
103
|
* `node_modules` 与目录内的其他文件,`git` 是一份全新的目录。
|
|
69
104
|
*/
|
|
70
105
|
export type InstallVia = MarketInstallSpec["type"] | "pull";
|
|
106
|
+
/**
|
|
107
|
+
* 一次「装依赖 + 跑装后步骤」的结果
|
|
108
|
+
*
|
|
109
|
+
* 与安装结果分开成型,因为这一步可以单独发起(面板的「装依赖并编译」):那时没有
|
|
110
|
+
* 取源方式,也没有「此后如何更新」可言。安装结果把它整个并进去。
|
|
111
|
+
*/
|
|
112
|
+
export interface SetupOutcome {
|
|
113
|
+
/** 是否声明了运行时依赖且尚未安装 */
|
|
114
|
+
readonly needsDependencies: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* 本次是否确实跑了包管理器装依赖
|
|
117
|
+
*
|
|
118
|
+
* 与 `needsDependencies` 分开:后者说的是「还缺不缺」,这一项说的是「刚才做了什么」。
|
|
119
|
+
* 两者都为假的常见情形是目录里本就有 `node_modules` —— 那时既没装、也不缺。
|
|
120
|
+
*/
|
|
121
|
+
readonly installedDeps?: boolean;
|
|
122
|
+
/** 用的是哪个包管理器,仅在确实装过或跑过 script 时存在 */
|
|
123
|
+
readonly packageManager?: string;
|
|
124
|
+
/**
|
|
125
|
+
* 装依赖失败的原因
|
|
126
|
+
*
|
|
127
|
+
* **装依赖失败不让整次安装失败**:插件目录已经就位,缺的只是依赖,向上抛会让使用者
|
|
128
|
+
* 以为「什么都没装成」而去重装,而重装同样会在这一步失败。故记在这里由面板说明。
|
|
129
|
+
*/
|
|
130
|
+
readonly dependencyError?: string;
|
|
131
|
+
/** 实际跑完的装后 script,按执行顺序 */
|
|
132
|
+
readonly ranScripts?: readonly string[];
|
|
133
|
+
/**
|
|
134
|
+
* 装后步骤失败的原因,附带失败在哪个 script 上
|
|
135
|
+
*
|
|
136
|
+
* 与 `dependencyError` 分开两个字段而非合成一个:两者的后手完全不同 —— 缺依赖是
|
|
137
|
+
* 去目录里执行包管理器,缺产物是去执行那个 script,而合成一个之后面板只能给出
|
|
138
|
+
* 一句两头都不准的提示。
|
|
139
|
+
*/
|
|
140
|
+
readonly setupError?: string;
|
|
141
|
+
}
|
|
142
|
+
/** 单独发起一次装依赖与装后步骤的结果 */
|
|
143
|
+
export interface PluginSetupResult extends SetupOutcome {
|
|
144
|
+
/** 插件名 */
|
|
145
|
+
readonly name: string;
|
|
146
|
+
/** 插件目录 */
|
|
147
|
+
readonly dir: string;
|
|
148
|
+
/** package.json 里声明的版本,读不到时 `0.0.0` */
|
|
149
|
+
readonly version: string;
|
|
150
|
+
}
|
|
71
151
|
/** 一次安装的结果 */
|
|
72
|
-
export interface InstallResult {
|
|
152
|
+
export interface InstallResult extends SetupOutcome {
|
|
73
153
|
/** 插件名 */
|
|
74
154
|
readonly name: string;
|
|
75
155
|
/** 安装目录 */
|
|
@@ -78,8 +158,6 @@ export interface InstallResult {
|
|
|
78
158
|
readonly via: InstallVia;
|
|
79
159
|
/** package.json 中声明的版本;无 package.json 时为索引声明的版本 */
|
|
80
160
|
readonly version: string;
|
|
81
|
-
/** 是否声明了运行时依赖且尚未安装 */
|
|
82
|
-
readonly needsDependencies: boolean;
|
|
83
161
|
/**
|
|
84
162
|
* 就地拉取时的旧版本号,仅 `via` 为 `pull` 时存在
|
|
85
163
|
*
|
|
@@ -149,6 +227,8 @@ export interface MarketDeps {
|
|
|
149
227
|
readonly coreVersion: string;
|
|
150
228
|
/** 执行 git 命令,缺省调用本机的 git */
|
|
151
229
|
readonly git?: GitRunner;
|
|
230
|
+
/** 执行包管理器,缺省调用本机的 pnpm / npm,见 `pm.ts` */
|
|
231
|
+
readonly pm?: PmRunner;
|
|
152
232
|
}
|
|
153
233
|
/**
|
|
154
234
|
* 校验插件名
|
|
@@ -197,10 +277,11 @@ export declare function tarballFromGit(url: string, branch?: string): string | u
|
|
|
197
277
|
* 数组挂到静态文件服务上作为私有源。
|
|
198
278
|
* @param raw 已解析的 JSON
|
|
199
279
|
* @param source 索引地址
|
|
280
|
+
* @param logger 日志器,供条目里的 `setup` 不合法时说明;不给则静默丢弃
|
|
200
281
|
* @returns 合法条目列表
|
|
201
282
|
* @throws 文档既不是数组也不含 `plugins` 数组时
|
|
202
283
|
*/
|
|
203
|
-
export declare function parseIndex(raw: unknown, source: string): MarketEntry[];
|
|
284
|
+
export declare function parseIndex(raw: unknown, source: string, logger?: Logger): MarketEntry[];
|
|
204
285
|
/**
|
|
205
286
|
* 插件市场
|
|
206
287
|
*
|
|
@@ -232,18 +313,44 @@ export declare class PluginMarket {
|
|
|
232
313
|
/**
|
|
233
314
|
* 安装一个插件
|
|
234
315
|
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
316
|
+
* 取源与校验在临时目录内完成,仅在校验通过后才移入插件目录,因此**取源**失败时插件
|
|
317
|
+
* 目录保持原状。装依赖与装后步骤在移入之后、于插件目录内进行 —— 不在临时目录里做完
|
|
318
|
+
* 再整体搬过去:pnpm 的 `node_modules` 里全是指向 store 的符号链接,换个路径就断,
|
|
319
|
+
* 而断掉的表现是加载时报找不到模块,离「我搬过了目录」很远。
|
|
320
|
+
*
|
|
321
|
+
* 代价是这一步失败会留下一个「装好了但缺依赖」的目录。那是可说清的状态(见返回值的
|
|
322
|
+
* `needsDependencies` 与 `setupError`),比一个链接全断的目录好收拾。
|
|
323
|
+
*
|
|
324
|
+
* 安装完成后不加载插件:加载时机由调用方决定。
|
|
238
325
|
* @param name 插件名
|
|
239
326
|
* @param opts 可选参数
|
|
240
327
|
* @param opts.replace 目标已存在时先删除再安装
|
|
328
|
+
* @param opts.dependencies 装完之后跑包管理器装依赖,并按索引声明跑装后步骤
|
|
241
329
|
* @returns 安装结果
|
|
242
330
|
* @throws 名称不合法、索引中无此插件、内核版本不满足、目标已存在或取源失败时
|
|
243
331
|
*/
|
|
244
332
|
install(name: string, opts?: {
|
|
245
333
|
replace?: boolean;
|
|
334
|
+
dependencies?: boolean;
|
|
246
335
|
}): Promise<InstallResult>;
|
|
336
|
+
/**
|
|
337
|
+
* 对一个已装好的插件重跑装依赖与装后步骤,不重新取源
|
|
338
|
+
*
|
|
339
|
+
* 三种情形要用到:**手工放进插件目录的插件**(压根没有安装动作可挂)、装的时候这一步
|
|
340
|
+
* 失败过、以及使用者自己 `git pull` 过而 `dist/` 已旧。
|
|
341
|
+
*
|
|
342
|
+
* **索引里没有这个插件也照做。** 手工放进去的插件多半不在任何索引里,此时只装依赖、
|
|
343
|
+
* 不跑装后步骤(无从知道该跑什么)—— 那仍然解决了「装了却缺依赖」这个主要情形。
|
|
344
|
+
* 拿不到条目就拒绝会让这个按钮恰在最需要它的场合失效。
|
|
345
|
+
*
|
|
346
|
+
* **即便不缺依赖也跑一次。** 使用者点这个按钮,多半正是因为 `package.json` 的依赖
|
|
347
|
+
* 变过而 `node_modules` 是旧的 —— 那种「旧」从目录存不存在上看不出来,而包管理器
|
|
348
|
+
* 自己比对 lock 文件本就是幂等的。
|
|
349
|
+
* @param name 插件名
|
|
350
|
+
* @returns 本次的结果
|
|
351
|
+
* @throws 名称不合法或插件目录不存在时
|
|
352
|
+
*/
|
|
353
|
+
setup(name: string): Promise<PluginSetupResult>;
|
|
247
354
|
/**
|
|
248
355
|
* 卸载一个插件
|
|
249
356
|
*
|
|
@@ -256,9 +363,13 @@ export declare class PluginMarket {
|
|
|
256
363
|
/**
|
|
257
364
|
* 更新一个插件:目录已是 git 仓库时就地拉取,否则退回重新安装
|
|
258
365
|
* @param name 插件名
|
|
366
|
+
* @param opts 可选参数
|
|
367
|
+
* @param opts.dependencies 更新完之后跑包管理器装依赖,并按索引声明跑装后步骤
|
|
259
368
|
* @returns 安装结果
|
|
260
369
|
* @throws 与 `install` 相同;就地拉取失败时抛出 git 的错误
|
|
261
370
|
*/
|
|
262
|
-
update(name: string
|
|
371
|
+
update(name: string, opts?: {
|
|
372
|
+
dependencies?: boolean;
|
|
373
|
+
}): Promise<InstallResult>;
|
|
263
374
|
}
|
|
264
375
|
//# sourceMappingURL=market.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"market.d.ts","sourceRoot":"","sources":["../../src/plugin/market.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"market.d.ts","sourceRoot":"","sources":["../../src/plugin/market.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin/market.js
CHANGED
|
@@ -9,15 +9,19 @@
|
|
|
9
9
|
* `joinWithin` 求值;两者任一不通过即拒绝,不做纠正后重试。
|
|
10
10
|
* 2) **先下载到临时目录,校验通过后再移入插件目录。** 中途失败时插件目录
|
|
11
11
|
* 保持原状,不会留下半个插件 —— 半个插件会在下次启动时被扫描到并加载失败。
|
|
12
|
-
* 3)
|
|
13
|
-
*
|
|
12
|
+
* 3) **跑包管理器要由调用方逐次明说(`opts.dependencies`),本模块不擅自决定。**
|
|
13
|
+
* 但那不是一道新的信任边界:插件的入口下一秒就会被 `import()` 进本进程,
|
|
14
|
+
* 与 install 脚本同属一道门。故缺省不跑的理由只是「别在使用者没预期时占用
|
|
15
|
+
* 十分钟网络」,而非安全 —— 面板据此在确认框里明说,然后一并请求。
|
|
14
16
|
* 4) **索引为不可信输入。** 逐字段校验类型,缺字段的条目整条丢弃而非补默认值:
|
|
15
17
|
* 索引写错时应当表现为"该插件不出现在列表里",而不是出现一个装不上的条目。
|
|
18
|
+
* `setup.scripts` 里的名字要拼进命令行,另有一道白名单,见 `pm.ts`。
|
|
16
19
|
*/
|
|
17
20
|
import { execFile } from "node:child_process";
|
|
18
21
|
import { cp, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
19
22
|
import { dirname, join } from "node:path";
|
|
20
23
|
import { isDirectory, isFile } from "../util/fs.js";
|
|
24
|
+
import { INSTALL_TIMEOUT_MS, SCRIPT_TIMEOUT_MS, isScriptName, runPm } from "./pm.js";
|
|
21
25
|
import { extractTarGz, joinWithin, singleRoot } from "./tar.js";
|
|
22
26
|
/** 合法插件名:字母或数字开头,其余可含字母、数字、点、下划线与连字符 */
|
|
23
27
|
const NAME_RE = /^[a-z\d][a-z\d._-]*$/i;
|
|
@@ -144,15 +148,49 @@ function parseInstall(raw) {
|
|
|
144
148
|
const branch = text(record, "branch");
|
|
145
149
|
return { type, url, ...(branch === undefined ? {} : { branch }) };
|
|
146
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* 解析装后步骤
|
|
153
|
+
*
|
|
154
|
+
* **一个名字不合法就整条丢弃,不做过滤后继续。** 过滤会得到一份「跑了一半」的装后
|
|
155
|
+
* 步骤:使用者看到装成功了,而实际少跑了 `build`,插件在加载时才报一条与原因无关的错。
|
|
156
|
+
* 整条丢弃则退回既有行为(只装依赖、并提示需自行处理),那是可理解的。
|
|
157
|
+
*
|
|
158
|
+
* `scripts` 为空数组也返回 undefined:声明了一个不跑任何东西的装后步骤,与没声明
|
|
159
|
+
* 是同一个意思,而留着它会让面板的确认框多出一句「将执行以下步骤:」后面跟着空白。
|
|
160
|
+
* @param raw 条目中的 `setup` 字段
|
|
161
|
+
* @param name 插件名,仅用于告警
|
|
162
|
+
* @param logger 日志器,用于说明为何丢弃
|
|
163
|
+
* @returns 装后步骤;字段缺失或不合法时 undefined
|
|
164
|
+
*/
|
|
165
|
+
function parseSetup(raw, name, logger) {
|
|
166
|
+
if (typeof raw !== "object" || raw === null)
|
|
167
|
+
return undefined;
|
|
168
|
+
const record = raw;
|
|
169
|
+
if (!Array.isArray(record.scripts))
|
|
170
|
+
return undefined;
|
|
171
|
+
const scripts = [];
|
|
172
|
+
for (const item of record.scripts) {
|
|
173
|
+
if (typeof item !== "string" || !isScriptName(item)) {
|
|
174
|
+
logger?.warn(`插件 ${name} 的索引里 setup.scripts 含不合法的 script 名,已忽略整个装后步骤`);
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
scripts.push(item);
|
|
178
|
+
}
|
|
179
|
+
if (scripts.length === 0)
|
|
180
|
+
return undefined;
|
|
181
|
+
// 缺省连 devDependencies 一起装:声明了装后步骤就意味着有东西要跑,而那多半要编译器
|
|
182
|
+
return { scripts, dev: record.dev !== false };
|
|
183
|
+
}
|
|
147
184
|
/**
|
|
148
185
|
* 把一条索引记录解析为条目
|
|
149
186
|
*
|
|
150
187
|
* 任一必填字段不合法即返回 undefined,由调用方整条丢弃。
|
|
151
188
|
* @param raw 索引记录
|
|
152
189
|
* @param source 该记录所属的索引地址
|
|
190
|
+
* @param logger 日志器,供 `setup` 不合法时说明
|
|
153
191
|
* @returns 条目;记录不合法时 undefined
|
|
154
192
|
*/
|
|
155
|
-
function parseEntry(raw, source) {
|
|
193
|
+
function parseEntry(raw, source, logger) {
|
|
156
194
|
if (typeof raw !== "object" || raw === null)
|
|
157
195
|
return undefined;
|
|
158
196
|
const record = raw;
|
|
@@ -167,6 +205,7 @@ function parseEntry(raw, source) {
|
|
|
167
205
|
const version = text(record, "version");
|
|
168
206
|
const homepage = text(record, "homepage");
|
|
169
207
|
const minCore = text(record, "minCore");
|
|
208
|
+
const setup = parseSetup(record.setup, name, logger);
|
|
170
209
|
return {
|
|
171
210
|
name,
|
|
172
211
|
title: text(record, "title") ?? name,
|
|
@@ -175,6 +214,7 @@ function parseEntry(raw, source) {
|
|
|
175
214
|
official: record.official === true,
|
|
176
215
|
install,
|
|
177
216
|
source,
|
|
217
|
+
...(setup === undefined ? {} : { setup }),
|
|
178
218
|
...(author === undefined ? {} : { author }),
|
|
179
219
|
...(version === undefined ? {} : { version }),
|
|
180
220
|
...(homepage === undefined ? {} : { homepage }),
|
|
@@ -188,10 +228,11 @@ function parseEntry(raw, source) {
|
|
|
188
228
|
* 数组挂到静态文件服务上作为私有源。
|
|
189
229
|
* @param raw 已解析的 JSON
|
|
190
230
|
* @param source 索引地址
|
|
231
|
+
* @param logger 日志器,供条目里的 `setup` 不合法时说明;不给则静默丢弃
|
|
191
232
|
* @returns 合法条目列表
|
|
192
233
|
* @throws 文档既不是数组也不含 `plugins` 数组时
|
|
193
234
|
*/
|
|
194
|
-
export function parseIndex(raw, source) {
|
|
235
|
+
export function parseIndex(raw, source, logger) {
|
|
195
236
|
const list = Array.isArray(raw)
|
|
196
237
|
? raw
|
|
197
238
|
: typeof raw === "object" && raw !== null && Array.isArray(raw.plugins)
|
|
@@ -201,7 +242,7 @@ export function parseIndex(raw, source) {
|
|
|
201
242
|
throw new Error("索引格式不符:期望数组或含 plugins 数组的对象");
|
|
202
243
|
const entries = [];
|
|
203
244
|
for (const item of list) {
|
|
204
|
-
const entry = parseEntry(item, source);
|
|
245
|
+
const entry = parseEntry(item, source, logger);
|
|
205
246
|
if (entry !== undefined)
|
|
206
247
|
entries.push(entry);
|
|
207
248
|
}
|
|
@@ -251,12 +292,15 @@ export class PluginMarket {
|
|
|
251
292
|
#gitAvailable;
|
|
252
293
|
/** 执行 git 命令,未注入时调用本机 git */
|
|
253
294
|
#git;
|
|
295
|
+
/** 执行包管理器,未注入时调用本机的 pnpm / npm */
|
|
296
|
+
#pm;
|
|
254
297
|
/**
|
|
255
298
|
* @param deps 依赖
|
|
256
299
|
*/
|
|
257
300
|
constructor(deps) {
|
|
258
301
|
this.#deps = deps;
|
|
259
302
|
this.#git = deps.git ?? runGit;
|
|
303
|
+
this.#pm = deps.pm ?? runPm;
|
|
260
304
|
}
|
|
261
305
|
/**
|
|
262
306
|
* 列出市场中的插件
|
|
@@ -333,7 +377,7 @@ export class PluginMarket {
|
|
|
333
377
|
timeout,
|
|
334
378
|
retry: 1
|
|
335
379
|
});
|
|
336
|
-
const entries = parseIndex(raw, url);
|
|
380
|
+
const entries = parseIndex(raw, url, this.#deps.logger);
|
|
337
381
|
for (const item of entries)
|
|
338
382
|
if (!merged.has(item.name))
|
|
339
383
|
merged.set(item.name, item);
|
|
@@ -412,12 +456,19 @@ export class PluginMarket {
|
|
|
412
456
|
/**
|
|
413
457
|
* 安装一个插件
|
|
414
458
|
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
459
|
+
* 取源与校验在临时目录内完成,仅在校验通过后才移入插件目录,因此**取源**失败时插件
|
|
460
|
+
* 目录保持原状。装依赖与装后步骤在移入之后、于插件目录内进行 —— 不在临时目录里做完
|
|
461
|
+
* 再整体搬过去:pnpm 的 `node_modules` 里全是指向 store 的符号链接,换个路径就断,
|
|
462
|
+
* 而断掉的表现是加载时报找不到模块,离「我搬过了目录」很远。
|
|
463
|
+
*
|
|
464
|
+
* 代价是这一步失败会留下一个「装好了但缺依赖」的目录。那是可说清的状态(见返回值的
|
|
465
|
+
* `needsDependencies` 与 `setupError`),比一个链接全断的目录好收拾。
|
|
466
|
+
*
|
|
467
|
+
* 安装完成后不加载插件:加载时机由调用方决定。
|
|
418
468
|
* @param name 插件名
|
|
419
469
|
* @param opts 可选参数
|
|
420
470
|
* @param opts.replace 目标已存在时先删除再安装
|
|
471
|
+
* @param opts.dependencies 装完之后跑包管理器装依赖,并按索引声明跑装后步骤
|
|
421
472
|
* @returns 安装结果
|
|
422
473
|
* @throws 名称不合法、索引中无此插件、内核版本不满足、目标已存在或取源失败时
|
|
423
474
|
*/
|
|
@@ -440,21 +491,122 @@ export class PluginMarket {
|
|
|
440
491
|
await this.#assertLooksLikePlugin(root, name);
|
|
441
492
|
const manifest = await this.#manifest(root);
|
|
442
493
|
const version = manifest?.version ?? entry.version ?? "0.0.0";
|
|
443
|
-
const needsDependencies = Object.keys(manifest?.dependencies ?? {}).length > 0 && !(await isDirectory(join(root, "node_modules")));
|
|
444
494
|
if (exists)
|
|
445
495
|
await rm(target, { recursive: true, force: true });
|
|
446
496
|
await mkdir(this.#deps.pluginsDir, { recursive: true });
|
|
447
497
|
await this.#move(root, target);
|
|
448
498
|
this.#deps.logger.info(`插件 ${name}@${version} 已安装至 ${target}`);
|
|
449
|
-
if (needsDependencies)
|
|
450
|
-
this.#deps.logger.warn(`插件 ${name} 声明了运行时依赖,需在其目录内自行执行包管理器安装`);
|
|
451
499
|
// 带 `.git` 的目录此后可就地拉取;归档装出来的每次更新都要整目录重下
|
|
452
|
-
|
|
500
|
+
const updatable = via === "git" ? "pull" : "reinstall";
|
|
501
|
+
const done = await this.#finish(name, target, manifest, entry.setup, opts.dependencies === true);
|
|
502
|
+
return { name, dir: target, via, version, updatable, ...done };
|
|
453
503
|
}
|
|
454
504
|
finally {
|
|
455
505
|
await rm(staging, { recursive: true, force: true });
|
|
456
506
|
}
|
|
457
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* 收尾:按需装依赖、跑装后步骤
|
|
510
|
+
*
|
|
511
|
+
* **失败不向上抛。** 插件目录已经就位,缺的只是依赖或产物;抛出去会让使用者以为
|
|
512
|
+
* 「什么都没装成」而去重装,而重装同样会在这一步失败。故把原因记进返回值,由面板说明。
|
|
513
|
+
*
|
|
514
|
+
* 顺序是「先装依赖再跑脚本」,且装依赖失败就不跑脚本:脚本多半建立在依赖之上
|
|
515
|
+
* (`build` 要编译器),接着跑只会得到第二条更难懂的错误。
|
|
516
|
+
* @param name 插件名
|
|
517
|
+
* @param dir 插件目录
|
|
518
|
+
* @param manifest package.json 里的相关字段
|
|
519
|
+
* @param setup 索引声明的装后步骤
|
|
520
|
+
* @param wanted 调用方是否要求跑包管理器
|
|
521
|
+
* @param always 依赖不缺、也没有装后步骤时是否仍跑一遍装依赖
|
|
522
|
+
* @returns 结果中与依赖、装后步骤相关的那几项
|
|
523
|
+
*/
|
|
524
|
+
async #finish(name, dir, manifest, setup, wanted, always = false) {
|
|
525
|
+
const declared = Object.keys(manifest?.dependencies ?? {}).length > 0;
|
|
526
|
+
/*
|
|
527
|
+
* 「声明了依赖」与「还缺依赖」是两件事
|
|
528
|
+
*
|
|
529
|
+
* 就地拉取那条路上目录里往往已有一份 `node_modules`,此时 declared 为真而并不缺依赖。
|
|
530
|
+
* 判据取「目录里有没有 node_modules」——「旧的够不够新」要比对 lock 文件,本模块无从判断。
|
|
531
|
+
*/
|
|
532
|
+
const missing = declared && !(await isDirectory(join(dir, "node_modules")));
|
|
533
|
+
/*
|
|
534
|
+
* 声明了装后步骤就得跑,哪怕依赖不缺
|
|
535
|
+
*
|
|
536
|
+
* `build` 的产物在 `dist/`,而那一层多半被插件仓库 `.gitignore` 掉了 —— 就地拉取拉来
|
|
537
|
+
* 新提交之后,`node_modules` 还在(故 missing 为假)而 `dist/` 已经是旧的。此时跳过
|
|
538
|
+
* 装后步骤,插件跑的就还是上一版的代码,且毫无迹象。
|
|
539
|
+
*/
|
|
540
|
+
const wants = wanted && (missing || setup !== undefined || always);
|
|
541
|
+
if (!wants) {
|
|
542
|
+
if (missing)
|
|
543
|
+
this.#deps.logger.warn(`插件 ${name} 声明了运行时依赖,需在其目录内自行执行包管理器安装`);
|
|
544
|
+
return { needsDependencies: missing };
|
|
545
|
+
}
|
|
546
|
+
/*
|
|
547
|
+
* 压根没声明依赖的插件不跑包管理器
|
|
548
|
+
*
|
|
549
|
+
* 单文件插件与零依赖插件都属此列。对着一个没有 package.json 的目录跑 `pnpm install`
|
|
550
|
+
* 会得到一条与插件无关的报错(pnpm 找不到 lock 也找不到清单),而使用者点的按钮
|
|
551
|
+
* 叫「装依赖并编译」—— 那条报错只会让人以为插件坏了。
|
|
552
|
+
*/
|
|
553
|
+
if (!declared && setup === undefined)
|
|
554
|
+
return { needsDependencies: false };
|
|
555
|
+
let pm;
|
|
556
|
+
try {
|
|
557
|
+
// 有装后步骤时连 devDependencies 一起装:`build` 要的编译器在那里,见 pm.ts 文件头
|
|
558
|
+
pm = await this.#pm({ kind: "install", dev: setup?.dev === true }, dir, INSTALL_TIMEOUT_MS);
|
|
559
|
+
this.#deps.logger.info(`插件 ${name} 的依赖已由 ${pm} 装好`);
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
563
|
+
this.#deps.logger.error(`插件 ${name} 的依赖安装失败:${error}。请在 ${dir} 目录内自行执行包管理器`);
|
|
564
|
+
// 记 `dependencyError` 而非 `setupError`:两者的后手不同,见 SetupOutcome 的注释
|
|
565
|
+
return { needsDependencies: true, installedDeps: false, dependencyError: error };
|
|
566
|
+
}
|
|
567
|
+
const ranScripts = [];
|
|
568
|
+
for (const script of setup?.scripts ?? []) {
|
|
569
|
+
try {
|
|
570
|
+
await this.#pm({ kind: "run", script }, dir, SCRIPT_TIMEOUT_MS);
|
|
571
|
+
ranScripts.push(script);
|
|
572
|
+
this.#deps.logger.info(`插件 ${name} 的装后步骤 ${script} 已执行`);
|
|
573
|
+
}
|
|
574
|
+
catch (err) {
|
|
575
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
576
|
+
this.#deps.logger.error(`插件 ${name} 的装后步骤 ${script} 失败:${error}。请在 ${dir} 目录内自行执行 ${pm} run ${script}`);
|
|
577
|
+
return { needsDependencies: false, installedDeps: true, packageManager: pm, ranScripts, setupError: `${script}:${error}` };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return { needsDependencies: false, installedDeps: true, packageManager: pm, ranScripts };
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* 对一个已装好的插件重跑装依赖与装后步骤,不重新取源
|
|
584
|
+
*
|
|
585
|
+
* 三种情形要用到:**手工放进插件目录的插件**(压根没有安装动作可挂)、装的时候这一步
|
|
586
|
+
* 失败过、以及使用者自己 `git pull` 过而 `dist/` 已旧。
|
|
587
|
+
*
|
|
588
|
+
* **索引里没有这个插件也照做。** 手工放进去的插件多半不在任何索引里,此时只装依赖、
|
|
589
|
+
* 不跑装后步骤(无从知道该跑什么)—— 那仍然解决了「装了却缺依赖」这个主要情形。
|
|
590
|
+
* 拿不到条目就拒绝会让这个按钮恰在最需要它的场合失效。
|
|
591
|
+
*
|
|
592
|
+
* **即便不缺依赖也跑一次。** 使用者点这个按钮,多半正是因为 `package.json` 的依赖
|
|
593
|
+
* 变过而 `node_modules` 是旧的 —— 那种「旧」从目录存不存在上看不出来,而包管理器
|
|
594
|
+
* 自己比对 lock 文件本就是幂等的。
|
|
595
|
+
* @param name 插件名
|
|
596
|
+
* @returns 本次的结果
|
|
597
|
+
* @throws 名称不合法或插件目录不存在时
|
|
598
|
+
*/
|
|
599
|
+
async setup(name) {
|
|
600
|
+
assertPluginName(name);
|
|
601
|
+
const dir = joinWithin(this.#deps.pluginsDir, name);
|
|
602
|
+
if (!(await isDirectory(dir)))
|
|
603
|
+
throw new Error(`插件目录 ${name} 不存在`);
|
|
604
|
+
const manifest = await this.#manifest(dir);
|
|
605
|
+
// 索引取不到不算失败,见方法头
|
|
606
|
+
const entry = await this.entry(name).catch(() => undefined);
|
|
607
|
+
const done = await this.#finish(name, dir, manifest, entry?.setup, true, true);
|
|
608
|
+
return { name, dir, version: manifest?.version ?? "0.0.0", ...done };
|
|
609
|
+
}
|
|
458
610
|
/**
|
|
459
611
|
* 卸载一个插件
|
|
460
612
|
*
|
|
@@ -475,10 +627,12 @@ export class PluginMarket {
|
|
|
475
627
|
/**
|
|
476
628
|
* 更新一个插件:目录已是 git 仓库时就地拉取,否则退回重新安装
|
|
477
629
|
* @param name 插件名
|
|
630
|
+
* @param opts 可选参数
|
|
631
|
+
* @param opts.dependencies 更新完之后跑包管理器装依赖,并按索引声明跑装后步骤
|
|
478
632
|
* @returns 安装结果
|
|
479
633
|
* @throws 与 `install` 相同;就地拉取失败时抛出 git 的错误
|
|
480
634
|
*/
|
|
481
|
-
async update(name) {
|
|
635
|
+
async update(name, opts = {}) {
|
|
482
636
|
/*
|
|
483
637
|
* 优先就地拉取,因为重新安装会删掉目录里那份 `node_modules`
|
|
484
638
|
*
|
|
@@ -491,18 +645,19 @@ export class PluginMarket {
|
|
|
491
645
|
*/
|
|
492
646
|
assertPluginName(name);
|
|
493
647
|
const target = joinWithin(this.#deps.pluginsDir, name);
|
|
494
|
-
const pulled = await this.#tryPull(name, target);
|
|
495
|
-
return pulled ?? this.install(name, { replace: true });
|
|
648
|
+
const pulled = await this.#tryPull(name, target, opts.dependencies === true);
|
|
649
|
+
return pulled ?? this.install(name, { replace: true, ...opts });
|
|
496
650
|
}
|
|
497
651
|
/**
|
|
498
652
|
* 试着就地拉取一个插件
|
|
499
653
|
* @param name 插件名
|
|
500
654
|
* @param dir 插件安装目录
|
|
655
|
+
* @param dependencies 拉完之后按需装依赖、跑装后步骤
|
|
501
656
|
* @returns 拉取结果;不具备就地拉取条件(无 git、目录不是仓库、来源不是 git)时 undefined
|
|
502
657
|
* @throws 拉取过程本身失败时 —— 那意味着网络或仓库状态有问题,此时退回重新安装会把一次
|
|
503
658
|
* 可修复的失败变成一次目录删除
|
|
504
659
|
*/
|
|
505
|
-
async #tryPull(name, dir) {
|
|
660
|
+
async #tryPull(name, dir, dependencies) {
|
|
506
661
|
/*
|
|
507
662
|
* 两处刻意的做法
|
|
508
663
|
*
|
|
@@ -554,23 +709,29 @@ export class PluginMarket {
|
|
|
554
709
|
const changed = nowAt !== wasAt;
|
|
555
710
|
const manifest = await this.#manifest(dir);
|
|
556
711
|
const version = manifest?.version ?? entry.version ?? "0.0.0";
|
|
557
|
-
const needsDependencies = Object.keys(manifest?.dependencies ?? {}).length > 0 && !(await isDirectory(join(dir, "node_modules")));
|
|
558
712
|
if (changed)
|
|
559
713
|
this.#deps.logger.info(`插件 ${name} 已就地更新至 ${version}(${wasAt.slice(0, 7)} → ${nowAt.slice(0, 7)})`);
|
|
560
714
|
else
|
|
561
715
|
this.#deps.logger.info(`插件 ${name} 已是最新版本 ${version}`);
|
|
562
|
-
|
|
563
|
-
|
|
716
|
+
/*
|
|
717
|
+
* 远端没有新提交、且依赖不缺时不跑收尾
|
|
718
|
+
*
|
|
719
|
+
* 那一次「更新」什么都没改,重跑 `build` 只是白等一遍编译,而 `install:browser`
|
|
720
|
+
* 那类装后步骤更可能去重下一份运行时。缺依赖是例外 —— 那与有没有新提交无关,
|
|
721
|
+
* 使用者点这一下要的就是把它补齐。
|
|
722
|
+
*/
|
|
723
|
+
const missing = Object.keys(manifest?.dependencies ?? {}).length > 0 && !(await isDirectory(join(dir, "node_modules")));
|
|
724
|
+
const done = await this.#finish(name, dir, manifest, entry.setup, dependencies && (changed || missing));
|
|
564
725
|
return {
|
|
565
726
|
name,
|
|
566
727
|
dir,
|
|
567
728
|
via: "pull",
|
|
568
729
|
version,
|
|
569
|
-
needsDependencies,
|
|
570
730
|
changed,
|
|
571
731
|
// 就地拉取过一次,说明目录确实是 git 仓库,此后照旧走这条路
|
|
572
732
|
updatable: "pull",
|
|
573
|
-
...(before === undefined ? {} : { fromVersion: before })
|
|
733
|
+
...(before === undefined ? {} : { fromVersion: before }),
|
|
734
|
+
...done
|
|
574
735
|
};
|
|
575
736
|
}
|
|
576
737
|
/**
|