dshmarket 1.20.3 → 1.21.0
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 +68 -3
- package/lib/sources.js +1 -1
- package/lib/types/dsh-cli.d.ts +48 -10
- package/lib/types/sources.d.ts +2 -0
- package/package.json +1 -1
- package/src/dsh-cli.ts +120 -14
- package/src/sources.ts +1 -1
package/lib/dsh-cli.js
CHANGED
|
@@ -15,6 +15,8 @@ import { createProgressTracker } from './ndjson.js';
|
|
|
15
15
|
import { pluginArgsFor } from './pnpm-compat.js';
|
|
16
16
|
import { isDshProfileName, profileDir } from './profile.js';
|
|
17
17
|
import { activeRegion, DEFAULT_NPM_REGISTRY, routesFor } from './regions.js';
|
|
18
|
+
import { NPM_NAME_RE } from './sources.js';
|
|
19
|
+
import { fetchNpmLatest } from './updates.js';
|
|
18
20
|
// 15 min default (slow networks + git installs), overridable for CI/tests.
|
|
19
21
|
// (#6 by @qichuang321.)
|
|
20
22
|
/**
|
|
@@ -185,7 +187,10 @@ export function toolSearchDirs(platform = process.platform, env = process.env, h
|
|
|
185
187
|
dirs.push(join(home, 'Library', 'pnpm'), join(home, '.local', 'share', 'pnpm'));
|
|
186
188
|
}
|
|
187
189
|
dirs.push(nodeBinDir, ...extraPathDirs);
|
|
188
|
-
|
|
190
|
+
// Deduped: PNPM_HOME usually names one of the defaults below it, and a
|
|
191
|
+
// list that says the same directory twice reads as carelessness in the
|
|
192
|
+
// one place a user goes looking for an answer.
|
|
193
|
+
return [...new Set(dirs.filter(dir => dir.trim() !== ''))];
|
|
189
194
|
}
|
|
190
195
|
function spawnEnv() {
|
|
191
196
|
// pnpm v10+ blocks forever on a silent interactive prompt without a TTY;
|
|
@@ -276,6 +281,46 @@ export function dshArgv() {
|
|
|
276
281
|
// Bare `dsh` is a .cmd shim on Windows that only a shell can start (#13).
|
|
277
282
|
return { file: 'dsh', args: [], cwd: undefined, viaShell: winCmdShim };
|
|
278
283
|
}
|
|
284
|
+
/** An npm name with a fully pinned version — the only target their boundary takes. */
|
|
285
|
+
const EXACT_NPM_TARGET_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
286
|
+
/**
|
|
287
|
+
* Rewrite an `add` argv into the shape Anywhere Labs' install boundary
|
|
288
|
+
* accepts, or null when it cannot be expressed there.
|
|
289
|
+
*
|
|
290
|
+
* Their validator wants exactly one target of the form `name@1.2.3` — not a
|
|
291
|
+
* bare name, not `@latest`, and not a `github:` source (read from
|
|
292
|
+
* `validateExternalMarketInstallArgs`, dsh-plugin-desktop/src/pnpm.ts). The
|
|
293
|
+
* market sends a bare name for a registry plugin and `dshmarket@latest` for
|
|
294
|
+
* itself, so both need the version resolved before that boundary will take
|
|
295
|
+
* them.
|
|
296
|
+
*
|
|
297
|
+
* Returning null is a normal outcome, not a failure: a github-sourced plugin
|
|
298
|
+
* has no `name@version` spelling at all. The caller falls back to the
|
|
299
|
+
* ordinary path, which on that host reports their own refusal — an accurate
|
|
300
|
+
* message about their contract, rather than one this package invented.
|
|
301
|
+
*/
|
|
302
|
+
async function exactNpmArgs(args) {
|
|
303
|
+
const targets = args.slice(1).filter(argument => !argument.startsWith('-'));
|
|
304
|
+
const target = targets[0];
|
|
305
|
+
if (targets.length !== 1 || target === undefined)
|
|
306
|
+
return null;
|
|
307
|
+
if (EXACT_NPM_TARGET_RE.test(target))
|
|
308
|
+
return [...args];
|
|
309
|
+
// A bare name, or one pinned to a dist-tag. Only a registry package can be
|
|
310
|
+
// resolved; `github:owner/repo` and file paths stop here.
|
|
311
|
+
const at = target.lastIndexOf('@');
|
|
312
|
+
const name = at > 0 ? target.slice(0, at) : target;
|
|
313
|
+
if (!NPM_NAME_RE.test(name))
|
|
314
|
+
return null;
|
|
315
|
+
const version = await fetchNpmLatest(name);
|
|
316
|
+
if (version === null)
|
|
317
|
+
return null;
|
|
318
|
+
const rewritten = `${name}@${version}`;
|
|
319
|
+
if (!EXACT_NPM_TARGET_RE.test(rewritten))
|
|
320
|
+
return null;
|
|
321
|
+
logEvent('info', 'install', `desktop install boundary needs an exact version: ${target} -> ${rewritten}`);
|
|
322
|
+
return args.map(argument => (argument === target ? rewritten : argument));
|
|
323
|
+
}
|
|
279
324
|
/**
|
|
280
325
|
* Kill a spawned child and, on Windows, its whole process tree — `kill()`
|
|
281
326
|
* there only terminates the wrapper, leaving pnpm children running.
|
|
@@ -478,7 +523,18 @@ export function provisionHint(corepackOutput, npmOutput, npmFound = true) {
|
|
|
478
523
|
if (/ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|network|proxy|certificate/i.test(`${corepackOutput}\n${npmOutput}`)) {
|
|
479
524
|
return '装 pnpm 时网络失败。若你在受限网络下,corepack 的 shim 也下载不到 pnpm 本体——请改用完整安装或指定镜像:brew install pnpm(macOS/Linux),或 npm i -g pnpm --registry <你的镜像> / Network failure while installing pnpm. On a restricted network the corepack shim cannot download pnpm either — install it fully or point at a mirror: `brew install pnpm`, or `npm i -g pnpm --registry <your mirror>`';
|
|
480
525
|
}
|
|
481
|
-
|
|
526
|
+
// Everything reported success and pnpm still will not run (#228 by
|
|
527
|
+
// @ZhengXin1023: corepack exit=0, npm -g exit=0, npm found — and the
|
|
528
|
+
// install button stayed locked with nothing said).
|
|
529
|
+
//
|
|
530
|
+
// This used to return undefined, which left the case that most needs an
|
|
531
|
+
// explanation with none: the user is told "setup failed" while every step
|
|
532
|
+
// they can see succeeded, and their complaint was exactly that — "又不告诉
|
|
533
|
+
// 我怎么手动配置". Whatever the cause, the actionable question is the same
|
|
534
|
+
// one, so ask it: where is pnpm, and is that anywhere this process looks?
|
|
535
|
+
const searched = toolSearchDirs().join(process.platform === 'win32' ? ' ; ' : ' : ');
|
|
536
|
+
const locate = process.platform === 'win32' ? 'where pnpm' : 'which pnpm';
|
|
537
|
+
return `pnpm 装好了,但这个 dsh 进程仍然启动不了它——安装步骤都成功,只是装到的位置不在它搜索的范围内。已找过:${searched}。请在终端执行 \`${locate}\` 看 pnpm 实际在哪:如果它不在上面这些目录里,把该目录设为 PNPM_HOME 后重启 dsh(\`export PNPM_HOME=<那个目录>\`),或者干脆从一个能直接运行 pnpm 的终端里启动 dsh。注意必须重启——正在运行的进程读不到新设的环境变量 / pnpm is installed but this dsh process still cannot start it: every step succeeded, the binary just landed somewhere this process does not look. Searched: ${searched}. Run \`${locate}\` in a terminal to see where pnpm actually is; if that directory is not in the list above, set PNPM_HOME to it and restart dsh (\`export PNPM_HOME=<that directory>\`), or simply start dsh from a terminal where \`pnpm\` already runs. The restart matters — a running process cannot see a newly set variable`;
|
|
482
538
|
}
|
|
483
539
|
/** Singleton progress state; the status route reads it, runDshPlugin writes it. */
|
|
484
540
|
export const progress = {
|
|
@@ -677,7 +733,16 @@ export function createDesktopPluginRuntime(service, activeProfileDir, invokingDi
|
|
|
677
733
|
const abort = new AbortController();
|
|
678
734
|
let handle;
|
|
679
735
|
try {
|
|
680
|
-
|
|
736
|
+
// `add` goes through Anywhere Labs' install boundary when that host
|
|
737
|
+
// publishes one, because their Desktop rejects `add` on `runPlugin`
|
|
738
|
+
// outright. Feature-detected, never assumed: this method is theirs
|
|
739
|
+
// alone, and on every other client — including the other desktop app
|
|
740
|
+
// in #292 — the ordinary call below is what runs, unchanged.
|
|
741
|
+
const boundary = prepared.args[0] === 'add' ? service.runExternalMarketPluginInstall : undefined;
|
|
742
|
+
const viaBoundary = boundary === undefined ? null : await exactNpmArgs(prepared.args);
|
|
743
|
+
handle = boundary === undefined || viaBoundary === null
|
|
744
|
+
? service.runPlugin(prepared.args, invokingDir, abort.signal)
|
|
745
|
+
: boundary.call(service, viaBoundary, invokingDir, abort.signal);
|
|
681
746
|
}
|
|
682
747
|
catch (error) {
|
|
683
748
|
const message = error instanceof Error ? error.message : String(error);
|
package/lib/sources.js
CHANGED
|
@@ -9,7 +9,7 @@ function validSubpath(subpath) {
|
|
|
9
9
|
return !subpath.split('/').some(seg => seg === '' || seg === '.' || seg === '..');
|
|
10
10
|
}
|
|
11
11
|
/** Registry tarball names must be plain npm package names, nothing fancier. */
|
|
12
|
-
const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
12
|
+
export const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
|
|
13
13
|
/**
|
|
14
14
|
* Parse a registry source url: a github repo, optionally with a
|
|
15
15
|
* `/tree/<branch>/<subpath>` suffix (how the curated list links monorepo
|
package/lib/types/dsh-cli.d.ts
CHANGED
|
@@ -154,17 +154,55 @@ export interface PluginCommandRuntime {
|
|
|
154
154
|
}>;
|
|
155
155
|
cancelActive(): boolean;
|
|
156
156
|
}
|
|
157
|
-
/**
|
|
157
|
+
/** One running package operation, however it was started. */
|
|
158
|
+
export interface DesktopPnpmHandleLike {
|
|
159
|
+
readonly stdout: NodeJS.ReadableStream;
|
|
160
|
+
readonly stderr: NodeJS.ReadableStream;
|
|
161
|
+
readonly done: Promise<{
|
|
162
|
+
readonly exitCode: number | null;
|
|
163
|
+
readonly signal: NodeJS.Signals | null;
|
|
164
|
+
}>;
|
|
165
|
+
cancel(): void;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Structural subset of DSH Desktop's public `desktopPnpm` contract.
|
|
169
|
+
*
|
|
170
|
+
* Anywhere Labs' DSH Desktop is ONE third-party client among several, and
|
|
171
|
+
* this interface exists only for it. Nothing here is part of the official
|
|
172
|
+
* DSH protocol — `desktopPnpm`, `installPlugin` and the install boundary
|
|
173
|
+
* below appear nowhere in `@deepseek-ai/*`. Every other client the market
|
|
174
|
+
* runs under, including other desktop apps, installs through the ordinary
|
|
175
|
+
* `dsh plugin --profile <p> add` CLI, and so does the market itself when
|
|
176
|
+
* none of these services are present.
|
|
177
|
+
*
|
|
178
|
+
* That is why every member past `runPlugin` is optional and reached by
|
|
179
|
+
* feature detection. A host that does not publish one simply never enters
|
|
180
|
+
* the branch, and the ordinary path it already used stays untouched — the
|
|
181
|
+
* cost of accommodating one vendor must not be paid by the others, or by
|
|
182
|
+
* the far larger number of people on plain `dsh web`.
|
|
183
|
+
*/
|
|
158
184
|
export interface DesktopPnpmLike {
|
|
159
|
-
runPlugin(args: readonly string[], invokingDir: string, signal?: AbortSignal):
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
185
|
+
runPlugin(args: readonly string[], invokingDir: string, signal?: AbortSignal): DesktopPnpmHandleLike;
|
|
186
|
+
/**
|
|
187
|
+
* Desktop 2.x refuses `add` through `runPlugin` — "plugin add must use the
|
|
188
|
+
* recoverable install boundary" (#215, #219, #272) — and offers this
|
|
189
|
+
* instead, which their launcher enables only for the selected market
|
|
190
|
+
* provider. Same arguments, same handle, no recovery receipt and no
|
|
191
|
+
* write-ahead log for the caller to reconcile.
|
|
192
|
+
*
|
|
193
|
+
* Optional because it is theirs: absent on every other host, including
|
|
194
|
+
* the other third-party desktop client in #292, which installs perfectly
|
|
195
|
+
* well through the ordinary CLI.
|
|
196
|
+
*
|
|
197
|
+
* Read from their published source rather than assumed: it accepts ONLY
|
|
198
|
+
* `add` with exactly one target of the form `name@exact.version`
|
|
199
|
+
* (`validateExternalMarketInstallArgs` in dsh-plugin-desktop/src/pnpm.ts).
|
|
200
|
+
* A `github:owner/repo` target is rejected before any process starts, so
|
|
201
|
+
* the 1085 catalog entries with no npm package — 57% of it — cannot be
|
|
202
|
+
* installed on that host by any spelling this market could send. That is
|
|
203
|
+
* a gap in their contract, not something to work around here.
|
|
204
|
+
*/
|
|
205
|
+
runExternalMarketPluginInstall?(args: readonly string[], invokingDir: string, signal?: AbortSignal): DesktopPnpmHandleLike;
|
|
168
206
|
}
|
|
169
207
|
/** Desktop runtime also owns cleanup of any operation started by this fiber. */
|
|
170
208
|
export interface DesktopPluginRuntime extends PluginCommandRuntime {
|
package/lib/types/sources.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Registry-source knowledge: how a curated registry entry's URL maps to an
|
|
3
3
|
* installable pnpm target. Pure string logic, no I/O.
|
|
4
4
|
*/
|
|
5
|
+
/** Registry tarball names must be plain npm package names, nothing fancier. */
|
|
6
|
+
export declare const NPM_NAME_RE: RegExp;
|
|
5
7
|
/**
|
|
6
8
|
* Parse a registry source url: a github repo, optionally with a
|
|
7
9
|
* `/tree/<branch>/<subpath>` suffix (how the curated list links monorepo
|
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.
|
|
4
|
+
"version": "1.21.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
package/src/dsh-cli.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { createProgressTracker, type ProgressPhase } from './ndjson.ts'
|
|
|
17
17
|
import { pluginArgsFor } from './pnpm-compat.ts'
|
|
18
18
|
import { isDshProfileName, profileDir } from './profile.ts'
|
|
19
19
|
import { activeRegion, DEFAULT_NPM_REGISTRY, routesFor, type Region } from './regions.ts'
|
|
20
|
+
import { NPM_NAME_RE } from './sources.ts'
|
|
21
|
+
import { fetchNpmLatest } from './updates.ts'
|
|
20
22
|
|
|
21
23
|
// 15 min default (slow networks + git installs), overridable for CI/tests.
|
|
22
24
|
// (#6 by @qichuang321.)
|
|
@@ -186,7 +188,10 @@ export function toolSearchDirs(
|
|
|
186
188
|
dirs.push(join(home, 'Library', 'pnpm'), join(home, '.local', 'share', 'pnpm'))
|
|
187
189
|
}
|
|
188
190
|
dirs.push(nodeBinDir, ...extraPathDirs)
|
|
189
|
-
|
|
191
|
+
// Deduped: PNPM_HOME usually names one of the defaults below it, and a
|
|
192
|
+
// list that says the same directory twice reads as carelessness in the
|
|
193
|
+
// one place a user goes looking for an answer.
|
|
194
|
+
return [...new Set(dirs.filter(dir => dir.trim() !== ''))]
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
function spawnEnv(): NodeJS.ProcessEnv {
|
|
@@ -328,21 +333,102 @@ export interface PluginCommandRuntime {
|
|
|
328
333
|
cancelActive(): boolean
|
|
329
334
|
}
|
|
330
335
|
|
|
331
|
-
/**
|
|
336
|
+
/** One running package operation, however it was started. */
|
|
337
|
+
export interface DesktopPnpmHandleLike {
|
|
338
|
+
readonly stdout: NodeJS.ReadableStream
|
|
339
|
+
readonly stderr: NodeJS.ReadableStream
|
|
340
|
+
readonly done: Promise<{
|
|
341
|
+
readonly exitCode: number | null
|
|
342
|
+
readonly signal: NodeJS.Signals | null
|
|
343
|
+
}>
|
|
344
|
+
cancel(): void
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Structural subset of DSH Desktop's public `desktopPnpm` contract.
|
|
349
|
+
*
|
|
350
|
+
* Anywhere Labs' DSH Desktop is ONE third-party client among several, and
|
|
351
|
+
* this interface exists only for it. Nothing here is part of the official
|
|
352
|
+
* DSH protocol — `desktopPnpm`, `installPlugin` and the install boundary
|
|
353
|
+
* below appear nowhere in `@deepseek-ai/*`. Every other client the market
|
|
354
|
+
* runs under, including other desktop apps, installs through the ordinary
|
|
355
|
+
* `dsh plugin --profile <p> add` CLI, and so does the market itself when
|
|
356
|
+
* none of these services are present.
|
|
357
|
+
*
|
|
358
|
+
* That is why every member past `runPlugin` is optional and reached by
|
|
359
|
+
* feature detection. A host that does not publish one simply never enters
|
|
360
|
+
* the branch, and the ordinary path it already used stays untouched — the
|
|
361
|
+
* cost of accommodating one vendor must not be paid by the others, or by
|
|
362
|
+
* the far larger number of people on plain `dsh web`.
|
|
363
|
+
*/
|
|
332
364
|
export interface DesktopPnpmLike {
|
|
333
365
|
runPlugin(
|
|
334
366
|
args: readonly string[],
|
|
335
367
|
invokingDir: string,
|
|
336
368
|
signal?: AbortSignal,
|
|
337
|
-
):
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
369
|
+
): DesktopPnpmHandleLike
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Desktop 2.x refuses `add` through `runPlugin` — "plugin add must use the
|
|
373
|
+
* recoverable install boundary" (#215, #219, #272) — and offers this
|
|
374
|
+
* instead, which their launcher enables only for the selected market
|
|
375
|
+
* provider. Same arguments, same handle, no recovery receipt and no
|
|
376
|
+
* write-ahead log for the caller to reconcile.
|
|
377
|
+
*
|
|
378
|
+
* Optional because it is theirs: absent on every other host, including
|
|
379
|
+
* the other third-party desktop client in #292, which installs perfectly
|
|
380
|
+
* well through the ordinary CLI.
|
|
381
|
+
*
|
|
382
|
+
* Read from their published source rather than assumed: it accepts ONLY
|
|
383
|
+
* `add` with exactly one target of the form `name@exact.version`
|
|
384
|
+
* (`validateExternalMarketInstallArgs` in dsh-plugin-desktop/src/pnpm.ts).
|
|
385
|
+
* A `github:owner/repo` target is rejected before any process starts, so
|
|
386
|
+
* the 1085 catalog entries with no npm package — 57% of it — cannot be
|
|
387
|
+
* installed on that host by any spelling this market could send. That is
|
|
388
|
+
* a gap in their contract, not something to work around here.
|
|
389
|
+
*/
|
|
390
|
+
runExternalMarketPluginInstall?(
|
|
391
|
+
args: readonly string[],
|
|
392
|
+
invokingDir: string,
|
|
393
|
+
signal?: AbortSignal,
|
|
394
|
+
): DesktopPnpmHandleLike
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** An npm name with a fully pinned version — the only target their boundary takes. */
|
|
398
|
+
const EXACT_NPM_TARGET_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Rewrite an `add` argv into the shape Anywhere Labs' install boundary
|
|
402
|
+
* accepts, or null when it cannot be expressed there.
|
|
403
|
+
*
|
|
404
|
+
* Their validator wants exactly one target of the form `name@1.2.3` — not a
|
|
405
|
+
* bare name, not `@latest`, and not a `github:` source (read from
|
|
406
|
+
* `validateExternalMarketInstallArgs`, dsh-plugin-desktop/src/pnpm.ts). The
|
|
407
|
+
* market sends a bare name for a registry plugin and `dshmarket@latest` for
|
|
408
|
+
* itself, so both need the version resolved before that boundary will take
|
|
409
|
+
* them.
|
|
410
|
+
*
|
|
411
|
+
* Returning null is a normal outcome, not a failure: a github-sourced plugin
|
|
412
|
+
* has no `name@version` spelling at all. The caller falls back to the
|
|
413
|
+
* ordinary path, which on that host reports their own refusal — an accurate
|
|
414
|
+
* message about their contract, rather than one this package invented.
|
|
415
|
+
*/
|
|
416
|
+
async function exactNpmArgs(args: readonly string[]): Promise<string[] | null> {
|
|
417
|
+
const targets = args.slice(1).filter(argument => !argument.startsWith('-'))
|
|
418
|
+
const target = targets[0]
|
|
419
|
+
if (targets.length !== 1 || target === undefined) return null
|
|
420
|
+
if (EXACT_NPM_TARGET_RE.test(target)) return [...args]
|
|
421
|
+
// A bare name, or one pinned to a dist-tag. Only a registry package can be
|
|
422
|
+
// resolved; `github:owner/repo` and file paths stop here.
|
|
423
|
+
const at = target.lastIndexOf('@')
|
|
424
|
+
const name = at > 0 ? target.slice(0, at) : target
|
|
425
|
+
if (!NPM_NAME_RE.test(name)) return null
|
|
426
|
+
const version = await fetchNpmLatest(name)
|
|
427
|
+
if (version === null) return null
|
|
428
|
+
const rewritten = `${name}@${version}`
|
|
429
|
+
if (!EXACT_NPM_TARGET_RE.test(rewritten)) return null
|
|
430
|
+
logEvent('info', 'install', `desktop install boundary needs an exact version: ${target} -> ${rewritten}`)
|
|
431
|
+
return args.map(argument => (argument === target ? rewritten : argument))
|
|
346
432
|
}
|
|
347
433
|
|
|
348
434
|
/** Desktop runtime also owns cleanup of any operation started by this fiber. */
|
|
@@ -553,7 +639,18 @@ export function provisionHint(corepackOutput: string, npmOutput: string, npmFoun
|
|
|
553
639
|
if (/ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|network|proxy|certificate/i.test(`${corepackOutput}\n${npmOutput}`)) {
|
|
554
640
|
return '装 pnpm 时网络失败。若你在受限网络下,corepack 的 shim 也下载不到 pnpm 本体——请改用完整安装或指定镜像:brew install pnpm(macOS/Linux),或 npm i -g pnpm --registry <你的镜像> / Network failure while installing pnpm. On a restricted network the corepack shim cannot download pnpm either — install it fully or point at a mirror: `brew install pnpm`, or `npm i -g pnpm --registry <your mirror>`'
|
|
555
641
|
}
|
|
556
|
-
|
|
642
|
+
// Everything reported success and pnpm still will not run (#228 by
|
|
643
|
+
// @ZhengXin1023: corepack exit=0, npm -g exit=0, npm found — and the
|
|
644
|
+
// install button stayed locked with nothing said).
|
|
645
|
+
//
|
|
646
|
+
// This used to return undefined, which left the case that most needs an
|
|
647
|
+
// explanation with none: the user is told "setup failed" while every step
|
|
648
|
+
// they can see succeeded, and their complaint was exactly that — "又不告诉
|
|
649
|
+
// 我怎么手动配置". Whatever the cause, the actionable question is the same
|
|
650
|
+
// one, so ask it: where is pnpm, and is that anywhere this process looks?
|
|
651
|
+
const searched = toolSearchDirs().join(process.platform === 'win32' ? ' ; ' : ' : ')
|
|
652
|
+
const locate = process.platform === 'win32' ? 'where pnpm' : 'which pnpm'
|
|
653
|
+
return `pnpm 装好了,但这个 dsh 进程仍然启动不了它——安装步骤都成功,只是装到的位置不在它搜索的范围内。已找过:${searched}。请在终端执行 \`${locate}\` 看 pnpm 实际在哪:如果它不在上面这些目录里,把该目录设为 PNPM_HOME 后重启 dsh(\`export PNPM_HOME=<那个目录>\`),或者干脆从一个能直接运行 pnpm 的终端里启动 dsh。注意必须重启——正在运行的进程读不到新设的环境变量 / pnpm is installed but this dsh process still cannot start it: every step succeeded, the binary just landed somewhere this process does not look. Searched: ${searched}. Run \`${locate}\` in a terminal to see where pnpm actually is; if that directory is not in the list above, set PNPM_HOME to it and restart dsh (\`export PNPM_HOME=<that directory>\`), or simply start dsh from a terminal where \`pnpm\` already runs. The restart matters — a running process cannot see a newly set variable`
|
|
557
654
|
}
|
|
558
655
|
|
|
559
656
|
/** Live progress of the running plugin command, for the status route. */
|
|
@@ -785,9 +882,18 @@ export function createDesktopPluginRuntime(
|
|
|
785
882
|
}
|
|
786
883
|
|
|
787
884
|
const abort = new AbortController()
|
|
788
|
-
let handle:
|
|
885
|
+
let handle: DesktopPnpmHandleLike
|
|
789
886
|
try {
|
|
790
|
-
|
|
887
|
+
// `add` goes through Anywhere Labs' install boundary when that host
|
|
888
|
+
// publishes one, because their Desktop rejects `add` on `runPlugin`
|
|
889
|
+
// outright. Feature-detected, never assumed: this method is theirs
|
|
890
|
+
// alone, and on every other client — including the other desktop app
|
|
891
|
+
// in #292 — the ordinary call below is what runs, unchanged.
|
|
892
|
+
const boundary = prepared.args[0] === 'add' ? service.runExternalMarketPluginInstall : undefined
|
|
893
|
+
const viaBoundary = boundary === undefined ? null : await exactNpmArgs(prepared.args)
|
|
894
|
+
handle = boundary === undefined || viaBoundary === null
|
|
895
|
+
? service.runPlugin(prepared.args, invokingDir, abort.signal)
|
|
896
|
+
: boundary.call(service, viaBoundary, invokingDir, abort.signal)
|
|
791
897
|
} catch (error) {
|
|
792
898
|
const message = error instanceof Error ? error.message : String(error)
|
|
793
899
|
const busy = /another desktop pnpm operation is already running/i.test(message)
|
package/src/sources.ts
CHANGED
|
@@ -11,7 +11,7 @@ function validSubpath(subpath: string): boolean {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/** Registry tarball names must be plain npm package names, nothing fancier. */
|
|
14
|
-
const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
|
|
14
|
+
export const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Parse a registry source url: a github repo, optionally with a
|