@koishi-ce/plugin-market 1.0.12 → 1.0.13
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 +3 -3
- package/lib/index.d.ts +4 -0
- package/lib/index.mjs +5 -1
- package/package.json +5 -4
- package/src/__tests__/index.test.ts +71 -6
- package/src/node/installer.ts +1 -1
- package/src/node/market.ts +11 -3
package/lib/index.d.ts
CHANGED
|
@@ -113,6 +113,10 @@ declare class MarketProvider$1 extends MarketProvider {
|
|
|
113
113
|
* 服务端瞬态错误(408 / 5xx)可安全重试;allowNotFound 时把 404
|
|
114
114
|
* 也视为瞬态——镜像索引以「302 指向版本化文件」方式发布,切换文件
|
|
115
115
|
* 的部署窗口会出现短时 404。
|
|
116
|
+
*
|
|
117
|
+
* 无 code 且无 response 的 HTTP.Error 是 plugin-http 对 fetch 网络层
|
|
118
|
+
* 异常(证书验证失败、连接重置、DNS 等)的统一包装——代理节点切换
|
|
119
|
+
* 等瞬态网络抖动即此形态,重试可自愈。
|
|
116
120
|
*/
|
|
117
121
|
private isRetryable;
|
|
118
122
|
/**
|
package/lib/index.mjs
CHANGED
|
@@ -359,10 +359,14 @@ var MarketProvider$1 = class extends MarketProvider {
|
|
|
359
359
|
* 服务端瞬态错误(408 / 5xx)可安全重试;allowNotFound 时把 404
|
|
360
360
|
* 也视为瞬态——镜像索引以「302 指向版本化文件」方式发布,切换文件
|
|
361
361
|
* 的部署窗口会出现短时 404。
|
|
362
|
+
*
|
|
363
|
+
* 无 code 且无 response 的 HTTP.Error 是 plugin-http 对 fetch 网络层
|
|
364
|
+
* 异常(证书验证失败、连接重置、DNS 等)的统一包装——代理节点切换
|
|
365
|
+
* 等瞬态网络抖动即此形态,重试可自愈。
|
|
362
366
|
*/
|
|
363
367
|
isRetryable(error, allowNotFound = false) {
|
|
364
368
|
if (!this.ctx.http.isError(error)) return false;
|
|
365
|
-
if (!error.response) return error.code === "ETIMEDOUT";
|
|
369
|
+
if (!error.response) return error.code === void 0 || error.code === "ETIMEDOUT";
|
|
366
370
|
const { status } = error.response;
|
|
367
371
|
return status === 429 || status === 408 || allowNotFound && status === 404 || status !== void 0 && status >= 500;
|
|
368
372
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koishi-ce/plugin-market",
|
|
3
3
|
"description": "Manage your bots and plugins with console",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.13",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
7
7
|
"typings": "lib/index.d.ts",
|
|
@@ -60,9 +60,10 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@koishi-ce/
|
|
64
|
-
"@koishi-ce/
|
|
65
|
-
"@koishi-ce/
|
|
63
|
+
"@koishi-ce/plugin-database-memory": "^1.1.0",
|
|
64
|
+
"@koishi-ce/client": "^1.2.3",
|
|
65
|
+
"@koishi-ce/loader": "^1.0.8",
|
|
66
|
+
"@koishi-ce/plugin-config": "^1.1.2",
|
|
66
67
|
"@koishijs/market": "^4.2.10",
|
|
67
68
|
"@types/semver": "^7.5.8",
|
|
68
69
|
"@vueuse/core": "^14.4.0",
|
|
@@ -16,9 +16,10 @@ import {
|
|
|
16
16
|
rmSync,
|
|
17
17
|
writeFileSync,
|
|
18
18
|
} from "node:fs";
|
|
19
|
+
import net from "node:net";
|
|
19
20
|
import { tmpdir } from "node:os";
|
|
20
21
|
import { join } from "node:path";
|
|
21
|
-
import memory from "@
|
|
22
|
+
import memory from "@koishi-ce/plugin-database-memory";
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* market 插件测试:
|
|
@@ -297,8 +298,7 @@ type TestApp = InstanceType<typeof App>;
|
|
|
297
298
|
|
|
298
299
|
const app = new App();
|
|
299
300
|
|
|
300
|
-
|
|
301
|
-
app.plugin(memory as unknown as typeof memory.default);
|
|
301
|
+
app.plugin(memory);
|
|
302
302
|
app.plugin(http);
|
|
303
303
|
// Console 基类的 static inject 是 cordis 3 旧形态,与 Plugin.Constructor 期待类型不兼容,仅做类型层转型
|
|
304
304
|
app.plugin(
|
|
@@ -1014,9 +1014,7 @@ describe("镜像索引瞬态重试", () => {
|
|
|
1014
1014
|
timeout?: number;
|
|
1015
1015
|
}) {
|
|
1016
1016
|
const mirror = new App();
|
|
1017
|
-
mirror.plugin(
|
|
1018
|
-
memory as unknown as typeof memory.default,
|
|
1019
|
-
);
|
|
1017
|
+
mirror.plugin(memory);
|
|
1020
1018
|
mirror.plugin(http);
|
|
1021
1019
|
mirror.plugin(
|
|
1022
1020
|
FakeConsole as unknown as Plugin.Constructor<TestApp>,
|
|
@@ -1072,4 +1070,71 @@ describe("镜像索引瞬态重试", () => {
|
|
|
1072
1070
|
},
|
|
1073
1071
|
15000,
|
|
1074
1072
|
);
|
|
1073
|
+
|
|
1074
|
+
// 裸 TCP 桩:前 planned 次请求收到请求头后直接销毁连接(复现
|
|
1075
|
+
// 证书验证失败 / 连接重置等 fetch 网络层瞬态错误),随后回正常索引
|
|
1076
|
+
function startFlakyTcpServer(planned: number) {
|
|
1077
|
+
let hits = 0;
|
|
1078
|
+
const server = net.createServer((socket) => {
|
|
1079
|
+
socket.on("data", () => {
|
|
1080
|
+
hits++;
|
|
1081
|
+
if (hits <= planned) {
|
|
1082
|
+
socket.destroy();
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const body = JSON.stringify({
|
|
1086
|
+
version: "4",
|
|
1087
|
+
objects: [
|
|
1088
|
+
{
|
|
1089
|
+
package: {
|
|
1090
|
+
name: "koishi-plugin-demo",
|
|
1091
|
+
version: "2.0.0",
|
|
1092
|
+
date: "2024-06-01T00:00:00Z",
|
|
1093
|
+
keywords: ["koishi", "plugin"],
|
|
1094
|
+
},
|
|
1095
|
+
},
|
|
1096
|
+
],
|
|
1097
|
+
});
|
|
1098
|
+
socket.end(
|
|
1099
|
+
`HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: ${body.length}\r\nConnection: close\r\n\r\n${body}`,
|
|
1100
|
+
);
|
|
1101
|
+
});
|
|
1102
|
+
});
|
|
1103
|
+
return new Promise<{
|
|
1104
|
+
port: number;
|
|
1105
|
+
hits: () => number;
|
|
1106
|
+
close: () => void;
|
|
1107
|
+
}>((resolve) => {
|
|
1108
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1109
|
+
const { port } =
|
|
1110
|
+
server.address() as net.AddressInfo;
|
|
1111
|
+
resolve({
|
|
1112
|
+
port,
|
|
1113
|
+
hits: () => hits,
|
|
1114
|
+
close: () => {
|
|
1115
|
+
server.close();
|
|
1116
|
+
},
|
|
1117
|
+
});
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
it("网络层瞬态错误(连接重置)经退避重试后恢复", async () => {
|
|
1123
|
+
const stub = await startFlakyTcpServer(2);
|
|
1124
|
+
const mirror = await createMirrorApp({
|
|
1125
|
+
endpoint: `http://127.0.0.1:${stub.port}/`,
|
|
1126
|
+
});
|
|
1127
|
+
const svc = mirror.get("console.services.market");
|
|
1128
|
+
// 前两次连接被重置(fetch 抛 TypeError,plugin-http 包装为无 code
|
|
1129
|
+
// 无 response 的 HTTP.Error),退避重试后第三次成功
|
|
1130
|
+
await svc?.start(true);
|
|
1131
|
+
expect(stub.hits()).toBeGreaterThanOrEqual(3);
|
|
1132
|
+
const payload = await svc?.get();
|
|
1133
|
+
expect(Object.keys(payload?.data ?? {})).toEqual([
|
|
1134
|
+
"koishi-plugin-demo",
|
|
1135
|
+
]);
|
|
1136
|
+
expect(payload?.total).toBe(1);
|
|
1137
|
+
await mirror.stop();
|
|
1138
|
+
stub.close();
|
|
1139
|
+
}, 10000);
|
|
1075
1140
|
});
|
package/src/node/installer.ts
CHANGED
|
@@ -174,7 +174,7 @@ class Installer extends Service {
|
|
|
174
174
|
Promise<Dict<Pick<RemotePackage, DependencyMetaKey>>>
|
|
175
175
|
> = {};
|
|
176
176
|
private manifest: LocalPackage;
|
|
177
|
-
private
|
|
177
|
+
declare private depTask: Promise<Dict<Dependency>>;
|
|
178
178
|
private flushData: () => void;
|
|
179
179
|
|
|
180
180
|
override config: Installer.Config;
|
package/src/node/market.ts
CHANGED
|
@@ -22,8 +22,8 @@ const logger = new Logger("market");
|
|
|
22
22
|
|
|
23
23
|
// registry 接口对无认证请求有速率限制:搜索接口(/-/v1/search)超频时
|
|
24
24
|
// 返回 429,collect 一旦失败会中断整个市场数据刷新(prepare 置 _error,
|
|
25
|
-
//
|
|
26
|
-
//
|
|
25
|
+
// 页面清空)。对限流、请求超时、fetch 网络层瞬态错误与 5xx 做指数退避
|
|
26
|
+
// 重试;镜像索引的 404(部署窗口内版本化文件尚未就绪)同样按瞬态处理。
|
|
27
27
|
/** 首次请求失败后的最大重试次数 */
|
|
28
28
|
const maxRetries = 2;
|
|
29
29
|
/** 重试的基础退避时长,按尝试次数指数放大 */
|
|
@@ -69,13 +69,21 @@ class MarketProvider extends BaseMarketProvider {
|
|
|
69
69
|
* 服务端瞬态错误(408 / 5xx)可安全重试;allowNotFound 时把 404
|
|
70
70
|
* 也视为瞬态——镜像索引以「302 指向版本化文件」方式发布,切换文件
|
|
71
71
|
* 的部署窗口会出现短时 404。
|
|
72
|
+
*
|
|
73
|
+
* 无 code 且无 response 的 HTTP.Error 是 plugin-http 对 fetch 网络层
|
|
74
|
+
* 异常(证书验证失败、连接重置、DNS 等)的统一包装——代理节点切换
|
|
75
|
+
* 等瞬态网络抖动即此形态,重试可自愈。
|
|
72
76
|
*/
|
|
73
77
|
private isRetryable(
|
|
74
78
|
error: unknown,
|
|
75
79
|
allowNotFound = false,
|
|
76
80
|
): error is HTTP.Error {
|
|
77
81
|
if (!this.ctx.http.isError(error)) return false;
|
|
78
|
-
if (!error.response)
|
|
82
|
+
if (!error.response)
|
|
83
|
+
return (
|
|
84
|
+
error.code === undefined ||
|
|
85
|
+
error.code === "ETIMEDOUT"
|
|
86
|
+
);
|
|
79
87
|
const { status } = error.response;
|
|
80
88
|
return (
|
|
81
89
|
status === 429 ||
|