dshmarket 1.57.0 → 1.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/check.js +26 -9
- package/lib/routes.js +33 -9
- package/lib/types/check.d.ts +2 -0
- package/package.json +1 -1
- package/src/check.ts +28 -9
- package/src/routes.ts +36 -8
package/lib/check.js
CHANGED
|
@@ -732,6 +732,7 @@ export function buildBundleLayers(profileDirectory, bundleNames, specs, dshInsta
|
|
|
732
732
|
kind: INBOX_BUNDLES.has(name) ? 'official' : 'community',
|
|
733
733
|
directory,
|
|
734
734
|
patchPath: null,
|
|
735
|
+
patchPaths: [],
|
|
735
736
|
error: null,
|
|
736
737
|
entries: [],
|
|
737
738
|
parseError: null,
|
|
@@ -763,23 +764,34 @@ export function buildBundleLayers(profileDirectory, bundleNames, specs, dshInsta
|
|
|
763
764
|
layer.error = 'bundle package.json is unreadable';
|
|
764
765
|
return layer;
|
|
765
766
|
}
|
|
767
|
+
// A bundle may declare ONE patch file or a LIST of them: dsh 0.1.7's own
|
|
768
|
+
// `@deepseek-ai/dsh-web-app` ships five (a base patch plus four presets),
|
|
769
|
+
// and the official headless template includes that bundle — so requiring
|
|
770
|
+
// a string reported "the profile will fail to boot" for the DEFAULT
|
|
771
|
+
// layout, while `dsh --dump-config` composed it fine (#676).
|
|
766
772
|
const declared = bundleManifest.dsh?.bundle?.patch;
|
|
767
|
-
|
|
773
|
+
const declaredList = typeof declared === 'string'
|
|
774
|
+
? [declared]
|
|
775
|
+
: Array.isArray(declared) ? declared.filter((item) => typeof item === 'string') : [];
|
|
776
|
+
if (declaredList.length === 0) {
|
|
768
777
|
layer.error = 'bundle declares no dsh.bundle.patch — the profile will fail to boot';
|
|
769
778
|
return layer;
|
|
770
779
|
}
|
|
771
|
-
const
|
|
772
|
-
if (
|
|
773
|
-
layer.error = `declared patch ${
|
|
780
|
+
const missing = declaredList.find(relative => !existsSync(join(directory, relative)));
|
|
781
|
+
if (missing !== undefined) {
|
|
782
|
+
layer.error = `declared patch ${missing} is missing — the profile will fail to boot`;
|
|
774
783
|
return layer;
|
|
775
784
|
}
|
|
776
|
-
layer
|
|
777
|
-
|
|
778
|
-
|
|
785
|
+
// The first declared file is the layer's patch for reporting; every one
|
|
786
|
+
// of them contributes entries, because the composer applies them all.
|
|
787
|
+
layer.patchPath = join(directory, declaredList[0]);
|
|
788
|
+
layer.patchPaths = declaredList.map(relative => join(directory, relative));
|
|
789
|
+
const parsed = declaredList.map(relative => parsePatchFile(join(directory, relative)));
|
|
790
|
+
if (parsed.some(patches => patches === null)) {
|
|
779
791
|
layer.parseError = 'patch file is not a valid entry list';
|
|
780
792
|
return layer;
|
|
781
793
|
}
|
|
782
|
-
layer.entries = collectInsertIds(patches);
|
|
794
|
+
layer.entries = parsed.flatMap(patches => collectInsertIds(patches));
|
|
783
795
|
const order = bundleManifest.dsh?.bundle?.order;
|
|
784
796
|
if (order !== null && typeof order === 'object' && !Array.isArray(order)) {
|
|
785
797
|
const listOf = (value) => Array.isArray(value)
|
|
@@ -796,7 +808,12 @@ export function buildBundleLayers(profileDirectory, bundleNames, specs, dshInsta
|
|
|
796
808
|
const layers = bundles.map((bundle) => ({
|
|
797
809
|
label: bundle.name,
|
|
798
810
|
kind: 'bundle',
|
|
799
|
-
|
|
811
|
+
// EVERY declared file, not the first: the composer applies them all, and
|
|
812
|
+
// a composition built from one file would miss the preset rows — so a
|
|
813
|
+
// duplicate id or an orphan in a second file was invisible here (#688).
|
|
814
|
+
patches: bundle.parseError === null
|
|
815
|
+
? bundle.patchPaths.flatMap(path => parsePatchFile(path) ?? [])
|
|
816
|
+
: [],
|
|
800
817
|
parseError: bundle.parseError,
|
|
801
818
|
}));
|
|
802
819
|
return { bundles, layers };
|
package/lib/routes.js
CHANGED
|
@@ -477,12 +477,15 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup) {
|
|
|
477
477
|
const result = await hotMount(host, dir, name);
|
|
478
478
|
ok = result.ok;
|
|
479
479
|
reason = result.reason ?? undefined;
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
|
|
485
|
-
|
|
480
|
+
// Deliberately NOT clearing replacedWhileLive here (#685). This used
|
|
481
|
+
// to say "a mount that succeeded imported the module as it is on
|
|
482
|
+
// disk NOW", which is false exactly when the flag is set: it is only
|
|
483
|
+
// set when the host half was LIVE at update time, i.e. this process
|
|
484
|
+
// has already evaluated that module URL, and Node's ESM cache serves
|
|
485
|
+
// any later import of the same URL — the profile layout is hoisted,
|
|
486
|
+
// so an update rewrites the files in place and the URL never changes.
|
|
487
|
+
// Off-and-on re-creates the fiber around the OLD module. Only a
|
|
488
|
+
// restart ends the process that holds it, and the flag with it.
|
|
486
489
|
}
|
|
487
490
|
}
|
|
488
491
|
else {
|
|
@@ -2287,7 +2290,14 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup) {
|
|
|
2287
2290
|
// A carrier toggle moves the bundle in/out of dsh.profile.bundles,
|
|
2288
2291
|
// which only takes effect on the next composition — always a restart.
|
|
2289
2292
|
// Non-carrier plugins keep the live-mount based decision.
|
|
2290
|
-
|
|
2293
|
+
// A plugin replaced on disk while its host half was running is
|
|
2294
|
+
// still serving the module this process imported, whatever the
|
|
2295
|
+
// loader's inventory says — re-enabling it re-creates the fiber
|
|
2296
|
+
// around the cached old build (#685, measured end to end with a
|
|
2297
|
+
// module-scope version marker). Enabling cannot make it current;
|
|
2298
|
+
// only a restart can.
|
|
2299
|
+
const staleModule = enabled && replacedWhileLive.has(name);
|
|
2300
|
+
const restart = isCarrier || staleModule ? true : enabled ? !liveAfter : liveAfter;
|
|
2291
2301
|
// A client-part plugin's UI is in the page already — toggling it
|
|
2292
2302
|
// needs a browser refresh to show the change (same signal the
|
|
2293
2303
|
// install flow uses for the hot banner).
|
|
@@ -2298,7 +2308,13 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup) {
|
|
|
2298
2308
|
enabled,
|
|
2299
2309
|
disabled: [...disabled],
|
|
2300
2310
|
live: listHotMounts(),
|
|
2301
|
-
|
|
2311
|
+
// The same verdict the listing gives (#685): the reply used the
|
|
2312
|
+
// loader inventory alone and said `live` for a plugin serving
|
|
2313
|
+
// its old build, while a refresh of the listing — which applies
|
|
2314
|
+
// activationAfterReplace — said `restart`. One moment, one story.
|
|
2315
|
+
activation: {
|
|
2316
|
+
[name]: activationAfterReplace(verifyActivation(config.profile, name, liveNames(), activeProfileDir, offNow), replacedWhileLive.has(name)),
|
|
2317
|
+
},
|
|
2302
2318
|
reason,
|
|
2303
2319
|
patchRows,
|
|
2304
2320
|
patchWrite: patchWrite ?? { ok: true, reason: null },
|
|
@@ -3504,7 +3520,15 @@ export function mountMarketRoutes(host, config, commandRuntime, agentsLookup) {
|
|
|
3504
3520
|
const trial = trialValidate(activeProfileDir, stack.community);
|
|
3505
3521
|
if (!trial.ok) {
|
|
3506
3522
|
ok = false;
|
|
3507
|
-
|
|
3523
|
+
// Name the LAYER, not only the message: the first error is
|
|
3524
|
+
// often about a different bundle than the one being updated
|
|
3525
|
+
// (#688 — the official dsh-web-app's patch list, blamed on
|
|
3526
|
+
// whatever plugin the user happened to update), and a message
|
|
3527
|
+
// without the layer reads as an accusation of the wrong package.
|
|
3528
|
+
const firstIssue = trial.errors[0];
|
|
3529
|
+
const first = firstIssue === undefined
|
|
3530
|
+
? 'the composition would not boot'
|
|
3531
|
+
: `${firstIssue.layer}: ${firstIssue.message}`;
|
|
3508
3532
|
const rollback = await rollbackAttemptBuild();
|
|
3509
3533
|
rollbackOk = rollback.ok;
|
|
3510
3534
|
rollbackDetail = rollback.detail;
|
package/lib/types/check.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface BundleLayer {
|
|
|
43
43
|
directory: string | null;
|
|
44
44
|
/** Absolute path of the layer's patch file; null when undeclared/missing. */
|
|
45
45
|
patchPath: string | null;
|
|
46
|
+
/** Every declared patch file, in declaration order; `patchPath` is the first (#688). */
|
|
47
|
+
patchPaths: string[];
|
|
46
48
|
/**
|
|
47
49
|
* An in-box bundle whose directory could not be located — a gap in what
|
|
48
50
|
* this process can see, not a defect in the profile (#369). Distinct from
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dshmarket",
|
|
3
3
|
"description": "Visual plugin market inside DeepSeek Harness — browse, search, and one-click install community plugins. · DSH 可视化插件市场:逛一逛,点一下,装好。",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.58.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
package/src/check.ts
CHANGED
|
@@ -62,6 +62,8 @@ export interface BundleLayer {
|
|
|
62
62
|
directory: string | null
|
|
63
63
|
/** Absolute path of the layer's patch file; null when undeclared/missing. */
|
|
64
64
|
patchPath: string | null
|
|
65
|
+
/** Every declared patch file, in declaration order; `patchPath` is the first (#688). */
|
|
66
|
+
patchPaths: string[]
|
|
65
67
|
/**
|
|
66
68
|
* An in-box bundle whose directory could not be located — a gap in what
|
|
67
69
|
* this process can see, not a defect in the profile (#369). Distinct from
|
|
@@ -916,6 +918,7 @@ export function buildBundleLayers(
|
|
|
916
918
|
kind: INBOX_BUNDLES.has(name) ? 'official' : 'community',
|
|
917
919
|
directory,
|
|
918
920
|
patchPath: null,
|
|
921
|
+
patchPaths: [],
|
|
919
922
|
error: null,
|
|
920
923
|
entries: [],
|
|
921
924
|
parseError: null,
|
|
@@ -946,23 +949,34 @@ export function buildBundleLayers(
|
|
|
946
949
|
layer.error = 'bundle package.json is unreadable'
|
|
947
950
|
return layer
|
|
948
951
|
}
|
|
952
|
+
// A bundle may declare ONE patch file or a LIST of them: dsh 0.1.7's own
|
|
953
|
+
// `@deepseek-ai/dsh-web-app` ships five (a base patch plus four presets),
|
|
954
|
+
// and the official headless template includes that bundle — so requiring
|
|
955
|
+
// a string reported "the profile will fail to boot" for the DEFAULT
|
|
956
|
+
// layout, while `dsh --dump-config` composed it fine (#676).
|
|
949
957
|
const declared = bundleManifest.dsh?.bundle?.patch
|
|
950
|
-
|
|
958
|
+
const declaredList = typeof declared === 'string'
|
|
959
|
+
? [declared]
|
|
960
|
+
: Array.isArray(declared) ? declared.filter((item): item is string => typeof item === 'string') : []
|
|
961
|
+
if (declaredList.length === 0) {
|
|
951
962
|
layer.error = 'bundle declares no dsh.bundle.patch — the profile will fail to boot'
|
|
952
963
|
return layer
|
|
953
964
|
}
|
|
954
|
-
const
|
|
955
|
-
if (
|
|
956
|
-
layer.error = `declared patch ${
|
|
965
|
+
const missing = declaredList.find(relative => !existsSync(join(directory, relative)))
|
|
966
|
+
if (missing !== undefined) {
|
|
967
|
+
layer.error = `declared patch ${missing} is missing — the profile will fail to boot`
|
|
957
968
|
return layer
|
|
958
969
|
}
|
|
959
|
-
layer
|
|
960
|
-
|
|
961
|
-
|
|
970
|
+
// The first declared file is the layer's patch for reporting; every one
|
|
971
|
+
// of them contributes entries, because the composer applies them all.
|
|
972
|
+
layer.patchPath = join(directory, declaredList[0]!)
|
|
973
|
+
layer.patchPaths = declaredList.map(relative => join(directory, relative))
|
|
974
|
+
const parsed = declaredList.map(relative => parsePatchFile(join(directory, relative)))
|
|
975
|
+
if (parsed.some(patches => patches === null)) {
|
|
962
976
|
layer.parseError = 'patch file is not a valid entry list'
|
|
963
977
|
return layer
|
|
964
978
|
}
|
|
965
|
-
layer.entries = collectInsertIds(patches)
|
|
979
|
+
layer.entries = parsed.flatMap(patches => collectInsertIds(patches!))
|
|
966
980
|
const order = bundleManifest.dsh?.bundle?.order
|
|
967
981
|
if (order !== null && typeof order === 'object' && !Array.isArray(order)) {
|
|
968
982
|
const listOf = (value: unknown): string[] | undefined => Array.isArray(value)
|
|
@@ -979,7 +993,12 @@ export function buildBundleLayers(
|
|
|
979
993
|
const layers: LayerInput[] = bundles.map((bundle) => ({
|
|
980
994
|
label: bundle.name,
|
|
981
995
|
kind: 'bundle' as const,
|
|
982
|
-
|
|
996
|
+
// EVERY declared file, not the first: the composer applies them all, and
|
|
997
|
+
// a composition built from one file would miss the preset rows — so a
|
|
998
|
+
// duplicate id or an orphan in a second file was invisible here (#688).
|
|
999
|
+
patches: bundle.parseError === null
|
|
1000
|
+
? bundle.patchPaths.flatMap(path => parsePatchFile(path) ?? [])
|
|
1001
|
+
: [],
|
|
983
1002
|
parseError: bundle.parseError,
|
|
984
1003
|
}))
|
|
985
1004
|
return { bundles, layers }
|
package/src/routes.ts
CHANGED
|
@@ -538,11 +538,15 @@ export function mountMarketRoutes(
|
|
|
538
538
|
const result = await hotMount(host, dir, name)
|
|
539
539
|
ok = result.ok
|
|
540
540
|
reason = result.reason ?? undefined
|
|
541
|
-
//
|
|
542
|
-
//
|
|
543
|
-
//
|
|
544
|
-
//
|
|
545
|
-
|
|
541
|
+
// Deliberately NOT clearing replacedWhileLive here (#685). This used
|
|
542
|
+
// to say "a mount that succeeded imported the module as it is on
|
|
543
|
+
// disk NOW", which is false exactly when the flag is set: it is only
|
|
544
|
+
// set when the host half was LIVE at update time, i.e. this process
|
|
545
|
+
// has already evaluated that module URL, and Node's ESM cache serves
|
|
546
|
+
// any later import of the same URL — the profile layout is hoisted,
|
|
547
|
+
// so an update rewrites the files in place and the URL never changes.
|
|
548
|
+
// Off-and-on re-creates the fiber around the OLD module. Only a
|
|
549
|
+
// restart ends the process that holds it, and the flag with it.
|
|
546
550
|
}
|
|
547
551
|
} else {
|
|
548
552
|
ok = await hotUnmount(name) || await themes.setEntryDisabled(name, true)
|
|
@@ -2421,7 +2425,14 @@ export function mountMarketRoutes(
|
|
|
2421
2425
|
// A carrier toggle moves the bundle in/out of dsh.profile.bundles,
|
|
2422
2426
|
// which only takes effect on the next composition — always a restart.
|
|
2423
2427
|
// Non-carrier plugins keep the live-mount based decision.
|
|
2424
|
-
|
|
2428
|
+
// A plugin replaced on disk while its host half was running is
|
|
2429
|
+
// still serving the module this process imported, whatever the
|
|
2430
|
+
// loader's inventory says — re-enabling it re-creates the fiber
|
|
2431
|
+
// around the cached old build (#685, measured end to end with a
|
|
2432
|
+
// module-scope version marker). Enabling cannot make it current;
|
|
2433
|
+
// only a restart can.
|
|
2434
|
+
const staleModule = enabled && replacedWhileLive.has(name)
|
|
2435
|
+
const restart = isCarrier || staleModule ? true : enabled ? !liveAfter : liveAfter
|
|
2425
2436
|
// A client-part plugin's UI is in the page already — toggling it
|
|
2426
2437
|
// needs a browser refresh to show the change (same signal the
|
|
2427
2438
|
// install flow uses for the hot banner).
|
|
@@ -2432,7 +2443,16 @@ export function mountMarketRoutes(
|
|
|
2432
2443
|
enabled,
|
|
2433
2444
|
disabled: [...disabled],
|
|
2434
2445
|
live: listHotMounts(),
|
|
2435
|
-
|
|
2446
|
+
// The same verdict the listing gives (#685): the reply used the
|
|
2447
|
+
// loader inventory alone and said `live` for a plugin serving
|
|
2448
|
+
// its old build, while a refresh of the listing — which applies
|
|
2449
|
+
// activationAfterReplace — said `restart`. One moment, one story.
|
|
2450
|
+
activation: {
|
|
2451
|
+
[name]: activationAfterReplace(
|
|
2452
|
+
verifyActivation(config.profile, name, liveNames(), activeProfileDir, offNow),
|
|
2453
|
+
replacedWhileLive.has(name),
|
|
2454
|
+
),
|
|
2455
|
+
},
|
|
2436
2456
|
reason,
|
|
2437
2457
|
patchRows,
|
|
2438
2458
|
patchWrite: patchWrite ?? { ok: true, reason: null },
|
|
@@ -3641,7 +3661,15 @@ sendJson(response, 200, { updates })
|
|
|
3641
3661
|
const trial = trialValidate(activeProfileDir, stack.community)
|
|
3642
3662
|
if (!trial.ok) {
|
|
3643
3663
|
ok = false
|
|
3644
|
-
|
|
3664
|
+
// Name the LAYER, not only the message: the first error is
|
|
3665
|
+
// often about a different bundle than the one being updated
|
|
3666
|
+
// (#688 — the official dsh-web-app's patch list, blamed on
|
|
3667
|
+
// whatever plugin the user happened to update), and a message
|
|
3668
|
+
// without the layer reads as an accusation of the wrong package.
|
|
3669
|
+
const firstIssue = trial.errors[0]
|
|
3670
|
+
const first = firstIssue === undefined
|
|
3671
|
+
? 'the composition would not boot'
|
|
3672
|
+
: `${firstIssue.layer}: ${firstIssue.message}`
|
|
3645
3673
|
const rollback = await rollbackAttemptBuild()
|
|
3646
3674
|
rollbackOk = rollback.ok
|
|
3647
3675
|
rollbackDetail = rollback.detail
|