dshmarket 1.66.29 → 1.66.31
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 +15 -0
- package/lib/pnpm-compat.js +20 -1
- package/lib/profile.js +49 -1
- package/lib/verify.js +23 -9
- package/package.json +1 -1
- package/src/install.ts +14 -0
- package/src/pnpm-compat.ts +20 -1
- package/src/profile.ts +51 -1
- package/src/verify.ts +28 -14
package/lib/install.js
CHANGED
|
@@ -339,6 +339,21 @@ export async function withHoistRecovery(run, profile, pluginArgs, profileDirecto
|
|
|
339
339
|
: `这台宿主(官方桌面端)自己执行安装,不接受市场附加的 pnpm 参数(${unavailableOption}),所以这一步没有自动重试。 / This host (the official desktop app) runs the install itself and takes no pnpm options from the market (${unavailableOption}), so that automatic retry could not be attempted.`;
|
|
340
340
|
result = { ...result, stderr: `${result.stderr}\n\n${note}` };
|
|
341
341
|
}
|
|
342
|
+
// pnpm can append a second rule even on success (#883, #878), including
|
|
343
|
+
// after a recovery retry or an official Desktop manager install. Repair
|
|
344
|
+
// the final file now, before another plugin operation reads it; a successful
|
|
345
|
+
// command must never be retried just because its exclusions needed merging.
|
|
346
|
+
if (ok(result) && (verb === 'add' || verb === 'remove' || verb === 'install' || verb === 'update')) {
|
|
347
|
+
try {
|
|
348
|
+
const merged = mergeDuplicateReleaseAgeExcludes(profile, profileDirectory);
|
|
349
|
+
if (merged.length > 0) {
|
|
350
|
+
logEvent('warn', 'install', `merged duplicate minimumReleaseAgeExclude rules for ${merged.join(', ')} after success (#883, #878)`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
logEvent('warn', 'install', `could not merge minimumReleaseAgeExclude after success (#883, #878): ${String(error)}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
342
357
|
return result;
|
|
343
358
|
}
|
|
344
359
|
/**
|
package/lib/pnpm-compat.js
CHANGED
|
@@ -362,8 +362,27 @@ export function classifyPnpmFailure(output, exitCode) {
|
|
|
362
362
|
// (MINIMUM_RELEASE_AGE_VIOLATION), and a later add can fail re-resolving
|
|
363
363
|
// the young dep (NO_MATURE_MATCHING_VERSION). Recovery is a one-shot
|
|
364
364
|
// --config.minimum-release-age=0 retry, automated in withHoistRecovery.
|
|
365
|
+
//
|
|
366
|
+
// That gate has a THIRD face, and it is the one that fails AFTER the
|
|
367
|
+
// lockfile check has said yes. pnpm skips that check when the verification
|
|
368
|
+
// cache holds a run whose recorded window is at least as wide as the
|
|
369
|
+
// current one (`canTrustPastCheck`: past >= current, pnpm's own
|
|
370
|
+
// lockfile-verified cache), and the resolver then applies the same policy
|
|
371
|
+
// from scratch. A re-resolved dependency still inside the window comes back
|
|
372
|
+
// as a `policyViolation`, which the CLI only knows how to consume through a
|
|
373
|
+
// handler it never wires on this call path — so it throws
|
|
374
|
+
// RESOLUTION_POLICY_VIOLATIONS_UNHANDLED instead of reporting anything the
|
|
375
|
+
// user can act on. Measured on the bundled pnpm 11.7.0 (DSH Desktop), with
|
|
376
|
+
// a cached pass in place: verification prints "passes supply-chain
|
|
377
|
+
// policies", and the next phase dies with "2 resolution-policy violations
|
|
378
|
+
// were produced but no handleResolutionPolicyViolations callback was wired".
|
|
379
|
+
// Same cause, same cure as the two shapes above — pnpm's hint names
|
|
380
|
+
// `setupPolicyHandlers`, which an integration calling the CLI cannot wire,
|
|
381
|
+
// so the only answer is the one-shot override. Classifying it here is what
|
|
382
|
+
// connects it to that retry; without it the raw internal hint was surfaced.
|
|
365
383
|
if (output.includes('ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION')
|
|
366
|
-
|| output.includes('ERR_PNPM_NO_MATURE_MATCHING_VERSION')
|
|
384
|
+
|| output.includes('ERR_PNPM_NO_MATURE_MATCHING_VERSION')
|
|
385
|
+
|| output.includes('ERR_PNPM_RESOLUTION_POLICY_VIOLATIONS_UNHANDLED')) {
|
|
367
386
|
return {
|
|
368
387
|
code: 'release-age-violation',
|
|
369
388
|
recoverable: false,
|
package/lib/profile.js
CHANGED
|
@@ -139,7 +139,13 @@ export function readManifestDeps(profile, explicitDir) {
|
|
|
139
139
|
export function readDependencyOwners(profile, names, explicitDir) {
|
|
140
140
|
const installed = new Set(names);
|
|
141
141
|
const owners = {};
|
|
142
|
-
|
|
142
|
+
// Who may be an OWNER is not the same question as who gets a card (#892).
|
|
143
|
+
// pnpm hoists a transitive dependency's directory to `node_modules/<name>`
|
|
144
|
+
// as well, so `ws` — a dependency of a plugin, never a profile dependency —
|
|
145
|
+
// is where `bufferutil` and `utf-8-validate` are declared. Scanning only the
|
|
146
|
+
// profile's own entries could therefore never answer "who asked for these",
|
|
147
|
+
// and the two native peers stayed on the list as plugins nobody chose.
|
|
148
|
+
for (const owner of topLevelPackageNames(profile, names, explicitDir)) {
|
|
143
149
|
const manifest = readInstalledManifest(profile, owner, explicitDir);
|
|
144
150
|
if (typeof manifest !== 'object' || manifest === null)
|
|
145
151
|
continue;
|
|
@@ -156,6 +162,48 @@ export function readDependencyOwners(profile, names, explicitDir) {
|
|
|
156
162
|
}
|
|
157
163
|
return owners;
|
|
158
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Every package that could be an owner, sorted: the profile's own entries
|
|
167
|
+
* plus every package sitting at the profile's top level.
|
|
168
|
+
*
|
|
169
|
+
* The manifest's keys are the floor, not the ceiling — a missing or
|
|
170
|
+
* unreadable `node_modules` leaves the answer exactly as it was before the
|
|
171
|
+
* top level was scanned, so this can only ever find MORE owners.
|
|
172
|
+
*
|
|
173
|
+
* One manifest read per top-level package, measured at ~30 ms for 300
|
|
174
|
+
* packages on a warm cache, once per `/installed` request — which already
|
|
175
|
+
* reads a manifest, a patch file and an entry artifact for every card.
|
|
176
|
+
*/
|
|
177
|
+
function topLevelPackageNames(profile, names, explicitDir) {
|
|
178
|
+
const found = new Set(names);
|
|
179
|
+
const root = join(profileDir(profile, explicitDir), 'node_modules');
|
|
180
|
+
let entries = [];
|
|
181
|
+
try {
|
|
182
|
+
entries = readdirSync(root, { withFileTypes: true })
|
|
183
|
+
// A scope directory is named `@scope`, so the leading `@` belongs here;
|
|
184
|
+
// the package's own namespace is added below.
|
|
185
|
+
.filter(dirent => dirent.isDirectory() && /^@?[A-Za-z0-9._~-]+$/.test(dirent.name))
|
|
186
|
+
.map(dirent => dirent.name);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return [...found].sort();
|
|
190
|
+
}
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
if (!entry.startsWith('@')) {
|
|
193
|
+
found.add(entry);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
// A scope directory holds the packages themselves.
|
|
197
|
+
try {
|
|
198
|
+
for (const inner of readdirSync(join(root, entry), { withFileTypes: true })) {
|
|
199
|
+
if (inner.isDirectory() && /^[A-Za-z0-9._~-]+$/.test(inner.name))
|
|
200
|
+
found.add(`${entry}/${inner.name}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch { /* unreadable scope — its packages simply cannot own anything */ }
|
|
204
|
+
}
|
|
205
|
+
return [...found].sort();
|
|
206
|
+
}
|
|
159
207
|
function objectRecord(value) {
|
|
160
208
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
161
209
|
? value
|
package/lib/verify.js
CHANGED
|
@@ -33,7 +33,7 @@ import { join } from 'node:path';
|
|
|
33
33
|
import { listHotMounts, parseSimplePatch } from './hot.js';
|
|
34
34
|
import { userPatchPackageReferences } from './patch.js';
|
|
35
35
|
import { nameMatchesPackage } from './entry-identity.js';
|
|
36
|
-
import { bundlePatchInsertedIds, declaredBundlePatchFiles, hasDshManifest, hasLoadableEntry, profileDir, readInstalled } from './profile.js';
|
|
36
|
+
import { bundlePatchInsertedIds, declaredBundlePatchFiles, hasDshManifest, hasLoadableEntry, pluginSubdirs, profileDir, readInstalled } from './profile.js';
|
|
37
37
|
import { compareSemver, composeLayers, parsePatchFile, satisfiesRange } from './check.js';
|
|
38
38
|
import { dshHostInfo } from './dsh-install.js';
|
|
39
39
|
/**
|
|
@@ -422,19 +422,33 @@ export function verifyActivation(profile, name, live = new Set(listHotMounts()),
|
|
|
422
422
|
hot: false,
|
|
423
423
|
};
|
|
424
424
|
}
|
|
425
|
-
|
|
426
|
-
|
|
425
|
+
if (inBundles) {
|
|
426
|
+
return {
|
|
427
427
|
state: 'restart',
|
|
428
428
|
reasons: ['由你自己的 cordis.patch.yml 按名加载,重启后生效 / loaded by name from your own cordis.patch.yml — live after a restart'],
|
|
429
429
|
bundle: true,
|
|
430
430
|
hot: false,
|
|
431
|
-
}
|
|
432
|
-
: {
|
|
433
|
-
state: 'inert',
|
|
434
|
-
reasons: ['普通依赖(未声明 dsh 元数据),不是 profile 层插件;若它由某个 bundle patch 按名加载,启动后会显示为已加载 / a plain dependency with no dsh metadata — not a profile-layer plugin; if some bundle patch loads it by name it will read as live once running'],
|
|
435
|
-
bundle: false,
|
|
436
|
-
hot: false,
|
|
437
431
|
};
|
|
432
|
+
}
|
|
433
|
+
// A collection repository's ROOT package says nothing about itself and
|
|
434
|
+
// holds the plugins one level down (#892). Installed whole — which the
|
|
435
|
+
// market used to do before it learned to retarget one, and which a user
|
|
436
|
+
// can still do by hand — it lands on this list as "installed, not in
|
|
437
|
+
// effect" with no way out of that sentence. Name the plugins it does
|
|
438
|
+
// carry and the way to install one: the dead end is the bug.
|
|
439
|
+
const carried = pluginSubdirs(dir);
|
|
440
|
+
const shown = carried.slice(0, 3).join('、');
|
|
441
|
+
const more = carried.length > 3 ? `等 ${carried.length} 个` : '';
|
|
442
|
+
const shownEn = carried.slice(0, 3).join(', ');
|
|
443
|
+
const moreEn = carried.length > 3 ? ` and ${carried.length - 3} more` : '';
|
|
444
|
+
return {
|
|
445
|
+
state: 'inert',
|
|
446
|
+
reasons: [carried.length > 0
|
|
447
|
+
? `这是集合仓库的根包,本身不是插件。可安装的子包:${shown}${more}。请装子包而不是它(仓库地址后加 #path:/子包目录) / this is a collection repository's root package, not a plugin itself; install one of its plugins instead — ${shownEn}${moreEn} — by adding #path:/<subdirectory> to the repository URL`
|
|
448
|
+
: '普通依赖(未声明 dsh 元数据),不是 profile 层插件;若它由某个 bundle patch 按名加载,启动后会显示为已加载 / a plain dependency with no dsh metadata — not a profile-layer plugin; if some bundle patch loads it by name it will read as live once running'],
|
|
449
|
+
bundle: false,
|
|
450
|
+
hot: false,
|
|
451
|
+
};
|
|
438
452
|
}
|
|
439
453
|
// Carrier bundles (#103) ship no entry of their own — what they mount is
|
|
440
454
|
// the point — so judge by "is anything loadable", not by this package's
|
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.66.
|
|
4
|
+
"version": "1.66.31",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
package/src/install.ts
CHANGED
|
@@ -345,6 +345,20 @@ export async function withHoistRecovery(
|
|
|
345
345
|
: `这台宿主(官方桌面端)自己执行安装,不接受市场附加的 pnpm 参数(${unavailableOption}),所以这一步没有自动重试。 / This host (the official desktop app) runs the install itself and takes no pnpm options from the market (${unavailableOption}), so that automatic retry could not be attempted.`
|
|
346
346
|
result = { ...result, stderr: `${result.stderr}\n\n${note}` }
|
|
347
347
|
}
|
|
348
|
+
// pnpm can append a second rule even on success (#883, #878), including
|
|
349
|
+
// after a recovery retry or an official Desktop manager install. Repair
|
|
350
|
+
// the final file now, before another plugin operation reads it; a successful
|
|
351
|
+
// command must never be retried just because its exclusions needed merging.
|
|
352
|
+
if (ok(result) && (verb === 'add' || verb === 'remove' || verb === 'install' || verb === 'update')) {
|
|
353
|
+
try {
|
|
354
|
+
const merged = mergeDuplicateReleaseAgeExcludes(profile, profileDirectory)
|
|
355
|
+
if (merged.length > 0) {
|
|
356
|
+
logEvent('warn', 'install', `merged duplicate minimumReleaseAgeExclude rules for ${merged.join(', ')} after success (#883, #878)`)
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
logEvent('warn', 'install', `could not merge minimumReleaseAgeExclude after success (#883, #878): ${String(error)}`)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
348
362
|
return result
|
|
349
363
|
}
|
|
350
364
|
|
package/src/pnpm-compat.ts
CHANGED
|
@@ -399,8 +399,27 @@ export function classifyPnpmFailure(output: string, exitCode?: number | null): P
|
|
|
399
399
|
// (MINIMUM_RELEASE_AGE_VIOLATION), and a later add can fail re-resolving
|
|
400
400
|
// the young dep (NO_MATURE_MATCHING_VERSION). Recovery is a one-shot
|
|
401
401
|
// --config.minimum-release-age=0 retry, automated in withHoistRecovery.
|
|
402
|
+
//
|
|
403
|
+
// That gate has a THIRD face, and it is the one that fails AFTER the
|
|
404
|
+
// lockfile check has said yes. pnpm skips that check when the verification
|
|
405
|
+
// cache holds a run whose recorded window is at least as wide as the
|
|
406
|
+
// current one (`canTrustPastCheck`: past >= current, pnpm's own
|
|
407
|
+
// lockfile-verified cache), and the resolver then applies the same policy
|
|
408
|
+
// from scratch. A re-resolved dependency still inside the window comes back
|
|
409
|
+
// as a `policyViolation`, which the CLI only knows how to consume through a
|
|
410
|
+
// handler it never wires on this call path — so it throws
|
|
411
|
+
// RESOLUTION_POLICY_VIOLATIONS_UNHANDLED instead of reporting anything the
|
|
412
|
+
// user can act on. Measured on the bundled pnpm 11.7.0 (DSH Desktop), with
|
|
413
|
+
// a cached pass in place: verification prints "passes supply-chain
|
|
414
|
+
// policies", and the next phase dies with "2 resolution-policy violations
|
|
415
|
+
// were produced but no handleResolutionPolicyViolations callback was wired".
|
|
416
|
+
// Same cause, same cure as the two shapes above — pnpm's hint names
|
|
417
|
+
// `setupPolicyHandlers`, which an integration calling the CLI cannot wire,
|
|
418
|
+
// so the only answer is the one-shot override. Classifying it here is what
|
|
419
|
+
// connects it to that retry; without it the raw internal hint was surfaced.
|
|
402
420
|
if (output.includes('ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION')
|
|
403
|
-
|| output.includes('ERR_PNPM_NO_MATURE_MATCHING_VERSION')
|
|
421
|
+
|| output.includes('ERR_PNPM_NO_MATURE_MATCHING_VERSION')
|
|
422
|
+
|| output.includes('ERR_PNPM_RESOLUTION_POLICY_VIOLATIONS_UNHANDLED')) {
|
|
404
423
|
return {
|
|
405
424
|
code: 'release-age-violation',
|
|
406
425
|
recoverable: false,
|
package/src/profile.ts
CHANGED
|
@@ -151,7 +151,13 @@ export function readDependencyOwners(
|
|
|
151
151
|
): Record<string, string> {
|
|
152
152
|
const installed = new Set(names)
|
|
153
153
|
const owners: Record<string, string> = {}
|
|
154
|
-
|
|
154
|
+
// Who may be an OWNER is not the same question as who gets a card (#892).
|
|
155
|
+
// pnpm hoists a transitive dependency's directory to `node_modules/<name>`
|
|
156
|
+
// as well, so `ws` — a dependency of a plugin, never a profile dependency —
|
|
157
|
+
// is where `bufferutil` and `utf-8-validate` are declared. Scanning only the
|
|
158
|
+
// profile's own entries could therefore never answer "who asked for these",
|
|
159
|
+
// and the two native peers stayed on the list as plugins nobody chose.
|
|
160
|
+
for (const owner of topLevelPackageNames(profile, names, explicitDir)) {
|
|
155
161
|
const manifest = readInstalledManifest(profile, owner, explicitDir)
|
|
156
162
|
if (typeof manifest !== 'object' || manifest === null) continue
|
|
157
163
|
const declared = manifest as { dependencies?: unknown; peerDependencies?: unknown }
|
|
@@ -166,6 +172,50 @@ export function readDependencyOwners(
|
|
|
166
172
|
return owners
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Every package that could be an owner, sorted: the profile's own entries
|
|
177
|
+
* plus every package sitting at the profile's top level.
|
|
178
|
+
*
|
|
179
|
+
* The manifest's keys are the floor, not the ceiling — a missing or
|
|
180
|
+
* unreadable `node_modules` leaves the answer exactly as it was before the
|
|
181
|
+
* top level was scanned, so this can only ever find MORE owners.
|
|
182
|
+
*
|
|
183
|
+
* One manifest read per top-level package, measured at ~30 ms for 300
|
|
184
|
+
* packages on a warm cache, once per `/installed` request — which already
|
|
185
|
+
* reads a manifest, a patch file and an entry artifact for every card.
|
|
186
|
+
*/
|
|
187
|
+
function topLevelPackageNames(
|
|
188
|
+
profile: string,
|
|
189
|
+
names: readonly string[],
|
|
190
|
+
explicitDir?: string,
|
|
191
|
+
): string[] {
|
|
192
|
+
const found = new Set(names)
|
|
193
|
+
const root = join(profileDir(profile, explicitDir), 'node_modules')
|
|
194
|
+
let entries: string[] = []
|
|
195
|
+
try {
|
|
196
|
+
entries = readdirSync(root, { withFileTypes: true })
|
|
197
|
+
// A scope directory is named `@scope`, so the leading `@` belongs here;
|
|
198
|
+
// the package's own namespace is added below.
|
|
199
|
+
.filter(dirent => dirent.isDirectory() && /^@?[A-Za-z0-9._~-]+$/.test(dirent.name))
|
|
200
|
+
.map(dirent => dirent.name)
|
|
201
|
+
} catch {
|
|
202
|
+
return [...found].sort()
|
|
203
|
+
}
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
if (!entry.startsWith('@')) {
|
|
206
|
+
found.add(entry)
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
// A scope directory holds the packages themselves.
|
|
210
|
+
try {
|
|
211
|
+
for (const inner of readdirSync(join(root, entry), { withFileTypes: true })) {
|
|
212
|
+
if (inner.isDirectory() && /^[A-Za-z0-9._~-]+$/.test(inner.name)) found.add(`${entry}/${inner.name}`)
|
|
213
|
+
}
|
|
214
|
+
} catch { /* unreadable scope — its packages simply cannot own anything */ }
|
|
215
|
+
}
|
|
216
|
+
return [...found].sort()
|
|
217
|
+
}
|
|
218
|
+
|
|
169
219
|
/** Exact rollback state owned by one profile package operation. */
|
|
170
220
|
export interface ProfileManifestSnapshot {
|
|
171
221
|
dependencies: Record<string, string>
|
package/src/verify.ts
CHANGED
|
@@ -34,7 +34,7 @@ import { join } from 'node:path'
|
|
|
34
34
|
import { listHotMounts, parseSimplePatch } from './hot.ts'
|
|
35
35
|
import { userPatchPackageReferences } from './patch.ts'
|
|
36
36
|
import { nameMatchesPackage } from './entry-identity.ts'
|
|
37
|
-
import { bundlePatchInsertedIds, declaredBundlePatchFiles, hasDshManifest, hasLoadableEntry, profileDir, readInstalled } from './profile.ts'
|
|
37
|
+
import { bundlePatchInsertedIds, declaredBundlePatchFiles, hasDshManifest, hasLoadableEntry, pluginSubdirs, profileDir, readInstalled } from './profile.ts'
|
|
38
38
|
import { compareSemver, composeLayers, parsePatchFile, satisfiesRange } from './check.ts'
|
|
39
39
|
import { dshHostInfo } from './dsh-install.ts'
|
|
40
40
|
|
|
@@ -498,19 +498,33 @@ export function verifyActivation(
|
|
|
498
498
|
hot: false,
|
|
499
499
|
}
|
|
500
500
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
501
|
+
if (inBundles) {
|
|
502
|
+
return {
|
|
503
|
+
state: 'restart',
|
|
504
|
+
reasons: ['由你自己的 cordis.patch.yml 按名加载,重启后生效 / loaded by name from your own cordis.patch.yml — live after a restart'],
|
|
505
|
+
bundle: true,
|
|
506
|
+
hot: false,
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// A collection repository's ROOT package says nothing about itself and
|
|
510
|
+
// holds the plugins one level down (#892). Installed whole — which the
|
|
511
|
+
// market used to do before it learned to retarget one, and which a user
|
|
512
|
+
// can still do by hand — it lands on this list as "installed, not in
|
|
513
|
+
// effect" with no way out of that sentence. Name the plugins it does
|
|
514
|
+
// carry and the way to install one: the dead end is the bug.
|
|
515
|
+
const carried = pluginSubdirs(dir)
|
|
516
|
+
const shown = carried.slice(0, 3).join('、')
|
|
517
|
+
const more = carried.length > 3 ? `等 ${carried.length} 个` : ''
|
|
518
|
+
const shownEn = carried.slice(0, 3).join(', ')
|
|
519
|
+
const moreEn = carried.length > 3 ? ` and ${carried.length - 3} more` : ''
|
|
520
|
+
return {
|
|
521
|
+
state: 'inert',
|
|
522
|
+
reasons: [carried.length > 0
|
|
523
|
+
? `这是集合仓库的根包,本身不是插件。可安装的子包:${shown}${more}。请装子包而不是它(仓库地址后加 #path:/子包目录) / this is a collection repository's root package, not a plugin itself; install one of its plugins instead — ${shownEn}${moreEn} — by adding #path:/<subdirectory> to the repository URL`
|
|
524
|
+
: '普通依赖(未声明 dsh 元数据),不是 profile 层插件;若它由某个 bundle patch 按名加载,启动后会显示为已加载 / a plain dependency with no dsh metadata — not a profile-layer plugin; if some bundle patch loads it by name it will read as live once running'],
|
|
525
|
+
bundle: false,
|
|
526
|
+
hot: false,
|
|
527
|
+
}
|
|
514
528
|
}
|
|
515
529
|
// Carrier bundles (#103) ship no entry of their own — what they mount is
|
|
516
530
|
// the point — so judge by "is anything loadable", not by this package's
|