@koishi-ce/plugin-market 1.0.9 → 1.0.11

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.
@@ -25,6 +25,11 @@ export function apply(ctx: Context, _config: Config) {
25
25
  ctx.console.addEntry(
26
26
  base
27
27
  ? [`${base}/dist/index.js`, `${base}/dist/style.css`]
28
- : [import.meta.url.replace(/\/src\/[^/]+\/[^/]+$/, "/client/index.ts")],
28
+ : [
29
+ import.meta.url.replace(
30
+ /\/src\/[^/]+\/[^/]+$/,
31
+ "/client/index.ts",
32
+ ),
33
+ ],
29
34
  );
30
35
  }
@@ -12,10 +12,14 @@ export default class MarketProvider extends BaseMarketProvider {
12
12
 
13
13
  override async get() {
14
14
  const market = await this.prepare();
15
- if (!market) return { data: {}, failed: 0, total: 0, progress: 0 };
15
+ if (!market)
16
+ return { data: {}, failed: 0, total: 0, progress: 0 };
16
17
  return {
17
18
  data: Object.fromEntries(
18
- market.objects.map((item) => [item.package.name, item]),
19
+ market.objects.map((item) => [
20
+ item.package.name,
21
+ item,
22
+ ]),
19
23
  ),
20
24
  failed: 0,
21
25
  total: market.objects.length,
package/src/node/deps.ts CHANGED
@@ -4,10 +4,15 @@
4
4
 
5
5
  import { DataService } from "@koishi-ce/console";
6
6
  import type { Context, Dict } from "@koishi-ce/koishi";
7
- import type { DependencyMetaKey, RemotePackage } from "@koishi-ce/registry";
7
+ import type {
8
+ DependencyMetaKey,
9
+ RemotePackage,
10
+ } from "@koishi-ce/registry";
8
11
  import type { Dependency } from "./installer.ts";
9
12
 
10
- class DependencyProvider extends DataService<Dict<Dependency>> {
13
+ class DependencyProvider extends DataService<
14
+ Dict<Dependency>
15
+ > {
11
16
  constructor(ctx: Context) {
12
17
  super(ctx, "dependencies", { authority: 4 });
13
18
  }
package/src/node/index.ts CHANGED
@@ -3,10 +3,21 @@
3
3
  // Copyright (c) 2026-present Koishi-CE contributors.
4
4
 
5
5
  import { resolve } from "node:path";
6
- import { type Context, type Dict, pick, Schema } from "@koishi-ce/koishi";
7
- import type { DependencyMetaKey, RemotePackage } from "@koishi-ce/registry";
6
+ import {
7
+ type Context,
8
+ type Dict,
9
+ pick,
10
+ Schema,
11
+ } from "@koishi-ce/koishi";
12
+ import type {
13
+ DependencyMetaKey,
14
+ RemotePackage,
15
+ } from "@koishi-ce/registry";
8
16
  import { gt } from "semver";
9
- import { DependencyProvider, RegistryProvider } from "./deps.ts";
17
+ import {
18
+ DependencyProvider,
19
+ RegistryProvider,
20
+ } from "./deps.ts";
10
21
  import Installer from "./installer.ts";
11
22
  import messageZhCN from "./locales/message.zh-CN.yml";
12
23
  import schemaZhCN from "./locales/schema.zh-CN.yml";
@@ -31,15 +42,25 @@ declare module "@koishi-ce/console" {
31
42
  }
32
43
 
33
44
  interface Events {
34
- "market/install"(deps: Dict<string>, forced?: boolean): Promise<number>;
45
+ "market/install"(
46
+ deps: Dict<string>,
47
+ forced?: boolean,
48
+ ): Promise<number>;
35
49
  "market/registry"(
36
50
  names: string[],
37
- ): Promise<Dict<Dict<Pick<RemotePackage, DependencyMetaKey>>>>;
51
+ ): Promise<
52
+ Dict<Dict<Pick<RemotePackage, DependencyMetaKey>>>
53
+ >;
38
54
  }
39
55
  }
40
56
 
41
57
  export const name = "market";
42
- export const inject = ["http"];
58
+ // loader 由宿主以 builtin 服务提供,生产恒存在;测试等裸 App 环境可缺席,
59
+ // 声明为非必需注入以免 cordis 报「property loader is not registered」
60
+ export const inject = {
61
+ http: { required: true },
62
+ loader: { required: false },
63
+ };
43
64
 
44
65
  export const usage = `
45
66
  如果插件市场页面提示「无法连接到插件市场」,则可以选择一个 Koishi 社区提供的镜像地址,填入下方对应的配置项中。
@@ -101,12 +122,18 @@ export function apply(ctx: Context, config: Config) {
101
122
  return session.text(".already-installed");
102
123
 
103
124
  // find proper version
104
- const result = await ctx.installer.findVersion(names);
125
+ const result =
126
+ await ctx.installer.findVersion(names);
105
127
  if (!result) return session.text(".not-found");
106
128
 
107
129
  // set restart message
108
130
  ctx.loader.envData.message = {
109
- ...pick(session, ["sid", "channelId", "guildId", "isDirect"]),
131
+ ...pick(session, [
132
+ "sid",
133
+ "channelId",
134
+ "guildId",
135
+ "isDirect",
136
+ ]),
110
137
  content: session.text(".success"),
111
138
  };
112
139
  await ctx.installer.install(result);
@@ -125,7 +152,8 @@ export function apply(ctx: Context, config: Config) {
125
152
  const names = ctx.installer.resolveName(name);
126
153
  const deps = await ctx.installer.getDeps();
127
154
  const installed = names.find((name) => deps[name]);
128
- if (!installed) return session.text(".not-installed");
155
+ if (!installed)
156
+ return session.text(".not-installed");
129
157
 
130
158
  await ctx.installer.install({ [installed]: null });
131
159
  return session.text(".success");
@@ -145,7 +173,9 @@ export function apply(ctx: Context, config: Config) {
145
173
  const names = ctx.installer.resolveName(name);
146
174
  return names.find((name) => deps[name]);
147
175
  })
148
- .filter((name): name is string => name !== undefined);
176
+ .filter(
177
+ (name): name is string => name !== undefined,
178
+ );
149
179
  if (options?.self) resolved.push("koishi");
150
180
  return resolved;
151
181
  }
@@ -154,17 +184,25 @@ export function apply(ctx: Context, config: Config) {
154
184
  ctx.installer.refresh(true);
155
185
  const deps = await ctx.installer.getDeps();
156
186
  names = (
157
- await getPackages(names.filter((name): name is string => !!name))
187
+ await getPackages(
188
+ names.filter((name): name is string => !!name),
189
+ )
158
190
  ).filter((name) => {
159
- const { latest, resolved, invalid } = deps[name] ?? {};
160
- if (latest === undefined || resolved === undefined) return false;
191
+ const { latest, resolved, invalid } =
192
+ deps[name] ?? {};
193
+ if (
194
+ latest === undefined ||
195
+ resolved === undefined
196
+ )
197
+ return false;
161
198
  try {
162
199
  return !invalid && gt(latest, resolved);
163
200
  } catch {
164
201
  return false;
165
202
  }
166
203
  });
167
- if (!names.length) return session.text(".all-updated");
204
+ if (!names.length)
205
+ return session.text(".all-updated");
168
206
 
169
207
  const output = names.map((name) => {
170
208
  const { latest, resolved } = deps[name] ?? {};
@@ -180,15 +218,24 @@ export function apply(ctx: Context, config: Config) {
180
218
  }
181
219
 
182
220
  ctx.loader.envData.message = {
183
- ...pick(session, ["sid", "channelId", "guildId", "isDirect"]),
221
+ ...pick(session, [
222
+ "sid",
223
+ "channelId",
224
+ "guildId",
225
+ "isDirect",
226
+ ]),
184
227
  content: session.text(".success"),
185
228
  };
186
229
  await ctx.installer.install(
187
- names.reduce<Dict<string | null>>((result, name) => {
188
- const latest = deps[name]?.latest;
189
- if (latest !== undefined) result[name] = latest;
190
- return result;
191
- }, {}),
230
+ names.reduce<Dict<string | null>>(
231
+ (result, name) => {
232
+ const latest = deps[name]?.latest;
233
+ if (latest !== undefined)
234
+ result[name] = latest;
235
+ return result;
236
+ },
237
+ {},
238
+ ),
192
239
  );
193
240
  ctx.loader.envData.message = null;
194
241
  return session.text(".success");
@@ -210,7 +257,10 @@ export function apply(ctx: Context, config: Config) {
210
257
  ctx.console.addListener(
211
258
  "market/install",
212
259
  async (deps, forced) => {
213
- const code = await ctx.installer.install(deps, forced);
260
+ const code = await ctx.installer.install(
261
+ deps,
262
+ forced,
263
+ );
214
264
  ctx.get("console")?.refresh("dependencies");
215
265
  ctx.get("console")?.refresh("registry");
216
266
  ctx.get("console")?.refresh("packages");
@@ -223,7 +273,9 @@ export function apply(ctx: Context, config: Config) {
223
273
  "market/registry",
224
274
  async (names) => {
225
275
  const meta = await Promise.all(
226
- names.map((name) => ctx.installer.getPackage(name)),
276
+ names.map((name) =>
277
+ ctx.installer.getPackage(name),
278
+ ),
227
279
  );
228
280
  return Object.fromEntries(
229
281
  meta.map((meta, index) => [names[index], meta]),
@@ -44,7 +44,9 @@ const logger = new Logger("market");
44
44
  * 两者被覆盖或删除都会让 peer 解析失去归属,重新拉下 npm 官方包形成
45
45
  * 第二份框架副本。
46
46
  */
47
- function isGuardedRequest(request: string | undefined): boolean {
47
+ function isGuardedRequest(
48
+ request: string | undefined,
49
+ ): boolean {
48
50
  return (
49
51
  request?.startsWith("workspace:") === true ||
50
52
  request?.startsWith("npm:@koishi-ce") === true
@@ -52,10 +54,16 @@ function isGuardedRequest(request: string | undefined): boolean {
52
54
  }
53
55
 
54
56
  /** 从单个 .npmrc 文件提取 registry 配置项;文件不存在或读取出错一律视为未配置 */
55
- function readNpmrcRegistry(file: string): string | undefined {
57
+ function readNpmrcRegistry(
58
+ file: string,
59
+ ): string | undefined {
56
60
  try {
57
- for (const line of readFileSync(file, "utf8").split(/\r?\n/)) {
58
- const matched = /^\s*registry\s*=\s*(\S+)\s*$/.exec(line);
61
+ for (const line of readFileSync(file, "utf8").split(
62
+ /\r?\n/,
63
+ )) {
64
+ const matched = /^\s*registry\s*=\s*(\S+)\s*$/.exec(
65
+ line,
66
+ );
59
67
  const value = matched?.[1];
60
68
  if (value) return value;
61
69
  }
@@ -72,14 +80,20 @@ function readNpmrcRegistry(file: string): string | undefined {
72
80
  * 源(对齐被移除的 get-registry 的默认值),保证 http 恒有 endpoint
73
81
  * 可拼接相对 URL——缺省时相对请求会在 resolveURL 抛 Invalid URL。
74
82
  */
75
- function getLocalRegistry(cwd: string, userHome: string = homedir()): string {
83
+ function getLocalRegistry(
84
+ cwd: string,
85
+ userHome: string = homedir(),
86
+ ): string {
76
87
  const candidates = [
77
88
  process.env["npm_config_registry"],
78
89
  readNpmrcRegistry(join(cwd, ".npmrc")),
79
90
  readNpmrcRegistry(join(userHome, ".npmrc")),
80
91
  ];
81
92
  for (const candidate of candidates) {
82
- if (candidate?.startsWith("https://") || candidate?.startsWith("http://")) {
93
+ if (
94
+ candidate?.startsWith("https://") ||
95
+ candidate?.startsWith("http://")
96
+ ) {
83
97
  return candidate;
84
98
  }
85
99
  }
@@ -116,9 +130,15 @@ function loadManifest(name: string) {
116
130
  // resolvePackageJson 以纯 fs 探测为主路径:市场安装流程在包落盘前的
117
131
  // 探测不能触碰解析 API,否则触发 Bun 的父目录快照缓存(装完即失败)
118
132
  const filename = resolvePackageJson(name);
119
- const meta: LocalPackage = JSON.parse(readFileSync(filename, "utf8"));
133
+ const meta: LocalPackage = JSON.parse(
134
+ readFileSync(filename, "utf8"),
135
+ );
120
136
  meta.dependencies ||= {};
121
- defineProperty(meta, "$workspace", !filename.includes("node_modules"));
137
+ defineProperty(
138
+ meta,
139
+ "$workspace",
140
+ !filename.includes("node_modules"),
141
+ );
122
142
  return meta;
123
143
  }
124
144
 
@@ -143,8 +163,12 @@ function getVersions(versions: RemotePackage[]) {
143
163
  class Installer extends Service {
144
164
  declare http: HTTP;
145
165
  declare endpoint: string | undefined;
146
- public fullCache: Dict<Dict<Pick<RemotePackage, DependencyMetaKey>>> = {};
147
- public tempCache: Dict<Dict<Pick<RemotePackage, DependencyMetaKey>>> = {};
166
+ public fullCache: Dict<
167
+ Dict<Pick<RemotePackage, DependencyMetaKey>>
168
+ > = {};
169
+ public tempCache: Dict<
170
+ Dict<Pick<RemotePackage, DependencyMetaKey>>
171
+ > = {};
148
172
 
149
173
  private pkgTasks: Dict<
150
174
  Promise<Dict<Pick<RemotePackage, DependencyMetaKey>>>
@@ -160,7 +184,9 @@ class Installer extends Service {
160
184
  this.config = config;
161
185
  this.manifest = loadManifest(this.cwd);
162
186
  this.flushData = ctx.throttle(() => {
163
- ctx.get("console")?.broadcast("market/registry", this.tempCache);
187
+ ctx
188
+ .get("console")
189
+ ?.broadcast("market/registry", this.tempCache);
164
190
  this.tempCache = {};
165
191
  }, 500);
166
192
  }
@@ -185,7 +211,10 @@ class Installer extends Service {
185
211
  const [left, right] = name.split("/");
186
212
  return [`${left}/koishi-plugin-${right}`];
187
213
  } else {
188
- return [`@koishijs/plugin-${name}`, `koishi-plugin-${name}`];
214
+ return [
215
+ `@koishijs/plugin-${name}`,
216
+ `koishi-plugin-${name}`,
217
+ ];
189
218
  }
190
219
  }
191
220
 
@@ -193,7 +222,9 @@ class Installer extends Service {
193
222
  const entries = await Promise.all(
194
223
  names.map(async (name) => {
195
224
  try {
196
- const versions = Object.entries(await this.getPackage(name));
225
+ const versions = Object.entries(
226
+ await this.getPackage(name),
227
+ );
197
228
  const [latest] = versions;
198
229
  if (!latest) return undefined;
199
230
  return { [name]: latest[0] };
@@ -202,19 +233,30 @@ class Installer extends Service {
202
233
  }
203
234
  }),
204
235
  );
205
- return entries.find((entry): entry is Dict<string> => entry !== undefined);
236
+ return entries.find(
237
+ (entry): entry is Dict<string> => entry !== undefined,
238
+ );
206
239
  }
207
240
 
208
241
  private async _getPackage(name: string) {
209
242
  try {
210
- const registry = await this.http.get<Registry>(`/${name}`);
243
+ const registry = await this.http.get<Registry>(
244
+ `/${name}`,
245
+ );
211
246
  const versions = getVersions(
212
- Object.values(registry.versions).filter((remote) => {
213
- if (name === "koishi") return satisfies(remote.version, "4");
214
- return !Scanner.isPlugin(name) || Scanner.isCompatible("4", remote);
215
- }),
247
+ Object.values(registry.versions).filter(
248
+ (remote) => {
249
+ if (name === "koishi")
250
+ return satisfies(remote.version, "4");
251
+ return (
252
+ !Scanner.isPlugin(name) ||
253
+ Scanner.isCompatible("4", remote)
254
+ );
255
+ },
256
+ ),
216
257
  );
217
- this.fullCache[name] = this.tempCache[name] = versions;
258
+ this.fullCache[name] = this.tempCache[name] =
259
+ versions;
218
260
  this.flushData();
219
261
  return versions;
220
262
  } catch (error) {
@@ -224,9 +266,12 @@ class Installer extends Service {
224
266
  }
225
267
 
226
268
  setPackage(name: string, versions: RemotePackage[]) {
227
- this.fullCache[name] = this.tempCache[name] = getVersions(versions);
269
+ this.fullCache[name] = this.tempCache[name] =
270
+ getVersions(versions);
228
271
  this.flushData();
229
- this.pkgTasks[name] = Promise.resolve(this.fullCache[name]);
272
+ this.pkgTasks[name] = Promise.resolve(
273
+ this.fullCache[name],
274
+ );
230
275
  }
231
276
 
232
277
  getPackage(name: string) {
@@ -234,27 +279,36 @@ class Installer extends Service {
234
279
  }
235
280
 
236
281
  private async _getDeps() {
237
- const result = valueMap(this.manifest.dependencies, (request) => {
238
- return { request: request.replace(/^[~^]/, "") } as Dependency;
239
- });
240
- await mapLimit(Object.keys(result), 10, async (name) => {
241
- const dep = result[name];
242
- if (!dep) return;
243
- try {
244
- // some dependencies may be left with no local installation
245
- const meta = loadManifest(name);
246
- dep.resolved = meta.version;
247
- dep.workspace = meta.$workspace;
248
- if (meta.$workspace) return;
249
- } catch {}
250
-
251
- if (!valid(dep.request)) {
252
- dep.invalid = true;
253
- }
282
+ const result = valueMap(
283
+ this.manifest.dependencies,
284
+ (request) => {
285
+ return {
286
+ request: request.replace(/^[~^]/, ""),
287
+ } as Dependency;
288
+ },
289
+ );
290
+ await mapLimit(
291
+ Object.keys(result),
292
+ 10,
293
+ async (name) => {
294
+ const dep = result[name];
295
+ if (!dep) return;
296
+ try {
297
+ // some dependencies may be left with no local installation
298
+ const meta = loadManifest(name);
299
+ dep.resolved = meta.version;
300
+ dep.workspace = meta.$workspace;
301
+ if (meta.$workspace) return;
302
+ } catch {}
303
+
304
+ if (!valid(dep.request)) {
305
+ dep.invalid = true;
306
+ }
254
307
 
255
- const versions = await this.getPackage(name);
256
- if (versions) dep.latest = Object.keys(versions)[0];
257
- });
308
+ const versions = await this.getPackage(name);
309
+ if (versions) dep.latest = Object.keys(versions)[0];
310
+ },
311
+ );
258
312
  return result;
259
313
  }
260
314
 
@@ -311,26 +365,35 @@ class Installer extends Service {
311
365
  const filename = resolve(this.cwd, "package.json");
312
366
  // 现读现写:this.manifest 原为构造期的启动快照,运行期间根
313
367
  // package.json 可能已被外部更新,基于快照整体重写会抹掉变更
314
- this.manifest = JSON.parse(readFileSync(filename, "utf8")) as LocalPackage;
368
+ this.manifest = JSON.parse(
369
+ readFileSync(filename, "utf8"),
370
+ ) as LocalPackage;
315
371
  this.manifest.dependencies ||= {};
316
372
  for (const key in deps) {
317
373
  if (deps[key]) {
318
- if (isGuardedRequest(this.manifest.dependencies[key])) {
374
+ if (
375
+ isGuardedRequest(this.manifest.dependencies[key])
376
+ ) {
319
377
  continue;
320
378
  }
321
379
  this.manifest.dependencies[key] = deps[key];
322
- } else if (!isGuardedRequest(this.manifest.dependencies[key])) {
380
+ } else if (
381
+ !isGuardedRequest(this.manifest.dependencies[key])
382
+ ) {
323
383
  delete this.manifest.dependencies[key];
324
384
  }
325
385
  }
326
386
  this.manifest.dependencies = Object.fromEntries(
327
- Object.entries(this.manifest.dependencies).sort((a, b) =>
328
- a[0].localeCompare(b[0]),
387
+ Object.entries(this.manifest.dependencies).sort(
388
+ (a, b) => a[0].localeCompare(b[0]),
329
389
  ),
330
390
  );
331
391
  // 仓库格式权威是 biome(tab 缩进),按 tab 写出避免装插件后
332
392
  // package.json 被重排成空格、lint 报格式漂移
333
- await Bun.write(filename, `${JSON.stringify(this.manifest, null, "\t")}\n`);
393
+ await Bun.write(
394
+ filename,
395
+ `${JSON.stringify(this.manifest, null, "\t")}\n`,
396
+ );
334
397
  }
335
398
 
336
399
  private _install() {
@@ -353,7 +416,10 @@ class Installer extends Service {
353
416
  });
354
417
  }
355
418
 
356
- async install(deps: Dict<string | null>, forced?: boolean) {
419
+ async install(
420
+ deps: Dict<string | null>,
421
+ forced?: boolean,
422
+ ) {
357
423
  const localDeps = this._getLocalDeps(deps);
358
424
  await this.override(deps);
359
425
 
@@ -365,7 +431,9 @@ class Installer extends Service {
365
431
  local?.workspace ||
366
432
  (request &&
367
433
  local?.resolved &&
368
- satisfies(local.resolved, request, { includePrerelease: true }))
434
+ satisfies(local.resolved, request, {
435
+ includePrerelease: true,
436
+ }))
369
437
  )
370
438
  continue;
371
439
  shouldInstall = true;
@@ -386,7 +454,8 @@ class Installer extends Service {
386
454
  if (newDep.resolved === local.resolved) continue;
387
455
  // 版本变化且旧版本仍驻留内存时才需要整进程重载(判定不走
388
456
  // 解析 API,规避 Bun 负缓存,见 isResidentInCache)
389
- if (isResidentInCache(name)) this.ctx.loader.fullReload();
457
+ if (isResidentInCache(name))
458
+ this.ctx.loader.fullReload();
390
459
  }
391
460
  this.refreshData();
392
461
 
@@ -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 {
package/src/node/proc.ts CHANGED
@@ -17,9 +17,15 @@
17
17
  * 不真正拉起安装进程(此前 mock execa 包名,全局替换同样可行,
18
18
  * 但本地模块拦截范围更小、不受其它包使用 child_process 的干扰)。
19
19
  */
20
- import { type ChildProcess, spawn } from "node:child_process";
20
+ import {
21
+ type ChildProcess,
22
+ spawn,
23
+ } from "node:child_process";
21
24
 
22
25
  /** 在指定工作目录启动 bun 子进程(args 已含子命令与参数),stdio 三路 pipe。 */
23
- export function spawnBun(args: string[], cwd: string): ChildProcess {
26
+ export function spawnBun(
27
+ args: string[],
28
+ cwd: string,
29
+ ): ChildProcess {
24
30
  return spawn(process.execPath, args, { cwd });
25
31
  }