@koishi-ce/plugin-market 1.0.9 → 1.0.10
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 +1 -1
- package/dist/style.css +1 -1
- package/lib/index.d.ts +8 -1
- package/lib/index.mjs +4 -1
- package/package.json +3 -3
- package/src/__tests__/index.test.ts +580 -281
- package/src/browser/index.ts +6 -1
- package/src/browser/market.ts +6 -2
- package/src/node/deps.ts +7 -2
- package/src/node/index.ts +74 -22
- package/src/node/installer.ts +120 -51
- package/src/node/market.ts +42 -21
- package/src/node/proc.ts +8 -2
- package/src/shared/index.ts +27 -12
|
@@ -2,8 +2,20 @@
|
|
|
2
2
|
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
3
|
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
afterAll,
|
|
7
|
+
beforeAll,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
it,
|
|
11
|
+
mock,
|
|
12
|
+
} from "bun:test";
|
|
13
|
+
import {
|
|
14
|
+
mkdirSync,
|
|
15
|
+
mkdtempSync,
|
|
16
|
+
rmSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from "node:fs";
|
|
7
19
|
import { tmpdir } from "node:os";
|
|
8
20
|
import { join } from "node:path";
|
|
9
21
|
import memory from "@koishijs/plugin-database-memory";
|
|
@@ -35,24 +47,25 @@ const spawnBunMock = (_args: string[], _cwd: string) => {
|
|
|
35
47
|
},
|
|
36
48
|
stderr: {
|
|
37
49
|
on(event: string, cb: (data: Buffer) => void) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
// 同步派发:异步派发会晚于用例的日志静默窗口,导致转发告警漏出刷屏
|
|
51
|
+
if (event === "data")
|
|
52
|
+
cb(Buffer.from("stderr line\n"));
|
|
41
53
|
return this;
|
|
42
54
|
},
|
|
43
55
|
},
|
|
44
56
|
stdout: {
|
|
45
57
|
on(event: string, cb: (data: Buffer) => void) {
|
|
46
|
-
if (event === "data")
|
|
47
|
-
|
|
48
|
-
}
|
|
58
|
+
if (event === "data")
|
|
59
|
+
cb(Buffer.from("stdout line\n"));
|
|
49
60
|
return this;
|
|
50
61
|
},
|
|
51
62
|
},
|
|
52
63
|
};
|
|
53
64
|
};
|
|
54
65
|
|
|
55
|
-
mock.module("../node/proc.ts", () => ({
|
|
66
|
+
mock.module("../node/proc.ts", () => ({
|
|
67
|
+
spawnBun: spawnBunMock,
|
|
68
|
+
}));
|
|
56
69
|
|
|
57
70
|
import type { Entry } from "@koishi-ce/console";
|
|
58
71
|
// 均为 type-only 导入:编译期擦除,不干扰 mock.module 先于插件加载的时序
|
|
@@ -60,18 +73,29 @@ import type { Plugin } from "@koishi-ce/koishi";
|
|
|
60
73
|
import type { RemotePackage } from "@koishi-ce/registry";
|
|
61
74
|
|
|
62
75
|
const { Console } = await import("@koishi-ce/console");
|
|
63
|
-
const { App, Service } = await import(
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
const { App, Logger, Service } = await import(
|
|
77
|
+
"@koishi-ce/koishi"
|
|
78
|
+
);
|
|
79
|
+
const http = (await import("@koishi-ce/plugin-http"))
|
|
80
|
+
.default;
|
|
81
|
+
const { isResidentInCache } = await import(
|
|
82
|
+
"@koishi-ce/registry"
|
|
83
|
+
);
|
|
66
84
|
const market = await import("../node/index.ts");
|
|
67
|
-
const { default: Installer } = await import(
|
|
68
|
-
|
|
85
|
+
const { default: Installer } = await import(
|
|
86
|
+
"../node/installer.ts"
|
|
87
|
+
);
|
|
88
|
+
const mockPlugin = (await import("@koishi-ce/plugin-mock"))
|
|
89
|
+
.default;
|
|
69
90
|
// 加载包入口占位文件(纯 re-export,无独立逻辑),保证 src 全量被加载
|
|
70
91
|
await import("../index.ts");
|
|
71
92
|
|
|
72
93
|
/** 控制台服务桩:仅实现入口登记所需的最小面。 */
|
|
73
94
|
class FakeConsole extends Console {
|
|
74
|
-
protected resolveEntry(
|
|
95
|
+
protected resolveEntry(
|
|
96
|
+
_files: Entry.Files,
|
|
97
|
+
_key: string,
|
|
98
|
+
): string[] {
|
|
75
99
|
return [];
|
|
76
100
|
}
|
|
77
101
|
}
|
|
@@ -84,7 +108,9 @@ class FakeLoader extends Service {
|
|
|
84
108
|
return ["group:entry", "plugins"];
|
|
85
109
|
}
|
|
86
110
|
fullReload() {}
|
|
87
|
-
constructor(
|
|
111
|
+
constructor(
|
|
112
|
+
ctx: ConstructorParameters<typeof Service>[0],
|
|
113
|
+
) {
|
|
88
114
|
super(ctx, "loader", true);
|
|
89
115
|
}
|
|
90
116
|
}
|
|
@@ -104,8 +130,37 @@ const registryData: {
|
|
|
104
130
|
};
|
|
105
131
|
} = {};
|
|
106
132
|
|
|
107
|
-
/** 搜索接口响应(/-/v1/search,collect
|
|
108
|
-
let searchResponse: {
|
|
133
|
+
/** 搜索接口响应(/-/v1/search,collect 阶段消费)。初始给空结果,避免启动期空转 404。 */
|
|
134
|
+
let searchResponse: {
|
|
135
|
+
objects: unknown[];
|
|
136
|
+
total: number;
|
|
137
|
+
} | null = {
|
|
138
|
+
objects: [],
|
|
139
|
+
total: 0,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/** 声明预期触发 market 域告警(404 / 限流等桩失败路径)的用例:执行期间静默该域。 */
|
|
143
|
+
const itQuiet = (
|
|
144
|
+
name: string,
|
|
145
|
+
fn: () => Promise<void> | void,
|
|
146
|
+
timeout?: number,
|
|
147
|
+
) =>
|
|
148
|
+
it(
|
|
149
|
+
name,
|
|
150
|
+
async () => {
|
|
151
|
+
const levels = Logger.levels as Record<
|
|
152
|
+
string,
|
|
153
|
+
number
|
|
154
|
+
>;
|
|
155
|
+
levels["market"] = 0;
|
|
156
|
+
try {
|
|
157
|
+
await fn();
|
|
158
|
+
} finally {
|
|
159
|
+
delete levels["market"];
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
timeout,
|
|
163
|
+
);
|
|
109
164
|
|
|
110
165
|
const registryServer = Bun.serve({
|
|
111
166
|
port: 0,
|
|
@@ -119,7 +174,9 @@ const registryServer = Bun.serve({
|
|
|
119
174
|
return Response.json(searchResponse);
|
|
120
175
|
}
|
|
121
176
|
// 模拟 registry 限流:恒定 429 + 极短的 Retry-After,驱动重试后失败
|
|
122
|
-
if (
|
|
177
|
+
if (
|
|
178
|
+
url.pathname.includes("koishi-plugin-ratelimited")
|
|
179
|
+
) {
|
|
123
180
|
return new Response("rate limited", {
|
|
124
181
|
status: 429,
|
|
125
182
|
headers: { "Retry-After": "0.01" },
|
|
@@ -137,18 +194,44 @@ const registryServer = Bun.serve({
|
|
|
137
194
|
// 预置一个兼容插件包:最新版 2.0.0,旧版 1.0.0 均声明 koishi ^4 peer
|
|
138
195
|
registryData["koishi-plugin-demo"] = {
|
|
139
196
|
versions: {
|
|
140
|
-
"1.0.0": {
|
|
141
|
-
|
|
197
|
+
"1.0.0": {
|
|
198
|
+
version: "1.0.0",
|
|
199
|
+
peerDependencies: { koishi: "^4.17.0" },
|
|
200
|
+
},
|
|
201
|
+
"2.0.0": {
|
|
202
|
+
version: "2.0.0",
|
|
203
|
+
peerDependencies: { koishi: "^4.17.0" },
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
time: {
|
|
207
|
+
"1.0.0": "2024-01-01T00:00:00Z",
|
|
208
|
+
"2.0.0": "2024-06-01T00:00:00Z",
|
|
142
209
|
},
|
|
143
|
-
time: { "1.0.0": "2024-01-01T00:00:00Z", "2.0.0": "2024-06-01T00:00:00Z" },
|
|
144
210
|
};
|
|
145
211
|
// 预置一个未安装的插件包(plugin.install 安装路径使用)
|
|
146
212
|
registryData["koishi-plugin-newpkg"] = {
|
|
147
213
|
versions: {
|
|
148
|
-
"1.0.0": {
|
|
214
|
+
"1.0.0": {
|
|
215
|
+
version: "1.0.0",
|
|
216
|
+
peerDependencies: { koishi: "^4.17.0" },
|
|
217
|
+
},
|
|
149
218
|
},
|
|
150
219
|
time: { "1.0.0": "2024-01-01T00:00:00Z" },
|
|
151
220
|
};
|
|
221
|
+
// 预置初始清单里三个非插件依赖的远端条目:Installer.refresh() 每轮都会对全部
|
|
222
|
+
// 清单依赖发起 registry 查询,缺桩会让每个用例都刷 404 告警
|
|
223
|
+
registryData["koishi"] = {
|
|
224
|
+
versions: { "4.18.0": { version: "4.18.0" } },
|
|
225
|
+
time: { "4.18.0": "2024-01-01T00:00:00Z" },
|
|
226
|
+
};
|
|
227
|
+
registryData["market-alias"] = {
|
|
228
|
+
versions: { "1.0.0": { version: "1.0.0" } },
|
|
229
|
+
time: { "1.0.0": "2024-01-01T00:00:00Z" },
|
|
230
|
+
};
|
|
231
|
+
registryData["bad-range"] = {
|
|
232
|
+
versions: { "1.0.0": { version: "1.0.0" } },
|
|
233
|
+
time: { "1.0.0": "2024-01-01T00:00:00Z" },
|
|
234
|
+
};
|
|
152
235
|
|
|
153
236
|
/** 临时宿主目录(Installer 的 cwd 与 override 写盘目标)。 */
|
|
154
237
|
const initialDependencies = {
|
|
@@ -166,7 +249,10 @@ const originalCwd = process.cwd();
|
|
|
166
249
|
writeFileSync(
|
|
167
250
|
join(tmp, "package.json"),
|
|
168
251
|
JSON.stringify(
|
|
169
|
-
{
|
|
252
|
+
{
|
|
253
|
+
name: "market-host",
|
|
254
|
+
dependencies: initialDependencies,
|
|
255
|
+
},
|
|
170
256
|
null,
|
|
171
257
|
"\t",
|
|
172
258
|
),
|
|
@@ -182,10 +268,14 @@ const app = new App();
|
|
|
182
268
|
app.plugin(memory as unknown as typeof memory.default);
|
|
183
269
|
app.plugin(http);
|
|
184
270
|
// Console 基类的 static inject 是 cordis 3 旧形态,与 Plugin.Constructor 期待类型不兼容,仅做类型层转型
|
|
185
|
-
app.plugin(
|
|
271
|
+
app.plugin(
|
|
272
|
+
FakeConsole as unknown as Plugin.Constructor<TestApp>,
|
|
273
|
+
);
|
|
186
274
|
app.plugin(FakeLoader);
|
|
187
275
|
app.plugin(market, {
|
|
188
|
-
registry: {
|
|
276
|
+
registry: {
|
|
277
|
+
endpoint: `http://127.0.0.1:${registryServer.port}/`,
|
|
278
|
+
},
|
|
189
279
|
});
|
|
190
280
|
app.plugin(mockPlugin);
|
|
191
281
|
|
|
@@ -207,21 +297,31 @@ afterAll(async () => {
|
|
|
207
297
|
|
|
208
298
|
describe("market 插件", () => {
|
|
209
299
|
it("注册三个数据服务与浏览器监听器", () => {
|
|
210
|
-
expect(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
expect(
|
|
214
|
-
|
|
300
|
+
expect(
|
|
301
|
+
app.get("console.services.market"),
|
|
302
|
+
).toBeDefined();
|
|
303
|
+
expect(
|
|
304
|
+
app.get("console.services.dependencies"),
|
|
305
|
+
).toBeDefined();
|
|
306
|
+
expect(
|
|
307
|
+
app.get("console.services.registry"),
|
|
308
|
+
).toBeDefined();
|
|
309
|
+
expect(
|
|
310
|
+
app.console.listeners["market/install"],
|
|
311
|
+
).toBeDefined();
|
|
312
|
+
expect(
|
|
313
|
+
app.console.listeners["market/registry"],
|
|
314
|
+
).toBeDefined();
|
|
215
315
|
});
|
|
216
316
|
|
|
217
317
|
it("resolveName 解析插件短名的候选全名", () => {
|
|
218
318
|
const installer = app.installer;
|
|
219
|
-
expect(
|
|
220
|
-
"@koishijs/plugin-echo",
|
|
221
|
-
]);
|
|
222
|
-
expect(
|
|
223
|
-
"koishi-plugin-echo",
|
|
224
|
-
]);
|
|
319
|
+
expect(
|
|
320
|
+
installer.resolveName("@koishijs/plugin-echo"),
|
|
321
|
+
).toEqual(["@koishijs/plugin-echo"]);
|
|
322
|
+
expect(
|
|
323
|
+
installer.resolveName("koishi-plugin-echo"),
|
|
324
|
+
).toEqual(["koishi-plugin-echo"]);
|
|
225
325
|
expect(installer.resolveName("@scope/echo")).toEqual([
|
|
226
326
|
"@scope/koishi-plugin-echo",
|
|
227
327
|
]);
|
|
@@ -234,27 +334,40 @@ describe("market 插件", () => {
|
|
|
234
334
|
it("getDeps 汇总本地依赖并带出远端最新版", async () => {
|
|
235
335
|
const deps = await app.installer.getDeps();
|
|
236
336
|
// 语义化区间去除前缀符号
|
|
237
|
-
expect(deps["koishi-plugin-demo"]?.request).toBe(
|
|
337
|
+
expect(deps["koishi-plugin-demo"]?.request).toBe(
|
|
338
|
+
"1.0.0",
|
|
339
|
+
);
|
|
238
340
|
// 远端最新版(本地 registry 预置 2.0.0)
|
|
239
|
-
expect(deps["koishi-plugin-demo"]?.latest).toBe(
|
|
341
|
+
expect(deps["koishi-plugin-demo"]?.latest).toBe(
|
|
342
|
+
"2.0.0",
|
|
343
|
+
);
|
|
240
344
|
// 非法 semver 标记 invalid
|
|
241
345
|
expect(deps["bad-range"]?.invalid).toBe(true);
|
|
242
346
|
});
|
|
243
347
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
348
|
+
itQuiet(
|
|
349
|
+
"findVersion 返回首个存在的候选包版本",
|
|
350
|
+
async () => {
|
|
351
|
+
const found = await app.installer.findVersion([
|
|
352
|
+
"@koishijs/plugin-none",
|
|
353
|
+
"koishi-plugin-demo",
|
|
354
|
+
]);
|
|
355
|
+
expect(found).toEqual({
|
|
356
|
+
"koishi-plugin-demo": "2.0.0",
|
|
357
|
+
});
|
|
358
|
+
// 全部不存在时返回 undefined
|
|
359
|
+
expect(
|
|
360
|
+
await app.installer.findVersion([
|
|
361
|
+
"@koishijs/plugin-none",
|
|
362
|
+
]),
|
|
363
|
+
).toBeUndefined();
|
|
364
|
+
},
|
|
365
|
+
);
|
|
255
366
|
|
|
256
|
-
|
|
257
|
-
const versions = await app.installer.getPackage(
|
|
367
|
+
itQuiet("getPackage 拉取失败时回退为空表", async () => {
|
|
368
|
+
const versions = await app.installer.getPackage(
|
|
369
|
+
"koishi-plugin-missing",
|
|
370
|
+
);
|
|
258
371
|
expect(versions).toEqual({});
|
|
259
372
|
});
|
|
260
373
|
|
|
@@ -267,59 +380,79 @@ describe("market 插件", () => {
|
|
|
267
380
|
} as unknown as RemotePackage,
|
|
268
381
|
]);
|
|
269
382
|
expect(
|
|
270
|
-
Object.keys(
|
|
383
|
+
Object.keys(
|
|
384
|
+
app.installer.fullCache["koishi-plugin-demo"] ?? {},
|
|
385
|
+
),
|
|
271
386
|
).toEqual(["3.0.0"]);
|
|
272
387
|
// 等待节流窗口
|
|
273
|
-
await new Promise((resolve) =>
|
|
388
|
+
await new Promise((resolve) =>
|
|
389
|
+
setTimeout(resolve, 600),
|
|
390
|
+
);
|
|
274
391
|
});
|
|
275
392
|
});
|
|
276
393
|
|
|
277
394
|
describe("Installer 安装链路", () => {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
"
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
395
|
+
itQuiet(
|
|
396
|
+
"install 尊重护栏依赖并执行子进程安装",
|
|
397
|
+
async () => {
|
|
398
|
+
nextExitCode = 0;
|
|
399
|
+
spawnCalls.length = 0;
|
|
400
|
+
const code = await app.installer.install({
|
|
401
|
+
koishi: "2.0.0",
|
|
402
|
+
"market-alias": null,
|
|
403
|
+
"koishi-plugin-demo": "^1.0.0",
|
|
404
|
+
});
|
|
405
|
+
expect(code).toBe(0);
|
|
406
|
+
// 触发了包管理器安装(bun install --registry …)
|
|
407
|
+
expect(spawnCalls.length).toBe(1);
|
|
408
|
+
expect(spawnCalls[0]?.[0]).toBe("install");
|
|
409
|
+
// 重新读取临时 package.json:护栏项保持原样,新依赖加入
|
|
410
|
+
const manifest = JSON.parse(
|
|
411
|
+
await Bun.file(join(tmp, "package.json")).text(),
|
|
412
|
+
) as { dependencies: Record<string, string> };
|
|
413
|
+
expect(manifest.dependencies["koishi"]).toBe(
|
|
414
|
+
"workspace:*",
|
|
415
|
+
);
|
|
416
|
+
expect(manifest.dependencies["market-alias"]).toBe(
|
|
417
|
+
"npm:@koishi-ce/anything@^1.0.0",
|
|
418
|
+
);
|
|
419
|
+
expect(
|
|
420
|
+
manifest.dependencies["koishi-plugin-demo"],
|
|
421
|
+
).toBe("^1.0.0");
|
|
422
|
+
},
|
|
423
|
+
15000,
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
itQuiet(
|
|
427
|
+
"install 强制时无视本地满足也要装",
|
|
428
|
+
async () => {
|
|
429
|
+
nextExitCode = 0;
|
|
430
|
+
spawnCalls.length = 0;
|
|
431
|
+
const code = await app.installer.install(
|
|
432
|
+
{ "koishi-plugin-demo": "^1.0.0" },
|
|
433
|
+
true,
|
|
434
|
+
);
|
|
435
|
+
expect(code).toBe(0);
|
|
436
|
+
expect(spawnCalls.length).toBe(1);
|
|
437
|
+
},
|
|
438
|
+
15000,
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
itQuiet(
|
|
442
|
+
"子进程非零退出码向上传递",
|
|
443
|
+
async () => {
|
|
444
|
+
nextExitCode = 1;
|
|
445
|
+
spawnCalls.length = 0;
|
|
446
|
+
const code = await app.installer.install(
|
|
447
|
+
{ "koishi-plugin-demo": "^2.0.0" },
|
|
448
|
+
true,
|
|
449
|
+
);
|
|
450
|
+
expect(code).toBe(1);
|
|
451
|
+
},
|
|
452
|
+
15000,
|
|
453
|
+
);
|
|
321
454
|
|
|
322
|
-
|
|
455
|
+
itQuiet("子进程 spawn 失败返回 -1", async () => {
|
|
323
456
|
nextSpawnError = true;
|
|
324
457
|
nextExitCode = 0;
|
|
325
458
|
const code = await app.installer.exec(["install"]);
|
|
@@ -327,12 +460,15 @@ describe("Installer 安装链路", () => {
|
|
|
327
460
|
expect(code).toBe(-1);
|
|
328
461
|
});
|
|
329
462
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
463
|
+
itQuiet(
|
|
464
|
+
"exec 收集子进程 stdout / stderr 输出行",
|
|
465
|
+
async () => {
|
|
466
|
+
nextExitCode = 0;
|
|
467
|
+
nextSpawnError = false;
|
|
468
|
+
const code = await app.installer.exec(["install"]);
|
|
469
|
+
expect(code).toBe(0);
|
|
470
|
+
},
|
|
471
|
+
);
|
|
336
472
|
});
|
|
337
473
|
|
|
338
474
|
describe("isResidentInCache(装后重载判定)", () => {
|
|
@@ -342,9 +478,16 @@ describe("isResidentInCache(装后重载判定)", () => {
|
|
|
342
478
|
mkdirSync(pkgDir, { recursive: true });
|
|
343
479
|
writeFileSync(
|
|
344
480
|
join(pkgDir, "package.json"),
|
|
345
|
-
JSON.stringify({
|
|
481
|
+
JSON.stringify({
|
|
482
|
+
name,
|
|
483
|
+
version: "1.0.0",
|
|
484
|
+
main: "index.js",
|
|
485
|
+
}),
|
|
486
|
+
);
|
|
487
|
+
writeFileSync(
|
|
488
|
+
join(pkgDir, "index.js"),
|
|
489
|
+
"module.exports = {}",
|
|
346
490
|
);
|
|
347
|
-
writeFileSync(join(pkgDir, "index.js"), "module.exports = {}");
|
|
348
491
|
return pkgDir;
|
|
349
492
|
}
|
|
350
493
|
|
|
@@ -352,16 +495,22 @@ describe("isResidentInCache(装后重载判定)", () => {
|
|
|
352
495
|
const pkgDir = placePkg("koishi-plugin-resident-check");
|
|
353
496
|
// 入口经 require 进入 require.cache,模拟旧版本驻留内存
|
|
354
497
|
require(join(pkgDir, "index.js"));
|
|
355
|
-
expect(
|
|
498
|
+
expect(
|
|
499
|
+
isResidentInCache("koishi-plugin-resident-check"),
|
|
500
|
+
).toBe(true);
|
|
356
501
|
});
|
|
357
502
|
|
|
358
503
|
it("已安装但无模块驻留内存时返回 false", () => {
|
|
359
504
|
placePkg("koishi-plugin-idle-check");
|
|
360
|
-
expect(
|
|
505
|
+
expect(
|
|
506
|
+
isResidentInCache("koishi-plugin-idle-check"),
|
|
507
|
+
).toBe(false);
|
|
361
508
|
});
|
|
362
509
|
|
|
363
510
|
it("包不存在时保守返回 true(宁可多重载不漏判)", () => {
|
|
364
|
-
expect(
|
|
511
|
+
expect(
|
|
512
|
+
isResidentInCache("koishi-plugin-absent-check"),
|
|
513
|
+
).toBe(true);
|
|
365
514
|
});
|
|
366
515
|
});
|
|
367
516
|
|
|
@@ -389,7 +538,9 @@ describe("registry 配置探测", () => {
|
|
|
389
538
|
app3.plugin(http);
|
|
390
539
|
app3.plugin(Installer, {});
|
|
391
540
|
await app3.start();
|
|
392
|
-
expect(app3.installer.endpoint).toBe(
|
|
541
|
+
expect(app3.installer.endpoint).toBe(
|
|
542
|
+
"http://registry.example.npm/",
|
|
543
|
+
);
|
|
393
544
|
await app3.stop();
|
|
394
545
|
rmSync(join(tmp, ".npmrc"), { force: true });
|
|
395
546
|
});
|
|
@@ -410,109 +561,178 @@ describe("MarketProvider 市场数据服务", () => {
|
|
|
410
561
|
keywords: ["koishi", "plugin", "Tool"],
|
|
411
562
|
},
|
|
412
563
|
},
|
|
413
|
-
{
|
|
564
|
+
{
|
|
565
|
+
package: {
|
|
566
|
+
name: "not-a-plugin",
|
|
567
|
+
date: "2024-01-01T00:00:00Z",
|
|
568
|
+
},
|
|
569
|
+
},
|
|
414
570
|
],
|
|
415
571
|
total: 1,
|
|
416
572
|
};
|
|
417
573
|
// start(true) 强制刷新市场数据(重新 collect)
|
|
418
574
|
await svc?.start(true);
|
|
419
575
|
// 等待节流窗口与逐包分析完成
|
|
420
|
-
await new Promise((resolve) =>
|
|
576
|
+
await new Promise((resolve) =>
|
|
577
|
+
setTimeout(resolve, 700),
|
|
578
|
+
);
|
|
421
579
|
const payload = await svc?.get();
|
|
422
580
|
expect(payload).toBeDefined();
|
|
423
581
|
// 非 plugin 条目被剔除,只保留 demo
|
|
424
|
-
expect(Object.keys(payload?.data ?? {})).toEqual([
|
|
582
|
+
expect(Object.keys(payload?.data ?? {})).toEqual([
|
|
583
|
+
"koishi-plugin-demo",
|
|
584
|
+
]);
|
|
425
585
|
expect(payload?.total).toBe(1);
|
|
426
586
|
expect(payload?.failed).toBe(0);
|
|
427
|
-
expect(payload?.registry).toBe(
|
|
587
|
+
expect(payload?.registry).toBe(
|
|
588
|
+
`http://127.0.0.1:${registryServer.port}/`,
|
|
589
|
+
);
|
|
428
590
|
});
|
|
429
591
|
|
|
430
592
|
it("依赖 / 注册表数据服务读取安装器缓存", async () => {
|
|
431
|
-
const dependencies = await app
|
|
432
|
-
|
|
433
|
-
|
|
593
|
+
const dependencies = await app
|
|
594
|
+
.get("console.services.dependencies")
|
|
595
|
+
?.get();
|
|
596
|
+
expect(
|
|
597
|
+
dependencies?.["koishi-plugin-demo"]?.request,
|
|
598
|
+
).toBeTruthy();
|
|
599
|
+
expect(
|
|
600
|
+
dependencies?.["koishi-plugin-demo"]?.latest,
|
|
601
|
+
).toBe("2.0.0");
|
|
434
602
|
|
|
435
|
-
const registry = await app
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
603
|
+
const registry = await app
|
|
604
|
+
.get("console.services.registry")
|
|
605
|
+
?.get();
|
|
606
|
+
expect(
|
|
607
|
+
Object.keys(registry?.["koishi-plugin-demo"] ?? {}),
|
|
608
|
+
).toContain("2.0.0");
|
|
439
609
|
});
|
|
440
610
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
611
|
+
itQuiet(
|
|
612
|
+
"搜索接口失败时 get 返回空数据与错误标记",
|
|
613
|
+
async () => {
|
|
614
|
+
const svc = app.get("console.services.market");
|
|
615
|
+
searchResponse = null;
|
|
616
|
+
// 强制重扫:collect 失败置 _error,get 返回空 payload
|
|
617
|
+
await svc?.start(true);
|
|
618
|
+
const payload = await svc?.get();
|
|
619
|
+
expect(payload).toEqual({
|
|
620
|
+
data: {},
|
|
621
|
+
failed: 0,
|
|
622
|
+
total: 0,
|
|
623
|
+
progress: 0,
|
|
624
|
+
});
|
|
625
|
+
searchResponse = {
|
|
626
|
+
objects: [],
|
|
627
|
+
total: 0,
|
|
628
|
+
};
|
|
629
|
+
},
|
|
630
|
+
);
|
|
453
631
|
|
|
454
632
|
it("控制台连接事件在数据过期时触发刷新", async () => {
|
|
455
633
|
const svc = app.get("console.services.market");
|
|
456
634
|
expect(svc).toBeDefined();
|
|
457
635
|
// 伪造一个在线客户端,使连接事件通过在线检查(broadcast 需可用的 socket)
|
|
458
|
-
const fakeClient = {
|
|
459
|
-
|
|
636
|
+
const fakeClient = {
|
|
637
|
+
id: "conn-1",
|
|
638
|
+
socket: { send() {} },
|
|
639
|
+
};
|
|
640
|
+
(app.console.clients as Record<string, unknown>)[
|
|
641
|
+
"conn-1"
|
|
642
|
+
] = fakeClient;
|
|
460
643
|
// 刚刷新过:12 小时窗口内直接返回,不重新收集
|
|
461
|
-
const timestamp = svc?.[
|
|
644
|
+
const timestamp = svc?.[
|
|
645
|
+
"_timestamp" as keyof typeof svc
|
|
646
|
+
] as number;
|
|
462
647
|
// console/connection 载荷声明为 Client,桩对象仅含在线检查所需的最小面,类型层断言穿透
|
|
463
648
|
app.emit("console/connection", fakeClient as never);
|
|
464
|
-
expect(
|
|
649
|
+
expect(
|
|
650
|
+
svc?.["_timestamp" as keyof typeof svc] as number,
|
|
651
|
+
).toBe(timestamp);
|
|
465
652
|
// 将时间戳回拨到窗口外,连接事件重新触发 start(异步监听,稍等)
|
|
466
653
|
(svc as Record<string, unknown>)["_timestamp"] = 0;
|
|
467
654
|
app.emit("console/connection", fakeClient as never);
|
|
468
655
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
469
656
|
expect(
|
|
470
|
-
(svc as Record<string, unknown>)[
|
|
657
|
+
(svc as Record<string, unknown>)[
|
|
658
|
+
"_timestamp"
|
|
659
|
+
] as number,
|
|
471
660
|
).toBeGreaterThan(0);
|
|
472
661
|
delete app.console.clients["conn-1"];
|
|
473
662
|
});
|
|
474
663
|
});
|
|
475
664
|
|
|
476
665
|
describe("market 聊天指令", () => {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
666
|
+
itQuiet(
|
|
667
|
+
"plugin.install 缺参与未找到的报错路径",
|
|
668
|
+
async () => {
|
|
669
|
+
const missing = await client.receive(
|
|
670
|
+
"plugin.install",
|
|
671
|
+
);
|
|
672
|
+
expect(missing[0]).toContain("请输入插件名。");
|
|
673
|
+
const notFound = await client.receive(
|
|
674
|
+
"plugin.install absent-pkg",
|
|
675
|
+
);
|
|
676
|
+
expect(notFound[0]).toContain("未找到该插件。");
|
|
677
|
+
},
|
|
678
|
+
);
|
|
483
679
|
|
|
484
680
|
it("plugin.install 已安装时提示重复", async () => {
|
|
485
|
-
const replies = await client.receive(
|
|
681
|
+
const replies = await client.receive(
|
|
682
|
+
"plugin.install demo",
|
|
683
|
+
);
|
|
486
684
|
expect(replies[0]).toContain("该插件已安装。");
|
|
487
685
|
});
|
|
488
686
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
687
|
+
itQuiet(
|
|
688
|
+
"plugin.install 安装新插件并写入依赖",
|
|
689
|
+
async () => {
|
|
690
|
+
nextExitCode = 0;
|
|
691
|
+
spawnCalls.length = 0;
|
|
692
|
+
const replies = await client.receive(
|
|
693
|
+
"plugin.install newpkg",
|
|
694
|
+
);
|
|
695
|
+
expect(replies[0]).toContain("安装成功!");
|
|
696
|
+
expect(spawnCalls.length).toBe(1);
|
|
697
|
+
const manifest = JSON.parse(
|
|
698
|
+
await Bun.file(join(tmp, "package.json")).text(),
|
|
699
|
+
) as { dependencies: Record<string, string> };
|
|
700
|
+
expect(
|
|
701
|
+
manifest.dependencies["koishi-plugin-newpkg"],
|
|
702
|
+
).toBe("1.0.0");
|
|
703
|
+
// 重启消息在安装完成后复位(Loader 与桩形状不同,经 unknown 二段式断言)
|
|
704
|
+
expect(
|
|
705
|
+
(app.loader as unknown as FakeLoader).envData[
|
|
706
|
+
"message"
|
|
707
|
+
],
|
|
708
|
+
).toBeNull();
|
|
709
|
+
},
|
|
710
|
+
15000,
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
itQuiet(
|
|
714
|
+
"plugin.uninstall 卸载依赖并从清单移除",
|
|
715
|
+
async () => {
|
|
716
|
+
nextExitCode = 0;
|
|
717
|
+
spawnCalls.length = 0;
|
|
718
|
+
const replies = await client.receive(
|
|
719
|
+
"plugin.uninstall newpkg",
|
|
720
|
+
);
|
|
721
|
+
expect(replies[0]).toContain("卸载成功!");
|
|
722
|
+
const manifest = JSON.parse(
|
|
723
|
+
await Bun.file(join(tmp, "package.json")).text(),
|
|
724
|
+
) as { dependencies: Record<string, string> };
|
|
725
|
+
expect(
|
|
726
|
+
manifest.dependencies["koishi-plugin-newpkg"],
|
|
727
|
+
).toBeUndefined();
|
|
728
|
+
},
|
|
729
|
+
15000,
|
|
730
|
+
);
|
|
513
731
|
|
|
514
732
|
it("plugin.uninstall 未安装时提示", async () => {
|
|
515
|
-
const replies = await client.receive(
|
|
733
|
+
const replies = await client.receive(
|
|
734
|
+
"plugin.uninstall absent-pkg",
|
|
735
|
+
);
|
|
516
736
|
expect(replies[0]).toContain("该插件未安装。");
|
|
517
737
|
});
|
|
518
738
|
|
|
@@ -523,48 +743,73 @@ describe("market 聊天指令", () => {
|
|
|
523
743
|
});
|
|
524
744
|
|
|
525
745
|
describe("market 进阶链路", () => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
746
|
+
itQuiet("宿主配置不可写时不加载安装器", async () => {
|
|
747
|
+
// apply 的「仅告警并跳过」正是被测行为,静默 app 域避免预期告警刷屏
|
|
748
|
+
const levels = Logger.levels as Record<string, number>;
|
|
749
|
+
levels["app"] = 0;
|
|
750
|
+
try {
|
|
751
|
+
const appNoLoader = new App();
|
|
752
|
+
appNoLoader.plugin(http);
|
|
753
|
+
appNoLoader.plugin(
|
|
754
|
+
FakeConsole as unknown as Plugin.Constructor<TestApp>,
|
|
755
|
+
);
|
|
756
|
+
appNoLoader.plugin(market, {
|
|
757
|
+
registry: {
|
|
758
|
+
endpoint: `http://127.0.0.1:${registryServer.port}/`,
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
await appNoLoader.start();
|
|
762
|
+
// apply 在 loader 缺席时仅告警并提前返回
|
|
763
|
+
expect(appNoLoader.installer).toBeUndefined();
|
|
764
|
+
await appNoLoader.stop();
|
|
765
|
+
} finally {
|
|
766
|
+
delete levels["app"];
|
|
767
|
+
}
|
|
537
768
|
});
|
|
538
769
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
770
|
+
itQuiet(
|
|
771
|
+
"浏览器 market/install 监听器执行安装并刷新服务",
|
|
772
|
+
async () => {
|
|
773
|
+
nextExitCode = 0;
|
|
774
|
+
const listener =
|
|
775
|
+
app.console.listeners["market/install"];
|
|
776
|
+
expect(listener).toBeDefined();
|
|
777
|
+
const code = (await listener?.callback.call(
|
|
778
|
+
{} as never,
|
|
779
|
+
{ "koishi-plugin-newpkg": "1.0.0" },
|
|
780
|
+
true,
|
|
781
|
+
)) as number;
|
|
782
|
+
expect(code).toBe(0);
|
|
783
|
+
},
|
|
784
|
+
15000,
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
itQuiet(
|
|
788
|
+
"浏览器 market/registry 监听器批量查询包元数据",
|
|
789
|
+
async () => {
|
|
790
|
+
const listener =
|
|
791
|
+
app.console.listeners["market/registry"];
|
|
792
|
+
expect(listener).toBeDefined();
|
|
793
|
+
const meta = (await listener?.callback.call(
|
|
794
|
+
{} as never,
|
|
795
|
+
["koishi-plugin-demo", "koishi-plugin-missing"],
|
|
796
|
+
)) as Record<string, unknown>;
|
|
797
|
+
expect(
|
|
798
|
+
Object.keys(meta["koishi-plugin-demo"] ?? {}),
|
|
799
|
+
).toContain("2.0.0");
|
|
800
|
+
expect(meta["koishi-plugin-missing"]).toEqual({});
|
|
801
|
+
},
|
|
802
|
+
);
|
|
561
803
|
|
|
562
804
|
it("搜索结果中不兼容的包被跳过(analyze onSkipped/ignored)", async () => {
|
|
563
805
|
const svc = app.get("console.services.market");
|
|
564
806
|
// ghost 有 registry 条目,但版本声明的 koishi peer 与 4.x 不相交
|
|
565
807
|
registryData["koishi-plugin-ghost"] = {
|
|
566
808
|
versions: {
|
|
567
|
-
"1.0.0": {
|
|
809
|
+
"1.0.0": {
|
|
810
|
+
version: "1.0.0",
|
|
811
|
+
peerDependencies: { koishi: "^5.0.0" },
|
|
812
|
+
},
|
|
568
813
|
},
|
|
569
814
|
time: { "1.0.0": "2024-01-01T00:00:00Z" },
|
|
570
815
|
};
|
|
@@ -582,7 +827,9 @@ describe("market 进阶链路", () => {
|
|
|
582
827
|
};
|
|
583
828
|
await svc?.start(true);
|
|
584
829
|
// collect 对 analyze 为即发即忘,等待逐包分析完成
|
|
585
|
-
await new Promise((resolve) =>
|
|
830
|
+
await new Promise((resolve) =>
|
|
831
|
+
setTimeout(resolve, 300),
|
|
832
|
+
);
|
|
586
833
|
const payload = await svc?.get();
|
|
587
834
|
// 无兼容版本:对象标记 ignored,不进入数据缓存。
|
|
588
835
|
// progress 恒为 0:Scanner 以 defineProperty 定义 progress(不可写),
|
|
@@ -594,83 +841,135 @@ describe("market 进阶链路", () => {
|
|
|
594
841
|
delete registryData["koishi-plugin-ghost"];
|
|
595
842
|
});
|
|
596
843
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
844
|
+
itQuiet(
|
|
845
|
+
"搜索结果中被限流的包经重试后计入 failed(onFailure)",
|
|
846
|
+
async () => {
|
|
847
|
+
const svc = app.get("console.services.market");
|
|
848
|
+
searchResponse = {
|
|
849
|
+
objects: [
|
|
850
|
+
{
|
|
851
|
+
package: {
|
|
852
|
+
name: "koishi-plugin-ratelimited",
|
|
853
|
+
version: "1.0.0",
|
|
854
|
+
date: "2024-01-01T00:00:00Z",
|
|
855
|
+
},
|
|
856
|
+
},
|
|
857
|
+
],
|
|
858
|
+
total: 1,
|
|
859
|
+
};
|
|
860
|
+
await svc?.start(true);
|
|
861
|
+
// 等待限流重试(Retry-After 10ms × 3 次)与即发即忘的 analyze。
|
|
862
|
+
// 注意不能经 get() 断言:super.start() 会清空 _task,get() 触发的
|
|
863
|
+
// 二次 collect 会把 failed 重置(即发即忘的 analyze 尚未完成)。
|
|
864
|
+
await new Promise((resolve) =>
|
|
865
|
+
setTimeout(resolve, 400),
|
|
866
|
+
);
|
|
867
|
+
// 不可达/被限流的包名进入 failed 列表(上一个用例中 registry
|
|
868
|
+
// 条目已删除的 ghost 包经 404 路径同样落入此处)
|
|
869
|
+
const provider = svc as unknown as {
|
|
870
|
+
failed: string[];
|
|
871
|
+
};
|
|
872
|
+
expect(
|
|
873
|
+
provider.failed.some((name) =>
|
|
874
|
+
name.startsWith("koishi-plugin-"),
|
|
875
|
+
),
|
|
876
|
+
).toBe(true);
|
|
877
|
+
},
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
itQuiet(
|
|
881
|
+
"plugin.upgrade 检出可升级项并输出确认提示",
|
|
882
|
+
async () => {
|
|
883
|
+
// 本地放置旧版安装,使 resolved 有值且低于远端 latest
|
|
884
|
+
mkdirSync(
|
|
885
|
+
join(tmp, "node_modules", "koishi-plugin-demo"),
|
|
601
886
|
{
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
887
|
+
recursive: true,
|
|
888
|
+
},
|
|
889
|
+
);
|
|
890
|
+
writeFileSync(
|
|
891
|
+
join(
|
|
892
|
+
tmp,
|
|
893
|
+
"node_modules",
|
|
894
|
+
"koishi-plugin-demo",
|
|
895
|
+
"package.json",
|
|
896
|
+
),
|
|
897
|
+
JSON.stringify({
|
|
898
|
+
name: "koishi-plugin-demo",
|
|
899
|
+
version: "1.0.0",
|
|
900
|
+
}),
|
|
901
|
+
);
|
|
902
|
+
app.installer.refresh();
|
|
903
|
+
|
|
904
|
+
nextExitCode = 0;
|
|
905
|
+
// 发出升级指令(异步等待确认),再以 Y 回复确认。
|
|
906
|
+
// 注:mock 环境下指令 ctx 对 loader 服务的可见性受 cordis
|
|
907
|
+
// isolate 语义限制(见仓库测试任务记录),确认后的安装段
|
|
908
|
+
// 由 market/install 监听器用例覆盖。
|
|
909
|
+
const question = client.receive(
|
|
910
|
+
"plugin.upgrade demo",
|
|
911
|
+
);
|
|
912
|
+
await new Promise((resolve) =>
|
|
913
|
+
setTimeout(resolve, 200),
|
|
914
|
+
);
|
|
915
|
+
await client.receive("Y");
|
|
916
|
+
const replies = await question;
|
|
917
|
+
const output = replies.join("\n");
|
|
918
|
+
expect(output).toContain("koishi-plugin-demo");
|
|
919
|
+
expect(output).toContain("1.0.0 -> 2.0.0");
|
|
920
|
+
},
|
|
921
|
+
20000,
|
|
922
|
+
);
|
|
923
|
+
|
|
924
|
+
itQuiet(
|
|
925
|
+
"plugin.upgrade 对本地畸形版本静默跳过(非法 semver catch)",
|
|
926
|
+
async () => {
|
|
927
|
+
// registry 侧提供合法 latest;本地安装产物的 version 是畸形串,
|
|
928
|
+
// request(清单声明)合法故不标记 invalid,gt(latest, resolved)
|
|
929
|
+
// 解析失败进入 catch 分支:该包被过滤,视为无可升级项
|
|
930
|
+
registryData["koishi-plugin-weird"] = {
|
|
931
|
+
versions: {
|
|
932
|
+
"2.0.0": {
|
|
933
|
+
version: "2.0.0",
|
|
934
|
+
peerDependencies: { koishi: "^4.17.0" },
|
|
606
935
|
},
|
|
607
936
|
},
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
expect(output).toContain("1.0.0 -> 2.0.0");
|
|
647
|
-
}, 20000);
|
|
648
|
-
|
|
649
|
-
it("plugin.upgrade 对本地畸形版本静默跳过(非法 semver catch)", async () => {
|
|
650
|
-
// registry 侧提供合法 latest;本地安装产物的 version 是畸形串,
|
|
651
|
-
// request(清单声明)合法故不标记 invalid,gt(latest, resolved)
|
|
652
|
-
// 解析失败进入 catch 分支:该包被过滤,视为无可升级项
|
|
653
|
-
registryData["koishi-plugin-weird"] = {
|
|
654
|
-
versions: {
|
|
655
|
-
"2.0.0": { version: "2.0.0", peerDependencies: { koishi: "^4.17.0" } },
|
|
656
|
-
},
|
|
657
|
-
};
|
|
658
|
-
mkdirSync(join(tmp, "node_modules", "koishi-plugin-weird"), {
|
|
659
|
-
recursive: true,
|
|
660
|
-
});
|
|
661
|
-
writeFileSync(
|
|
662
|
-
join(tmp, "node_modules", "koishi-plugin-weird", "package.json"),
|
|
663
|
-
JSON.stringify({ name: "koishi-plugin-weird", version: "not.a.version" }),
|
|
664
|
-
);
|
|
665
|
-
nextExitCode = 0;
|
|
666
|
-
// 经 install 注入清单声明(顺带刷新 Installer 的 manifest 快照)
|
|
667
|
-
const code = await app.installer.install({
|
|
668
|
-
"koishi-plugin-weird": "1.0.0",
|
|
669
|
-
});
|
|
670
|
-
expect(code).toBe(0);
|
|
671
|
-
const deps = await app.installer.getDeps();
|
|
672
|
-
expect(deps["koishi-plugin-weird"]?.resolved).toBe("not.a.version");
|
|
673
|
-
const replies = await client.receive("plugin.upgrade weird");
|
|
674
|
-
expect(replies[0]).toContain("所有插件已是最新版本。");
|
|
675
|
-
}, 20000);
|
|
937
|
+
};
|
|
938
|
+
mkdirSync(
|
|
939
|
+
join(tmp, "node_modules", "koishi-plugin-weird"),
|
|
940
|
+
{
|
|
941
|
+
recursive: true,
|
|
942
|
+
},
|
|
943
|
+
);
|
|
944
|
+
writeFileSync(
|
|
945
|
+
join(
|
|
946
|
+
tmp,
|
|
947
|
+
"node_modules",
|
|
948
|
+
"koishi-plugin-weird",
|
|
949
|
+
"package.json",
|
|
950
|
+
),
|
|
951
|
+
JSON.stringify({
|
|
952
|
+
name: "koishi-plugin-weird",
|
|
953
|
+
version: "not.a.version",
|
|
954
|
+
}),
|
|
955
|
+
);
|
|
956
|
+
nextExitCode = 0;
|
|
957
|
+
// 经 install 注入清单声明(顺带刷新 Installer 的 manifest 快照)
|
|
958
|
+
const code = await app.installer.install({
|
|
959
|
+
"koishi-plugin-weird": "1.0.0",
|
|
960
|
+
});
|
|
961
|
+
expect(code).toBe(0);
|
|
962
|
+
const deps = await app.installer.getDeps();
|
|
963
|
+
expect(deps["koishi-plugin-weird"]?.resolved).toBe(
|
|
964
|
+
"not.a.version",
|
|
965
|
+
);
|
|
966
|
+
const replies = await client.receive(
|
|
967
|
+
"plugin.upgrade weird",
|
|
968
|
+
);
|
|
969
|
+
expect(replies[0]).toContain(
|
|
970
|
+
"所有插件已是最新版本。",
|
|
971
|
+
);
|
|
972
|
+
},
|
|
973
|
+
20000,
|
|
974
|
+
);
|
|
676
975
|
});
|