@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.
@@ -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]),
@@ -3,7 +3,6 @@
3
3
  // Copyright (c) 2026-present Koishi-CE contributors.
4
4
 
5
5
  import { readFileSync } from "node:fs";
6
- import { createRequire } from "node:module";
7
6
  import { homedir } from "node:os";
8
7
  import { join, resolve } from "node:path";
9
8
  import type {} from "@koishi-ce/console";
@@ -24,14 +23,14 @@ import type {} from "@koishi-ce/plugin-market";
24
23
  import Scanner, {
25
24
  type DependencyMetaKey,
26
25
  isResidentInCache,
26
+ mapLimit,
27
27
  type PackageJson,
28
28
  type Registry,
29
29
  type RemotePackage,
30
30
  resolvePackageJson,
31
31
  } from "@koishi-ce/registry";
32
- import spawn from "execa";
33
- import pMap from "p-map";
34
32
  import { compare, satisfies, valid } from "semver";
33
+ import { spawnBun } from "./proc.ts";
35
34
 
36
35
  const logger = new Logger("market");
37
36
 
@@ -45,7 +44,9 @@ const logger = new Logger("market");
45
44
  * 两者被覆盖或删除都会让 peer 解析失去归属,重新拉下 npm 官方包形成
46
45
  * 第二份框架副本。
47
46
  */
48
- function isGuardedRequest(request: string | undefined): boolean {
47
+ function isGuardedRequest(
48
+ request: string | undefined,
49
+ ): boolean {
49
50
  return (
50
51
  request?.startsWith("workspace:") === true ||
51
52
  request?.startsWith("npm:@koishi-ce") === true
@@ -53,10 +54,16 @@ function isGuardedRequest(request: string | undefined): boolean {
53
54
  }
54
55
 
55
56
  /** 从单个 .npmrc 文件提取 registry 配置项;文件不存在或读取出错一律视为未配置 */
56
- function readNpmrcRegistry(file: string): string | undefined {
57
+ function readNpmrcRegistry(
58
+ file: string,
59
+ ): string | undefined {
57
60
  try {
58
- for (const line of readFileSync(file, "utf8").split(/\r?\n/)) {
59
- 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
+ );
60
67
  const value = matched?.[1];
61
68
  if (value) return value;
62
69
  }
@@ -73,26 +80,26 @@ function readNpmrcRegistry(file: string): string | undefined {
73
80
  * 源(对齐被移除的 get-registry 的默认值),保证 http 恒有 endpoint
74
81
  * 可拼接相对 URL——缺省时相对请求会在 resolveURL 抛 Invalid URL。
75
82
  */
76
- function getLocalRegistry(cwd: string, userHome: string = homedir()): string {
83
+ function getLocalRegistry(
84
+ cwd: string,
85
+ userHome: string = homedir(),
86
+ ): string {
77
87
  const candidates = [
78
88
  process.env["npm_config_registry"],
79
89
  readNpmrcRegistry(join(cwd, ".npmrc")),
80
90
  readNpmrcRegistry(join(userHome, ".npmrc")),
81
91
  ];
82
92
  for (const candidate of candidates) {
83
- if (candidate?.startsWith("https://") || candidate?.startsWith("http://")) {
93
+ if (
94
+ candidate?.startsWith("https://") ||
95
+ candidate?.startsWith("http://")
96
+ ) {
84
97
  return candidate;
85
98
  }
86
99
  }
87
100
  return "https://registry.npmjs.org/";
88
101
  }
89
102
 
90
- // 经 createRequire 加载 CJS 包并就地断言签名:不走 ESM 导入互操作,
91
- // 规避多包合并类型检查(大一统 tsconfig)下 export = 交织失效问题
92
- const whichPMRuns = createRequire(import.meta.url)("which-pm-runs") as () =>
93
- | undefined
94
- | { name: string; version: string };
95
-
96
103
  export interface Dependency {
97
104
  /**
98
105
  * requested semver range
@@ -112,34 +119,26 @@ export interface Dependency {
112
119
  latest?: string | undefined;
113
120
  }
114
121
 
115
- export interface YarnLog {
116
- type: "warning" | "info" | "error" | string;
117
- name: number | null;
118
- displayName: string;
119
- indent?: string;
120
- data: string;
121
- }
122
-
123
- const levelMap = {
124
- info: "info",
125
- warning: "debug",
126
- error: "warn",
127
- } as const;
128
-
129
- export interface LocalPackage extends PackageJson {
122
+ interface LocalPackage extends PackageJson {
130
123
  private?: boolean;
131
124
  $workspace?: boolean;
132
125
  /** loadManifest 归一化保证 dependencies 必有 */
133
126
  dependencies: Record<string, string>;
134
127
  }
135
128
 
136
- export function loadManifest(name: string) {
129
+ function loadManifest(name: string) {
137
130
  // resolvePackageJson 以纯 fs 探测为主路径:市场安装流程在包落盘前的
138
131
  // 探测不能触碰解析 API,否则触发 Bun 的父目录快照缓存(装完即失败)
139
132
  const filename = resolvePackageJson(name);
140
- const meta: LocalPackage = JSON.parse(readFileSync(filename, "utf8"));
133
+ const meta: LocalPackage = JSON.parse(
134
+ readFileSync(filename, "utf8"),
135
+ );
141
136
  meta.dependencies ||= {};
142
- defineProperty(meta, "$workspace", !filename.includes("node_modules"));
137
+ defineProperty(
138
+ meta,
139
+ "$workspace",
140
+ !filename.includes("node_modules"),
141
+ );
143
142
  return meta;
144
143
  }
145
144
 
@@ -164,13 +163,16 @@ function getVersions(versions: RemotePackage[]) {
164
163
  class Installer extends Service {
165
164
  declare http: HTTP;
166
165
  declare endpoint: string | undefined;
167
- public fullCache: Dict<Dict<Pick<RemotePackage, DependencyMetaKey>>> = {};
168
- 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
+ > = {};
169
172
 
170
173
  private pkgTasks: Dict<
171
174
  Promise<Dict<Pick<RemotePackage, DependencyMetaKey>>>
172
175
  > = {};
173
- private agent = whichPMRuns();
174
176
  private manifest: LocalPackage;
175
177
  private declare depTask: Promise<Dict<Dependency>>;
176
178
  private flushData: () => void;
@@ -182,7 +184,9 @@ class Installer extends Service {
182
184
  this.config = config;
183
185
  this.manifest = loadManifest(this.cwd);
184
186
  this.flushData = ctx.throttle(() => {
185
- ctx.get("console")?.broadcast("market/registry", this.tempCache);
187
+ ctx
188
+ .get("console")
189
+ ?.broadcast("market/registry", this.tempCache);
186
190
  this.tempCache = {};
187
191
  }, 500);
188
192
  }
@@ -207,7 +211,10 @@ class Installer extends Service {
207
211
  const [left, right] = name.split("/");
208
212
  return [`${left}/koishi-plugin-${right}`];
209
213
  } else {
210
- return [`@koishijs/plugin-${name}`, `koishi-plugin-${name}`];
214
+ return [
215
+ `@koishijs/plugin-${name}`,
216
+ `koishi-plugin-${name}`,
217
+ ];
211
218
  }
212
219
  }
213
220
 
@@ -215,7 +222,9 @@ class Installer extends Service {
215
222
  const entries = await Promise.all(
216
223
  names.map(async (name) => {
217
224
  try {
218
- const versions = Object.entries(await this.getPackage(name));
225
+ const versions = Object.entries(
226
+ await this.getPackage(name),
227
+ );
219
228
  const [latest] = versions;
220
229
  if (!latest) return undefined;
221
230
  return { [name]: latest[0] };
@@ -224,19 +233,30 @@ class Installer extends Service {
224
233
  }
225
234
  }),
226
235
  );
227
- return entries.find((entry): entry is Dict<string> => entry !== undefined);
236
+ return entries.find(
237
+ (entry): entry is Dict<string> => entry !== undefined,
238
+ );
228
239
  }
229
240
 
230
241
  private async _getPackage(name: string) {
231
242
  try {
232
- const registry = await this.http.get<Registry>(`/${name}`);
243
+ const registry = await this.http.get<Registry>(
244
+ `/${name}`,
245
+ );
233
246
  const versions = getVersions(
234
- Object.values(registry.versions).filter((remote) => {
235
- if (name === "koishi") return satisfies(remote.version, "4");
236
- return !Scanner.isPlugin(name) || Scanner.isCompatible("4", remote);
237
- }),
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
+ ),
238
257
  );
239
- this.fullCache[name] = this.tempCache[name] = versions;
258
+ this.fullCache[name] = this.tempCache[name] =
259
+ versions;
240
260
  this.flushData();
241
261
  return versions;
242
262
  } catch (error) {
@@ -246,9 +266,12 @@ class Installer extends Service {
246
266
  }
247
267
 
248
268
  setPackage(name: string, versions: RemotePackage[]) {
249
- this.fullCache[name] = this.tempCache[name] = getVersions(versions);
269
+ this.fullCache[name] = this.tempCache[name] =
270
+ getVersions(versions);
250
271
  this.flushData();
251
- this.pkgTasks[name] = Promise.resolve(this.fullCache[name]);
272
+ this.pkgTasks[name] = Promise.resolve(
273
+ this.fullCache[name],
274
+ );
252
275
  }
253
276
 
254
277
  getPackage(name: string) {
@@ -256,11 +279,17 @@ class Installer extends Service {
256
279
  }
257
280
 
258
281
  private async _getDeps() {
259
- const result = valueMap(this.manifest.dependencies, (request) => {
260
- return { request: request.replace(/^[~^]/, "") } as Dependency;
261
- });
262
- await pMap(
282
+ const result = valueMap(
283
+ this.manifest.dependencies,
284
+ (request) => {
285
+ return {
286
+ request: request.replace(/^[~^]/, ""),
287
+ } as Dependency;
288
+ },
289
+ );
290
+ await mapLimit(
263
291
  Object.keys(result),
292
+ 10,
264
293
  async (name) => {
265
294
  const dep = result[name];
266
295
  if (!dep) return;
@@ -279,7 +308,6 @@ class Installer extends Service {
279
308
  const versions = await this.getPackage(name);
280
309
  if (versions) dep.latest = Object.keys(versions)[0];
281
310
  },
282
- { concurrency: 10 },
283
311
  );
284
312
  return result;
285
313
  }
@@ -303,19 +331,18 @@ class Installer extends Service {
303
331
  }
304
332
 
305
333
  async exec(args: string[]) {
306
- const name = this.agent?.name ?? "npm";
307
- const useJson = name === "yarn" && (this.agent?.version ?? "1") >= "2";
308
- if (name !== "yarn") args.unshift("install");
334
+ // Bun-first:CE 生态只存在 bun 这一种包管理器,直接驱动 bun 执行
335
+ // 安装(上游经 which-pm-runs 探测 npm/yarn/bun,此处固定为 bun)
336
+ args.unshift("install");
309
337
  return new Promise<number>((resolve) => {
310
- if (useJson) args.push("--json");
311
- const child = spawn(name, args, { cwd: this.cwd });
338
+ const child = spawnBun(args, this.cwd);
312
339
  child.on("exit", (code) => resolve(code ?? -1));
313
340
  child.on("error", () => resolve(-1));
314
341
 
315
342
  let stderr = "";
316
343
  child.stderr?.on("data", (data) => {
317
- data = stderr + data.toString();
318
- const lines = data.split("\n");
344
+ stderr += data.toString();
345
+ const lines = stderr.split("\n");
319
346
  stderr = lines.pop() ?? "";
320
347
  for (const line of lines) {
321
348
  logger.warn(line);
@@ -324,23 +351,11 @@ class Installer extends Service {
324
351
 
325
352
  let stdout = "";
326
353
  child.stdout?.on("data", (data) => {
327
- data = stdout + data.toString();
328
- const lines = data.split("\n");
354
+ stdout += data.toString();
355
+ const lines = stdout.split("\n");
329
356
  stdout = lines.pop() ?? "";
330
357
  for (const line of lines) {
331
- if (!useJson || line[0] !== "{") {
332
- logger.info(line);
333
- continue;
334
- }
335
- try {
336
- const { type, data } = JSON.parse(line) as YarnLog;
337
- const level =
338
- type in levelMap ? levelMap[type as keyof typeof levelMap] : null;
339
- (level ? logger[level] : logger.info)(data);
340
- } catch (error) {
341
- logger.warn(line);
342
- logger.warn(error);
343
- }
358
+ logger.info(line);
344
359
  }
345
360
  });
346
361
  });
@@ -350,26 +365,35 @@ class Installer extends Service {
350
365
  const filename = resolve(this.cwd, "package.json");
351
366
  // 现读现写:this.manifest 原为构造期的启动快照,运行期间根
352
367
  // package.json 可能已被外部更新,基于快照整体重写会抹掉变更
353
- this.manifest = JSON.parse(readFileSync(filename, "utf8")) as LocalPackage;
368
+ this.manifest = JSON.parse(
369
+ readFileSync(filename, "utf8"),
370
+ ) as LocalPackage;
354
371
  this.manifest.dependencies ||= {};
355
372
  for (const key in deps) {
356
373
  if (deps[key]) {
357
- if (isGuardedRequest(this.manifest.dependencies[key])) {
374
+ if (
375
+ isGuardedRequest(this.manifest.dependencies[key])
376
+ ) {
358
377
  continue;
359
378
  }
360
379
  this.manifest.dependencies[key] = deps[key];
361
- } else if (!isGuardedRequest(this.manifest.dependencies[key])) {
380
+ } else if (
381
+ !isGuardedRequest(this.manifest.dependencies[key])
382
+ ) {
362
383
  delete this.manifest.dependencies[key];
363
384
  }
364
385
  }
365
386
  this.manifest.dependencies = Object.fromEntries(
366
- Object.entries(this.manifest.dependencies).sort((a, b) =>
367
- a[0].localeCompare(b[0]),
387
+ Object.entries(this.manifest.dependencies).sort(
388
+ (a, b) => a[0].localeCompare(b[0]),
368
389
  ),
369
390
  );
370
391
  // 仓库格式权威是 biome(tab 缩进),按 tab 写出避免装插件后
371
392
  // package.json 被重排成空格、lint 报格式漂移
372
- 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
+ );
373
397
  }
374
398
 
375
399
  private _install() {
@@ -392,7 +416,10 @@ class Installer extends Service {
392
416
  });
393
417
  }
394
418
 
395
- async install(deps: Dict<string | null>, forced?: boolean) {
419
+ async install(
420
+ deps: Dict<string | null>,
421
+ forced?: boolean,
422
+ ) {
396
423
  const localDeps = this._getLocalDeps(deps);
397
424
  await this.override(deps);
398
425
 
@@ -404,7 +431,9 @@ class Installer extends Service {
404
431
  local?.workspace ||
405
432
  (request &&
406
433
  local?.resolved &&
407
- satisfies(local.resolved, request, { includePrerelease: true }))
434
+ satisfies(local.resolved, request, {
435
+ includePrerelease: true,
436
+ }))
408
437
  )
409
438
  continue;
410
439
  shouldInstall = true;
@@ -425,7 +454,8 @@ class Installer extends Service {
425
454
  if (newDep.resolved === local.resolved) continue;
426
455
  // 版本变化且旧版本仍驻留内存时才需要整进程重载(判定不走
427
456
  // 解析 API,规避 Bun 负缓存,见 isResidentInCache)
428
- if (isResidentInCache(name)) this.ctx.loader.fullReload();
457
+ if (isResidentInCache(name))
458
+ this.ctx.loader.fullReload();
429
459
  }
430
460
  this.refreshData();
431
461