@chatcode/cco-market 1.45.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/LICENSE +21 -0
- package/README.md +146 -0
- package/README.zh.md +143 -0
- package/UPDATE-API-V1.md +132 -0
- package/client/client.js +11534 -0
- package/cordis.patch.yml +5 -0
- package/lib/accelerate.js +184 -0
- package/lib/agents.js +36 -0
- package/lib/backup.js +572 -0
- package/lib/catalog-local-match.js +126 -0
- package/lib/catalog-npm.js +111 -0
- package/lib/changelog.js +242 -0
- package/lib/channels.js +64 -0
- package/lib/check.js +1067 -0
- package/lib/compatibility.js +198 -0
- package/lib/diagnostics.js +49 -0
- package/lib/discovery-compatibility.js +260 -0
- package/lib/dsh-cli.js +949 -0
- package/lib/dsh-install.js +108 -0
- package/lib/gist.js +352 -0
- package/lib/groups.js +97 -0
- package/lib/home-paths.js +42 -0
- package/lib/hot.js +519 -0
- package/lib/http.js +39 -0
- package/lib/index.js +97 -0
- package/lib/install.js +387 -0
- package/lib/log.js +197 -0
- package/lib/ndjson.js +154 -0
- package/lib/net.js +98 -0
- package/lib/order.js +284 -0
- package/lib/patch.js +521 -0
- package/lib/pnpm-compat.js +490 -0
- package/lib/presets.js +278 -0
- package/lib/profile.js +911 -0
- package/lib/region-probe.js +93 -0
- package/lib/regions.js +253 -0
- package/lib/registry.js +204 -0
- package/lib/restart.js +373 -0
- package/lib/routes.js +4372 -0
- package/lib/settings.js +109 -0
- package/lib/skill-market/api.js +119 -0
- package/lib/skill-market/config.js +29 -0
- package/lib/skill-market/installer.js +352 -0
- package/lib/skill-market/runtime.js +13 -0
- package/lib/skill-market/tui.js +119 -0
- package/lib/skill-market/types.js +1 -0
- package/lib/skill-market/web-routes.js +123 -0
- package/lib/snapshot.js +500 -0
- package/lib/source-migration.js +54 -0
- package/lib/sources.js +596 -0
- package/lib/store.js +91 -0
- package/lib/themes.js +102 -0
- package/lib/trial.js +116 -0
- package/lib/types/accelerate.d.ts +85 -0
- package/lib/types/agents.d.ts +22 -0
- package/lib/types/backup.d.ts +131 -0
- package/lib/types/catalog-local-match.d.ts +50 -0
- package/lib/types/catalog-npm.d.ts +47 -0
- package/lib/types/changelog.d.ts +102 -0
- package/lib/types/channels.d.ts +56 -0
- package/lib/types/check.d.ts +266 -0
- package/lib/types/compatibility.d.ts +64 -0
- package/lib/types/diagnostics.d.ts +29 -0
- package/lib/types/discovery-compatibility.d.ts +80 -0
- package/lib/types/dsh-cli.d.ts +324 -0
- package/lib/types/dsh-install.d.ts +46 -0
- package/lib/types/gist.d.ts +53 -0
- package/lib/types/groups.d.ts +34 -0
- package/lib/types/home-paths.d.ts +16 -0
- package/lib/types/hot.d.ts +221 -0
- package/lib/types/http.d.ts +12 -0
- package/lib/types/index.d.ts +14 -0
- package/lib/types/install.d.ts +172 -0
- package/lib/types/log.d.ts +43 -0
- package/lib/types/ndjson.d.ts +52 -0
- package/lib/types/net.d.ts +58 -0
- package/lib/types/order.d.ts +100 -0
- package/lib/types/patch.d.ts +129 -0
- package/lib/types/pnpm-compat.d.ts +93 -0
- package/lib/types/presets.d.ts +90 -0
- package/lib/types/profile.d.ts +253 -0
- package/lib/types/region-probe.d.ts +50 -0
- package/lib/types/regions.d.ts +122 -0
- package/lib/types/registry.d.ts +86 -0
- package/lib/types/restart.d.ts +194 -0
- package/lib/types/routes.d.ts +67 -0
- package/lib/types/settings.d.ts +78 -0
- package/lib/types/skill-market/api.d.ts +10 -0
- package/lib/types/skill-market/config.d.ts +3 -0
- package/lib/types/skill-market/installer.d.ts +23 -0
- package/lib/types/skill-market/runtime.d.ts +10 -0
- package/lib/types/skill-market/tui.d.ts +4 -0
- package/lib/types/skill-market/types.d.ts +71 -0
- package/lib/types/skill-market/web-routes.d.ts +19 -0
- package/lib/types/snapshot.d.ts +93 -0
- package/lib/types/source-migration.d.ts +11 -0
- package/lib/types/sources.d.ts +216 -0
- package/lib/types/store.d.ts +30 -0
- package/lib/types/themes.d.ts +40 -0
- package/lib/types/trial.d.ts +61 -0
- package/lib/types/update-api-v1.d.ts +66 -0
- package/lib/types/updates.d.ts +123 -0
- package/lib/types/verify.d.ts +139 -0
- package/lib/update-api-v1.js +215 -0
- package/lib/updates.js +361 -0
- package/lib/verify.js +453 -0
- package/package.json +121 -0
- package/src/accelerate.ts +213 -0
- package/src/agents.ts +43 -0
- package/src/backup.ts +583 -0
- package/src/catalog-local-match.ts +144 -0
- package/src/catalog-npm.ts +120 -0
- package/src/changelog.ts +282 -0
- package/src/channels.ts +70 -0
- package/src/check.ts +1239 -0
- package/src/client/CommentsModal.tsx +119 -0
- package/src/client/Diagnostics.tsx +907 -0
- package/src/client/ErrorBoundary.tsx +111 -0
- package/src/client/InstallToast.tsx +31 -0
- package/src/client/Market.module.css +830 -0
- package/src/client/MarketSection.tsx +5301 -0
- package/src/client/OperationsPanel.tsx +365 -0
- package/src/client/SettingsCard.tsx +621 -0
- package/src/client/SkillMarket.module.css +21 -0
- package/src/client/SkillMarketSection.tsx +162 -0
- package/src/client/comments.ts +54 -0
- package/src/client/globals.d.ts +13 -0
- package/src/client/index.ts +180 -0
- package/src/client/locales.ts +1112 -0
- package/src/client/market-data.ts +1321 -0
- package/src/client/operations.ts +201 -0
- package/src/client/preset-panel.tsx +263 -0
- package/src/client/primitives.d.ts +140 -0
- package/src/client/self-check.ts +147 -0
- package/src/client/snapshot-panel.tsx +244 -0
- package/src/compatibility.ts +237 -0
- package/src/diagnostics.ts +84 -0
- package/src/discovery-compatibility.ts +315 -0
- package/src/dsh-cli.ts +1126 -0
- package/src/dsh-install.ts +118 -0
- package/src/gist.ts +362 -0
- package/src/groups.ts +111 -0
- package/src/home-paths.ts +53 -0
- package/src/hot.ts +628 -0
- package/src/http.ts +41 -0
- package/src/index.ts +128 -0
- package/src/install.ts +420 -0
- package/src/log.ts +206 -0
- package/src/ndjson.ts +185 -0
- package/src/net.ts +106 -0
- package/src/order.ts +303 -0
- package/src/patch.ts +522 -0
- package/src/pnpm-compat.ts +527 -0
- package/src/presets.ts +344 -0
- package/src/profile.ts +940 -0
- package/src/region-probe.ts +97 -0
- package/src/regions.ts +310 -0
- package/src/registry.ts +250 -0
- package/src/restart.ts +396 -0
- package/src/routes.ts +4506 -0
- package/src/settings.ts +141 -0
- package/src/skill-market/api.ts +130 -0
- package/src/skill-market/config.ts +32 -0
- package/src/skill-market/installer.ts +337 -0
- package/src/skill-market/runtime.ts +14 -0
- package/src/skill-market/tui.ts +157 -0
- package/src/skill-market/types.ts +80 -0
- package/src/skill-market/web-routes.ts +132 -0
- package/src/snapshot.ts +532 -0
- package/src/source-migration.ts +61 -0
- package/src/sources.ts +565 -0
- package/src/store.ts +89 -0
- package/src/themes.ts +125 -0
- package/src/trial.ts +156 -0
- package/src/update-api-v1.ts +277 -0
- package/src/updates.ts +400 -0
- package/src/verify.ts +492 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pnpm compatibility layer — everything the market needs to know about how
|
|
3
|
+
* different pnpm majors behave inside a dsh profile directory, kept pure and
|
|
4
|
+
* separately testable (test/unit + test/integration exercise this module
|
|
5
|
+
* against real pnpm 9/10/11).
|
|
6
|
+
*
|
|
7
|
+
* Verified behavior matrix (2026-08, pnpm 9.15.9 / 10.28.2 / 11.21.0):
|
|
8
|
+
* - workspace root, `add` without -w: pnpm 9 fails ERR_PNPM_ADDING_TO_ROOT;
|
|
9
|
+
* pnpm 10/11 succeed.
|
|
10
|
+
* - `add -w` where NO pnpm-workspace.yaml exists: ALL majors fail with
|
|
11
|
+
* "--workspace-root may only be used inside a workspace".
|
|
12
|
+
* - modules dir built by pnpm 9, then pnpm 10/11 mutate it: a modules-layout
|
|
13
|
+
* compatibility error (public-hoist-pattern on Unix; virtual-store path
|
|
14
|
+
* length can be the first mismatch pnpm reports on Windows).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync } from 'node:fs'
|
|
18
|
+
import { join } from 'node:path'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Decide the argv for a `dsh plugin <add|remove> …` call in the given profile.
|
|
23
|
+
*
|
|
24
|
+
* pnpm 9 refuses to add at a workspace root without -w (#17, #20); every
|
|
25
|
+
* pnpm major refuses -w when the directory is NOT a workspace. So the flag
|
|
26
|
+
* is injected exactly when the profile has a pnpm-workspace.yaml.
|
|
27
|
+
* @param profileDir - resolved profile directory (owns pnpm-workspace.yaml, or not).
|
|
28
|
+
* @param pluginArgs - the raw args, e.g. ['add', 'dshmarket@latest'].
|
|
29
|
+
* @returns args with -w injected when — and only when — the profile is a workspace root.
|
|
30
|
+
*/
|
|
31
|
+
export function pluginArgsFor(profileDir: string, pluginArgs: string[]): string[] {
|
|
32
|
+
if (pluginArgs[0] !== 'add' && pluginArgs[0] !== 'remove') return pluginArgs
|
|
33
|
+
if (!existsSync(join(profileDir, 'pnpm-workspace.yaml'))) return pluginArgs
|
|
34
|
+
return [pluginArgs[0], '-w', ...pluginArgs.slice(1)]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One recognized pnpm failure, with a bilingual explanation for the UI. */
|
|
38
|
+
/**
|
|
39
|
+
* The namespace whose packages the dsh runtime provides rather than npm.
|
|
40
|
+
*
|
|
41
|
+
* A peer dependency on one of these is a statement about the host, not a
|
|
42
|
+
* package to download — and several of them are never published at all.
|
|
43
|
+
*/
|
|
44
|
+
export const HOST_NAMESPACE_RE = /^@deepseek-ai\//
|
|
45
|
+
|
|
46
|
+
export interface PnpmFailure {
|
|
47
|
+
code: 'adding-to-root' | 'not-a-workspace' | 'hoist-pattern-diff' | 'pnpm-missing' | 'release-age-violation'
|
|
48
|
+
| 'ignored-builds' | 'git-prepare-not-allowed' | 'git-prepare-failed' | 'tarball-url-mismatch'
|
|
49
|
+
| 'fetch-404' | 'transient-network' | 'fetch-timeout'
|
|
50
|
+
| 'unexpected-store' | 'patch-failed' | 'missing-tarball-integrity' | 'windows-file-locked'
|
|
51
|
+
| 'pnpm-unusable' | 'missing-local-dependency'
|
|
52
|
+
/** Bilingual, actionable message shown to the user instead of the raw wall of text. */
|
|
53
|
+
message: string
|
|
54
|
+
/** True when re-running `pnpm install` in the profile is the documented recovery. */
|
|
55
|
+
recoverable: boolean
|
|
56
|
+
/**
|
|
57
|
+
* Show this message INSTEAD of the captured output, not after it.
|
|
58
|
+
*
|
|
59
|
+
* Normally the raw text is worth keeping: it is pnpm's own account of what
|
|
60
|
+
* happened, and the explanation sits under it. Set only where the captured
|
|
61
|
+
* bytes carry nothing a user can read — cmd.exe writes its errors in the
|
|
62
|
+
* OEM code page, which arrives here as replacement characters, so pasting
|
|
63
|
+
* them under an explanation adds noise and hides the explanation (#502).
|
|
64
|
+
*/
|
|
65
|
+
replaceOutput?: boolean
|
|
66
|
+
/**
|
|
67
|
+
* The package pnpm could not resolve, when the failure names one.
|
|
68
|
+
*
|
|
69
|
+
* Exposed because the NAME alone does not say what went wrong: the same
|
|
70
|
+
* 404 is a ghost entry the user must delete when the package is a direct
|
|
71
|
+
* dependency of the profile, and an unpublished host peer the market can
|
|
72
|
+
* retry around when it is not (#289). Only a caller holding the profile
|
|
73
|
+
* manifest can tell those apart, so the classifier reports the fact and
|
|
74
|
+
* leaves the judgement to it.
|
|
75
|
+
*/
|
|
76
|
+
pkg?: string
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Momentary network failures — worth exactly one automatic retry (#83).
|
|
81
|
+
* pnpm 5xx fetch codes, its meta-fetch give-up, and the raw socket errors
|
|
82
|
+
* that surface through dsh's wrapper. Permanent shapes (404, auth) are
|
|
83
|
+
* deliberately absent: retrying those just doubles the wait for bad news.
|
|
84
|
+
*/
|
|
85
|
+
export function isTransientPnpmFailure(output: string): boolean {
|
|
86
|
+
return /ERR_PNPM_FETCH_5\d\d|ERR_PNPM_META_FETCH_FAIL|FetchError|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|socket hang up|network timeout/i.test(output)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* pnpm's per-request fetch timeout: the abort surfaces as a DOMException
|
|
91
|
+
* ("The operation was aborted due to timeout", code 23) through undici —
|
|
92
|
+
* pnpm logs it as `GET … error (23)` before giving up. This is the failure
|
|
93
|
+
* shape for large tarballs (github: sources download the WHOLE repo, even
|
|
94
|
+
* for a `#path:` subdirectory plugin) on slow networks: pnpm's default
|
|
95
|
+
* 60-second limit is simply not enough, so a plain retry fails again at the
|
|
96
|
+
* same limit. The market's recovery re-runs once with a longer
|
|
97
|
+
* fetchTimeout (see withHoistRecovery).
|
|
98
|
+
*/
|
|
99
|
+
export function isFetchTimeoutFailure(output: string): boolean {
|
|
100
|
+
return /operation was aborted due to timeout|TimeoutError|error \(23\)/i.test(output)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Add human diagnostics decoded from pnpm's NDJSON reporter to its raw output.
|
|
105
|
+
*
|
|
106
|
+
* Mutating market commands always use `--reporter=ndjson`, so an error's
|
|
107
|
+
* message normally arrives as a JSON string: quotes are escaped and embedded
|
|
108
|
+
* newlines are `\n`. Matching only the raw stream therefore misses the exact
|
|
109
|
+
* production form even when it works against pnpm's pretty reporter.
|
|
110
|
+
*/
|
|
111
|
+
function withDecodedPnpmDiagnostics(output: string): string {
|
|
112
|
+
const messages: string[] = []
|
|
113
|
+
for (const line of output.split(/\r?\n/)) {
|
|
114
|
+
const trimmed = line.trim()
|
|
115
|
+
if (!trimmed.startsWith('{')) continue
|
|
116
|
+
try {
|
|
117
|
+
const event = JSON.parse(trimmed) as { message?: unknown; err?: unknown }
|
|
118
|
+
if (typeof event.message === 'string') messages.push(event.message)
|
|
119
|
+
if (typeof event.err === 'object' && event.err !== null) {
|
|
120
|
+
const message = (event.err as Record<string, unknown>).message
|
|
121
|
+
if (typeof message === 'string') messages.push(message)
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// Human reporter output and truncated NDJSON remain available verbatim.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return messages.length === 0 ? output : `${output}\n${messages.join('\n')}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Every package pnpm named as having no lockfile integrity, in order.
|
|
132
|
+
*
|
|
133
|
+
* Two shapes, because pnpm 11 rewrote this diagnostic and the market has to
|
|
134
|
+
* read both (verified against 10.28.2 / 11.21.0 / 11.22.0):
|
|
135
|
+
*
|
|
136
|
+
* - Up to pnpm 11.20, one package per error, quoted with its full URL:
|
|
137
|
+
* `Cannot install package "name@https://…": its lockfile entry has no
|
|
138
|
+
* "integrity" field`.
|
|
139
|
+
* - From pnpm 11.21, a supply-chain policy pass verifies the WHOLE lockfile
|
|
140
|
+
* up front and reports every violator at once, as indented
|
|
141
|
+
* ` name@version <reason>` lines under an `N lockfile entries failed
|
|
142
|
+
* verification:` header (pnpm's own `formatEntry`; the mixed-code variant
|
|
143
|
+
* inserts `[MISSING_TARBALL_INTEGRITY]` before the reason).
|
|
144
|
+
*
|
|
145
|
+
* Only the first shape was recognized, so on current pnpm the market fell
|
|
146
|
+
* back to a message that said an entry was bad without saying which one —
|
|
147
|
+
* and since pnpm refuses even to uninstall the offender, naming it is the
|
|
148
|
+
* whole of the user's recovery path (#422).
|
|
149
|
+
*
|
|
150
|
+
* The name is still never guessed. A candidate counts only when it carries a
|
|
151
|
+
* version-shaped suffix, which is what keeps a bare URL out and stops
|
|
152
|
+
* `alias@npm:real@1.0.0` from being read as `real` — `:` and `/` are absent
|
|
153
|
+
* from the version character class, and the lookbehind rejects a name that
|
|
154
|
+
* is really the tail of a longer token.
|
|
155
|
+
* @param diagnostic - decoded pnpm output.
|
|
156
|
+
* @returns the distinct package names, or an empty array when none is unambiguous.
|
|
157
|
+
*/
|
|
158
|
+
function integrityViolators(diagnostic: string): string[] {
|
|
159
|
+
const NAME = String.raw`(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*`
|
|
160
|
+
const found: string[] = []
|
|
161
|
+
const add = (name: string | undefined): void => {
|
|
162
|
+
if (name !== undefined && !found.includes(name)) found.push(name)
|
|
163
|
+
}
|
|
164
|
+
add(new RegExp(String.raw`Cannot (?:install|fetch) package\s+"(${NAME})@https?:\/\/[^"\s]+"(?: from the lockfile)?:\s*(?:its lockfile entry|it) has no "integrity" field`, 'i')
|
|
165
|
+
.exec(diagnostic)?.[1])
|
|
166
|
+
const listed = new RegExp(
|
|
167
|
+
String.raw`(?<![\w./:@-])(${NAME})@[A-Za-z0-9._+-]+(?:\s+\[MISSING_TARBALL_INTEGRITY\])? has no "integrity" field`,
|
|
168
|
+
'gi',
|
|
169
|
+
)
|
|
170
|
+
for (const match of diagnostic.matchAll(listed)) add(match[1])
|
|
171
|
+
return found
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Map a failed pnpm run's combined output to a known failure mode.
|
|
176
|
+
*
|
|
177
|
+
* dsh's own wrapper line ("dsh: pnpm failed in profile directory …") names no
|
|
178
|
+
* cause, so the market must recognize pnpm's real diagnostics itself (#20).
|
|
179
|
+
* @param output - stdout+stderr of the failed run.
|
|
180
|
+
* @param exitCode - the run's exit status, when the caller has it (null when
|
|
181
|
+
* the process was signalled). Only a
|
|
182
|
+
* failure whose whole signal IS the status reads it (#502); everything else
|
|
183
|
+
* is recognized from what pnpm said.
|
|
184
|
+
* @returns the classified failure, or null when unrecognized (raw output is then shown as-is).
|
|
185
|
+
*/
|
|
186
|
+
export function classifyPnpmFailure(output: string, exitCode?: number | null): PnpmFailure | null {
|
|
187
|
+
if (output.includes('ERR_PNPM_PUBLIC_HOIST_PATTERN_DIFF')
|
|
188
|
+
|| output.includes('ERR_PNPM_VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF')) {
|
|
189
|
+
return {
|
|
190
|
+
code: 'hoist-pattern-diff',
|
|
191
|
+
recoverable: true,
|
|
192
|
+
message: 'profile 的 node_modules 是旧版 pnpm 创建的,与当前 pnpm 的默认配置不兼容,需要重建后重试 / this profile\'s node_modules was created by a different pnpm major; it must be rebuilt (pnpm install) before changes can be applied',
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// #244: the store recorded in node_modules/.modules.yaml is not the one
|
|
196
|
+
// this pnpm resolves by default (a pnpm upgrade, or a machine where
|
|
197
|
+
// ~/.pnpm-store predates the %LOCALAPPDATA% default). pnpm 11's
|
|
198
|
+
// checkCompatibility then refuses EVERY add and remove, so nothing in the
|
|
199
|
+
// market works until the profile is relinked.
|
|
200
|
+
//
|
|
201
|
+
// No automatic recovery, deliberately. The reporter established that on
|
|
202
|
+
// pnpm 11 `store-dir` is honoured from NOTHING but the CLI flag — not the
|
|
203
|
+
// project .npmrc, not the user .npmrc, not pnpm-workspace.yaml in either
|
|
204
|
+
// casing — so the only self-heal available is to re-run with
|
|
205
|
+
// --store-dir pointed at whatever .modules.yaml happens to name. That
|
|
206
|
+
// silently adopts a store path which may be stale, wrong, or on a drive
|
|
207
|
+
// that no longer exists, and it relinks the entire node_modules to do it:
|
|
208
|
+
// a repair that goes wrong here leaves a profile in worse shape than the
|
|
209
|
+
// clear error it replaced. The paths are in the message; the choice is
|
|
210
|
+
// the user's.
|
|
211
|
+
if (output.includes('ERR_PNPM_UNEXPECTED_STORE')) {
|
|
212
|
+
const linked = /currently linked from the store at "([^"]+)"/.exec(output)?.[1]
|
|
213
|
+
const wanted = /wants to use the store at "([^"]+)"/.exec(output)?.[1]
|
|
214
|
+
const detail = linked !== undefined && wanted !== undefined
|
|
215
|
+
? `\n node_modules → ${linked}\n pnpm 现在想用 / pnpm now wants → ${wanted}`
|
|
216
|
+
: ''
|
|
217
|
+
return {
|
|
218
|
+
code: 'unexpected-store',
|
|
219
|
+
recoverable: false,
|
|
220
|
+
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)`,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// #367: pnpm verifies every tarball resolution in the lockfile before it
|
|
224
|
+
// performs ANY add/remove. One old or half-written entry without an
|
|
225
|
+
// integrity hash therefore blocks unrelated plugin operations too.
|
|
226
|
+
//
|
|
227
|
+
// Do not auto-repair this. Computing and writing a hash means choosing to
|
|
228
|
+
// trust the bytes currently served by the URL, which is a supply-chain
|
|
229
|
+
// decision the market cannot safely make for the user. So the ONE thing
|
|
230
|
+
// this message has to get right is WHICH entry to remove — see
|
|
231
|
+
// integrityViolators for why that was previously lost on pnpm 11.
|
|
232
|
+
if (output.includes('ERR_PNPM_MISSING_TARBALL_INTEGRITY')) {
|
|
233
|
+
const named = integrityViolators(withDecodedPnpmDiagnostics(output))
|
|
234
|
+
// Only a single unambiguous name goes in `pkg`: callers treat it as "the
|
|
235
|
+
// package this failure is about", which a list is not.
|
|
236
|
+
const pkg = named.length === 1 ? named[0] : undefined
|
|
237
|
+
const zh = named.length === 0 ? '' : `(${named.join('、')})`
|
|
238
|
+
const en = named.length === 0 ? '' : ` (${named.join(', ')})`
|
|
239
|
+
return {
|
|
240
|
+
code: 'missing-tarball-integrity',
|
|
241
|
+
recoverable: false,
|
|
242
|
+
pkg,
|
|
243
|
+
message: `profile 的 pnpm-lock.yaml 里有 tarball 依赖${zh}缺少 integrity,pnpm 因此拒绝这个 profile 里的所有安装和卸载——包括卸载它自己,所以装不回来也删不掉。这种条目通常是旧版市场留下的:它把 GitHub 插件写成了带镜像前缀的 tarball 地址,pnpm 认不出那是 GitHub,就要求一个它自己从不为 GitHub 源写入的校验值。新版市场改为交给 pnpm 原生的 GitHub 地址,不会再产生这种条目(#385)。请在 pnpm-lock.yaml 里删掉上面点名的那条依赖记录后重试,市场会用当前方式把它重新装回来;不要删整个 pnpm-lock.yaml,那会让其余插件全部重新解析版本。市场不会自动为未经验证的字节生成校验值 / a tarball dependency${en} in this profile's pnpm-lock.yaml has no integrity, so pnpm refuses every install and uninstall in this profile — including uninstalling that dependency itself, so it can be neither repaired nor removed. Entries like this usually come from an older market version, which installed GitHub plugins from a mirror-prefixed tarball URL: pnpm cannot tell that is GitHub, so it demands a checksum it never writes for GitHub sources. Current versions hand pnpm its own native GitHub target instead and no longer produce such entries (#385). Delete the named dependency's entry from pnpm-lock.yaml and retry — the market will reinstall it the current way. Do not delete the whole pnpm-lock.yaml; that re-resolves the versions of every other plugin too. The market will not generate a checksum for unverified bytes automatically`,
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// #455 by @Asheblog: pnpm 11's supply-chain check compares every lockfile tarball URL
|
|
247
|
+
// against the registry's published metadata. A lockfile generated against
|
|
248
|
+
// registry.npmjs.org fails wholesale when the profile resolves a mirror
|
|
249
|
+
// registry (npmmirror), because the mirror's metadata names its own
|
|
250
|
+
// tarball host. pnpm refuses every operation while the mismatch stands,
|
|
251
|
+
// and the same check runs inside a git-hosted package's own build — which
|
|
252
|
+
// is how a floating github: dependency can break updates of unrelated
|
|
253
|
+
// plugins (the inner failure surfaces as ERR_PNPM_PREPARE_PACKAGE).
|
|
254
|
+
if (output.includes('ERR_PNPM_TARBALL_URL_MISMATCH')) {
|
|
255
|
+
const diagnostic = withDecodedPnpmDiagnostics(output)
|
|
256
|
+
const NAME = String.raw`(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*`
|
|
257
|
+
const named = [...diagnostic.matchAll(new RegExp(String.raw`(?<![\w./:@-])(${NAME})@[A-Za-z0-9._+-]+ has a tarball URL`, 'gi'))]
|
|
258
|
+
.map(match => match[1])
|
|
259
|
+
.filter((name, index, all) => all.indexOf(name) === index)
|
|
260
|
+
const pkg = named.length === 1 ? named[0] : undefined
|
|
261
|
+
const zh = named.length === 0 ? '' : `(${named.join('、')})`
|
|
262
|
+
const en = named.length === 0 ? '' : ` (${named.join(', ')})`
|
|
263
|
+
return {
|
|
264
|
+
code: 'tarball-url-mismatch',
|
|
265
|
+
recoverable: false,
|
|
266
|
+
...(pkg === undefined ? {} : { pkg }),
|
|
267
|
+
message: `pnpm-lock.yaml 里有一些条目的 tarball 地址与当前 registry 发布的元数据不一致${zh},pnpm 11 的供应链检查因此拒绝这个 profile 里的所有操作。这通常发生在镜像 registry(如 npmmirror)与用 npmjs.org 生成的 lockfile 相遇时——例如某个 git 插件仓库自带的 lockfile。可以按 pnpm 的提示执行 pnpm clean --lockfile 后重新安装,或把 registry 切回生成该 lockfile 的源 / some entries in pnpm-lock.yaml have tarball URLs that do not match the current registry's published metadata${en}; pnpm 11's supply-chain check therefore refuses every operation in this profile. This usually happens when a mirror registry (e.g. npmmirror) meets a lockfile generated against npmjs.org — for example a git-hosted plugin repository's own lockfile. Run pnpm clean --lockfile and reinstall as pnpm suggests, or switch the registry back to the source the lockfile was generated against`,
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// #222 by @MicroMilo: a patch in the profile that no longer applies.
|
|
271
|
+
//
|
|
272
|
+
// pnpm exits 1 (verified against 10.29.3) but it has ALREADY written the
|
|
273
|
+
// package — unpatched. So the profile is left holding the pristine version
|
|
274
|
+
// the patch existed to fix, and the damage shows up at the next boot as
|
|
275
|
+
// "failed to load plugins" rather than here, where it happened.
|
|
276
|
+
//
|
|
277
|
+
// Almost always the package moved the file the patch names: the reported
|
|
278
|
+
// case patched `client/client.js` in a package that had started shipping
|
|
279
|
+
// `lib/client.js`. That is not something the market can repair — the patch
|
|
280
|
+
// is the user's, and guessing a new target would be inventing a change
|
|
281
|
+
// they did not write — so it says exactly what is wrong and where.
|
|
282
|
+
if (output.includes('ERR_PNPM_PATCH_FAILED')) {
|
|
283
|
+
const patch = /Could not apply patch (\S+)/.exec(output)?.[1]
|
|
284
|
+
const which = patch === undefined ? '' : `(${patch})`
|
|
285
|
+
const whichEn = patch === undefined ? '' : ` (${patch})`
|
|
286
|
+
return {
|
|
287
|
+
code: 'patch-failed',
|
|
288
|
+
recoverable: false,
|
|
289
|
+
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`,
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (output.includes('ERR_PNPM_ADDING_TO_ROOT')) {
|
|
293
|
+
return {
|
|
294
|
+
code: 'adding-to-root',
|
|
295
|
+
recoverable: false,
|
|
296
|
+
message: 'pnpm 拒绝在 workspace 根目录安装(缺少 -w)。这是市场的 bug,请升级 dshmarket 到最新版 / pnpm refused to add at a workspace root (missing -w); this is a market bug — please update dshmarket',
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (/--workspace-root may only be used inside a workspace/i.test(output)) {
|
|
300
|
+
return {
|
|
301
|
+
code: 'not-a-workspace',
|
|
302
|
+
recoverable: false,
|
|
303
|
+
message: 'profile 目录不是 pnpm workspace,却传入了 -w。这是市场的 bug,请升级 dshmarket 到最新版 / -w was passed but the profile is not a pnpm workspace; this is a market bug — please update dshmarket',
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// #39: once a release younger than minimumReleaseAge is in the lockfile
|
|
307
|
+
// (fresh install or a force-update), pnpm 11 verifies the WHOLE lockfile
|
|
308
|
+
// before ANY later mutation — uninstalling even an unrelated plugin fails
|
|
309
|
+
// (MINIMUM_RELEASE_AGE_VIOLATION), and a later add can fail re-resolving
|
|
310
|
+
// the young dep (NO_MATURE_MATCHING_VERSION). Recovery is a one-shot
|
|
311
|
+
// --config.minimumReleaseAge=0 retry, automated in withHoistRecovery.
|
|
312
|
+
if (output.includes('ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION')
|
|
313
|
+
|| output.includes('ERR_PNPM_NO_MATURE_MATCHING_VERSION')) {
|
|
314
|
+
return {
|
|
315
|
+
code: 'release-age-violation',
|
|
316
|
+
recoverable: false,
|
|
317
|
+
message: '这个 profile 里有一个刚发布不久的插件版本,pnpm 的安全等待期检查因此拒绝了本次改动(即使改的是别的插件)。市场已自动放行重试一次;若仍看到本条,请导出日志反馈 / a recently-published plugin version in this profile trips pnpm\'s fresh-release safety check, blocking any change (even to other plugins); the market retries once with a one-shot bypass — if you still see this, export the log and report it',
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// #69: pnpm >= 10 blocks dependency build scripts by default. The install
|
|
321
|
+
// route has long surfaced this via the approve-builds banner (#6, #56),
|
|
322
|
+
// but as a hard failure (pnpm 11 exits 1) the raw stack leaked through —
|
|
323
|
+
// and the update route showed it verbatim.
|
|
324
|
+
if (output.includes('ERR_PNPM_IGNORED_BUILDS')) {
|
|
325
|
+
return {
|
|
326
|
+
code: 'ignored-builds',
|
|
327
|
+
recoverable: false,
|
|
328
|
+
message: '有依赖需要执行构建脚本,被 pnpm 默认拦截。点击「允许构建脚本并重试」放行后重试即可 / a dependency needs to run build scripts, which pnpm blocks by default — click "Allow build scripts and retry" to approve and retry',
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// #68: git-hosted packages with a prepare/prepack script are rejected in
|
|
332
|
+
// pnpm's FETCHER, before anything lands in node_modules — so the package
|
|
333
|
+
// the user must approve is not installed yet, and pnpm's own hint names a
|
|
334
|
+
// commit-pinned codeload URL that changes on every push.
|
|
335
|
+
if (output.includes('ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED')) {
|
|
336
|
+
return {
|
|
337
|
+
code: 'git-prepare-not-allowed',
|
|
338
|
+
recoverable: false,
|
|
339
|
+
message: '这个 git 插件需要在安装时执行构建脚本,被 pnpm 默认拦截。点击「允许构建脚本并重试」放行后重试即可 / this git-hosted plugin needs to run its build script at install time, which pnpm blocks by default — click "Allow build scripts and retry" to approve and retry',
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// #455 by @Asheblog: a git-hosted package with a prepare/prepack script failed its own
|
|
343
|
+
// build. pnpm runs `pnpm install` inside the fetched repository and
|
|
344
|
+
// rethrows the inner failure as ERR_PNPM_PREPARE_PACKAGE with only a
|
|
345
|
+
// one-line summary — the inner diagnostics (which package, which registry)
|
|
346
|
+
// are not propagated, so the user sees "pnpm install Exit status 1" with no
|
|
347
|
+
// cause. The most common cause on pnpm 11 is the repository's own
|
|
348
|
+
// pnpm-lock.yaml failing the supply-chain check against a mirror registry
|
|
349
|
+
// (tarball URLs pointing at registry.npmjs.org while the profile resolves
|
|
350
|
+
// npmmirror); the repository's build script itself failing, or a network
|
|
351
|
+
// problem, are the other ones.
|
|
352
|
+
if (output.includes('ERR_PNPM_PREPARE_PACKAGE')) {
|
|
353
|
+
const diagnostic = withDecodedPnpmDiagnostics(output)
|
|
354
|
+
const named = /Failed to prepare git-hosted package fetched from "https:\/\/codeload\.github\.com\/[^"]+":\s*((?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*)@[A-Za-z0-9._+-]+/i
|
|
355
|
+
.exec(diagnostic)?.[1]
|
|
356
|
+
const zh = named === undefined ? '' : `(${named})`
|
|
357
|
+
const en = named === undefined ? '' : ` (${named})`
|
|
358
|
+
return {
|
|
359
|
+
code: 'git-prepare-failed',
|
|
360
|
+
recoverable: false,
|
|
361
|
+
...(named === undefined ? {} : { pkg: named }),
|
|
362
|
+
message: `git 插件${zh}在安装时需要执行它自己仓库里的构建脚本(pnpm install),但构建失败了。最常见的原因是仓库自带的 pnpm-lock.yaml 与你的 registry 不兼容:lockfile 里的 tarball 地址指向 registry.npmjs.org,而你的 registry 是镜像(如 npmmirror),pnpm 11 的供应链检查会拒绝整个 lockfile;也可能是仓库的构建脚本本身报错或网络问题。可以先把这个依赖固定到已知可用的 commit(github:owner/repo#<commit>)再更新其他插件,或临时把 registry 切到 npmjs.org 重试 / a git-hosted plugin${en} failed to run its own build (pnpm install) during install. The most common cause is the repository's pnpm-lock.yaml being incompatible with your registry: its tarball URLs point at registry.npmjs.org while your registry is a mirror (e.g. npmmirror), which pnpm 11's supply-chain check rejects; the repository's build script itself failing, or a network problem, are the other causes. Pin that dependency to a known-good commit (github:owner/repo#<commit>) before updating other plugins, or temporarily switch the registry to npmjs.org and retry`,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// #65: a dependency that no longer resolves — an unpublished package left
|
|
366
|
+
// in the manifest by an earlier failed operation (pnpm writes package.json
|
|
367
|
+
// before it finishes), or a private-registry package without credentials.
|
|
368
|
+
// pnpm re-resolves EVERY direct dependency on any add, so one ghost entry
|
|
369
|
+
// blocks all later installs, of anything.
|
|
370
|
+
if (output.includes('ERR_PNPM_FETCH_404')) {
|
|
371
|
+
const pkg = /GET\s+\S*\/([^/\s]+):/.exec(output)?.[1].replace(/%2[Ff]/g, '/')
|
|
372
|
+
const zh = pkg === undefined ? '' : `(${pkg})`
|
|
373
|
+
const en = pkg === undefined ? '' : ` (${pkg})`
|
|
374
|
+
return {
|
|
375
|
+
code: 'fetch-404',
|
|
376
|
+
recoverable: false,
|
|
377
|
+
pkg,
|
|
378
|
+
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`,
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// #389 by @qq1054435284: on Windows, pnpm stages the new version in a
|
|
382
|
+
// sibling `<name>_tmp_<pid>_<n>` directory and renames it over the old one.
|
|
383
|
+
// Windows refuses that rename while any file underneath the target is open,
|
|
384
|
+
// and the process holding them is usually the one asking. POSIX does not
|
|
385
|
+
// have this problem: replacing an open file there leaves the old inode
|
|
386
|
+
// alive for whoever still holds it.
|
|
387
|
+
//
|
|
388
|
+
// Worded for the RENAME, not for an update (#441 by @yandidan1). The
|
|
389
|
+
// reporter met this while INSTALLING — a reinstall of a plugin they had
|
|
390
|
+
// just uninstalled — and was told their update had not applied, that their
|
|
391
|
+
// existing version was intact, and to disable the plugin under Installed,
|
|
392
|
+
// which they had already removed. The package pnpm names here is also not
|
|
393
|
+
// necessarily a plugin: theirs was `node-hid`, a dependency.
|
|
394
|
+
//
|
|
395
|
+
// The native-module sentence is the part that makes the advice usable in
|
|
396
|
+
// that case. Node never unloads a native addon: once a `.node` is loaded
|
|
397
|
+
// there is no dlclose, so disabling the plugin, unmounting it, or
|
|
398
|
+
// uninstalling it cannot release the file — only ending the process does.
|
|
399
|
+
// That is why "uninstall, then install again" fails on Windows for those
|
|
400
|
+
// plugins while it works for every other one.
|
|
401
|
+
//
|
|
402
|
+
// Not retried automatically. A retry from inside the same process cannot
|
|
403
|
+
// win, because that process is the thing holding the handles; retrying
|
|
404
|
+
// would only turn one clear failure into several slow ones. So this names
|
|
405
|
+
// the cause and the ways out instead of guessing.
|
|
406
|
+
if (/ERR_PNPM_EPERM|EPERM: operation not permitted, rename/i.test(output)) {
|
|
407
|
+
// Read through the NDJSON reporter like the integrity classifier does:
|
|
408
|
+
// in production this arrives JSON-escaped, so every separator is doubled
|
|
409
|
+
// and a single-character class silently matches nothing.
|
|
410
|
+
const diagnostic = withDecodedPnpmDiagnostics(output)
|
|
411
|
+
const pkg = /node_modules[\\/]+((?:@[a-z0-9][a-z0-9._-]*[\\/]+)?[a-z0-9][a-z0-9._-]*)_tmp_\d+/i
|
|
412
|
+
.exec(diagnostic)?.[1]?.replace(/\\+/g, '/')
|
|
413
|
+
const zh = pkg === undefined ? '' : `(${pkg})`
|
|
414
|
+
const en = pkg === undefined ? '' : ` (${pkg})`
|
|
415
|
+
return {
|
|
416
|
+
code: 'windows-file-locked',
|
|
417
|
+
recoverable: false,
|
|
418
|
+
...(pkg === undefined ? {} : { pkg }),
|
|
419
|
+
message: `Windows 不允许替换正在被打开的文件。pnpm 要用新目录替换${zh === '' ? '一个已装好的包' : ` ${pkg!}`},而它的文件正被运行中的 ChatCode CLI 打开着,改名因此失败,这一步没有生效——已经装好的内容没有被破坏。\n如果这个包带原生模块(.node 文件,例如 node-hid 这类),那么停用插件、甚至卸载插件都不够:原生模块一旦被加载,在进程退出前都不会释放。刚卸载完立刻重装同一个插件在 Windows 上失败,通常就是这个原因。\n可行的做法:完全退出 ChatCode CLI(不是刷新页面),重新启动后再操作一次;或退出后在命令行执行。杀毒软件或文件索引临时占用目录也会报同样的错,若都不适用可稍后重试。 / Windows will not replace a file that is open. pnpm tried to swap a new directory over${en === '' ? ' an installed package' : en}, whose files the running ChatCode CLI process holds open, so the rename failed and this step did not apply — what was already installed is intact. If that package ships a native module (a .node file, node-hid and friends), disabling the plugin — even uninstalling it — is not enough: once a native module is loaded it is not released until the process exits, which is the usual reason reinstalling a plugin right after uninstalling it fails on Windows. What works: quit ChatCode CLI completely (not a page refresh), start it again, and repeat the operation; or run it from the command line with the app closed. Antivirus or a file indexer holding the directory produces the same error, so a later retry is worth trying if neither applies.`,
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// #83: pnpm replays the WHOLE dependency tree on every add/remove, so a
|
|
423
|
+
// moment of network flakiness against ANY already-installed dependency
|
|
424
|
+
// (codeload tarball, registry meta) fails the run — and the market then
|
|
425
|
+
// reported "install failed" for a plugin that was perfectly fine, only for
|
|
426
|
+
// a plain retry to succeed seconds later. withHoistRecovery retries once;
|
|
427
|
+
// this message covers the case where the retry lost too.
|
|
428
|
+
if (isTransientPnpmFailure(output)) {
|
|
429
|
+
return {
|
|
430
|
+
code: 'transient-network',
|
|
431
|
+
recoverable: false,
|
|
432
|
+
message: '拉取依赖时网络临时失败(不一定是你正在装的插件——安装会重放整个依赖树,任何一个既有依赖抖动都会中断)。已自动重试一次仍失败,请稍后再试 / a transient network failure while fetching dependencies (not necessarily the plugin you are installing — installs replay the whole dependency tree, so any existing dependency can hiccup); one automatic retry failed too — please try again shortly',
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// pnpm's per-request fetch timeout (#…): large tarballs (github: sources
|
|
436
|
+
// fetch the whole repo even for a `#path:` subdirectory) on slow networks
|
|
437
|
+
// blow pnpm's default 60s limit. A plain retry fails again at the same
|
|
438
|
+
// limit, so withHoistRecovery retries with a longer fetchTimeout.
|
|
439
|
+
if (isFetchTimeoutFailure(output)) {
|
|
440
|
+
return {
|
|
441
|
+
code: 'fetch-timeout',
|
|
442
|
+
recoverable: false,
|
|
443
|
+
message: '下载超时:这个插件的安装包较大(github 源会下载整个仓库)或网络较慢,pnpm 默认的单次请求 60 秒限制不够用。市场已用更长的超时自动重试一次;若仍失败,请稍后再试或检查网络 / download timed out: this plugin ships a large tarball (github sources download the whole repository) or your network is slow, and pnpm\'s default 60-second per-request limit was not enough; the market retries once with a longer timeout — if it still fails, try again later or check the network',
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (output.includes('pnpm not found on PATH')) {
|
|
447
|
+
return {
|
|
448
|
+
code: 'pnpm-missing',
|
|
449
|
+
recoverable: false,
|
|
450
|
+
message: '找不到 pnpm,请先在市场页顶部一键安装组件 / pnpm is not on PATH — use the one-click setup at the top of the market page',
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Reported by @screamff on #436: a `file:` dependency whose tarball or
|
|
454
|
+
// directory is no longer on disk. pnpm re-resolves every direct dependency
|
|
455
|
+
// before ANY mutation, so one dead local path blocks every install and
|
|
456
|
+
// uninstall in the profile — including uninstalling the market, which is
|
|
457
|
+
// what the reporter was trying to do. Same family as the #65 ghost
|
|
458
|
+
// registry entry; the local form has its own error, and it names the PATH
|
|
459
|
+
// rather than the package, which is why "a plugin is missing" was never a
|
|
460
|
+
// usable description of it.
|
|
461
|
+
//
|
|
462
|
+
// Measured on pnpm 10.28.2 and 11.21.0 (both exit 254): the wording only
|
|
463
|
+
// differs in how the code is bracketed, and both carry the path and the
|
|
464
|
+
// "direct dependency" line. Both are required — an ENOENT from a build
|
|
465
|
+
// script is a different failure and must not wear this explanation.
|
|
466
|
+
const localMiss = /ENOENT: no such file or directory, open '([^']+)'/.exec(output)
|
|
467
|
+
if (localMiss !== null && output.includes('while installing a direct dependency')) {
|
|
468
|
+
return {
|
|
469
|
+
code: 'missing-local-dependency',
|
|
470
|
+
recoverable: false,
|
|
471
|
+
message: `profile 里有一个从本地文件安装的插件,它的文件已经不在了(${localMiss[1]})。pnpm 在做任何改动前都会重新解析全部直接依赖,所以这一条会挡住这个 profile 里的所有安装和卸载——包括卸载别的插件。请在 profile 的 package.json 的 dependencies 里删掉值等于上面这个路径的那一行(或在市场的「已安装」里卸载它),然后重试。 / a plugin in this profile was installed from a local file that no longer exists (${localMiss[1]}). pnpm re-resolves every direct dependency before making any change, so this one entry blocks every install and uninstall in the profile — including uninstalling other plugins. Remove the dependency whose value is that path from the profile's package.json (or uninstall it from the market's Installed list) and retry.`,
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// #502 by @Ztyss: a pnpm WAS found on PATH and could not be started.
|
|
475
|
+
//
|
|
476
|
+
// Different from `pnpm-missing` in the one way that matters to the user:
|
|
477
|
+
// the market's own setup does not fix it, because as far as PATH is
|
|
478
|
+
// concerned pnpm is already there. The reported case is a `pnpm.cmd`
|
|
479
|
+
// wrapper built out of environment variables that only exist in its
|
|
480
|
+
// installer's own process — expanded in the market's child process it
|
|
481
|
+
// collapses to an empty command, and cmd.exe answers 9009 with its message
|
|
482
|
+
// in the OEM code page, which reaches us as replacement characters. Three
|
|
483
|
+
// updates in a row failed showing the user nothing but that.
|
|
484
|
+
//
|
|
485
|
+
// 9009 is cmd.exe's "command not found" and is language-independent, so it
|
|
486
|
+
// leads; the text forms catch the same failure in a log with no exit code.
|
|
487
|
+
//
|
|
488
|
+
// #509 by @awslmowms added the other way a present pnpm fails to run: the
|
|
489
|
+
// spawn itself is refused, and dsh's wrapper rethrows Node's error
|
|
490
|
+
// verbatim — `EACCES, syscall: 'spawnSync pnpm'` on Ubuntu. Same class
|
|
491
|
+
// (pnpm is there, it will not run, and the market's own setup cannot help)
|
|
492
|
+
// but a different repair, so the message names the reason it was given.
|
|
493
|
+
//
|
|
494
|
+
// Last in the chain deliberately: pnpm's own errors never exit 9009 and
|
|
495
|
+
// never fail to spawn, and anything pnpm actually said has matched above.
|
|
496
|
+
const spawnRefused = /spawnSync pnpm/.test(output)
|
|
497
|
+
? /EACCES/.test(output) ? 'EACCES' : /ENOENT/.test(output) ? 'ENOENT' : 'unknown'
|
|
498
|
+
: null
|
|
499
|
+
const cmdNotFound = exitCode === 9009
|
|
500
|
+
|| /is not recognized as an internal or external command|不是内部或外部命令|不是內部或外部命令/.test(output)
|
|
501
|
+
if (cmdNotFound || spawnRefused !== null) {
|
|
502
|
+
// Each cause gets the repair that fits it. One generic sentence would
|
|
503
|
+
// send the EACCES reporter hunting for a missing wrapper variable and
|
|
504
|
+
// the Windows reporter reaching for chmod.
|
|
505
|
+
const why = spawnRefused === 'EACCES'
|
|
506
|
+
? {
|
|
507
|
+
zh: '系统拒绝执行它(EACCES,权限不足)。多半是那个文件没有执行权限,或者它所在的分区是以 noexec 挂载的。用 `ls -l $(command -v pnpm)` 看一眼权限位,必要时 `chmod +x`;如果 pnpm 装在 noexec 的分区上,换个位置重装。',
|
|
508
|
+
en: 'the system refused to execute it (EACCES, permission denied). Usually the file has no execute permission, or it lives on a partition mounted noexec. Check the permission bits with `ls -l $(command -v pnpm)` and `chmod +x` if needed; if pnpm sits on a noexec partition, reinstall it elsewhere.',
|
|
509
|
+
}
|
|
510
|
+
: spawnRefused === 'ENOENT'
|
|
511
|
+
? {
|
|
512
|
+
zh: '要执行的文件已经不在了(ENOENT)。PATH 里那条记录指向的 pnpm 被删掉或改名了,重新装一次 pnpm 即可。',
|
|
513
|
+
en: 'the file it tried to execute is gone (ENOENT). The pnpm that entry on PATH points at has been deleted or renamed; reinstall pnpm.',
|
|
514
|
+
}
|
|
515
|
+
: {
|
|
516
|
+
zh: '命令行没能把它启动起来(退出码 9009)。可能是它其实没装好,也可能它是一个包装脚本、而脚本需要的环境变量在市场启动的子进程里不存在。',
|
|
517
|
+
en: "the command line could not launch it (exit code 9009). Either it is not installed properly, or it is a wrapper script whose required environment variables are missing in the market's child process.",
|
|
518
|
+
}
|
|
519
|
+
return {
|
|
520
|
+
code: 'pnpm-unusable',
|
|
521
|
+
recoverable: false,
|
|
522
|
+
replaceOutput: true,
|
|
523
|
+
message: `找不到能用的 pnpm,插件没有任何改动。系统里确实有一个 pnpm,但${why.zh}\n在终端里执行一次 \`pnpm --version\` 可以确认:那里同样失败,说明要修的是这台机器上的 pnpm;那里正常,说明是启动市场的方式带来的环境差异,改用普通的 \`chatcode-cli web\` 启动可以绕开。 / pnpm could not be started, and nothing was changed. A pnpm does exist on PATH, but ${why.en}\nRun \`pnpm --version\` in a terminal to check: failing there too means pnpm itself needs fixing on this machine; working there means the difference comes from how the market was launched, and starting ChatCode CLI with a plain \`chatcode-cli web\` avoids it.`,
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return null
|
|
527
|
+
}
|