@koishi-ce/plugin-market 1.0.8 → 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.
@@ -41,7 +41,8 @@ class MarketProvider extends BaseMarketProvider {
41
41
  constructor(ctx: Context, config: MarketProvider.Config) {
42
42
  super(ctx);
43
43
  this.config = config;
44
- if (config.endpoint) this.http = ctx.http.extend(config);
44
+ if (config.endpoint)
45
+ this.http = ctx.http.extend(config);
45
46
  this.flushData = ctx.throttle(() => {
46
47
  ctx.console.broadcast("market/patch", {
47
48
  data: this.tempCache,
@@ -88,7 +89,11 @@ class MarketProvider extends BaseMarketProvider {
88
89
  // 可调用的 HTTP 实例本身),须包一层保持绑定
89
90
  return await http.get<T>(url, config);
90
91
  } catch (error) {
91
- if (attempt >= maxRetries || !this.isRetryable(error)) throw error;
92
+ if (
93
+ attempt >= maxRetries ||
94
+ !this.isRetryable(error)
95
+ )
96
+ throw error;
92
97
  const retryAfter = Number(
93
98
  error.response?.headers.get("retry-after") ?? "",
94
99
  );
@@ -113,16 +118,25 @@ class MarketProvider extends BaseMarketProvider {
113
118
  const registry = this.ctx.installer.http;
114
119
 
115
120
  this.failed = [];
116
- this.scanner = new Scanner(<T>(url: string, config?: RequestConfig) =>
117
- this.request<T>(registry, url, config),
121
+ this.scanner = new Scanner(
122
+ <T>(url: string, config?: RequestConfig) =>
123
+ this.request<T>(registry, url, config),
118
124
  );
119
125
  if (this.http) {
120
- const result = await this.request<SearchResult>(this.http, "");
121
- this.scanner.objects = result.objects.filter((object) => !object.ignored);
126
+ const result = await this.request<SearchResult>(
127
+ this.http,
128
+ "",
129
+ );
130
+ this.scanner.objects = result.objects.filter(
131
+ (object) => !object.ignored,
132
+ );
122
133
  this.scanner.total = this.scanner.objects.length;
123
- if (result.version !== undefined) this.scanner.version = result.version;
134
+ if (result.version !== undefined)
135
+ this.scanner.version = result.version;
124
136
  } else {
125
- await this.scanner.collect(timeout !== undefined ? { timeout } : {});
137
+ await this.scanner.collect(
138
+ timeout !== undefined ? { timeout } : {},
139
+ );
126
140
  }
127
141
 
128
142
  if (!this.scanner.version) {
@@ -150,16 +164,18 @@ class MarketProvider extends BaseMarketProvider {
150
164
  }
151
165
  },
152
166
  onRegistry: (registry, versions) => {
153
- this.ctx.installer.setPackage(registry.name, versions);
167
+ this.ctx.installer.setPackage(
168
+ registry.name,
169
+ versions,
170
+ );
154
171
  },
155
172
  onSuccess: (object, _versions) => {
156
173
  // npmmirror lacks `links` field
157
174
  object.package.links ||= {
158
175
  npm: `${registry.config.endpoint?.replace("registry.", "www.") ?? ""}/package/${object.package.name}`,
159
176
  };
160
- this.fullCache[object.package.name] = this.tempCache[
161
- object.package.name
162
- ] = object;
177
+ this.fullCache[object.package.name] =
178
+ this.tempCache[object.package.name] = object;
163
179
  },
164
180
  after: () => this.flushData(),
165
181
  });
@@ -170,13 +186,17 @@ class MarketProvider extends BaseMarketProvider {
170
186
 
171
187
  override async get() {
172
188
  await this.prepare();
173
- if (this._error) return { data: {}, failed: 0, total: 0, progress: 0 };
189
+ if (this._error)
190
+ return { data: {}, failed: 0, total: 0, progress: 0 };
174
191
  const gravatar = process.env["GRAVATAR_MIRROR"];
175
192
  return this.scanner.version
176
193
  ? {
177
194
  registry: this.ctx.installer.endpoint,
178
195
  data: Object.fromEntries(
179
- this.scanner.objects.map((item) => [item.package.name, item]),
196
+ this.scanner.objects.map((item) => [
197
+ item.package.name,
198
+ item,
199
+ ]),
180
200
  ),
181
201
  failed: 0,
182
202
  total: this.scanner.total,
@@ -195,13 +215,14 @@ class MarketProvider extends BaseMarketProvider {
195
215
 
196
216
  // erasableSyntaxOnly 禁止含运行时值的 namespace,
197
217
  // 原 namespace 内的 Config 常量移到此处的静态字段,对外形状不变
198
- static Config: Schema<MarketProvider.Config> = Schema.object({
199
- endpoint: Schema.string().role("link"),
200
- timeout: Schema.number()
201
- .role("time")
202
- .default(Time.second * 30),
203
- proxyAgent: Schema.string().role("link"),
204
- });
218
+ static Config: Schema<MarketProvider.Config> =
219
+ Schema.object({
220
+ endpoint: Schema.string().role("link"),
221
+ timeout: Schema.number()
222
+ .role("time")
223
+ .default(Time.second * 30),
224
+ proxyAgent: Schema.string().role("link"),
225
+ });
205
226
  }
206
227
 
207
228
  declare namespace MarketProvider {
@@ -0,0 +1,31 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ // Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ /**
6
+ * 安装子进程的创建封装(原依赖 execa)。
7
+ *
8
+ * execa 本质是 node:child_process 的事件流封装,本仓只用到其
9
+ * spawn + exit/error 事件 + stdout/stderr 流读取的子集,直接用
10
+ * node:child_process 等价实现即可,Bun 运行时完全兼容。刻意不用
11
+ * Bun.spawn 的捕获管道:win32 下其读端存在 EOF 竞态(见
12
+ * tooling/release/proc.ts 注释),而单进程安装场景 child_process
13
+ * 无此问题。子进程以 process.execPath 启动——宿主必为 Bun 运行时,
14
+ * 直接复用当前可执行文件,不依赖 PATH 中的 bun。
15
+ *
16
+ * 独立成模块是为了让测试能以 mock.module 按相对路径精确拦截,
17
+ * 不真正拉起安装进程(此前 mock execa 包名,全局替换同样可行,
18
+ * 但本地模块拦截范围更小、不受其它包使用 child_process 的干扰)。
19
+ */
20
+ import {
21
+ type ChildProcess,
22
+ spawn,
23
+ } from "node:child_process";
24
+
25
+ /** 在指定工作目录启动 bun 子进程(args 已含子命令与参数),stdio 三路 pipe。 */
26
+ export function spawnBun(
27
+ args: string[],
28
+ cwd: string,
29
+ ): ChildProcess {
30
+ return spawn(process.execPath, args, { cwd });
31
+ }
@@ -10,7 +10,10 @@ import {
10
10
  Logger,
11
11
  Time,
12
12
  } from "@koishi-ce/koishi";
13
- import type { SearchObject, SearchResult } from "@koishi-ce/registry";
13
+ import type {
14
+ SearchObject,
15
+ SearchResult,
16
+ } from "@koishi-ce/registry";
14
17
 
15
18
  declare module "@koishi-ce/console" {
16
19
  interface Events {
@@ -27,21 +30,31 @@ declare module "@koishi-ce/console" {
27
30
  const logger = new Logger("market");
28
31
 
29
32
  export abstract class MarketProvider extends DataService<MarketProvider.Payload> {
30
- private _task: Promise<SearchResult | undefined> | null = null;
33
+ private _task: Promise<SearchResult | undefined> | null =
34
+ null;
31
35
  private _timestamp = 0;
32
36
  protected _error: unknown;
33
37
 
34
38
  constructor(ctx: Context) {
35
39
  super(ctx, "market", { authority: 4 });
36
40
 
37
- ctx.console.addListener("market/refresh", () => this.start(true), {
38
- authority: 4,
39
- });
41
+ ctx.console.addListener(
42
+ "market/refresh",
43
+ () => this.start(true),
44
+ {
45
+ authority: 4,
46
+ },
47
+ );
40
48
 
41
49
  ctx.on("console/connection", async (client) => {
42
50
  if (!ctx.console.clients[client.id]) return;
43
- if (Date.now() - this._timestamp <= Time.hour * 12) return;
44
- if (await this.ctx.serial("console/intercept", client, { authority: 4 }))
51
+ if (Date.now() - this._timestamp <= Time.hour * 12)
52
+ return;
53
+ if (
54
+ await this.ctx.serial("console/intercept", client, {
55
+ authority: 4,
56
+ })
57
+ )
45
58
  return;
46
59
  this.start();
47
60
  });
@@ -57,11 +70,13 @@ export abstract class MarketProvider extends DataService<MarketProvider.Payload>
57
70
  abstract collect(): Promise<SearchResult | undefined>;
58
71
 
59
72
  async prepare(): Promise<SearchResult | undefined> {
60
- return (this._task ||= this.collect().catch((error: unknown) => {
61
- logger.warn(error);
62
- this._error = error;
63
- return undefined;
64
- }));
73
+ return (this._task ||= this.collect().catch(
74
+ (error: unknown) => {
75
+ logger.warn(error);
76
+ this._error = error;
77
+ return undefined;
78
+ },
79
+ ));
65
80
  }
66
81
  }
67
82