dsh-plugin-pack 1.0.19 → 1.0.20

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.
Files changed (2) hide show
  1. package/lib/lockfile.js +71 -31
  2. package/package.json +1 -1
package/lib/lockfile.js CHANGED
@@ -22,25 +22,69 @@ import { spawnSync } from 'node:child_process';
22
22
  import fs from 'node:fs';
23
23
  import path from 'node:path';
24
24
 
25
- /** 找 pnpm 可执行文件;找不到返回 null。 */
25
+ /**
26
+ * 找 pnpm —— 优先用 dsh 自带的那个。
27
+ *
28
+ * 为什么不能随便用 PATH 上的:dsh 内置 pnpm 11(store/v11),
29
+ * 用户命令行可能是 pnpm 10(store/v10)。store 目录不同、锁文件格式也可能不同,
30
+ * 用错版本去同步等于又把 dsh 的安装/卸载搞坏。
31
+ *
32
+ * 探测顺序:DSH_PNPM 环境变量 → dsh 运行时里的 pnpm.cjs → PATH 上的 pnpm。
33
+ *
34
+ * @returns {{ command: string, args: string[], version: string|null, bundled: boolean } | null}
35
+ */
26
36
  function findPnpm() {
27
- const candidates =
28
- process.platform === 'win32'
29
- ? ['pnpm.cmd', 'pnpm.exe', 'pnpm']
30
- : ['pnpm'];
37
+ const candidates = [];
38
+
39
+ if (process.env.DSH_PNPM && fs.existsSync(process.env.DSH_PNPM)) {
40
+ candidates.push({ command: process.execPath, args: [process.env.DSH_PNPM], bundled: true });
41
+ }
42
+
43
+ // dsh 的 node 通常在 <runtime>/node.exe,pnpm 在 <runtime>/dependencies/pnpm/bin/pnpm.cjs
44
+ const exeDir = path.dirname(process.execPath);
45
+ for (const rel of [
46
+ ['dependencies', 'pnpm', 'bin', 'pnpm.cjs'],
47
+ ['..', 'dependencies', 'pnpm', 'bin', 'pnpm.cjs'],
48
+ ['runtime', 'primary-runtime', 'dependencies', 'pnpm', 'bin', 'pnpm.cjs'],
49
+ ]) {
50
+ const candidate = path.join(exeDir, ...rel);
51
+ if (fs.existsSync(candidate)) {
52
+ candidates.push({ command: process.execPath, args: [candidate], bundled: true });
53
+ break;
54
+ }
55
+ }
56
+
57
+ // 兜底:PATH 上的 pnpm
58
+ const names = process.platform === 'win32' ? ['pnpm.cmd', 'pnpm.exe', 'pnpm'] : ['pnpm'];
59
+ for (const name of names) candidates.push({ command: name, args: [], bundled: false });
31
60
 
32
- for (const name of candidates) {
33
- const result = spawnSync(name, ['--version'], {
61
+ for (const candidate of candidates) {
62
+ const result = spawnSync(candidate.command, [...candidate.args, '--version'], {
34
63
  encoding: 'utf8',
35
- shell: process.platform === 'win32',
36
- timeout: 15000,
64
+ // 自带 pnpm 是 node 直接跑 .cjs,不需要 shell;PATH 上的需要
65
+ shell: process.platform === 'win32' && !candidate.bundled,
66
+ timeout: 20000,
37
67
  windowsHide: true,
38
68
  });
39
- if (!result.error && result.status === 0) return name;
69
+ if (!result.error && result.status === 0) {
70
+ const version = String(result.stdout ?? '').trim().split('\n')[0] || null;
71
+ return { ...candidate, version };
72
+ }
40
73
  }
41
74
  return null;
42
75
  }
43
76
 
77
+ /** 用找到的 pnpm 跑一条命令。 */
78
+ function runPnpm(pnpm, args, cwd, timeoutMs) {
79
+ return spawnSync(pnpm.command, [...pnpm.args, ...args], {
80
+ cwd,
81
+ encoding: 'utf8',
82
+ shell: process.platform === 'win32' && !pnpm.bundled,
83
+ timeout: timeoutMs,
84
+ windowsHide: true,
85
+ });
86
+ }
87
+
44
88
  /**
45
89
  * 让 `pnpm-lock.yaml` 与 `package.json` 对齐。
46
90
  *
@@ -68,23 +112,21 @@ export function syncLockfile(profileDir, options = {}) {
68
112
  const pnpm = findPnpm();
69
113
  if (!pnpm) return { ok: false, skipped: true, reason: '没找到 pnpm' };
70
114
 
71
- const result = spawnSync(pnpm, ['install', '--lockfile-only', '--reporter=silent'], {
72
- cwd: profile,
73
- encoding: 'utf8',
74
- shell: process.platform === 'win32',
75
- timeout: options.timeoutMs ?? 120000,
76
- windowsHide: true,
77
- env: { ...process.env, CI: '' },
78
- });
115
+ const result = runPnpm(
116
+ pnpm,
117
+ ['install', '--lockfile-only', '--config.confirmModulesPurge=false'],
118
+ profile,
119
+ options.timeoutMs ?? 180000,
120
+ );
79
121
 
80
122
  if (result.error) {
81
- return { ok: false, error: String(result.error.message ?? result.error) };
123
+ return { ok: false, error: String(result.error.message ?? result.error), pnpm: pnpm.version };
82
124
  }
83
125
  if (result.status !== 0) {
84
126
  const detail = `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
85
- return { ok: false, error: detail.slice(0, 400) || `pnpm 退出码 ${result.status}` };
127
+ return { ok: false, error: detail.slice(0, 400) || `pnpm 退出码 ${result.status}`, pnpm: pnpm.version };
86
128
  }
87
- return { ok: true };
129
+ return { ok: true, pnpm: pnpm.version };
88
130
  } catch (error) {
89
131
  return { ok: false, error: String(error?.message ?? error) };
90
132
  }
@@ -108,19 +150,17 @@ export function checkLockfile(profileDir) {
108
150
  const pnpm = findPnpm();
109
151
  if (!pnpm) return { ok: false, skipped: true, reason: '没找到 pnpm' };
110
152
 
111
- const result = spawnSync(pnpm, ['install', '--frozen-lockfile', '--lockfile-only'], {
112
- cwd: profile,
113
- encoding: 'utf8',
114
- shell: process.platform === 'win32',
115
- timeout: 120000,
116
- windowsHide: true,
117
- });
153
+ const result = runPnpm(pnpm, ['install', '--frozen-lockfile', '--lockfile-only'], profile, 180000);
118
154
  // 注意:这里不能用 --reporter=silent —— 它会把 ERR_PNPM_OUTDATED_LOCKFILE
119
155
  // 这段错误文本一起吞掉,导致分不清"锁文件不一致"和"其它失败"。
120
156
  const detail = `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
121
- if (/ERR_PNPM_OUTDATED_LOCKFILE/.test(detail)) return { ok: true, outdated: true };
122
- if (result.status === 0) return { ok: true, outdated: false };
123
- return { ok: false, error: detail.trim().slice(0, 400) || `pnpm 退出码 ${result.status}` };
157
+ if (/ERR_PNPM_OUTDATED_LOCKFILE/.test(detail)) return { ok: true, outdated: true, pnpm: pnpm.version };
158
+ if (result.status === 0) return { ok: true, outdated: false, pnpm: pnpm.version };
159
+ // 其它错误里也可能带 store 不一致
160
+ if (/ERR_PNPM_UNEXPECTED_STORE/.test(detail)) {
161
+ return { ok: false, storeMismatch: true, pnpm: pnpm.version, error: detail.trim().slice(0, 400) };
162
+ }
163
+ return { ok: false, error: detail.trim().slice(0, 400) || `pnpm 退出码 ${result.status}`, pnpm: pnpm.version };
124
164
  } catch (error) {
125
165
  return { ok: false, error: String(error?.message ?? error) };
126
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-pack",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "description": "DeepSeek Harness 插件整合包:一键把当前 profile 的第三方插件连依赖和配置导出成 zip(内部每个插件是标准 npm tgz),换机/换 profile 拖进去就能还原。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",