@koishi-ce/plugin-market 1.1.4 → 1.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/dist/index.js +2 -2
- package/dist/style.css +1 -1
- package/lib/index.d.ts +58 -17
- package/lib/index.mjs +192 -58
- package/package.json +3 -3
- package/src/{__tests__ → node/__tests__}/helpers.ts +3 -3
- package/src/{__tests__ → node/__tests__}/provider.test.ts +72 -8
- package/src/node/dependencies/service.ts +118 -0
- package/src/node/dependencies/snapshot.test.ts +176 -0
- package/src/node/dependencies/snapshot.ts +92 -0
- package/src/node/dependencies/types.ts +39 -0
- package/src/node/deps.ts +1 -14
- package/src/node/index.ts +7 -8
- package/src/node/installer/index.ts +38 -20
- package/src/{__tests__ → node/installer}/installer.test.ts +28 -1
- package/src/node/installer/manifest.ts +15 -0
- /package/src/{__tests__ → node/__tests__}/commands.test.ts +0 -0
- /package/src/{__tests__ → node/__tests__}/retry.test.ts +0 -0
|
@@ -33,11 +33,14 @@ import {
|
|
|
33
33
|
type RemotePackage,
|
|
34
34
|
} from "@koishi-ce/registry";
|
|
35
35
|
import { satisfies, valid } from "semver";
|
|
36
|
+
import type { Dependency } from "../dependencies/types.ts";
|
|
36
37
|
import { runBun } from "./exec.ts";
|
|
37
38
|
import { findMissingDeps } from "./integrity.ts";
|
|
38
39
|
import {
|
|
40
|
+
backupManifest,
|
|
39
41
|
type LocalPackage,
|
|
40
42
|
loadManifest,
|
|
43
|
+
restoreManifest,
|
|
41
44
|
writeManifest,
|
|
42
45
|
} from "./manifest.ts";
|
|
43
46
|
import {
|
|
@@ -52,24 +55,7 @@ import {
|
|
|
52
55
|
|
|
53
56
|
const logger = new Logger("market");
|
|
54
57
|
|
|
55
|
-
export
|
|
56
|
-
/**
|
|
57
|
-
* requested semver range
|
|
58
|
-
* @example `^1.2.3` -> `1.2.3`
|
|
59
|
-
*/
|
|
60
|
-
request: string;
|
|
61
|
-
/**
|
|
62
|
-
* installed package version
|
|
63
|
-
* @example `1.2.5`
|
|
64
|
-
*/
|
|
65
|
-
resolved?: string | undefined;
|
|
66
|
-
/** whether it is a workspace package */
|
|
67
|
-
workspace?: boolean | undefined;
|
|
68
|
-
/** valid (unsupported) syntax */
|
|
69
|
-
invalid?: boolean | undefined;
|
|
70
|
-
/** latest version */
|
|
71
|
-
latest?: string | undefined;
|
|
72
|
-
}
|
|
58
|
+
export type { Dependency };
|
|
73
59
|
|
|
74
60
|
class Installer extends Service {
|
|
75
61
|
declare http: HTTP;
|
|
@@ -78,8 +64,12 @@ class Installer extends Service {
|
|
|
78
64
|
public tempCache: Dict<VersionMap> = {};
|
|
79
65
|
|
|
80
66
|
private pkgTasks: Dict<Promise<VersionMap>> = {};
|
|
67
|
+
/** 各包最近一次拉取失败的归类(404 与否);依赖页的 error 徽标消费 */
|
|
68
|
+
public pkgErrors: Dict<{ notFound: boolean }> = {};
|
|
81
69
|
private manifest: LocalPackage;
|
|
82
|
-
declare private depTask:
|
|
70
|
+
declare private depTask:
|
|
71
|
+
| Promise<Dict<Dependency>>
|
|
72
|
+
| undefined;
|
|
83
73
|
private flushData: () => void;
|
|
84
74
|
|
|
85
75
|
override config: Installer.Config;
|
|
@@ -149,11 +139,23 @@ class Installer extends Service {
|
|
|
149
139
|
private async _getPackage(name: string) {
|
|
150
140
|
try {
|
|
151
141
|
const versions = await fetchVersions(this.http, name);
|
|
142
|
+
delete this.pkgErrors[name];
|
|
152
143
|
this.fullCache[name] = this.tempCache[name] =
|
|
153
144
|
versions;
|
|
154
145
|
this.flushData();
|
|
155
146
|
return versions;
|
|
156
147
|
} catch (error) {
|
|
148
|
+
// 404 与否决定负缓存策略:404 是 registry 的确定性答复,
|
|
149
|
+
// resolved 空表照常驻留 pkgTasks(会话内不再重试);其余
|
|
150
|
+
// (网络抖动、超时等瞬态)清除任务让下次调用重试
|
|
151
|
+
const notFound =
|
|
152
|
+
this.http.isError(error) &&
|
|
153
|
+
error.response?.status === 404;
|
|
154
|
+
this.pkgErrors[name] = { notFound };
|
|
155
|
+
if (!notFound) {
|
|
156
|
+
// 本任务自身 catch 后恒 resolve,直接移除即可让下次调用重试
|
|
157
|
+
delete this.pkgTasks[name];
|
|
158
|
+
}
|
|
157
159
|
logger.warn(error);
|
|
158
160
|
return {};
|
|
159
161
|
}
|
|
@@ -217,6 +219,7 @@ class Installer extends Service {
|
|
|
217
219
|
|
|
218
220
|
refresh(refresh = false) {
|
|
219
221
|
this.pkgTasks = {};
|
|
222
|
+
this.pkgErrors = {};
|
|
220
223
|
this.fullCache = {};
|
|
221
224
|
this.tempCache = {};
|
|
222
225
|
this.depTask = this._getDeps();
|
|
@@ -224,6 +227,12 @@ class Installer extends Service {
|
|
|
224
227
|
this.refreshData();
|
|
225
228
|
}
|
|
226
229
|
|
|
230
|
+
/** 轻量重建:重读宿主清单并重置依赖汇总任务(registry 缓存不动)。 */
|
|
231
|
+
reload() {
|
|
232
|
+
this.manifest = loadManifest(this.cwd);
|
|
233
|
+
this.depTask = undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
227
236
|
async exec(args: string[]) {
|
|
228
237
|
// Bun-first:CE 生态只存在 bun 这一种包管理器,直接驱动 bun 执行
|
|
229
238
|
// 安装(上游经 which-pm-runs 探测 npm/yarn/bun,此处固定为 bun)
|
|
@@ -260,6 +269,8 @@ class Installer extends Service {
|
|
|
260
269
|
forced?: boolean,
|
|
261
270
|
) {
|
|
262
271
|
const localDeps = this._getLocalDeps(deps);
|
|
272
|
+
// 备份清单原文:主安装失败时整体还原,防止半改状态残留在磁盘
|
|
273
|
+
const backup = await backupManifest(this.cwd);
|
|
263
274
|
await this.override(deps);
|
|
264
275
|
|
|
265
276
|
let shouldInstall = forced === true;
|
|
@@ -281,7 +292,14 @@ class Installer extends Service {
|
|
|
281
292
|
|
|
282
293
|
if (shouldInstall) {
|
|
283
294
|
const code = await this._install();
|
|
284
|
-
if (code)
|
|
295
|
+
if (code) {
|
|
296
|
+
// 安装失败:还原清单原文并轻量重建内存态
|
|
297
|
+
// (registry 缓存与依赖服务快照不受影响,后者由
|
|
298
|
+
// 安装监听器统一 refresh 触发重建)
|
|
299
|
+
await restoreManifest(this.cwd, backup);
|
|
300
|
+
this.reload();
|
|
301
|
+
return code;
|
|
302
|
+
}
|
|
285
303
|
// isolated 布局的增量安装可能漏建新包目录的依赖链接(机理
|
|
286
304
|
// 见 integrity.ts 模块注释):装完即校验,缺失时补跑一次
|
|
287
305
|
// 安装——bun 按磁盘实际状态重建链接,秒级自愈,无需人工
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
startApp,
|
|
21
21
|
stopApp,
|
|
22
22
|
tmp,
|
|
23
|
-
} from "
|
|
23
|
+
} from "../__tests__/helpers.ts";
|
|
24
24
|
|
|
25
25
|
const { isResidentInCache } = await import(
|
|
26
26
|
"@koishi-ce/registry"
|
|
@@ -90,6 +90,33 @@ describe("Installer 安装链路", () => {
|
|
|
90
90
|
15000,
|
|
91
91
|
);
|
|
92
92
|
|
|
93
|
+
itQuiet(
|
|
94
|
+
"安装失败时还原 package.json 备份(失败回滚链)",
|
|
95
|
+
async () => {
|
|
96
|
+
setNextExitCode(1);
|
|
97
|
+
const before = await Bun.file(
|
|
98
|
+
join(tmp, "package.json"),
|
|
99
|
+
).text();
|
|
100
|
+
const code = await app.installer.install(
|
|
101
|
+
{ "koishi-plugin-demo": "^2.0.0" },
|
|
102
|
+
true,
|
|
103
|
+
);
|
|
104
|
+
expect(code).toBe(1);
|
|
105
|
+
// 失败后清单原文原样还原(依赖改动不留半改状态)
|
|
106
|
+
const after = await Bun.file(
|
|
107
|
+
join(tmp, "package.json"),
|
|
108
|
+
).text();
|
|
109
|
+
expect(after).toBe(before);
|
|
110
|
+
// 内存清单同步重建:还原后的清单仍是 1.0.0 请求
|
|
111
|
+
const deps = await app.installer.getDeps();
|
|
112
|
+
expect(deps["koishi-plugin-demo"]?.request).toBe(
|
|
113
|
+
"1.0.0",
|
|
114
|
+
);
|
|
115
|
+
setNextExitCode(0);
|
|
116
|
+
},
|
|
117
|
+
15000,
|
|
118
|
+
);
|
|
119
|
+
|
|
93
120
|
itQuiet("子进程 spawn 失败返回 -1", async () => {
|
|
94
121
|
setNextSpawnError(true);
|
|
95
122
|
setNextExitCode(0);
|
|
@@ -62,6 +62,21 @@ export function loadManifest(name: string): LocalPackage {
|
|
|
62
62
|
return meta;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/** 读取项目根 package.json 原文(安装前的备份快照)。 */
|
|
66
|
+
export async function backupManifest(
|
|
67
|
+
cwd: string,
|
|
68
|
+
): Promise<string> {
|
|
69
|
+
return Bun.file(resolve(cwd, "package.json")).text();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 把备份原文原样写回项目根 package.json(安装失败的回滚路径)。 */
|
|
73
|
+
export async function restoreManifest(
|
|
74
|
+
cwd: string,
|
|
75
|
+
backup: string,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await Bun.write(resolve(cwd, "package.json"), backup);
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
/**
|
|
66
81
|
* 把依赖增删写回项目根 package.json,返回写回后的清单。
|
|
67
82
|
*
|
|
File without changes
|
|
File without changes
|