dshmarket 1.19.0 → 1.19.1
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/install.js +52 -3
- package/lib/pnpm-compat.js +9 -0
- package/lib/routes.js +1 -1
- package/lib/types/install.d.ts +35 -1
- package/lib/types/pnpm-compat.d.ts +18 -0
- package/package.json +1 -1
- package/src/install.ts +63 -3
- package/src/pnpm-compat.ts +20 -0
- package/src/routes.ts +1 -1
package/lib/install.js
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
|
-
import { classifyPnpmFailure } from './pnpm-compat.js';
|
|
10
|
-
import { conflictingEntryIds, hasDshManifest, hasLoadableEntry, pluginSubdirs, profileDir, readInstalled, readProfileBundles } from './profile.js';
|
|
9
|
+
import { classifyPnpmFailure, HOST_NAMESPACE_RE } from './pnpm-compat.js';
|
|
10
|
+
import { conflictingEntryIds, hasDshManifest, hasLoadableEntry, pluginSubdirs, profileDir, readInstalled, readManifestDeps, readProfileBundles } from './profile.js';
|
|
11
11
|
import { logEvent } from './log.js';
|
|
12
12
|
import { cleanOrphanedStore } from './store.js';
|
|
13
13
|
/** One-shot bypass for pnpm's fresh-release hold; scoped to a single command. */
|
|
@@ -20,6 +20,44 @@ export const RELEASE_AGE_OVERRIDE = '--config.minimumReleaseAge=0';
|
|
|
20
20
|
* this override once. Scoped to a single command like RELEASE_AGE_OVERRIDE.
|
|
21
21
|
*/
|
|
22
22
|
export const FETCH_TIMEOUT_OVERRIDE = '--config.fetchTimeout=600000';
|
|
23
|
+
/**
|
|
24
|
+
* Stop pnpm downloading a plugin's peer dependencies (#289 by @00080000).
|
|
25
|
+
*
|
|
26
|
+
* The last resort for a peer that cannot be downloaded because it does not
|
|
27
|
+
* exist on any registry: the dsh runtime injects several `@deepseek-ai/*`
|
|
28
|
+
* packages and never publishes them, and since pnpm 8 `auto-install-peers`
|
|
29
|
+
* defaults on, so pnpm walks the peer list and 404s on one.
|
|
30
|
+
*
|
|
31
|
+
* Only on the retry, never by default. Turning it off wholesale would also
|
|
32
|
+
* stop pnpm installing the peers a plugin legitimately needs from npm, and
|
|
33
|
+
* that failure would surface much later — as a missing module at runtime
|
|
34
|
+
* rather than a clear error at install time. Narrow beats early here.
|
|
35
|
+
*
|
|
36
|
+
* Verified against pnpm 10.29.3: `peerDependencyRules.ignoreMissing` does
|
|
37
|
+
* NOT prevent the fetch (it only silences the warning), so this flag is the
|
|
38
|
+
* only lever that actually works.
|
|
39
|
+
*/
|
|
40
|
+
export const AUTO_INSTALL_PEERS_OFF = '--config.auto-install-peers=false';
|
|
41
|
+
/**
|
|
42
|
+
* Whether an unresolvable package is a host peer pnpm went looking for on
|
|
43
|
+
* its own, rather than something the profile actually asks for.
|
|
44
|
+
*
|
|
45
|
+
* The same 404 means two different things and wants two different answers.
|
|
46
|
+
* A `@deepseek-ai/*` package that IS in the profile manifest is a ghost
|
|
47
|
+
* entry — left by an earlier failed operation, or hand-added — and the user
|
|
48
|
+
* has to remove that line; retrying would only fail again. One that is NOT
|
|
49
|
+
* in the manifest was never asked for by anybody: pnpm reached it by walking
|
|
50
|
+
* an installed plugin's peerDependencies, which in this ecosystem name what
|
|
51
|
+
* the runtime provides rather than what npm carries.
|
|
52
|
+
*
|
|
53
|
+
* Reading the manifest is what separates them, so this cannot live in the
|
|
54
|
+
* pure classifier.
|
|
55
|
+
*/
|
|
56
|
+
export function isUnpublishedHostPeer(pkg, profile, explicitDir) {
|
|
57
|
+
if (pkg === undefined || !HOST_NAMESPACE_RE.test(pkg))
|
|
58
|
+
return false;
|
|
59
|
+
return !Object.hasOwn(readManifestDeps(profile, explicitDir), pkg);
|
|
60
|
+
}
|
|
23
61
|
/**
|
|
24
62
|
* Run one plugin command with automatic recovery from three known pnpm traps:
|
|
25
63
|
*
|
|
@@ -40,7 +78,7 @@ export const FETCH_TIMEOUT_OVERRIDE = '--config.fetchTimeout=600000';
|
|
|
40
78
|
* appended to stderr so the UI shows an actionable message instead of a
|
|
41
79
|
* wall of text (#20 bug 3). Cancelled runs are never recovered.
|
|
42
80
|
*/
|
|
43
|
-
export async function withHoistRecovery(run, profile, pluginArgs) {
|
|
81
|
+
export async function withHoistRecovery(run, profile, pluginArgs, profileDirectory) {
|
|
44
82
|
let result = await run(profile, pluginArgs);
|
|
45
83
|
const ok = (r) => r.exitCode === 0 && !r.timedOut && !r.cancelled;
|
|
46
84
|
if (!ok(result) && !result.cancelled) {
|
|
@@ -59,6 +97,17 @@ export async function withHoistRecovery(run, profile, pluginArgs) {
|
|
|
59
97
|
logEvent('warn', 'install', `a too-young release blocks pnpm's lockfile verification (#39) — retrying once with ${RELEASE_AGE_OVERRIDE}`);
|
|
60
98
|
result = await run(profile, [pluginArgs[0], RELEASE_AGE_OVERRIDE, ...pluginArgs.slice(1)]);
|
|
61
99
|
}
|
|
100
|
+
else if (failure?.code === 'fetch-404'
|
|
101
|
+
&& isUnpublishedHostPeer(failure.pkg, profile, profileDirectory)
|
|
102
|
+
&& (pluginArgs[0] === 'add' || pluginArgs[0] === 'remove')
|
|
103
|
+
&& !pluginArgs.includes(AUTO_INSTALL_PEERS_OFF)) {
|
|
104
|
+
// The plugin is fine; pnpm went looking for a package the host injects
|
|
105
|
+
// and npm has never carried. Every fresh profile hits this, whatever
|
|
106
|
+
// the plugin, so failing here would be failing for something the user
|
|
107
|
+
// cannot fix and did not cause.
|
|
108
|
+
logEvent('warn', 'install', `${failure.pkg ?? 'a host package'} is a peer the runtime provides and npm does not carry (#289) — retrying once with ${AUTO_INSTALL_PEERS_OFF}`);
|
|
109
|
+
result = await run(profile, [pluginArgs[0], AUTO_INSTALL_PEERS_OFF, ...pluginArgs.slice(1)]);
|
|
110
|
+
}
|
|
62
111
|
else if (failure?.code === 'transient-network'
|
|
63
112
|
&& (pluginArgs[0] === 'add' || pluginArgs[0] === 'remove')) {
|
|
64
113
|
// #83: pnpm replays the whole tree, so any existing dependency's
|
package/lib/pnpm-compat.js
CHANGED
|
@@ -31,6 +31,14 @@ export function pluginArgsFor(profileDir, pluginArgs) {
|
|
|
31
31
|
return pluginArgs;
|
|
32
32
|
return [pluginArgs[0], '-w', ...pluginArgs.slice(1)];
|
|
33
33
|
}
|
|
34
|
+
/** One recognized pnpm failure, with a bilingual explanation for the UI. */
|
|
35
|
+
/**
|
|
36
|
+
* The namespace whose packages the dsh runtime provides rather than npm.
|
|
37
|
+
*
|
|
38
|
+
* A peer dependency on one of these is a statement about the host, not a
|
|
39
|
+
* package to download — and several of them are never published at all.
|
|
40
|
+
*/
|
|
41
|
+
export const HOST_NAMESPACE_RE = /^@deepseek-ai\//;
|
|
34
42
|
/**
|
|
35
43
|
* Momentary network failures — worth exactly one automatic retry (#83).
|
|
36
44
|
* pnpm 5xx fetch codes, its meta-fetch give-up, and the raw socket errors
|
|
@@ -159,6 +167,7 @@ export function classifyPnpmFailure(output) {
|
|
|
159
167
|
return {
|
|
160
168
|
code: 'fetch-404',
|
|
161
169
|
recoverable: false,
|
|
170
|
+
pkg,
|
|
162
171
|
message: `有一个依赖在 registry 上不存在${zh},pnpm 因此拒绝任何安装操作。它可能是之前失败操作残留在 profile package.json 里的幽灵依赖(可手动删除该行),也可能是需要登录的私有包 / a dependency cannot be resolved from the registry${en}; pnpm refuses every install while it is present. It may be a ghost entry left in the profile's package.json by an earlier failed operation (remove that line by hand), or a private package needing registry credentials`,
|
|
163
172
|
};
|
|
164
173
|
}
|
package/lib/routes.js
CHANGED
|
@@ -384,7 +384,7 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup) {
|
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
/** Every plugin command goes through the pnpm-drift recovery wrapper (#20). */
|
|
387
|
-
const runPlugin = (profile, args) => withHoistRecovery(commands.runPlugin, profile, args);
|
|
387
|
+
const runPlugin = (profile, args) => withHoistRecovery(commands.runPlugin, profile, args, activeProfileDir);
|
|
388
388
|
/**
|
|
389
389
|
* Undo a clean-exit update whose new build cannot boot. Restoring only the
|
|
390
390
|
* manifest pin (the original #159 behavior) leaves the bad package files
|
package/lib/types/install.d.ts
CHANGED
|
@@ -15,6 +15,40 @@ export declare const RELEASE_AGE_OVERRIDE = "--config.minimumReleaseAge=0";
|
|
|
15
15
|
* this override once. Scoped to a single command like RELEASE_AGE_OVERRIDE.
|
|
16
16
|
*/
|
|
17
17
|
export declare const FETCH_TIMEOUT_OVERRIDE = "--config.fetchTimeout=600000";
|
|
18
|
+
/**
|
|
19
|
+
* Stop pnpm downloading a plugin's peer dependencies (#289 by @00080000).
|
|
20
|
+
*
|
|
21
|
+
* The last resort for a peer that cannot be downloaded because it does not
|
|
22
|
+
* exist on any registry: the dsh runtime injects several `@deepseek-ai/*`
|
|
23
|
+
* packages and never publishes them, and since pnpm 8 `auto-install-peers`
|
|
24
|
+
* defaults on, so pnpm walks the peer list and 404s on one.
|
|
25
|
+
*
|
|
26
|
+
* Only on the retry, never by default. Turning it off wholesale would also
|
|
27
|
+
* stop pnpm installing the peers a plugin legitimately needs from npm, and
|
|
28
|
+
* that failure would surface much later — as a missing module at runtime
|
|
29
|
+
* rather than a clear error at install time. Narrow beats early here.
|
|
30
|
+
*
|
|
31
|
+
* Verified against pnpm 10.29.3: `peerDependencyRules.ignoreMissing` does
|
|
32
|
+
* NOT prevent the fetch (it only silences the warning), so this flag is the
|
|
33
|
+
* only lever that actually works.
|
|
34
|
+
*/
|
|
35
|
+
export declare const AUTO_INSTALL_PEERS_OFF = "--config.auto-install-peers=false";
|
|
36
|
+
/**
|
|
37
|
+
* Whether an unresolvable package is a host peer pnpm went looking for on
|
|
38
|
+
* its own, rather than something the profile actually asks for.
|
|
39
|
+
*
|
|
40
|
+
* The same 404 means two different things and wants two different answers.
|
|
41
|
+
* A `@deepseek-ai/*` package that IS in the profile manifest is a ghost
|
|
42
|
+
* entry — left by an earlier failed operation, or hand-added — and the user
|
|
43
|
+
* has to remove that line; retrying would only fail again. One that is NOT
|
|
44
|
+
* in the manifest was never asked for by anybody: pnpm reached it by walking
|
|
45
|
+
* an installed plugin's peerDependencies, which in this ecosystem name what
|
|
46
|
+
* the runtime provides rather than what npm carries.
|
|
47
|
+
*
|
|
48
|
+
* Reading the manifest is what separates them, so this cannot live in the
|
|
49
|
+
* pure classifier.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isUnpublishedHostPeer(pkg: string | undefined, profile: string, explicitDir?: string): boolean;
|
|
18
52
|
/**
|
|
19
53
|
* Run one plugin command with automatic recovery from three known pnpm traps:
|
|
20
54
|
*
|
|
@@ -35,7 +69,7 @@ export declare const FETCH_TIMEOUT_OVERRIDE = "--config.fetchTimeout=600000";
|
|
|
35
69
|
* appended to stderr so the UI shows an actionable message instead of a
|
|
36
70
|
* wall of text (#20 bug 3). Cancelled runs are never recovered.
|
|
37
71
|
*/
|
|
38
|
-
export declare function withHoistRecovery(run: PluginRunner, profile: string, pluginArgs: string[]): Promise<InstallResult>;
|
|
72
|
+
export declare function withHoistRecovery(run: PluginRunner, profile: string, pluginArgs: string[], profileDirectory?: string): Promise<InstallResult>;
|
|
39
73
|
/**
|
|
40
74
|
* The most specific description of a failed run available, for logs.
|
|
41
75
|
*
|
|
@@ -24,12 +24,30 @@
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function pluginArgsFor(profileDir: string, pluginArgs: string[]): string[];
|
|
26
26
|
/** One recognized pnpm failure, with a bilingual explanation for the UI. */
|
|
27
|
+
/**
|
|
28
|
+
* The namespace whose packages the dsh runtime provides rather than npm.
|
|
29
|
+
*
|
|
30
|
+
* A peer dependency on one of these is a statement about the host, not a
|
|
31
|
+
* package to download — and several of them are never published at all.
|
|
32
|
+
*/
|
|
33
|
+
export declare const HOST_NAMESPACE_RE: RegExp;
|
|
27
34
|
export interface PnpmFailure {
|
|
28
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';
|
|
29
36
|
/** Bilingual, actionable message shown to the user instead of the raw wall of text. */
|
|
30
37
|
message: string;
|
|
31
38
|
/** True when re-running `pnpm install` in the profile is the documented recovery. */
|
|
32
39
|
recoverable: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The package pnpm could not resolve, when the failure names one.
|
|
42
|
+
*
|
|
43
|
+
* Exposed because the NAME alone does not say what went wrong: the same
|
|
44
|
+
* 404 is a ghost entry the user must delete when the package is a direct
|
|
45
|
+
* dependency of the profile, and an unpublished host peer the market can
|
|
46
|
+
* retry around when it is not (#289). Only a caller holding the profile
|
|
47
|
+
* manifest can tell those apart, so the classifier reports the fact and
|
|
48
|
+
* leaves the judgement to it.
|
|
49
|
+
*/
|
|
50
|
+
pkg?: string;
|
|
33
51
|
}
|
|
34
52
|
/**
|
|
35
53
|
* Momentary network failures — worth exactly one automatic retry (#83).
|
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.19.
|
|
4
|
+
"version": "1.19.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
package/src/install.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
import { existsSync } from 'node:fs'
|
|
9
9
|
import { join } from 'node:path'
|
|
10
10
|
import type { InstallResult, PluginRunner } from './dsh-cli.ts'
|
|
11
|
-
import { classifyPnpmFailure, isTransientPnpmFailure } from './pnpm-compat.ts'
|
|
12
|
-
import { conflictingEntryIds, hasDshManifest, hasLoadableEntry, pluginSubdirs, profileDir, readInstalled, readProfileBundles } from './profile.ts'
|
|
11
|
+
import { classifyPnpmFailure, HOST_NAMESPACE_RE, isTransientPnpmFailure } from './pnpm-compat.ts'
|
|
12
|
+
import { conflictingEntryIds, hasDshManifest, hasLoadableEntry, pluginSubdirs, profileDir, readInstalled, readManifestDeps, readProfileBundles } from './profile.ts'
|
|
13
13
|
import { logEvent } from './log.ts'
|
|
14
14
|
import { cleanOrphanedStore } from './store.ts'
|
|
15
15
|
|
|
@@ -25,6 +25,49 @@ export const RELEASE_AGE_OVERRIDE = '--config.minimumReleaseAge=0'
|
|
|
25
25
|
*/
|
|
26
26
|
export const FETCH_TIMEOUT_OVERRIDE = '--config.fetchTimeout=600000'
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Stop pnpm downloading a plugin's peer dependencies (#289 by @00080000).
|
|
30
|
+
*
|
|
31
|
+
* The last resort for a peer that cannot be downloaded because it does not
|
|
32
|
+
* exist on any registry: the dsh runtime injects several `@deepseek-ai/*`
|
|
33
|
+
* packages and never publishes them, and since pnpm 8 `auto-install-peers`
|
|
34
|
+
* defaults on, so pnpm walks the peer list and 404s on one.
|
|
35
|
+
*
|
|
36
|
+
* Only on the retry, never by default. Turning it off wholesale would also
|
|
37
|
+
* stop pnpm installing the peers a plugin legitimately needs from npm, and
|
|
38
|
+
* that failure would surface much later — as a missing module at runtime
|
|
39
|
+
* rather than a clear error at install time. Narrow beats early here.
|
|
40
|
+
*
|
|
41
|
+
* Verified against pnpm 10.29.3: `peerDependencyRules.ignoreMissing` does
|
|
42
|
+
* NOT prevent the fetch (it only silences the warning), so this flag is the
|
|
43
|
+
* only lever that actually works.
|
|
44
|
+
*/
|
|
45
|
+
export const AUTO_INSTALL_PEERS_OFF = '--config.auto-install-peers=false'
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Whether an unresolvable package is a host peer pnpm went looking for on
|
|
49
|
+
* its own, rather than something the profile actually asks for.
|
|
50
|
+
*
|
|
51
|
+
* The same 404 means two different things and wants two different answers.
|
|
52
|
+
* A `@deepseek-ai/*` package that IS in the profile manifest is a ghost
|
|
53
|
+
* entry — left by an earlier failed operation, or hand-added — and the user
|
|
54
|
+
* has to remove that line; retrying would only fail again. One that is NOT
|
|
55
|
+
* in the manifest was never asked for by anybody: pnpm reached it by walking
|
|
56
|
+
* an installed plugin's peerDependencies, which in this ecosystem name what
|
|
57
|
+
* the runtime provides rather than what npm carries.
|
|
58
|
+
*
|
|
59
|
+
* Reading the manifest is what separates them, so this cannot live in the
|
|
60
|
+
* pure classifier.
|
|
61
|
+
*/
|
|
62
|
+
export function isUnpublishedHostPeer(
|
|
63
|
+
pkg: string | undefined,
|
|
64
|
+
profile: string,
|
|
65
|
+
explicitDir?: string,
|
|
66
|
+
): boolean {
|
|
67
|
+
if (pkg === undefined || !HOST_NAMESPACE_RE.test(pkg)) return false
|
|
68
|
+
return !Object.hasOwn(readManifestDeps(profile, explicitDir), pkg)
|
|
69
|
+
}
|
|
70
|
+
|
|
28
71
|
/**
|
|
29
72
|
* Run one plugin command with automatic recovery from three known pnpm traps:
|
|
30
73
|
*
|
|
@@ -45,7 +88,12 @@ export const FETCH_TIMEOUT_OVERRIDE = '--config.fetchTimeout=600000'
|
|
|
45
88
|
* appended to stderr so the UI shows an actionable message instead of a
|
|
46
89
|
* wall of text (#20 bug 3). Cancelled runs are never recovered.
|
|
47
90
|
*/
|
|
48
|
-
export async function withHoistRecovery(
|
|
91
|
+
export async function withHoistRecovery(
|
|
92
|
+
run: PluginRunner,
|
|
93
|
+
profile: string,
|
|
94
|
+
pluginArgs: string[],
|
|
95
|
+
profileDirectory?: string,
|
|
96
|
+
): Promise<InstallResult> {
|
|
49
97
|
let result = await run(profile, pluginArgs)
|
|
50
98
|
const ok = (r: InstallResult): boolean => r.exitCode === 0 && !r.timedOut && !r.cancelled
|
|
51
99
|
if (!ok(result) && !result.cancelled) {
|
|
@@ -63,6 +111,18 @@ export async function withHoistRecovery(run: PluginRunner, profile: string, plug
|
|
|
63
111
|
) {
|
|
64
112
|
logEvent('warn', 'install', `a too-young release blocks pnpm's lockfile verification (#39) — retrying once with ${RELEASE_AGE_OVERRIDE}`)
|
|
65
113
|
result = await run(profile, [pluginArgs[0], RELEASE_AGE_OVERRIDE, ...pluginArgs.slice(1)])
|
|
114
|
+
} else if (
|
|
115
|
+
failure?.code === 'fetch-404'
|
|
116
|
+
&& isUnpublishedHostPeer(failure.pkg, profile, profileDirectory)
|
|
117
|
+
&& (pluginArgs[0] === 'add' || pluginArgs[0] === 'remove')
|
|
118
|
+
&& !pluginArgs.includes(AUTO_INSTALL_PEERS_OFF)
|
|
119
|
+
) {
|
|
120
|
+
// The plugin is fine; pnpm went looking for a package the host injects
|
|
121
|
+
// and npm has never carried. Every fresh profile hits this, whatever
|
|
122
|
+
// the plugin, so failing here would be failing for something the user
|
|
123
|
+
// cannot fix and did not cause.
|
|
124
|
+
logEvent('warn', 'install', `${failure.pkg ?? 'a host package'} is a peer the runtime provides and npm does not carry (#289) — retrying once with ${AUTO_INSTALL_PEERS_OFF}`)
|
|
125
|
+
result = await run(profile, [pluginArgs[0], AUTO_INSTALL_PEERS_OFF, ...pluginArgs.slice(1)])
|
|
66
126
|
} else if (
|
|
67
127
|
failure?.code === 'transient-network'
|
|
68
128
|
&& (pluginArgs[0] === 'add' || pluginArgs[0] === 'remove')
|
package/src/pnpm-compat.ts
CHANGED
|
@@ -34,6 +34,14 @@ export function pluginArgsFor(profileDir: string, pluginArgs: string[]): string[
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/** One recognized pnpm failure, with a bilingual explanation for the UI. */
|
|
37
|
+
/**
|
|
38
|
+
* The namespace whose packages the dsh runtime provides rather than npm.
|
|
39
|
+
*
|
|
40
|
+
* A peer dependency on one of these is a statement about the host, not a
|
|
41
|
+
* package to download — and several of them are never published at all.
|
|
42
|
+
*/
|
|
43
|
+
export const HOST_NAMESPACE_RE = /^@deepseek-ai\//
|
|
44
|
+
|
|
37
45
|
export interface PnpmFailure {
|
|
38
46
|
code: 'adding-to-root' | 'not-a-workspace' | 'hoist-pattern-diff' | 'pnpm-missing' | 'release-age-violation'
|
|
39
47
|
| 'ignored-builds' | 'git-prepare-not-allowed' | 'fetch-404' | 'transient-network' | 'fetch-timeout'
|
|
@@ -42,6 +50,17 @@ export interface PnpmFailure {
|
|
|
42
50
|
message: string
|
|
43
51
|
/** True when re-running `pnpm install` in the profile is the documented recovery. */
|
|
44
52
|
recoverable: boolean
|
|
53
|
+
/**
|
|
54
|
+
* The package pnpm could not resolve, when the failure names one.
|
|
55
|
+
*
|
|
56
|
+
* Exposed because the NAME alone does not say what went wrong: the same
|
|
57
|
+
* 404 is a ghost entry the user must delete when the package is a direct
|
|
58
|
+
* dependency of the profile, and an unpublished host peer the market can
|
|
59
|
+
* retry around when it is not (#289). Only a caller holding the profile
|
|
60
|
+
* manifest can tell those apart, so the classifier reports the fact and
|
|
61
|
+
* leaves the judgement to it.
|
|
62
|
+
*/
|
|
63
|
+
pkg?: string
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
/**
|
|
@@ -174,6 +193,7 @@ export function classifyPnpmFailure(output: string): PnpmFailure | null {
|
|
|
174
193
|
return {
|
|
175
194
|
code: 'fetch-404',
|
|
176
195
|
recoverable: false,
|
|
196
|
+
pkg,
|
|
177
197
|
message: `有一个依赖在 registry 上不存在${zh},pnpm 因此拒绝任何安装操作。它可能是之前失败操作残留在 profile package.json 里的幽灵依赖(可手动删除该行),也可能是需要登录的私有包 / a dependency cannot be resolved from the registry${en}; pnpm refuses every install while it is present. It may be a ghost entry left in the profile's package.json by an earlier failed operation (remove that line by hand), or a private package needing registry credentials`,
|
|
178
198
|
}
|
|
179
199
|
}
|
package/src/routes.ts
CHANGED
|
@@ -425,7 +425,7 @@ export function mountMarketRoutes(
|
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
/** Every plugin command goes through the pnpm-drift recovery wrapper (#20). */
|
|
428
|
-
const runPlugin = (profile: string, args: string[]) => withHoistRecovery(commands.runPlugin, profile, args)
|
|
428
|
+
const runPlugin = (profile: string, args: string[]) => withHoistRecovery(commands.runPlugin, profile, args, activeProfileDir)
|
|
429
429
|
|
|
430
430
|
/**
|
|
431
431
|
* Undo a clean-exit update whose new build cannot boot. Restoring only the
|