dshmarket 1.73.0 → 1.73.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/profile.js +47 -2
- package/lib/routes.js +72 -4
- package/lib/types/profile.d.ts +23 -0
- package/package.json +1 -1
package/lib/profile.js
CHANGED
|
@@ -924,19 +924,64 @@ export function bundlePatchInsertedIds(dir) {
|
|
|
924
924
|
* conventional root cordis.patch.yml a package may ship instead.
|
|
925
925
|
*/
|
|
926
926
|
export function bundleOptOutRowIds(dir) {
|
|
927
|
+
return declaredPatchRowIds(dir, 'disabledIds');
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Row ids a package's own patch sources declare for one role — the loop the
|
|
931
|
+
* two readers above and below share.
|
|
932
|
+
*
|
|
933
|
+
* They read the same two sources: every declared `dsh.bundle.patch` (one file,
|
|
934
|
+
* or an ordered list of them, #792) and the conventional root
|
|
935
|
+
* `cordis.patch.yml` a package may ship instead. Keeping that loop in one place
|
|
936
|
+
* is the point — `bundlePatchInsertedIds` above is declared-files-only, and a
|
|
937
|
+
* copy of this scan that forgot the root file is exactly how the two sides of
|
|
938
|
+
* one question drift apart.
|
|
939
|
+
*
|
|
940
|
+
* A source that cannot be read contributes nothing rather than throwing: this
|
|
941
|
+
* answers "what does the package say it ships", and a file that is not there
|
|
942
|
+
* says nothing. That is the tolerant half of the family by design — the strict
|
|
943
|
+
* one (`readBundlePatchRows`) refuses the whole declaration, because the host
|
|
944
|
+
* loads none of a patch it cannot read completely.
|
|
945
|
+
*/
|
|
946
|
+
function declaredPatchRowIds(dir, role) {
|
|
927
947
|
const ids = new Set();
|
|
928
948
|
const read = (file) => {
|
|
929
949
|
try {
|
|
930
|
-
for (const id of parsePatchRows(readFileSync(file, 'utf8'))
|
|
950
|
+
for (const id of parsePatchRows(readFileSync(file, 'utf8'))[role])
|
|
931
951
|
ids.add(id);
|
|
932
952
|
}
|
|
933
|
-
catch { /* no such patch — nothing
|
|
953
|
+
catch { /* no such patch — nothing declared through it */ }
|
|
934
954
|
};
|
|
935
955
|
for (const file of declaredBundlePatchFiles(dir))
|
|
936
956
|
read(file);
|
|
937
957
|
read(join(dir, 'cordis.patch.yml'));
|
|
938
958
|
return [...ids];
|
|
939
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Row ids the installed copy of a package DECLARES it inserts (#1012).
|
|
962
|
+
*
|
|
963
|
+
* The evidence half of `rowIdsForPackage`: the same two patch sources (the
|
|
964
|
+
* declared `dsh.bundle.patch`, one file or an ordered list, and the
|
|
965
|
+
* conventional root `cordis.patch.yml`), insert rows only, and — unlike that
|
|
966
|
+
* reader — no live loader rows.
|
|
967
|
+
*
|
|
968
|
+
* Live rows are exactly what this cannot use. A rename transaction asks the
|
|
969
|
+
* question twice: of the old package just before it is removed (whose rows are
|
|
970
|
+
* still live, but whose patch files are about to be the only copy left) and of
|
|
971
|
+
* the new package right after its first install (which has never been booted,
|
|
972
|
+
* so the loader has never heard of it). Only the package's own declaration can
|
|
973
|
+
* answer for both.
|
|
974
|
+
*
|
|
975
|
+
* That is the same question, and the same parser, as `renamePredecessor` asks
|
|
976
|
+
* on the install path — an install never boots the package either, so it
|
|
977
|
+
* compares declarations too and the two refusals agree on what "one plugin"
|
|
978
|
+
* means (#1012). Where they differ is the root `cordis.patch.yml`: see
|
|
979
|
+
* `declaredPatchRowIds` for why this reader covers it and that gate's
|
|
980
|
+
* declared-only reader does not.
|
|
981
|
+
*/
|
|
982
|
+
export function shippedInsertedRowIds(dir) {
|
|
983
|
+
return declaredPatchRowIds(dir, 'insertedIds');
|
|
984
|
+
}
|
|
940
985
|
/**
|
|
941
986
|
* Rows of one patch file. Exported because a package may ship its patch at
|
|
942
987
|
* the conventional path INSTEAD of declaring `dsh.bundle.patch`, and the
|
package/lib/routes.js
CHANGED
|
@@ -24,7 +24,7 @@ import { diagnosePackageManifests } from './diagnostics.js';
|
|
|
24
24
|
import { BOOT_ID, cancelActive, probePnpm, progress, provisionPnpm, runDshPlugin, setBuildEnvSource, TARGET_RE, } from './dsh-cli.js';
|
|
25
25
|
import { packageOfEntryName } from './entry-identity.js';
|
|
26
26
|
import { PendingRowJournal, repairOrphanedRows } from './pending-rows.js';
|
|
27
|
-
import { addProfileBundle, bundleOptOutRowIds, bundlePatchInsertedIds, bundlesDroppedFromProfile, declareProfileDependency, dropFromManifest, generationManagedNames, hasLoadableEntry, holdsNativeAddon, INBOX_BUNDLES, isDshProfileName, profileDir, readDependencyOwners, readGitResolutionCommit, readInstalled, readInstalledManifest, readInstalledPackageName, readInstalledRepoEvidence, readInstalledVersion, readLockCommits, readProfileBundles, readProfileManifestSnapshot, removeProfileBundle, parsePatchRows, restoreProfileManifest, setAllowBuilds } from './profile.js';
|
|
27
|
+
import { addProfileBundle, bundleOptOutRowIds, bundlePatchInsertedIds, bundlesDroppedFromProfile, declareProfileDependency, dropFromManifest, generationManagedNames, hasLoadableEntry, holdsNativeAddon, INBOX_BUNDLES, isDshProfileName, profileDir, readDependencyOwners, readGitResolutionCommit, readInstalled, readInstalledManifest, readInstalledPackageName, readInstalledRepoEvidence, readInstalledVersion, readLockCommits, readProfileBundles, readProfileManifestSnapshot, removeProfileBundle, parsePatchRows, restoreProfileManifest, setAllowBuilds, shippedInsertedRowIds } from './profile.js';
|
|
28
28
|
import { assessProfile, classifyPeer, introducedDuplicateNames, introducedRisks } from './compatibility.js';
|
|
29
29
|
import { runningAgentIds } from './agents.js';
|
|
30
30
|
import { analyzeProfile, corePackageNames } from './check.js';
|
|
@@ -4052,6 +4052,12 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup, ho
|
|
|
4052
4052
|
const wasLive = verifyActivation(config.profile, name, liveNames(), activeProfileDir, disabled.has(name)).state === 'live'
|
|
4053
4053
|
&& hasHostHalf(config.profile, name, activeProfileDir);
|
|
4054
4054
|
const oldRows = rowIdsForPackage(host, activeProfileDir, name);
|
|
4055
|
+
// Identity evidence for a rename (#1012), read BEFORE the removal:
|
|
4056
|
+
// once `remove` runs, the installed old package — and with it the
|
|
4057
|
+
// only record of what it registers — is gone.
|
|
4058
|
+
const oldInsertIds = kind === 'npm-rename'
|
|
4059
|
+
? shippedInsertedRowIds(join(activeProfileDir, 'node_modules', name))
|
|
4060
|
+
: [];
|
|
4055
4061
|
const patchFlags = packagePatchFlags(host, activeProfileDir, [name], readUserPatchState(userPatchPath));
|
|
4056
4062
|
const wasDisabled = disabled.has(name);
|
|
4057
4063
|
const manifestBefore = manifestCapture.snapshot;
|
|
@@ -4077,13 +4083,21 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup, ho
|
|
|
4077
4083
|
? { ok: true, detail: null }
|
|
4078
4084
|
: { ok: false, detail: `${restoredEn} was restored without a loadable entry` };
|
|
4079
4085
|
};
|
|
4080
|
-
|
|
4086
|
+
// `noteZh`/`noteEn` carry the "what you can do next" sentence, and
|
|
4087
|
+
// only the branch that actually restored the old install may say
|
|
4088
|
+
// it: after a failed rollback there is nothing to reassure the
|
|
4089
|
+
// user with, so the note is dropped rather than contradicting the
|
|
4090
|
+
// "could not be verified" fallout.
|
|
4091
|
+
const failWithRollback = async (detail, extra = {}, status = 500, noteZh = '', noteEn = '') => {
|
|
4081
4092
|
const rollback = await rollbackMigration();
|
|
4082
4093
|
logEvent(rollback.ok ? 'warn' : 'error', 'source-migration', `${name}: migration failed — ${detail}; ${rollback.ok ? `${restoredEn} restored` : `rollback failed: ${rollback.detail ?? 'unknown'}`}`);
|
|
4083
|
-
|
|
4094
|
+
// A 409 whose rollback failed is not a conflict the user can walk
|
|
4095
|
+
// away from — the install itself is now unverified — so the
|
|
4096
|
+
// status follows the rollback, not the reason we got here.
|
|
4097
|
+
sendJson(response, rollback.ok ? status : 500, {
|
|
4084
4098
|
ok: false,
|
|
4085
4099
|
error: rollback.ok
|
|
4086
|
-
? `${detail};已恢复${restoredZh}
|
|
4100
|
+
? `${detail};已恢复${restoredZh}。${noteZh === '' ? '' : ` ${noteZh}`} / ${detail}; ${restoredEn} was restored.${noteEn === '' ? '' : ` ${noteEn}`}`
|
|
4087
4101
|
: `${detail};且${restoredZh}未能验证恢复(${rollback.detail ?? 'unknown'})。 / ${detail}; restoration of ${restoredEn} could not be verified (${rollback.detail ?? 'unknown'}).`,
|
|
4088
4102
|
rollback: rollback.ok,
|
|
4089
4103
|
...extra,
|
|
@@ -4107,6 +4121,37 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup, ho
|
|
|
4107
4121
|
await failWithRollback('npm 目标安装完成后未形成可加载且唯一的依赖。 / The npm target did not produce one loadable replacement dependency.');
|
|
4108
4122
|
return;
|
|
4109
4123
|
}
|
|
4124
|
+
// The catalog's `previousNpm` is a CLAIM that the two package names
|
|
4125
|
+
// are one plugin, and the transaction above already acted on it by
|
|
4126
|
+
// replacing what the user had. Ask the packages themselves: the
|
|
4127
|
+
// rows each side declares it inserts are registry evidence, and two
|
|
4128
|
+
// packages that register disjoint rows are not the same plugin —
|
|
4129
|
+
// whatever the catalog says. Refuse and put the old one back rather
|
|
4130
|
+
// than leave a stranger's rows running under the old plugin's name
|
|
4131
|
+
// (#1012). Same question, same parser as the install-path gate
|
|
4132
|
+
// (`renamePredecessor`), which compares the installed package's
|
|
4133
|
+
// declared rows against the incoming registry `cordis.patch.yml`;
|
|
4134
|
+
// here both packages are already on disk, so both sides are read
|
|
4135
|
+
// the same way — including the conventional root patch file, which
|
|
4136
|
+
// that gate only knows on the incoming side.
|
|
4137
|
+
//
|
|
4138
|
+
// Both sides must SHOW evidence: a package with no patch file
|
|
4139
|
+
// declares nothing, and silence is not a contradiction.
|
|
4140
|
+
if (kind === 'npm-rename') {
|
|
4141
|
+
const newInsertIds = shippedInsertedRowIds(join(activeProfileDir, 'node_modules', targetName));
|
|
4142
|
+
if (oldInsertIds.length > 0 && newInsertIds.length > 0 && !sameLoaderRows(oldInsertIds, newInsertIds)) {
|
|
4143
|
+
// Both sides are third-party YAML: clip each id so a hostile
|
|
4144
|
+
// patch cannot turn this refusal into a megabyte response.
|
|
4145
|
+
const rows = (ids) => {
|
|
4146
|
+
const clip = (id) => (id.length <= 48 ? id : `${id.slice(0, 48)}…`);
|
|
4147
|
+
return ids.length <= 3
|
|
4148
|
+
? ids.map(clip).join(', ')
|
|
4149
|
+
: `${ids.slice(0, 3).map(clip).join(', ')}, +${ids.length - 3}`;
|
|
4150
|
+
};
|
|
4151
|
+
await failWithRollback(`目录声明 ${name} 已改名为 ${targetName},但两边的注册行不同(${name}: ${rows(oldInsertIds)};${targetName}: ${rows(newInsertIds)}),说明它们不是同一个插件。 / The catalog declares ${name} was renamed to ${targetName}, but the two packages register different plugin rows (${name}: ${rows(oldInsertIds)}; ${targetName}: ${rows(newInsertIds)}), so they are not the same plugin.`, {}, 409, `原来的版本仍然可用;如果目录写错了,请报告这一条。`, `The original install is still usable; if the catalog is wrong, report that entry.`);
|
|
4152
|
+
return;
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4110
4155
|
const stack = readBundleStack(activeProfileDir);
|
|
4111
4156
|
const trial = trialValidate(activeProfileDir, stack.community, hostAnchorOption);
|
|
4112
4157
|
if (!trial.ok) {
|
|
@@ -5374,6 +5419,29 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup, ho
|
|
|
5374
5419
|
logEvent('warn', 'update-bundles-restored', `${name}: the desktop app removed its bundle row before refusing the update (bundle-in-use) — restored it so the plugin still loads on the next boot (#935)`);
|
|
5375
5420
|
}
|
|
5376
5421
|
}
|
|
5422
|
+
// A FAILED update can also drop rows it never intended to touch.
|
|
5423
|
+
// The rollback path ("restoration of the previous build could not
|
|
5424
|
+
// be verified") rewrites the manifest through pnpm and the plugin
|
|
5425
|
+
// command, and neither carries the bundle rows it did not own: a
|
|
5426
|
+
// bystander keeps its dependency and its node_modules but loses
|
|
5427
|
+
// its `dsh.profile.bundles` row, so it silently stops loading on
|
|
5428
|
+
// the next boot while this operation reported a failure about
|
|
5429
|
+
// something else entirely.
|
|
5430
|
+
//
|
|
5431
|
+
// Restoring is sound exactly because the operation FAILED: nobody
|
|
5432
|
+
// expressed a deliberate removal, and the package is still on
|
|
5433
|
+
// disk — putting the row back restores what the user had before
|
|
5434
|
+
// the operation. A successful update keeps the read-only stance
|
|
5435
|
+
// (#720/#696), where a missing row may be a deliberate switch-off.
|
|
5436
|
+
if (!ok && !cancelled) {
|
|
5437
|
+
const lostRows = bundlesDroppedFromProfile(manifestBefore, config.profile, activeProfileDir, new Set(removedDeclaration === null ? [] : [removedDeclaration.name])).filter(bundle => bundle !== name
|
|
5438
|
+
&& existsSync(join(activeProfileDir, 'node_modules', bundle, 'package.json')));
|
|
5439
|
+
for (const bundle of lostRows) {
|
|
5440
|
+
if (addProfileBundle(activeProfileDir, bundle)) {
|
|
5441
|
+
logEvent('warn', 'update-bundles-restored', `${bundle}: lost its dsh.profile.bundles row while ${name}'s update failed — restored it so the plugin still loads on the next boot (#720)`);
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5377
5445
|
logEvent(ok || cancelled ? 'info' : 'error', 'update', `${name} -> ${target} exit=${String(result.exitCode)}${result.timedOut ? ' TIMEOUT' : ''}${cancelled ? ' CANCELLED' : ''}${stale ? ` STALE(${staleReason ?? 'unknown'})` : ''}${ok || cancelled ? '' : ` err=${failureDetail(result)}`}`);
|
|
5378
5446
|
// A user-cancelled run is a quiet outcome, not an error.
|
|
5379
5447
|
sendJson(response, ok || cancelled ? 200 : result.busy === true ? 409 : 502, {
|
package/lib/types/profile.d.ts
CHANGED
|
@@ -324,6 +324,29 @@ export declare function bundlePatchInsertedIds(dir: string): string[];
|
|
|
324
324
|
* conventional root cordis.patch.yml a package may ship instead.
|
|
325
325
|
*/
|
|
326
326
|
export declare function bundleOptOutRowIds(dir: string): string[];
|
|
327
|
+
/**
|
|
328
|
+
* Row ids the installed copy of a package DECLARES it inserts (#1012).
|
|
329
|
+
*
|
|
330
|
+
* The evidence half of `rowIdsForPackage`: the same two patch sources (the
|
|
331
|
+
* declared `dsh.bundle.patch`, one file or an ordered list, and the
|
|
332
|
+
* conventional root `cordis.patch.yml`), insert rows only, and — unlike that
|
|
333
|
+
* reader — no live loader rows.
|
|
334
|
+
*
|
|
335
|
+
* Live rows are exactly what this cannot use. A rename transaction asks the
|
|
336
|
+
* question twice: of the old package just before it is removed (whose rows are
|
|
337
|
+
* still live, but whose patch files are about to be the only copy left) and of
|
|
338
|
+
* the new package right after its first install (which has never been booted,
|
|
339
|
+
* so the loader has never heard of it). Only the package's own declaration can
|
|
340
|
+
* answer for both.
|
|
341
|
+
*
|
|
342
|
+
* That is the same question, and the same parser, as `renamePredecessor` asks
|
|
343
|
+
* on the install path — an install never boots the package either, so it
|
|
344
|
+
* compares declarations too and the two refusals agree on what "one plugin"
|
|
345
|
+
* means (#1012). Where they differ is the root `cordis.patch.yml`: see
|
|
346
|
+
* `declaredPatchRowIds` for why this reader covers it and that gate's
|
|
347
|
+
* declared-only reader does not.
|
|
348
|
+
*/
|
|
349
|
+
export declare function shippedInsertedRowIds(dir: string): string[];
|
|
327
350
|
/**
|
|
328
351
|
* Rows of one patch file. Exported because a package may ship its patch at
|
|
329
352
|
* the conventional path INSTEAD of declaring `dsh.bundle.patch`, and the
|
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.73.
|
|
4
|
+
"version": "1.73.1",
|
|
5
5
|
"icon": "assets/logo.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|