dshmarket 1.20.0 → 1.20.2

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/dsh-cli.js CHANGED
@@ -143,15 +143,56 @@ export function proxyEnvForPnpm(env = process.env, region = 'global') {
143
143
  }
144
144
  return out;
145
145
  }
146
+ /**
147
+ * Directories to append to PATH so a spawned pnpm can be found (#32, #38,
148
+ * #167, #292).
149
+ *
150
+ * A GUI or desktop launch inherits none of the shell profile, so PATH holds
151
+ * whatever the launcher had — usually not the directory the user's package
152
+ * manager lives in. The market appends the places it is actually installed
153
+ * to rather than telling the user to fix their environment.
154
+ *
155
+ * Windows used to get only the Node directory, which made the market's own
156
+ * advice unfollowable: the error it prints recommends installing pnpm with
157
+ * `iwr https://get.pnpm.io/install.ps1`, and then it did not look where that
158
+ * installer puts it (#292). Both Windows layouts are covered now — the
159
+ * standalone installer's `%LOCALAPPDATA%\pnpm`, and `%APPDATA%\npm` where
160
+ * `npm i -g pnpm` writes `pnpm.cmd`.
161
+ *
162
+ * `PNPM_HOME` comes first on every platform: the installer sets it, so it is
163
+ * the one answer that is right even when the layout is not the default one.
164
+ *
165
+ * @param platform - `process.platform`, injectable for tests.
166
+ * @param env - environment, for PNPM_HOME and the Windows app-data roots.
167
+ * @param home - home directory, injectable for tests.
168
+ */
169
+ export function toolSearchDirs(platform = process.platform, env = process.env, home = homedir()) {
170
+ const dirs = [];
171
+ const pnpmHome = (env.PNPM_HOME ?? '').trim();
172
+ if (pnpmHome !== '')
173
+ dirs.push(pnpmHome);
174
+ if (platform === 'win32') {
175
+ const local = (env.LOCALAPPDATA ?? '').trim();
176
+ const roaming = (env.APPDATA ?? '').trim();
177
+ if (local !== '')
178
+ dirs.push(join(local, 'pnpm'));
179
+ if (roaming !== '')
180
+ dirs.push(join(roaming, 'npm'));
181
+ }
182
+ else {
183
+ dirs.push('/opt/homebrew/bin', '/usr/local/bin', join(home, '.local', 'bin'));
184
+ // Where the standalone installer lands when PNPM_HOME is unset.
185
+ dirs.push(join(home, 'Library', 'pnpm'), join(home, '.local', 'share', 'pnpm'));
186
+ }
187
+ dirs.push(nodeBinDir, ...extraPathDirs);
188
+ return dirs;
189
+ }
146
190
  function spawnEnv() {
147
191
  // pnpm v10+ blocks forever on a silent interactive prompt without a TTY;
148
192
  // CI mode forces it to act or fail instead of asking.
149
193
  const separator = process.platform === 'win32' ? ';' : ':';
150
194
  const parts = (process.env.PATH ?? '').split(separator).filter(part => part !== '');
151
- const candidates = process.platform === 'win32'
152
- ? [nodeBinDir, ...extraPathDirs]
153
- : ['/opt/homebrew/bin', '/usr/local/bin', join(homedir(), '.local', 'bin'), nodeBinDir, ...extraPathDirs];
154
- for (const bin of candidates) {
195
+ for (const bin of toolSearchDirs()) {
155
196
  if (!parts.includes(bin))
156
197
  parts.push(bin);
157
198
  }
@@ -417,7 +458,13 @@ export function provisionHint(corepackOutput, npmOutput, npmFound = true) {
417
458
  // holds on a Windows console that reports the same thing in a codepage we
418
459
  // cannot read (#167); the ENOENT match stays for callers without it.
419
460
  if (!npmFound || (/ENOENT/.test(corepackOutput) && /ENOENT/.test(npmOutput))) {
420
- return `这台机器的 dsh 进程找不到 npm/corepack(图形界面或桌面端启动时不继承终端 PATH)。已在 Node 自己的目录里找过(${nodeBinDir})也没有——多半是宿主内置的 Node 运行时不带 npm。请改从终端启动 dsh,或单独装一个 pnpm:Windows 用 iwr https://get.pnpm.io/install.ps1 -useb | iex,macOS/Linux 用 brew install pnpm / This dsh process cannot find npm/corepack (GUI and desktop launches skip your shell PATH). The directory Node itself runs from (${nodeBinDir}) was searched too a bundled Node runtime without npm is the usual cause. Start dsh from a terminal, or install pnpm on its own: \`iwr https://get.pnpm.io/install.ps1 -useb | iex\` (Windows) or \`brew install pnpm\` (macOS/Linux)`;
461
+ // The searched list is spelled out because the previous wording named
462
+ // only the Node directory, which was both incomplete and unhelpful: a
463
+ // user who HAD installed pnpm could not tell whether we looked in the
464
+ // right place (#292). And the restart note matters — the installer sets
465
+ // PNPM_HOME for new sessions, so a dsh already running cannot see it.
466
+ const searched = toolSearchDirs().join(process.platform === 'win32' ? ' ; ' : ' : ');
467
+ return `这台机器的 dsh 进程找不到 npm/corepack(图形界面或桌面端启动时不继承终端 PATH)——多半是宿主内置的 Node 运行时不带 npm。已找过:${searched}。请改从终端启动 dsh,或单独装一个 pnpm:Windows 用 iwr https://get.pnpm.io/install.ps1 -useb | iex,macOS/Linux 用 brew install pnpm。装完后请重启 dsh——安装器只对新开的会话生效,正在运行的进程看不到它 / This dsh process cannot find npm/corepack (GUI and desktop launches skip your shell PATH); a bundled Node runtime without npm is the usual cause. Searched: ${searched}. Start dsh from a terminal, or install pnpm on its own: \`iwr https://get.pnpm.io/install.ps1 -useb | iex\` (Windows) or \`brew install pnpm\` (macOS/Linux). Restart dsh afterwards — the installer only affects new sessions, so an already-running process cannot see it`;
421
468
  }
422
469
  if (/EEXIST|already exists|--force to overwrite/i.test(npmOutput)) {
423
470
  return 'pnpm 的可执行文件已存在(通常是 corepack 先放好了同名 shim),npm 拒绝覆盖。在终端里执行其一即可:corepack prepare pnpm@latest --activate(推荐,直接激活已有 shim)或 npm i -g pnpm --force / A pnpm executable already exists (usually a corepack shim), so npm refused to overwrite it. Run one of these in a terminal: `corepack prepare pnpm@latest --activate` (preferred — activates the shim already there) or `npm i -g pnpm --force`';
@@ -105,6 +105,28 @@ export function classifyPnpmFailure(output) {
105
105
  message: `这个 profile 的 node_modules 链接到的 pnpm store,和当前 pnpm 默认使用的 store 不是同一个,pnpm 因此拒绝所有安装与卸载。${detail}\n在 profile 目录里执行一次 \`pnpm install --store-dir <上面第一个路径>\` 重新链接即可(dsh 运行时可能占用文件,必要时先退出 dsh)/ this profile's node_modules is linked to a different pnpm store than the one pnpm now resolves, so pnpm refuses every install and uninstall.${detail}\nRelink by running \`pnpm install --store-dir <the first path above>\` once in the profile directory (stop dsh first if files are locked)`,
106
106
  };
107
107
  }
108
+ // #222 by @MicroMilo: a patch in the profile that no longer applies.
109
+ //
110
+ // pnpm exits 1 (verified against 10.29.3) but it has ALREADY written the
111
+ // package — unpatched. So the profile is left holding the pristine version
112
+ // the patch existed to fix, and the damage shows up at the next boot as
113
+ // "failed to load plugins" rather than here, where it happened.
114
+ //
115
+ // Almost always the package moved the file the patch names: the reported
116
+ // case patched `client/client.js` in a package that had started shipping
117
+ // `lib/client.js`. That is not something the market can repair — the patch
118
+ // is the user's, and guessing a new target would be inventing a change
119
+ // they did not write — so it says exactly what is wrong and where.
120
+ if (output.includes('ERR_PNPM_PATCH_FAILED')) {
121
+ const patch = /Could not apply patch (\S+)/.exec(output)?.[1];
122
+ const which = patch === undefined ? '' : `(${patch})`;
123
+ const whichEn = patch === undefined ? '' : ` (${patch})`;
124
+ return {
125
+ code: 'patch-failed',
126
+ recoverable: false,
127
+ message: `profile 里的一个 pnpm 补丁打不上了${which}。pnpm 会继续把这个包装上,但装的是没打补丁的原版——通常下次启动才会以「插件加载失败」暴露出来。多半是包升级后挪动了补丁指向的文件(例如补丁改的是 client/client.js,而新版本发的是 lib/client.js)。请更新或删掉这个补丁文件,以及 profile package.json 里 pnpm.patchedDependencies 中对应的那一条 / a pnpm patch in this profile no longer applies${whichEn}. pnpm still installs the package, but unpatched — which usually surfaces at the next boot as "failed to load plugins" rather than here. The usual cause is the package moving the file the patch targets (for example a patch against client/client.js when the release now ships lib/client.js). Update or remove that patch file and its entry under pnpm.patchedDependencies in the profile's package.json`,
128
+ };
129
+ }
108
130
  if (output.includes('ERR_PNPM_ADDING_TO_ROOT')) {
109
131
  return {
110
132
  code: 'adding-to-root',
@@ -54,6 +54,30 @@ export declare const nodeBinDir: string;
54
54
  * must keep excluding it.
55
55
  */
56
56
  export declare function proxyEnvForPnpm(env?: NodeJS.ProcessEnv, region?: Region): NodeJS.ProcessEnv;
57
+ /**
58
+ * Directories to append to PATH so a spawned pnpm can be found (#32, #38,
59
+ * #167, #292).
60
+ *
61
+ * A GUI or desktop launch inherits none of the shell profile, so PATH holds
62
+ * whatever the launcher had — usually not the directory the user's package
63
+ * manager lives in. The market appends the places it is actually installed
64
+ * to rather than telling the user to fix their environment.
65
+ *
66
+ * Windows used to get only the Node directory, which made the market's own
67
+ * advice unfollowable: the error it prints recommends installing pnpm with
68
+ * `iwr https://get.pnpm.io/install.ps1`, and then it did not look where that
69
+ * installer puts it (#292). Both Windows layouts are covered now — the
70
+ * standalone installer's `%LOCALAPPDATA%\pnpm`, and `%APPDATA%\npm` where
71
+ * `npm i -g pnpm` writes `pnpm.cmd`.
72
+ *
73
+ * `PNPM_HOME` comes first on every platform: the installer sets it, so it is
74
+ * the one answer that is right even when the layout is not the default one.
75
+ *
76
+ * @param platform - `process.platform`, injectable for tests.
77
+ * @param env - environment, for PNPM_HOME and the Windows app-data roots.
78
+ * @param home - home directory, injectable for tests.
79
+ */
80
+ export declare function toolSearchDirs(platform?: string, env?: NodeJS.ProcessEnv, home?: string): string[];
57
81
  /**
58
82
  * Windows npm/corepack/pnpm are `.cmd` shims. Node's `spawn` without a shell
59
83
  * cannot start them (ENOENT / EINVAL). Same pattern as dsh's `plugin` forwarder.
@@ -32,7 +32,7 @@ export declare function pluginArgsFor(profileDir: string, pluginArgs: string[]):
32
32
  */
33
33
  export declare const HOST_NAMESPACE_RE: RegExp;
34
34
  export interface PnpmFailure {
35
- code: 'adding-to-root' | 'not-a-workspace' | 'hoist-pattern-diff' | 'pnpm-missing' | 'release-age-violation' | 'ignored-builds' | 'git-prepare-not-allowed' | 'fetch-404' | 'transient-network' | 'fetch-timeout' | 'unexpected-store';
35
+ code: 'adding-to-root' | 'not-a-workspace' | 'hoist-pattern-diff' | 'pnpm-missing' | 'release-age-violation' | 'ignored-builds' | 'git-prepare-not-allowed' | 'fetch-404' | 'transient-network' | 'fetch-timeout' | 'unexpected-store' | 'patch-failed';
36
36
  /** Bilingual, actionable message shown to the user instead of the raw wall of text. */
37
37
  message: string;
38
38
  /** True when re-running `pnpm install` in the profile is the documented recovery. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dshmarket",
3
3
  "description": "Visual plugin market inside DeepSeek Harness — browse, search, and one-click install community plugins. · DSH 可视化插件市场:逛一逛,点一下,装好。",
4
- "version": "1.20.0",
4
+ "version": "1.20.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
package/src/dsh-cli.ts CHANGED
@@ -144,15 +144,57 @@ export function proxyEnvForPnpm(env: NodeJS.ProcessEnv = process.env, region: Re
144
144
  return out
145
145
  }
146
146
 
147
+ /**
148
+ * Directories to append to PATH so a spawned pnpm can be found (#32, #38,
149
+ * #167, #292).
150
+ *
151
+ * A GUI or desktop launch inherits none of the shell profile, so PATH holds
152
+ * whatever the launcher had — usually not the directory the user's package
153
+ * manager lives in. The market appends the places it is actually installed
154
+ * to rather than telling the user to fix their environment.
155
+ *
156
+ * Windows used to get only the Node directory, which made the market's own
157
+ * advice unfollowable: the error it prints recommends installing pnpm with
158
+ * `iwr https://get.pnpm.io/install.ps1`, and then it did not look where that
159
+ * installer puts it (#292). Both Windows layouts are covered now — the
160
+ * standalone installer's `%LOCALAPPDATA%\pnpm`, and `%APPDATA%\npm` where
161
+ * `npm i -g pnpm` writes `pnpm.cmd`.
162
+ *
163
+ * `PNPM_HOME` comes first on every platform: the installer sets it, so it is
164
+ * the one answer that is right even when the layout is not the default one.
165
+ *
166
+ * @param platform - `process.platform`, injectable for tests.
167
+ * @param env - environment, for PNPM_HOME and the Windows app-data roots.
168
+ * @param home - home directory, injectable for tests.
169
+ */
170
+ export function toolSearchDirs(
171
+ platform: string = process.platform,
172
+ env: NodeJS.ProcessEnv = process.env,
173
+ home: string = homedir(),
174
+ ): string[] {
175
+ const dirs: string[] = []
176
+ const pnpmHome = (env.PNPM_HOME ?? '').trim()
177
+ if (pnpmHome !== '') dirs.push(pnpmHome)
178
+ if (platform === 'win32') {
179
+ const local = (env.LOCALAPPDATA ?? '').trim()
180
+ const roaming = (env.APPDATA ?? '').trim()
181
+ if (local !== '') dirs.push(join(local, 'pnpm'))
182
+ if (roaming !== '') dirs.push(join(roaming, 'npm'))
183
+ } else {
184
+ dirs.push('/opt/homebrew/bin', '/usr/local/bin', join(home, '.local', 'bin'))
185
+ // Where the standalone installer lands when PNPM_HOME is unset.
186
+ dirs.push(join(home, 'Library', 'pnpm'), join(home, '.local', 'share', 'pnpm'))
187
+ }
188
+ dirs.push(nodeBinDir, ...extraPathDirs)
189
+ return dirs
190
+ }
191
+
147
192
  function spawnEnv(): NodeJS.ProcessEnv {
148
193
  // pnpm v10+ blocks forever on a silent interactive prompt without a TTY;
149
194
  // CI mode forces it to act or fail instead of asking.
150
195
  const separator = process.platform === 'win32' ? ';' : ':'
151
196
  const parts = (process.env.PATH ?? '').split(separator).filter(part => part !== '')
152
- const candidates = process.platform === 'win32'
153
- ? [nodeBinDir, ...extraPathDirs]
154
- : ['/opt/homebrew/bin', '/usr/local/bin', join(homedir(), '.local', 'bin'), nodeBinDir, ...extraPathDirs]
155
- for (const bin of candidates) {
197
+ for (const bin of toolSearchDirs()) {
156
198
  if (!parts.includes(bin)) parts.push(bin)
157
199
  }
158
200
  return { ...process.env, ...proxyEnvForPnpm(process.env, activeRegion()), CI: 'true', PATH: parts.join(separator) }
@@ -491,7 +533,13 @@ export function provisionHint(corepackOutput: string, npmOutput: string, npmFoun
491
533
  // holds on a Windows console that reports the same thing in a codepage we
492
534
  // cannot read (#167); the ENOENT match stays for callers without it.
493
535
  if (!npmFound || (/ENOENT/.test(corepackOutput) && /ENOENT/.test(npmOutput))) {
494
- return `这台机器的 dsh 进程找不到 npm/corepack(图形界面或桌面端启动时不继承终端 PATH)。已在 Node 自己的目录里找过(${nodeBinDir})也没有——多半是宿主内置的 Node 运行时不带 npm。请改从终端启动 dsh,或单独装一个 pnpm:Windows 用 iwr https://get.pnpm.io/install.ps1 -useb | iex,macOS/Linux 用 brew install pnpm / This dsh process cannot find npm/corepack (GUI and desktop launches skip your shell PATH). The directory Node itself runs from (${nodeBinDir}) was searched too a bundled Node runtime without npm is the usual cause. Start dsh from a terminal, or install pnpm on its own: \`iwr https://get.pnpm.io/install.ps1 -useb | iex\` (Windows) or \`brew install pnpm\` (macOS/Linux)`
536
+ // The searched list is spelled out because the previous wording named
537
+ // only the Node directory, which was both incomplete and unhelpful: a
538
+ // user who HAD installed pnpm could not tell whether we looked in the
539
+ // right place (#292). And the restart note matters — the installer sets
540
+ // PNPM_HOME for new sessions, so a dsh already running cannot see it.
541
+ const searched = toolSearchDirs().join(process.platform === 'win32' ? ' ; ' : ' : ')
542
+ return `这台机器的 dsh 进程找不到 npm/corepack(图形界面或桌面端启动时不继承终端 PATH)——多半是宿主内置的 Node 运行时不带 npm。已找过:${searched}。请改从终端启动 dsh,或单独装一个 pnpm:Windows 用 iwr https://get.pnpm.io/install.ps1 -useb | iex,macOS/Linux 用 brew install pnpm。装完后请重启 dsh——安装器只对新开的会话生效,正在运行的进程看不到它 / This dsh process cannot find npm/corepack (GUI and desktop launches skip your shell PATH); a bundled Node runtime without npm is the usual cause. Searched: ${searched}. Start dsh from a terminal, or install pnpm on its own: \`iwr https://get.pnpm.io/install.ps1 -useb | iex\` (Windows) or \`brew install pnpm\` (macOS/Linux). Restart dsh afterwards — the installer only affects new sessions, so an already-running process cannot see it`
495
543
  }
496
544
  if (/EEXIST|already exists|--force to overwrite/i.test(npmOutput)) {
497
545
  return 'pnpm 的可执行文件已存在(通常是 corepack 先放好了同名 shim),npm 拒绝覆盖。在终端里执行其一即可:corepack prepare pnpm@latest --activate(推荐,直接激活已有 shim)或 npm i -g pnpm --force / A pnpm executable already exists (usually a corepack shim), so npm refused to overwrite it. Run one of these in a terminal: `corepack prepare pnpm@latest --activate` (preferred — activates the shim already there) or `npm i -g pnpm --force`'
@@ -45,7 +45,7 @@ export const HOST_NAMESPACE_RE = /^@deepseek-ai\//
45
45
  export interface PnpmFailure {
46
46
  code: 'adding-to-root' | 'not-a-workspace' | 'hoist-pattern-diff' | 'pnpm-missing' | 'release-age-violation'
47
47
  | 'ignored-builds' | 'git-prepare-not-allowed' | 'fetch-404' | 'transient-network' | 'fetch-timeout'
48
- | 'unexpected-store'
48
+ | 'unexpected-store' | 'patch-failed'
49
49
  /** Bilingual, actionable message shown to the user instead of the raw wall of text. */
50
50
  message: string
51
51
  /** True when re-running `pnpm install` in the profile is the documented recovery. */
@@ -131,6 +131,28 @@ export function classifyPnpmFailure(output: string): PnpmFailure | null {
131
131
  message: `这个 profile 的 node_modules 链接到的 pnpm store,和当前 pnpm 默认使用的 store 不是同一个,pnpm 因此拒绝所有安装与卸载。${detail}\n在 profile 目录里执行一次 \`pnpm install --store-dir <上面第一个路径>\` 重新链接即可(dsh 运行时可能占用文件,必要时先退出 dsh)/ this profile's node_modules is linked to a different pnpm store than the one pnpm now resolves, so pnpm refuses every install and uninstall.${detail}\nRelink by running \`pnpm install --store-dir <the first path above>\` once in the profile directory (stop dsh first if files are locked)`,
132
132
  }
133
133
  }
134
+ // #222 by @MicroMilo: a patch in the profile that no longer applies.
135
+ //
136
+ // pnpm exits 1 (verified against 10.29.3) but it has ALREADY written the
137
+ // package — unpatched. So the profile is left holding the pristine version
138
+ // the patch existed to fix, and the damage shows up at the next boot as
139
+ // "failed to load plugins" rather than here, where it happened.
140
+ //
141
+ // Almost always the package moved the file the patch names: the reported
142
+ // case patched `client/client.js` in a package that had started shipping
143
+ // `lib/client.js`. That is not something the market can repair — the patch
144
+ // is the user's, and guessing a new target would be inventing a change
145
+ // they did not write — so it says exactly what is wrong and where.
146
+ if (output.includes('ERR_PNPM_PATCH_FAILED')) {
147
+ const patch = /Could not apply patch (\S+)/.exec(output)?.[1]
148
+ const which = patch === undefined ? '' : `(${patch})`
149
+ const whichEn = patch === undefined ? '' : ` (${patch})`
150
+ return {
151
+ code: 'patch-failed',
152
+ recoverable: false,
153
+ message: `profile 里的一个 pnpm 补丁打不上了${which}。pnpm 会继续把这个包装上,但装的是没打补丁的原版——通常下次启动才会以「插件加载失败」暴露出来。多半是包升级后挪动了补丁指向的文件(例如补丁改的是 client/client.js,而新版本发的是 lib/client.js)。请更新或删掉这个补丁文件,以及 profile package.json 里 pnpm.patchedDependencies 中对应的那一条 / a pnpm patch in this profile no longer applies${whichEn}. pnpm still installs the package, but unpatched — which usually surfaces at the next boot as "failed to load plugins" rather than here. The usual cause is the package moving the file the patch targets (for example a patch against client/client.js when the release now ships lib/client.js). Update or remove that patch file and its entry under pnpm.patchedDependencies in the profile's package.json`,
154
+ }
155
+ }
134
156
  if (output.includes('ERR_PNPM_ADDING_TO_ROOT')) {
135
157
  return {
136
158
  code: 'adding-to-root',