@jeik/dingtalk-connector 0.8.21-fix3 → 0.8.21-fix5

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.
@@ -7,7 +7,7 @@
7
7
  * node bin/dingtalk-connector.js install --local # local dev
8
8
  */
9
9
  import { createRequire } from 'node:module';
10
- import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
10
+ import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync, readdirSync, renameSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
12
  import { homedir } from 'node:os';
13
13
  import {
@@ -306,6 +306,60 @@ function getInstallSpec() {
306
306
  return PKG_NAME;
307
307
  }
308
308
 
309
+ // 在 plugins.load.paths 扫描范围内找会"遮蔽"npm 版的本地 dingtalk 插件副本。
310
+ // 原理:load.paths 扫到的插件 origin=config,优先级(rank 0)高于 npm 安装的 global(rank 2),
311
+ // 所以一份本地克隆会永久压过 npm 版,--force 重装也没用。这里浅扫一层定位它。
312
+ // ponytail: 只扫 load.paths 下一层目录;克隆嵌套更深的极少见,需要再加深度。
313
+ function findShadowingLocalPlugin(paths = readConfig()?.plugins?.load?.paths) {
314
+ if (!Array.isArray(paths)) return null;
315
+ const npmRoot = join(homedir(), '.openclaw', 'npm');
316
+ for (const p of paths) {
317
+ if (typeof p !== 'string') continue;
318
+ const base = p.startsWith('~') ? join(homedir(), p.slice(1)) : p;
319
+ let entries;
320
+ try { entries = readdirSync(base, { withFileTypes: true }); } catch { continue; }
321
+ for (const e of entries) {
322
+ if (!e.isDirectory()) continue;
323
+ // OpenClaw 扫描会跳过这些名字(discovery shouldIgnoreScannedDirectory),它们不会遮蔽,
324
+ // 已禁用的 .disabled 目录也在此跳过,避免重复禁用成 .disabled.disabled。
325
+ const lower = e.name.toLowerCase();
326
+ if (lower.endsWith('.bak') || lower.includes('.backup-') || lower.includes('.disabled')) continue;
327
+ const dir = join(base, e.name);
328
+ if (dir.startsWith(npmRoot)) continue; // npm 安装目录本身不算遮蔽
329
+ try {
330
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
331
+ const channels = pkg?.openclaw?.channels;
332
+ if (pkg?.name === PKG_NAME || (Array.isArray(channels) && channels.includes(CHANNEL_ID))) {
333
+ return dir;
334
+ }
335
+ } catch {}
336
+ }
337
+ }
338
+ return null;
339
+ }
340
+
341
+ // 检测并(经确认后)禁用遮蔽 npm 版的本地副本:把目录重命名为 .disabled(可随时改回)。
342
+ async function resolvePluginShadow() {
343
+ const shadow = findShadowingLocalPlugin();
344
+ if (!shadow) return;
345
+ console.log('\n' + orange('⚠ 检测到本地插件副本会覆盖刚装的 npm 版:'));
346
+ console.log(dim(` ${shadow}`));
347
+ console.log(dim(' 它在 plugins.load.paths 扫描范围内,优先级高于 npm 版,网关会一直用这份旧本地代码。') + '\n');
348
+ const ans = await askUserConfirmation(' 是否禁用它(重命名为 .disabled,随时可改回)?[Y/n] ');
349
+ if (ans === 'n' || ans === 'no') {
350
+ console.log(dim(' 已跳过。网关仍会用本地版;如需用 npm 版,请手动移走该目录或从 plugins.load.paths 移除其父路径。') + '\n');
351
+ return;
352
+ }
353
+ const disabled = shadow + '.disabled';
354
+ try {
355
+ if (existsSync(disabled)) rmSync(disabled, { recursive: true, force: true });
356
+ renameSync(shadow, disabled);
357
+ console.log(green(` ✔ 已禁用:${shadow} → ${disabled}`) + '\n');
358
+ } catch (err) {
359
+ console.log(red(` ⚠ 重命名失败:${err.message}。请手动处理该目录。`) + '\n');
360
+ }
361
+ }
362
+
309
363
  function installPlugin(force = false) {
310
364
  const spec = getInstallSpec();
311
365
  console.log('\n' + cyan(`📦 Installing ${spec}...`) + '\n');
@@ -731,10 +785,18 @@ Options:
731
785
  globalThis['proc' + 'ess'].exit(1);
732
786
  }
733
787
 
788
+ // 打印当前安装向导版本(= 本包版本),方便确认装的是哪一版
789
+ try {
790
+ const { version } = createRequire(import.meta.url)('../package.json');
791
+ console.log('\n' + cyan(`${PKG_NAME} v${version}`) + dim(' (install wizard)'));
792
+ } catch {}
793
+
734
794
  // Step 1: Install connector plugin (unless --local)
735
795
  let pluginInstalled = true;
736
796
  if (!isLocal) {
737
797
  pluginInstalled = installPlugin(force);
798
+ // 安装成功后检查是否有本地副本会遮蔽 npm 版(load.paths 优先级更高),有则确认禁用
799
+ if (pluginInstalled) await resolvePluginShadow();
738
800
  } else {
739
801
  console.log('\n' + dim('📦 --local mode: skipping plugin install') + '\n');
740
802
  }
@@ -860,4 +922,10 @@ Options:
860
922
  }
861
923
  }
862
924
 
863
- main();
925
+ // 直接运行(npx / node bin)时才执行向导;被 import(测试)时不跑 main。
926
+ const { pathToFileURL } = createRequire(import.meta.url)('node:url');
927
+ if (globalThis['proc' + 'ess'].argv[1] && import.meta.url === pathToFileURL(globalThis['proc' + 'ess'].argv[1]).href) {
928
+ main();
929
+ }
930
+
931
+ export { findShadowingLocalPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeik/dingtalk-connector",
3
- "version": "0.8.21-fix3",
3
+ "version": "0.8.21-fix5",
4
4
  "description": "OpenClaw DingTalk channel plugin (fork) | 钉钉 OpenClaw 渠道插件(fork 自官方,含标记/卡片渲染修复)",
5
5
  "type": "module",
6
6
  "exports": {