@koishi-ce/plugin-market 1.0.4 → 1.0.5

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/lib/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createRequire } from "node:module";
2
- import { join, resolve } from "node:path";
2
+ import { dirname, join, resolve, sep } from "node:path";
3
3
  import { Logger, Schema, Service, Time, defineProperty, pick, sleep, valueMap } from "@koishi-ce/koishi";
4
4
  import { compare, gt, satisfies, valid } from "semver";
5
5
  import { DataService } from "@koishi-ce/console";
6
- import { readFileSync } from "node:fs";
6
+ import { readFileSync, realpathSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import Scanner, { resolvePackageJson } from "@koishi-ce/registry";
9
9
  import spawn from "execa";
@@ -86,6 +86,32 @@ function loadManifest(name) {
86
86
  defineProperty(meta, "$workspace", !filename.includes("node_modules"));
87
87
  return meta;
88
88
  }
89
+ /** 缓存键归一(win32 大小写不敏感),对齐 require.cache 键与探测路径的比对 */
90
+ function normalizeCacheKey(path) {
91
+ return process.platform === "win32" ? path.toLowerCase() : path;
92
+ }
93
+ /**
94
+ * 判断某依赖包是否有模块驻留在 require.cache(旧版本仍在内存)。
95
+ *
96
+ * 原上游实现用 require.resolve(name)(FIXME koishijs/webui#273 的报错
97
+ * 源):安装流程在包落盘前的探测已把该裸名记入 Bun 的进程内解析负缓
98
+ * 存,装完后同进程内必然抛 ResolveMessage。改为经 resolvePackageJson
99
+ * (fs 探测兜底)取包目录后扫 require.cache 前缀;任何环节失败都保守
100
+ * 返回 true——多一次重载只是进程重启,漏判才会让旧版本代码继续驻留。
101
+ */
102
+ function isResidentInCache(name) {
103
+ try {
104
+ const dir = dirname(resolvePackageJson(name));
105
+ const prefixes = [normalizeCacheKey(dir + sep)];
106
+ try {
107
+ const real = realpathSync(dir);
108
+ if (real !== dir) prefixes.push(normalizeCacheKey(real + sep));
109
+ } catch {}
110
+ return Object.keys(__require.cache).some((key) => prefixes.some((prefix) => normalizeCacheKey(key).startsWith(prefix)));
111
+ } catch {
112
+ return true;
113
+ }
114
+ }
89
115
  function getVersions(versions) {
90
116
  return Object.fromEntries(versions.map((item) => [item.version, pick(item, [
91
117
  "peerDependencies",
@@ -284,12 +310,7 @@ var Installer = class extends Service {
284
310
  const newDep = newDeps[name];
285
311
  if (!local || !newDep || local.workspace) continue;
286
312
  if (newDep.resolved === local.resolved) continue;
287
- try {
288
- if (!(__require.resolve(name) in __require.cache)) continue;
289
- } catch (error) {
290
- logger$2.error(error);
291
- }
292
- this.ctx.loader.fullReload();
313
+ if (isResidentInCache(name)) this.ctx.loader.fullReload();
293
314
  }
294
315
  this.refreshData();
295
316
  return 0;
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",
4
+ "version": "1.0.5",
5
5
  "type": "module",
6
6
  "main": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -61,8 +61,8 @@
61
61
  },
62
62
  "devDependencies": {
63
63
  "@koishi-ce/client": "^1.0.1",
64
- "@koishi-ce/loader": "^1.0.1",
65
- "@koishi-ce/plugin-config": "^1.0.3",
64
+ "@koishi-ce/loader": "^1.0.2",
65
+ "@koishi-ce/plugin-config": "^1.0.4",
66
66
  "@koishijs/market": "^4.2.10",
67
67
  "@types/semver": "^7.5.8",
68
68
  "vue": "^3.5.12"
@@ -59,7 +59,9 @@ const { Console } = await import("@koishi-ce/console");
59
59
  const { App, Service } = await import("@koishi-ce/koishi");
60
60
  const http = (await import("@koishi-ce/plugin-http")).default;
61
61
  const market = await import("../node/index.ts");
62
- const { default: Installer } = await import("../node/installer.ts");
62
+ const { default: Installer, isResidentInCache } = await import(
63
+ "../node/installer.ts"
64
+ );
63
65
  const mockPlugin = (await import("@koishi-ce/plugin-mock")).default;
64
66
  // 加载包入口占位文件(纯 re-export,无独立逻辑),保证 src 全量被加载
65
67
  await import("../index.ts");
@@ -331,6 +333,36 @@ describe("Installer 安装链路", () => {
331
333
  });
332
334
  });
333
335
 
336
+ describe("isResidentInCache(装后重载判定)", () => {
337
+ /** 在临时 node_modules 放一个已安装包,返回其目录 */
338
+ function placePkg(name: string) {
339
+ const pkgDir = join(tmp, "node_modules", name);
340
+ mkdirSync(pkgDir, { recursive: true });
341
+ writeFileSync(
342
+ join(pkgDir, "package.json"),
343
+ JSON.stringify({ name, version: "1.0.0", main: "index.js" }),
344
+ );
345
+ writeFileSync(join(pkgDir, "index.js"), "module.exports = {}");
346
+ return pkgDir;
347
+ }
348
+
349
+ it("包目录下有模块驻留 require.cache 时返回 true", () => {
350
+ const pkgDir = placePkg("koishi-plugin-resident-check");
351
+ // 入口经 require 进入 require.cache,模拟旧版本驻留内存
352
+ require(join(pkgDir, "index.js"));
353
+ expect(isResidentInCache("koishi-plugin-resident-check")).toBe(true);
354
+ });
355
+
356
+ it("已安装但无模块驻留内存时返回 false", () => {
357
+ placePkg("koishi-plugin-idle-check");
358
+ expect(isResidentInCache("koishi-plugin-idle-check")).toBe(false);
359
+ });
360
+
361
+ it("包不存在时保守返回 true(宁可多重载不漏判)", () => {
362
+ expect(isResidentInCache("koishi-plugin-absent-check")).toBe(true);
363
+ });
364
+ });
365
+
334
366
  describe("registry 配置探测", () => {
335
367
  it("无显式 endpoint 时按 npmrc / 环境变量探测", async () => {
336
368
  // 环境变量优先:npm_config_registry 指向本地服务
@@ -1,7 +1,7 @@
1
- import { readFileSync } from "node:fs";
1
+ import { readFileSync, realpathSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { homedir } from "node:os";
4
- import { join, resolve } from "node:path";
4
+ import { dirname, join, resolve, sep } from "node:path";
5
5
  import type {} from "@koishi-ce/console";
6
6
  import {
7
7
  type Context,
@@ -138,6 +138,37 @@ export function loadManifest(name: string) {
138
138
  return meta;
139
139
  }
140
140
 
141
+ /** 缓存键归一(win32 大小写不敏感),对齐 require.cache 键与探测路径的比对 */
142
+ function normalizeCacheKey(path: string): string {
143
+ return process.platform === "win32" ? path.toLowerCase() : path;
144
+ }
145
+
146
+ /**
147
+ * 判断某依赖包是否有模块驻留在 require.cache(旧版本仍在内存)。
148
+ *
149
+ * 原上游实现用 require.resolve(name)(FIXME koishijs/webui#273 的报错
150
+ * 源):安装流程在包落盘前的探测已把该裸名记入 Bun 的进程内解析负缓
151
+ * 存,装完后同进程内必然抛 ResolveMessage。改为经 resolvePackageJson
152
+ * (fs 探测兜底)取包目录后扫 require.cache 前缀;任何环节失败都保守
153
+ * 返回 true——多一次重载只是进程重启,漏判才会让旧版本代码继续驻留。
154
+ */
155
+ export function isResidentInCache(name: string): boolean {
156
+ try {
157
+ const dir = dirname(resolvePackageJson(name));
158
+ const prefixes = [normalizeCacheKey(dir + sep)];
159
+ // 符号链接布局下缓存键可能是 realpath 形态,两种前缀都查
160
+ try {
161
+ const real = realpathSync(dir);
162
+ if (real !== dir) prefixes.push(normalizeCacheKey(real + sep));
163
+ } catch {}
164
+ return Object.keys(require.cache).some((key) =>
165
+ prefixes.some((prefix) => normalizeCacheKey(key).startsWith(prefix)),
166
+ );
167
+ } catch {
168
+ return true;
169
+ }
170
+ }
171
+
141
172
  function getVersions(versions: RemotePackage[]) {
142
173
  return Object.fromEntries(
143
174
  versions
@@ -418,14 +449,9 @@ class Installer extends Service {
418
449
  const newDep = newDeps[name];
419
450
  if (!local || !newDep || local.workspace) continue;
420
451
  if (newDep.resolved === local.resolved) continue;
421
- try {
422
- if (!(require.resolve(name) in require.cache)) continue;
423
- } catch (error) {
424
- // FIXME https://github.com/koishijs/webui/issues/273
425
- // I have no idea why this happens and how to fix it.
426
- logger.error(error);
427
- }
428
- this.ctx.loader.fullReload();
452
+ // 版本变化且旧版本仍驻留内存时才需要整进程重载(判定不走
453
+ // 解析 API,规避 Bun 负缓存,见 isResidentInCache)
454
+ if (isResidentInCache(name)) this.ctx.loader.fullReload();
429
455
  }
430
456
  this.refreshData();
431
457