@koishi-ce/plugin-market 1.1.4 → 1.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/index.js +2 -2
- package/dist/style.css +1 -1
- package/lib/index.d.ts +64 -17
- package/lib/index.mjs +195 -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 +116 -0
- package/src/node/dependencies/snapshot.test.ts +196 -0
- package/src/node/dependencies/snapshot.ts +97 -0
- package/src/node/dependencies/types.ts +45 -0
- package/src/node/deps.ts +1 -14
- package/src/node/index.ts +7 -8
- package/src/node/installer/index.ts +37 -21
- 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,25 +55,6 @@ import {
|
|
|
52
55
|
|
|
53
56
|
const logger = new Logger("market");
|
|
54
57
|
|
|
55
|
-
export interface Dependency {
|
|
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
|
-
}
|
|
73
|
-
|
|
74
58
|
class Installer extends Service {
|
|
75
59
|
declare http: HTTP;
|
|
76
60
|
declare endpoint: string | undefined;
|
|
@@ -78,8 +62,12 @@ class Installer extends Service {
|
|
|
78
62
|
public tempCache: Dict<VersionMap> = {};
|
|
79
63
|
|
|
80
64
|
private pkgTasks: Dict<Promise<VersionMap>> = {};
|
|
65
|
+
/** 各包最近一次拉取失败的归类(404 与否);依赖页的 error 徽标消费 */
|
|
66
|
+
public pkgErrors: Dict<{ notFound: boolean }> = {};
|
|
81
67
|
private manifest: LocalPackage;
|
|
82
|
-
declare private depTask:
|
|
68
|
+
declare private depTask:
|
|
69
|
+
| Promise<Dict<Dependency>>
|
|
70
|
+
| undefined;
|
|
83
71
|
private flushData: () => void;
|
|
84
72
|
|
|
85
73
|
override config: Installer.Config;
|
|
@@ -149,11 +137,23 @@ class Installer extends Service {
|
|
|
149
137
|
private async _getPackage(name: string) {
|
|
150
138
|
try {
|
|
151
139
|
const versions = await fetchVersions(this.http, name);
|
|
140
|
+
delete this.pkgErrors[name];
|
|
152
141
|
this.fullCache[name] = this.tempCache[name] =
|
|
153
142
|
versions;
|
|
154
143
|
this.flushData();
|
|
155
144
|
return versions;
|
|
156
145
|
} catch (error) {
|
|
146
|
+
// 404 与否决定负缓存策略:404 是 registry 的确定性答复,
|
|
147
|
+
// resolved 空表照常驻留 pkgTasks(会话内不再重试);其余
|
|
148
|
+
// (网络抖动、超时等瞬态)清除任务让下次调用重试
|
|
149
|
+
const notFound =
|
|
150
|
+
this.http.isError(error) &&
|
|
151
|
+
error.response?.status === 404;
|
|
152
|
+
this.pkgErrors[name] = { notFound };
|
|
153
|
+
if (!notFound) {
|
|
154
|
+
// 本任务自身 catch 后恒 resolve,直接移除即可让下次调用重试
|
|
155
|
+
delete this.pkgTasks[name];
|
|
156
|
+
}
|
|
157
157
|
logger.warn(error);
|
|
158
158
|
return {};
|
|
159
159
|
}
|
|
@@ -217,6 +217,7 @@ class Installer extends Service {
|
|
|
217
217
|
|
|
218
218
|
refresh(refresh = false) {
|
|
219
219
|
this.pkgTasks = {};
|
|
220
|
+
this.pkgErrors = {};
|
|
220
221
|
this.fullCache = {};
|
|
221
222
|
this.tempCache = {};
|
|
222
223
|
this.depTask = this._getDeps();
|
|
@@ -224,6 +225,12 @@ class Installer extends Service {
|
|
|
224
225
|
this.refreshData();
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
/** 轻量重建:重读宿主清单并重置依赖汇总任务(registry 缓存不动)。 */
|
|
229
|
+
reload() {
|
|
230
|
+
this.manifest = loadManifest(this.cwd);
|
|
231
|
+
this.depTask = undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
227
234
|
async exec(args: string[]) {
|
|
228
235
|
// Bun-first:CE 生态只存在 bun 这一种包管理器,直接驱动 bun 执行
|
|
229
236
|
// 安装(上游经 which-pm-runs 探测 npm/yarn/bun,此处固定为 bun)
|
|
@@ -260,6 +267,8 @@ class Installer extends Service {
|
|
|
260
267
|
forced?: boolean,
|
|
261
268
|
) {
|
|
262
269
|
const localDeps = this._getLocalDeps(deps);
|
|
270
|
+
// 备份清单原文:主安装失败时整体还原,防止半改状态残留在磁盘
|
|
271
|
+
const backup = await backupManifest(this.cwd);
|
|
263
272
|
await this.override(deps);
|
|
264
273
|
|
|
265
274
|
let shouldInstall = forced === true;
|
|
@@ -281,7 +290,14 @@ class Installer extends Service {
|
|
|
281
290
|
|
|
282
291
|
if (shouldInstall) {
|
|
283
292
|
const code = await this._install();
|
|
284
|
-
if (code)
|
|
293
|
+
if (code) {
|
|
294
|
+
// 安装失败:还原清单原文并轻量重建内存态
|
|
295
|
+
// (registry 缓存与依赖服务快照不受影响,后者由
|
|
296
|
+
// 安装监听器统一 refresh 触发重建)
|
|
297
|
+
await restoreManifest(this.cwd, backup);
|
|
298
|
+
this.reload();
|
|
299
|
+
return code;
|
|
300
|
+
}
|
|
285
301
|
// isolated 布局的增量安装可能漏建新包目录的依赖链接(机理
|
|
286
302
|
// 见 integrity.ts 模块注释):装完即校验,缺失时补跑一次
|
|
287
303
|
// 安装——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
|