dsh-plugin-shop 0.9.0 → 0.9.1-beta.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/client.js +767 -643
- package/lib/index.js +227 -86
- package/lib/typert.host.js +14 -12
- package/lib/typert.remote-client.js +12 -10
- package/lib/types/client/EntryIcon.d.ts +27 -0
- package/lib/types/client/present.d.ts +8 -0
- package/lib/types/host/catalog.d.ts +5 -0
- package/lib/types/host/index.d.ts +3 -0
- package/lib/types/host/types.d.ts +7 -0
- package/lib/types/shared/npm-name.d.ts +42 -0
- package/lib/types/shared/tar.d.ts +8 -3
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -180,11 +180,20 @@ function httpOrigin(baseUrl, fetchImpl) {
|
|
|
180
180
|
* Pure — bytes in, a path-to-bytes map out. It handles exactly what `npm
|
|
181
181
|
* pack` emits and refuses everything else loudly, because the alternative to
|
|
182
182
|
* a small strict reader is a fourth runtime dependency (design §4). */
|
|
183
|
-
/** Bytes up to the first NUL,
|
|
184
|
-
* with NULs, so a plain toString would carry them into
|
|
183
|
+
/** Bytes up to the first NUL, one character for each byte. Tar pads its
|
|
184
|
+
* fixed-width text fields with NULs, so a plain toString would carry them into
|
|
185
|
+
* the path.
|
|
186
|
+
*
|
|
187
|
+
* `latin1`, not `ascii`: a name is bytes, and `ascii` drops the high bit of
|
|
188
|
+
* every one, so a name written in UTF-8 came back as a different string (`é`,
|
|
189
|
+
* C3 A9, as `C)`) that could be another file's name. One character for each
|
|
190
|
+
* byte is lossless and is the same string for a name that is ASCII, which is
|
|
191
|
+
* every name the npm transport reads. A caller that wants the characters of a
|
|
192
|
+
* UTF-8 name decodes `Buffer.from(name, 'latin1')`, or looks a name up as
|
|
193
|
+
* `Buffer.from(wanted, 'utf8').toString('latin1')`. */
|
|
185
194
|
function cstring(field) {
|
|
186
195
|
const end = field.indexOf(0);
|
|
187
|
-
return field.subarray(0, end === -1 ? field.length : end).toString("
|
|
196
|
+
return field.subarray(0, end === -1 ? field.length : end).toString("latin1");
|
|
188
197
|
}
|
|
189
198
|
/** Tar sizes are octal text, NUL- or space-terminated. An unparseable size
|
|
190
199
|
* would desynchronise every subsequent header, so it throws rather than
|
|
@@ -206,13 +215,20 @@ function assertContained(path) {
|
|
|
206
215
|
/**
|
|
207
216
|
* Parse an uncompressed tar archive into path → bytes.
|
|
208
217
|
*
|
|
209
|
-
* Directory entries and
|
|
210
|
-
*
|
|
211
|
-
*
|
|
218
|
+
* Directory entries and the type flags that carry no file are skipped: npm
|
|
219
|
+
* packs regular files under `package/`, and a catalog tarball that needs
|
|
220
|
+
* anything else is not one we published. A pax (`x`) or GNU (`L`) header is not
|
|
221
|
+
* a file either, but it NAMES the next one: a path too long or too wide for a
|
|
222
|
+
* ustar header arrives this way, and skipping the header without reading it
|
|
223
|
+
* left its file under a truncated name, reported missing from an archive that
|
|
224
|
+
* ships it ({@link pathFromPax}).
|
|
225
|
+
* Each path is spelled by its bytes, one character each ({@link cstring}).
|
|
212
226
|
*/
|
|
213
227
|
function readTar(buffer) {
|
|
214
228
|
const files = /* @__PURE__ */ new Map();
|
|
215
229
|
let offset = 0;
|
|
230
|
+
/** The name a pax or GNU header gives the header after it. */
|
|
231
|
+
let extendedPath = null;
|
|
216
232
|
while (offset + 512 <= buffer.length) {
|
|
217
233
|
const header = buffer.subarray(offset, offset + 512);
|
|
218
234
|
if (header.every((byte) => byte === 0)) break;
|
|
@@ -220,13 +236,49 @@ function readTar(buffer) {
|
|
|
220
236
|
const prefix = cstring(header.subarray(345, 500));
|
|
221
237
|
const size = parseOctal(header.subarray(124, 136));
|
|
222
238
|
const typeflag = String.fromCharCode(header[156] ?? 0);
|
|
223
|
-
const
|
|
224
|
-
offset += 512;
|
|
225
|
-
if (typeflag === "
|
|
226
|
-
|
|
239
|
+
const data = buffer.subarray(offset + 512, offset + 512 + size);
|
|
240
|
+
offset += 512 + Math.ceil(size / 512) * 512;
|
|
241
|
+
if (typeflag === "x") {
|
|
242
|
+
extendedPath = pathFromPax(data) ?? extendedPath;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (typeflag === "L") {
|
|
246
|
+
extendedPath = cstring(data);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const path = extendedPath ?? (prefix === "" ? name : `${prefix}/${name}`);
|
|
250
|
+
extendedPath = null;
|
|
251
|
+
if (typeflag === "0" || typeflag === "\0") files.set(assertContained(path), data);
|
|
227
252
|
}
|
|
228
253
|
return files;
|
|
229
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* The `path` record of a pax extended header, in the reader's spelling, or null
|
|
257
|
+
* where the header names none.
|
|
258
|
+
*
|
|
259
|
+
* A pax record is `<length> <key>=<value>\n`, where `<length>` counts the whole
|
|
260
|
+
* record in bytes, its own digits included. The value is UTF-8 text, and the
|
|
261
|
+
* reader spells a name one character for each byte.
|
|
262
|
+
*/
|
|
263
|
+
function pathFromPax(data) {
|
|
264
|
+
let at = 0;
|
|
265
|
+
while (at < data.length) {
|
|
266
|
+
const space = data.indexOf(32, at);
|
|
267
|
+
if (space === -1) return null;
|
|
268
|
+
const length = Number.parseInt(data.subarray(at, space).toString("latin1"), 10);
|
|
269
|
+
if (!Number.isSafeInteger(length) || length <= 0 || at + length > data.length) return null;
|
|
270
|
+
const record = data.subarray(at, at + length);
|
|
271
|
+
const equals = record.indexOf(61, space - at);
|
|
272
|
+
if (equals !== -1) {
|
|
273
|
+
if (record.subarray(space - at + 1, equals).toString("utf8") === "path") {
|
|
274
|
+
const end = record[record.length - 1] === 10 ? record.length - 1 : record.length;
|
|
275
|
+
return record.subarray(equals + 1, end).toString("latin1");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
at += length;
|
|
279
|
+
}
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
230
282
|
//#endregion
|
|
231
283
|
//#region src/host/npm-origin.ts
|
|
232
284
|
/** The npm transport (design §2, §3): the catalog as a package.
|
|
@@ -520,6 +572,45 @@ function holderLabel(spec, name) {
|
|
|
520
572
|
if (spec.trim() === "") return "\"\"";
|
|
521
573
|
return spec.length > HOLDER_SPEC_CAP ? `${spec.slice(0, 79)}…` : spec;
|
|
522
574
|
}
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/shared/npm-name.ts
|
|
577
|
+
/**
|
|
578
|
+
* The npm package-name grammar a catalogue row's `name` must satisfy.
|
|
579
|
+
*
|
|
580
|
+
* ONE definition, read in three places: the Host's entry schema
|
|
581
|
+
* (`host/catalog.ts`), the registry's gate (`registry/scripts/src/gate.ts`)
|
|
582
|
+
* and `emit`'s last-line invariant. The dependency can only point this way —
|
|
583
|
+
* the package's tsconfig pins `rootDir: "src"`, so the registry reaches in
|
|
584
|
+
* and nothing here can reach out (the same reason `shop-like.ts` lives here).
|
|
585
|
+
*
|
|
586
|
+
* Why it is narrower than npm's own rule: `validate-npm-package-name` accepts
|
|
587
|
+
* a scope beginning with `_` or `.` — `@_iluxury/dsh-routerai-price-watcher`
|
|
588
|
+
* is valid for a NEW publication — while this grammar requires the scope's
|
|
589
|
+
* first character to be a letter or digit. That was a deliberate argv-safety
|
|
590
|
+
* choice when the rule lived only in the client: the name becomes half of an
|
|
591
|
+
* install spec, and everything this excludes is shell punctuation. It stopped
|
|
592
|
+
* being merely a client rule the moment a catalogue shipped, because the
|
|
593
|
+
* clients parse the WHOLE data file as one zod object: a single name outside
|
|
594
|
+
* this grammar costs every listing, not one. On 2026-10-30
|
|
595
|
+
* `@_iluxury/dsh-routerai-price-watcher` did exactly that — valid on npm, no
|
|
596
|
+
* gate rule to catch it, and every 0.8.0+ shop answered 「暂时无法读取目录」
|
|
597
|
+
* for a 21,493-row catalogue. The gate now admits only what this parses.
|
|
598
|
+
*
|
|
599
|
+
* The narrow side is therefore the WIRE CONTRACT, frozen by the installed
|
|
600
|
+
* base: widening it in a client release also widens the gate, because this is
|
|
601
|
+
* one constant, and the catalogue would then carry names every older client
|
|
602
|
+
* still refuses. Do not widen it without a way to know which client versions
|
|
603
|
+
* are installed; npm's per-version download counts cannot answer that (the
|
|
604
|
+
* measurement is in `emit.ts`'s `peers` comment).
|
|
605
|
+
*
|
|
606
|
+
* @module shared/npm-name
|
|
607
|
+
*/
|
|
608
|
+
/**
|
|
609
|
+
* An optional `@scope/`, then a name starting with a letter or digit and
|
|
610
|
+
* continuing in lowercase letters, digits, `.`, `_` or `-`. No uppercase, no
|
|
611
|
+
* leading dot or underscore on either segment, and no shell punctuation.
|
|
612
|
+
*/
|
|
613
|
+
const CATALOG_NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
523
614
|
/** A cached catalog younger than this is served without touching the network. */
|
|
524
615
|
const FRESH_MS = 3e5;
|
|
525
616
|
/** How long a probe may take before the race gives up on that origin. Long
|
|
@@ -550,9 +641,6 @@ async function withCommitBudget(id, what, start) {
|
|
|
550
641
|
clearTimeout(timer);
|
|
551
642
|
}
|
|
552
643
|
}
|
|
553
|
-
/** npm's package-name grammar. The value becomes half of an install spec, so
|
|
554
|
-
* aliases and shell punctuation must not reach the CLI. */
|
|
555
|
-
const NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
556
644
|
/** Canonical SemVer 2.0.0, including legal prerelease/build metadata. */
|
|
557
645
|
const NPM_VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/;
|
|
558
646
|
/** The repository binding used to build a GitHub install spec. */
|
|
@@ -653,10 +741,11 @@ const entrySchema = z.object({
|
|
|
653
741
|
unpackedSize: z.number().int().nonnegative().optional(),
|
|
654
742
|
installSize: z.number().int().nonnegative().optional(),
|
|
655
743
|
archived: z.boolean().optional(),
|
|
656
|
-
forked: z.boolean().optional()
|
|
744
|
+
forked: z.boolean().optional(),
|
|
745
|
+
icon: z.string().regex(/^[0-9a-f]{64}$/).optional()
|
|
657
746
|
}).superRefine((entry, ctx) => {
|
|
658
747
|
if (entry.source === "npm") {
|
|
659
|
-
if (!
|
|
748
|
+
if (!CATALOG_NPM_NAME.test(entry.name)) ctx.addIssue({
|
|
660
749
|
code: "custom",
|
|
661
750
|
path: ["name"],
|
|
662
751
|
message: `npm entry name ${JSON.stringify(entry.name)} is outside npm package-name grammar`
|
|
@@ -734,6 +823,10 @@ const pointerSchema = z.object({
|
|
|
734
823
|
stars: z.object({
|
|
735
824
|
url: z.string(),
|
|
736
825
|
sha256: z.string()
|
|
826
|
+
}).optional(),
|
|
827
|
+
icons: z.object({
|
|
828
|
+
url: z.string(),
|
|
829
|
+
sha256: z.string()
|
|
737
830
|
}).optional()
|
|
738
831
|
});
|
|
739
832
|
const nodeFs$1 = {
|
|
@@ -757,6 +850,34 @@ function parseStarsText(text) {
|
|
|
757
850
|
return {};
|
|
758
851
|
}
|
|
759
852
|
}
|
|
853
|
+
/** The longest a data URL holding MAX_CATALOG_ICON_BYTES can be: the base64
|
|
854
|
+
* alphabet is 4 characters per 3 bytes, and the prefix is the longest of the
|
|
855
|
+
* four media types' spellings. */
|
|
856
|
+
const MAX_ICON_DATA_URL_LENGTH = 26 + Math.ceil(32768 / 3) * 4;
|
|
857
|
+
/** The four types a catalogue icon can have (design §6.1); nothing else
|
|
858
|
+
* reaches an `img.src`. */
|
|
859
|
+
const ICON_DATA_URL = /^data:image\/(?:svg\+xml|png|jpeg|webp);base64,[A-Za-z0-9+/]+={0,2}$/;
|
|
860
|
+
/**
|
|
861
|
+
* Read and verify an icons sidecar; ANY irregularity degrades to no icons —
|
|
862
|
+
* icons are presentation, and they never cost the reader the catalog (§7).
|
|
863
|
+
* The checks are the grammar the build writes: a 64-hex sha256 keying a
|
|
864
|
+
* `data:` URL of the four types, each within the per-icon bound.
|
|
865
|
+
*/
|
|
866
|
+
function parseIconsText(text) {
|
|
867
|
+
try {
|
|
868
|
+
const parsed = JSON.parse(text);
|
|
869
|
+
if (typeof parsed.icons !== "object" || parsed.icons === null || Array.isArray(parsed.icons)) return {};
|
|
870
|
+
const out = Object.create(null);
|
|
871
|
+
for (const [key, value] of Object.entries(parsed.icons)) {
|
|
872
|
+
if (!/^[0-9a-f]{64}$/.test(key)) return {};
|
|
873
|
+
if (typeof value !== "string" || value.length > MAX_ICON_DATA_URL_LENGTH || !ICON_DATA_URL.test(value)) return {};
|
|
874
|
+
out[key] = value;
|
|
875
|
+
}
|
|
876
|
+
return out;
|
|
877
|
+
} catch {
|
|
878
|
+
return {};
|
|
879
|
+
}
|
|
880
|
+
}
|
|
760
881
|
/**
|
|
761
882
|
* Load the catalog snapshot: fetch the pointer, verify the data file's sha256
|
|
762
883
|
* against it, cache both on disk, and serve the cached copy with `stale: true`
|
|
@@ -806,13 +927,19 @@ async function loadCatalog(options) {
|
|
|
806
927
|
const starsText = fsImpl.read(cachePath(cacheDir, pointer.stars.url, "stars"));
|
|
807
928
|
if (createHash("sha256").update(starsText).digest("hex") === pointer.stars.sha256) stars = parseStarsText(starsText);
|
|
808
929
|
} catch {}
|
|
930
|
+
let icons = {};
|
|
931
|
+
if (pointer.icons !== void 0) try {
|
|
932
|
+
const iconsText = fsImpl.read(cachePath(cacheDir, pointer.icons.url, "icons"));
|
|
933
|
+
if (createHash("sha256").update(iconsText).digest("hex") === pointer.icons.sha256) icons = parseIconsText(iconsText);
|
|
934
|
+
} catch {}
|
|
809
935
|
return {
|
|
810
936
|
schemaVersion: pointer.schemaVersion,
|
|
811
937
|
builtAt: pointer.builtAt,
|
|
812
938
|
entries: data.plugins,
|
|
813
939
|
denied: data.denied,
|
|
814
940
|
notAShop: data.notAShop,
|
|
815
|
-
stars
|
|
941
|
+
stars,
|
|
942
|
+
icons
|
|
816
943
|
};
|
|
817
944
|
};
|
|
818
945
|
if (!refresh && fsImpl.exists(indexPath)) {
|
|
@@ -879,13 +1006,26 @@ async function loadCatalog(options) {
|
|
|
879
1006
|
}
|
|
880
1007
|
} catch {}
|
|
881
1008
|
}
|
|
1009
|
+
let icons = {};
|
|
1010
|
+
if (pointer.icons !== void 0) {
|
|
1011
|
+
const sidecar = pointer.icons;
|
|
1012
|
+
try {
|
|
1013
|
+
const iconsCachePath = cachePath(cacheDir, sidecar.url, "icons");
|
|
1014
|
+
const iconsText = await withCommitBudget(handle.id, `serve ${sidecar.url}`, (signal) => handle.file(sidecar.url, signal));
|
|
1015
|
+
if (createHash("sha256").update(iconsText).digest("hex") === sidecar.sha256) {
|
|
1016
|
+
icons = parseIconsText(iconsText);
|
|
1017
|
+
fsImpl.write(iconsCachePath, iconsText);
|
|
1018
|
+
}
|
|
1019
|
+
} catch {}
|
|
1020
|
+
}
|
|
882
1021
|
const snapshot = {
|
|
883
1022
|
schemaVersion: pointer.schemaVersion,
|
|
884
1023
|
builtAt: pointer.builtAt,
|
|
885
1024
|
entries: data.plugins,
|
|
886
1025
|
denied: data.denied,
|
|
887
1026
|
notAShop: data.notAShop,
|
|
888
|
-
stars
|
|
1027
|
+
stars,
|
|
1028
|
+
icons
|
|
889
1029
|
};
|
|
890
1030
|
try {
|
|
891
1031
|
fsImpl.write(dataCachePath, dataText);
|
|
@@ -1020,7 +1160,7 @@ function validateInstall(snapshot, args, installedSpec) {
|
|
|
1020
1160
|
if (denied !== void 0) return {
|
|
1021
1161
|
ok: false,
|
|
1022
1162
|
code: "denied",
|
|
1023
|
-
detail: `dsh-plugin-shop: ${args.name} is
|
|
1163
|
+
detail: `dsh-plugin-shop: installation of ${args.name} is blocked: ${denied.detail}`
|
|
1024
1164
|
};
|
|
1025
1165
|
let entry;
|
|
1026
1166
|
if (args.source === void 0) {
|
|
@@ -1028,7 +1168,7 @@ function validateInstall(snapshot, args, installedSpec) {
|
|
|
1028
1168
|
if (named.length > 1) return {
|
|
1029
1169
|
ok: false,
|
|
1030
1170
|
code: "ambiguous-identity",
|
|
1031
|
-
detail: `dsh-plugin-shop: the catalog
|
|
1171
|
+
detail: `dsh-plugin-shop: the catalog has ${named.length} plugins named ${args.name}. Refresh the shop and select the one you want to install.`
|
|
1032
1172
|
};
|
|
1033
1173
|
entry = named[0];
|
|
1034
1174
|
} else {
|
|
@@ -1053,7 +1193,7 @@ function validateInstall(snapshot, args, installedSpec) {
|
|
|
1053
1193
|
if (entry.version !== args.version) return {
|
|
1054
1194
|
ok: false,
|
|
1055
1195
|
code: "version-mismatch",
|
|
1056
|
-
detail: `dsh-plugin-shop: ${args.name}@${args.version}
|
|
1196
|
+
detail: `dsh-plugin-shop: you requested ${args.name}@${args.version}, but the catalog lists ${entry.version}. Refresh the shop and try again.`
|
|
1057
1197
|
};
|
|
1058
1198
|
if (installedSpec !== void 0) {
|
|
1059
1199
|
const verdict = specVerdict(entry, installedSpec);
|
|
@@ -1062,14 +1202,14 @@ function validateInstall(snapshot, args, installedSpec) {
|
|
|
1062
1202
|
return {
|
|
1063
1203
|
ok: false,
|
|
1064
1204
|
code: "name-taken",
|
|
1065
|
-
detail: `${verdict === "unknown" ? `dsh-plugin-shop: ${args.name} is already installed from ${holder}
|
|
1205
|
+
detail: `${verdict === "unknown" ? `dsh-plugin-shop: ${args.name} is already installed from ${holder}. The shop could not match it to a catalog entry.` : `dsh-plugin-shop: ${args.name} is already installed from ${holder}.`} Installing this plugin would replace it, since a profile can hold only one plugin of each name. To switch, uninstall the existing plugin first.`
|
|
1066
1206
|
};
|
|
1067
1207
|
}
|
|
1068
1208
|
}
|
|
1069
1209
|
if (entry.tier !== "verified" && !args.acknowledged) return {
|
|
1070
1210
|
ok: false,
|
|
1071
1211
|
code: "needs-acknowledgement",
|
|
1072
|
-
detail: entry.tier === "verified-stale" ? `dsh-plugin-shop: ${args.name}
|
|
1212
|
+
detail: entry.tier === "verified-stale" ? `dsh-plugin-shop: a different version of ${args.name} was reviewed. The current version has not been reviewed. Confirm the installation to continue.` : `dsh-plugin-shop: ${args.name} is a community plugin and has not been reviewed. Confirm the installation to continue.`
|
|
1073
1213
|
};
|
|
1074
1214
|
return {
|
|
1075
1215
|
ok: true,
|
|
@@ -1632,11 +1772,11 @@ function createBoundedLog() {
|
|
|
1632
1772
|
*/
|
|
1633
1773
|
function activationFailureDetail(args) {
|
|
1634
1774
|
const { expectedName, profile, before, after } = args;
|
|
1635
|
-
if (Object.hasOwn(after, expectedName)) return `${expectedName} is
|
|
1636
|
-
if (before === null) return `${expectedName} is
|
|
1775
|
+
if (Object.hasOwn(after, expectedName)) return `${expectedName} is installed as a profile dependency, but is not listed in dsh.profile.bundles. dsh has not enabled it as a plugin, so the shop cannot load it.`;
|
|
1776
|
+
if (before === null) return `${expectedName} is missing from both dsh.profile.bundles and the profile dependencies. The shop could not read the profile before installation, so it cannot tell what changed. Check the profile's package.json before trying again.`;
|
|
1637
1777
|
const added = Object.keys(after).filter((name) => !Object.hasOwn(before, name)).sort();
|
|
1638
|
-
if (added.length > 0) return `${expectedName} is not in the profile
|
|
1639
|
-
return `${expectedName} is
|
|
1778
|
+
if (added.length > 0) return `${expectedName} is not in the profile dependencies. The install added ${added.join(", ")} instead. To remove ${added[0]}, run: dsh plugin --profile ${profile} remove ${added[0]}`;
|
|
1779
|
+
return `${expectedName} is missing from both dsh.profile.bundles and the profile dependencies. Nothing was added. If this plugin was recently published, the catalog may be out of date. Refresh it and try again.`;
|
|
1640
1780
|
}
|
|
1641
1781
|
/** The §7.2 step-6 confirm: after a zero exit, re-read the profile manifest and
|
|
1642
1782
|
* verify the bundle actually landed in `dsh.profile.bundles`. Exit 0 alone is
|
|
@@ -1663,7 +1803,7 @@ function confirmBundleActivation(profile, home, expectedName, before) {
|
|
|
1663
1803
|
after
|
|
1664
1804
|
});
|
|
1665
1805
|
} catch {
|
|
1666
|
-
return `
|
|
1806
|
+
return `Could not confirm the installation because the profile manifest could not be read (${join(profileDir, "package.json")}). Check that file before trying again.`;
|
|
1667
1807
|
}
|
|
1668
1808
|
}
|
|
1669
1809
|
/** The uninstall mirror of §7.2 step 6: after a zero exit, re-read the profile
|
|
@@ -1673,9 +1813,9 @@ function confirmBundleRemoval(profile, home, expectedName) {
|
|
|
1673
1813
|
const profileDir = resolveProfileDir(profile, home);
|
|
1674
1814
|
try {
|
|
1675
1815
|
if (!readProfileManifest("dsh-plugin-shop", profileDir).dsh?.profile?.bundles?.includes(expectedName)) return null;
|
|
1676
|
-
return "
|
|
1816
|
+
return "The uninstall command finished, but the plugin is still listed in dsh.profile.bundles. Try uninstalling it again.";
|
|
1677
1817
|
} catch {
|
|
1678
|
-
return `
|
|
1818
|
+
return `Could not confirm the uninstall because the profile manifest could not be read (${join(profileDir, "package.json")}). Check that file and try again.`;
|
|
1679
1819
|
}
|
|
1680
1820
|
}
|
|
1681
1821
|
/** Lines a failed install's log tends to END on, none of which tell the user
|
|
@@ -1783,7 +1923,7 @@ const NATIVE_ALLOCATION_ABORT = /^memory allocation of \d+ bytes failed$/;
|
|
|
1783
1923
|
function nativeAllocationAbortDetail(usable) {
|
|
1784
1924
|
const abort = usable.find((line) => NATIVE_ALLOCATION_ABORT.test(line.trim()));
|
|
1785
1925
|
if (abort === void 0) return null;
|
|
1786
|
-
return `pnpm
|
|
1926
|
+
return `pnpm ran out of memory (${abort.trim()}). pnpm 12 versions before 12.7.0 have a known memory bug with shared missing peer dependencies (pnpm/pnpm#15362). Upgrade pnpm to 12.7.0 or later, or use pnpm 11, then install again. If your pnpm is already 12.7.0 or later, report the line above to pnpm.`;
|
|
1787
1927
|
}
|
|
1788
1928
|
/**
|
|
1789
1929
|
* The one log line worth putting in front of the user, plus the recovery hint.
|
|
@@ -1825,13 +1965,13 @@ function installFailureDetail(profile, log, hint = `pnpm failed in the profile.
|
|
|
1825
1965
|
* 2026-09-02 as "Update failed / dsh not found on PATH" on a working install.
|
|
1826
1966
|
*/
|
|
1827
1967
|
function spawnFailureDetail(code, message, dshBin, platform) {
|
|
1828
|
-
if (platform === "win32" && (code === "ENOENT" || code === "EINVAL")) return `
|
|
1829
|
-
if (code === "ENOENT") return "dsh not found on PATH
|
|
1830
|
-
return `
|
|
1968
|
+
if (platform === "win32" && (code === "ENOENT" || code === "EINVAL")) return `The shop could not find the dsh CLI entry file (${DSH_PACKAGE}, command: ${dshBin}). On Windows, the shop needs this file because it cannot run the .cmd launcher directly. Check that \`dsh --version\` works in a terminal, then reinstall the CLI if it does not.`;
|
|
1969
|
+
if (code === "ENOENT") return "dsh was not found on PATH. Install the dsh CLI or add it to PATH, then try again.";
|
|
1970
|
+
return `Could not start dsh: ${message}`;
|
|
1831
1971
|
}
|
|
1832
1972
|
/** The failure detail for a command stopped by the shop's deadline. */
|
|
1833
1973
|
function installTimeoutDetail(profile, timeoutMs) {
|
|
1834
|
-
return `dsh-plugin-shop: the command did not finish within ${Math.max(1, Math.round(timeoutMs / 1e3))}s and was stopped.
|
|
1974
|
+
return `dsh-plugin-shop: the command did not finish within ${Math.max(1, Math.round(timeoutMs / 1e3))}s and was stopped. To check the error and retry, run: dsh plugin --profile ${profile} install`;
|
|
1835
1975
|
}
|
|
1836
1976
|
/** The real kill. Exported for the tests that inject a RECORDING `KillFns`:
|
|
1837
1977
|
* a recorder that only records asserts that a kill was requested and leaves
|
|
@@ -4253,33 +4393,33 @@ function thrownTail(message, desktop) {
|
|
|
4253
4393
|
/** What each management code means, in dsh 0.1.7-rc.2's own terms (the
|
|
4254
4394
|
* implementation plan of 2026-09-27, Task 2, records where each is raised). */
|
|
4255
4395
|
const CODE_SENTENCE = {
|
|
4256
|
-
"unknown-plugin": "dsh
|
|
4257
|
-
"invalid-spec": "dsh could not read the install
|
|
4258
|
-
"ambiguous-install": "dsh could not
|
|
4396
|
+
"unknown-plugin": "dsh could not find a plugin with that ID",
|
|
4397
|
+
"invalid-spec": "dsh could not read the package name or install source",
|
|
4398
|
+
"ambiguous-install": "dsh could not identify the installed package",
|
|
4259
4399
|
"not-bundle": "the package is not a dsh bundle",
|
|
4260
4400
|
"not-removable": "dsh does not allow removing this bundle",
|
|
4261
|
-
"stop-profile": "
|
|
4401
|
+
"stop-profile": "this bundle is still running; stop dsh for this profile before removing it",
|
|
4262
4402
|
"bundle-in-use": "the bundle was switched off, but some of its plugins are still running",
|
|
4263
|
-
"stale-approval": "
|
|
4403
|
+
"stale-approval": "the build approval refers to a package that pnpm no longer lists",
|
|
4264
4404
|
"management-required": "the plugin belongs to dsh itself",
|
|
4265
|
-
unaddressable: "the plugin is not
|
|
4266
|
-
"operation-error": "dsh
|
|
4267
|
-
"already-installed": "this dsh
|
|
4405
|
+
unaddressable: "the plugin is not defined in this profile's configuration, so dsh cannot change it here",
|
|
4406
|
+
"operation-error": "dsh encountered an unexpected error",
|
|
4407
|
+
"already-installed": "this version of dsh cannot update an installed package directly; uninstall it, then install it again"
|
|
4268
4408
|
};
|
|
4269
4409
|
/** What each classified pnpm failure was, as dsh classifies it. */
|
|
4270
4410
|
const KIND_SENTENCE = {
|
|
4271
4411
|
"pnpm-missing": "pnpm could not be started",
|
|
4272
|
-
timeout: "pnpm
|
|
4273
|
-
"not-found": "
|
|
4274
|
-
"no-matching-version": "
|
|
4275
|
-
network: "the network failed",
|
|
4412
|
+
timeout: "pnpm took too long to finish",
|
|
4413
|
+
"not-found": "the package could not be found",
|
|
4414
|
+
"no-matching-version": "the requested version could not be found",
|
|
4415
|
+
network: "the network request failed",
|
|
4276
4416
|
"disk-full": "the disk is full",
|
|
4277
4417
|
permission: "permission was denied",
|
|
4278
4418
|
integrity: "the downloaded package failed its integrity check"
|
|
4279
4419
|
};
|
|
4280
4420
|
const WHERE = {
|
|
4281
4421
|
registry: " at the registry",
|
|
4282
|
-
"spec-host": "
|
|
4422
|
+
"spec-host": " while connecting to the package source"
|
|
4283
4423
|
};
|
|
4284
4424
|
/** An own-property read of one of the three tables above. They are object
|
|
4285
4425
|
* literals, so a bare index read answers for Object.prototype, and a code of
|
|
@@ -4306,10 +4446,10 @@ function restorationSentence(output) {
|
|
|
4306
4446
|
* so its restoration line is passed on. Without one, an install or update
|
|
4307
4447
|
* says what dsh's own pre-check says, and an uninstall says nothing of it. */
|
|
4308
4448
|
function versionRefusalDetail(context, change) {
|
|
4309
|
-
const refused = change.incompatible.map((issue) => `${issue.name}@${issue.version}
|
|
4449
|
+
const refused = change.incompatible.map((issue) => `${issue.name}@${issue.version} requires ${Object.entries(issue.peers).map(([peer, range]) => `${peer} ${range}`).join(", ")}, which dsh ${issue.runtimeVersion} does not satisfy`);
|
|
4310
4450
|
const restored = restorationSentence(change.output) ?? (context.operation === "uninstall" || change.truncated ? null : "Nothing was installed.");
|
|
4311
4451
|
const base = `dsh-plugin-shop: dsh refused the ${context.operation}: ${refused.join("; ")}.${restored === null ? "" : ` ${restored}`}`;
|
|
4312
|
-
if (context.desktop) return `${base}
|
|
4452
|
+
if (context.desktop) return `${base} Version exemptions are not available here: the dsh CLI cannot manage desktop profiles, and the shop cannot grant them.`;
|
|
4313
4453
|
const commands = change.incompatible.map((issue) => allowVersionCommand(context.profile, issue)).filter((command) => command !== null);
|
|
4314
4454
|
if (commands.length === 0) return base;
|
|
4315
4455
|
return `${base} To accept the risk of crashes or data loss for ${commands.length === 1 ? "this exact version" : "these exact versions"}, run: ${commands.join("; ")} - then ${context.operation} again.`;
|
|
@@ -4334,9 +4474,9 @@ function codeReason(change, code) {
|
|
|
4334
4474
|
* Add plugin dialog refuses a name the profile already holds, so it cannot
|
|
4335
4475
|
* rerun an update, and with no name listed it offers no such button. */
|
|
4336
4476
|
function desktopBuildsDetail(context, change, held) {
|
|
4337
|
-
const base = `dsh-plugin-shop: pnpm
|
|
4477
|
+
const base = `dsh-plugin-shop: pnpm blocked build scripts for ${held}. These scripts need approval, which the shop cannot grant.`;
|
|
4338
4478
|
if (context.operation !== "install" || change.pendingBuilds.length === 0) return base;
|
|
4339
|
-
return `${base} dsh's Plugins page
|
|
4479
|
+
return `${base} Try installing from dsh's Plugins page. If these scripts block the install there, use "Allow these scripts and retry" to approve them.`;
|
|
4340
4480
|
}
|
|
4341
4481
|
/** What a `cancelled` answer means, for every reader: the shop's deadline
|
|
4342
4482
|
* asked dsh to stop the operation, and dsh answers `cancelled` only once its
|
|
@@ -4349,7 +4489,7 @@ function desktopBuildsDetail(context, change, held) {
|
|
|
4349
4489
|
* update. */
|
|
4350
4490
|
function cancelledDetail(context) {
|
|
4351
4491
|
const seconds = Math.max(1, Math.round(context.timeoutMs / 1e3));
|
|
4352
|
-
return `dsh-plugin-shop: the ${context.operation} did not finish within ${seconds}s, so the shop cancelled it. dsh
|
|
4492
|
+
return `dsh-plugin-shop: the ${context.operation} did not finish within ${seconds}s, so the shop cancelled it. dsh restored the profile's package.json and pnpm-lock.yaml. Nothing was installed. Try the ${context.operation} again from the shop.`;
|
|
4353
4493
|
}
|
|
4354
4494
|
/** A `ChangeResult` as the shop's terminal install record: design
|
|
4355
4495
|
* 2026-09-26-plugin-manager-delegation, section 5, read by the first rule
|
|
@@ -4395,12 +4535,12 @@ function outcomeByRule(change, context) {
|
|
|
4395
4535
|
};
|
|
4396
4536
|
return {
|
|
4397
4537
|
state: "failed",
|
|
4398
|
-
detail: `dsh-plugin-shop: pnpm
|
|
4538
|
+
detail: `dsh-plugin-shop: pnpm blocked build scripts for ${held}. To review and allow them, run \`pnpm approve-builds\` in the profile directory, then ${context.operation} again.`
|
|
4399
4539
|
};
|
|
4400
4540
|
}
|
|
4401
4541
|
if (change.kind === "timeout" && change.failedAt === "spec-host") return {
|
|
4402
4542
|
state: "failed",
|
|
4403
|
-
detail: `dsh-plugin-shop: the ${context.operation}
|
|
4543
|
+
detail: `dsh-plugin-shop: the ${context.operation} stopped because the GitHub connection check timed out (5 s by default). pnpm has not run. Check your connection to GitHub and try again.`
|
|
4404
4544
|
};
|
|
4405
4545
|
const sentence = lookup(KIND_SENTENCE, change.kind);
|
|
4406
4546
|
if (sentence !== void 0) {
|
|
@@ -4433,7 +4573,7 @@ function outcomeByRule(change, context) {
|
|
|
4433
4573
|
activation: "restart"
|
|
4434
4574
|
};
|
|
4435
4575
|
if (change.application === "applied" || change.application === "overridden") {
|
|
4436
|
-
const note = change.application === "overridden" ? { detail: "Saved, but a
|
|
4576
|
+
const note = change.application === "overridden" ? { detail: "Saved, but a home or startup configuration overrides this setting and controls whether the plugin runs." } : {};
|
|
4437
4577
|
if (context.operation !== "uninstall" && context.alreadyImported && !(change.application === "applied" && context.reconcilesRunningPlugins)) return {
|
|
4438
4578
|
state: "done",
|
|
4439
4579
|
activation: "restart",
|
|
@@ -4452,7 +4592,7 @@ function outcomeByRule(change, context) {
|
|
|
4452
4592
|
}
|
|
4453
4593
|
return {
|
|
4454
4594
|
state: "failed",
|
|
4455
|
-
detail: `dsh-plugin-shop:
|
|
4595
|
+
detail: `dsh-plugin-shop: could not confirm the ${context.operation}. dsh returned an unrecognized status: "${change.application ?? "nothing"}".`
|
|
4456
4596
|
};
|
|
4457
4597
|
}
|
|
4458
4598
|
//#endregion
|
|
@@ -4565,7 +4705,7 @@ function startManagerOperation(options) {
|
|
|
4565
4705
|
} catch (error) {
|
|
4566
4706
|
settled = {
|
|
4567
4707
|
state: "failed",
|
|
4568
|
-
detail: `dsh-plugin-shop:
|
|
4708
|
+
detail: `dsh-plugin-shop: could not read the result from dsh: ${messageOf(error)}`
|
|
4569
4709
|
};
|
|
4570
4710
|
state = "failed";
|
|
4571
4711
|
return status();
|
|
@@ -4579,7 +4719,7 @@ function startManagerOperation(options) {
|
|
|
4579
4719
|
} catch (error) {
|
|
4580
4720
|
result = {
|
|
4581
4721
|
state: "failed",
|
|
4582
|
-
detail: `dsh-plugin-shop:
|
|
4722
|
+
detail: `dsh-plugin-shop: could not verify the installed plugin: ${messageOf(error)}`
|
|
4583
4723
|
};
|
|
4584
4724
|
}
|
|
4585
4725
|
settled = result;
|
|
@@ -4741,17 +4881,17 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
4741
4881
|
* service and to set an override that the platform check, being checked ahead
|
|
4742
4882
|
* of the systemd one, could never reach. */
|
|
4743
4883
|
const RESTART_BLOCKED_DETAIL = {
|
|
4744
|
-
desktop: "dsh-plugin-shop:
|
|
4745
|
-
"from-default-profile": "dsh-plugin-shop:
|
|
4746
|
-
windows: "dsh-plugin-shop:
|
|
4747
|
-
systemd: "dsh-plugin-shop:
|
|
4748
|
-
"port-zero": "dsh-plugin-shop:
|
|
4884
|
+
desktop: "dsh-plugin-shop: this profile is managed by the DeepSeek Harness desktop app. Restart the app to apply the change.",
|
|
4885
|
+
"from-default-profile": "dsh-plugin-shop: dsh was started with --from-default-profile. In dsh 0.2.1 and later, restarting with this flag recreates the profile from its template and deletes all installed plugins, including the shop. Restart dsh manually without this flag.",
|
|
4886
|
+
windows: "dsh-plugin-shop: restarting from the shop is not supported on Windows yet. Quit dsh and start it again to apply the change.",
|
|
4887
|
+
systemd: "dsh-plugin-shop: dsh is managed by systemd. Restart the service manually; restarting from the shop could stop the service without bringing it back. To allow restarts from the shop, set allowRestart: true in the shop configuration.",
|
|
4888
|
+
"port-zero": "dsh-plugin-shop: dsh was started with --port 0. Its port will change after a restart. Restart dsh manually and open the new address."
|
|
4749
4889
|
};
|
|
4750
4890
|
/** Why the shop changes nothing in the desktop profile, for every mutation
|
|
4751
4891
|
* that spawns dsh's CLI: install, uninstall and self-update. dsh refuses
|
|
4752
4892
|
* the profile before any of them would run (`isDesktopProfile`), and the
|
|
4753
4893
|
* failure that surfaced instead read "pnpm failed in the profile". */
|
|
4754
|
-
const DESKTOP_PROFILE_DETAIL = "dsh-plugin-shop:
|
|
4894
|
+
const DESKTOP_PROFILE_DETAIL = "dsh-plugin-shop: this profile is managed by the DeepSeek Harness desktop app. The shop cannot change it through the command line. Add or remove plugins from the app.";
|
|
4755
4895
|
/** Why a switch through dsh's `pluginManager` waits for an operation this
|
|
4756
4896
|
* gateway started: dsh holds its profile lock for a whole install or removal
|
|
4757
4897
|
* (`change()` wraps pnpm) and makes any other change wait for it up to its
|
|
@@ -4760,14 +4900,14 @@ const DESKTOP_PROFILE_DETAIL = "dsh-plugin-shop: the desktop profile is managed
|
|
|
4760
4900
|
* The shop's own row writer takes no dsh lock, so what it writes alone (the
|
|
4761
4901
|
* CLI path's switch, and switching a deselected package off) is not
|
|
4762
4902
|
* refused. */
|
|
4763
|
-
const switchWhileBusyDetail = (name) => `dsh-plugin-shop: an install, update or uninstall is still running in this profile
|
|
4903
|
+
const switchWhileBusyDetail = (name) => `dsh-plugin-shop: an install, update or uninstall is still running in this profile. Wait for it to finish before changing the enabled state of ${name}.`;
|
|
4764
4904
|
/** The line a record gains when dsh's plugin manager refuses an update in
|
|
4765
4905
|
* place and the shop continues it through dsh's CLI (design
|
|
4766
4906
|
* 2026-10-19-dsh-021-readiness §2): what dsh refused, and the command that
|
|
4767
4907
|
* runs instead. It names a `dsh plugin` command, so it never reaches a
|
|
4768
4908
|
* desktop reader: the continuation does not run in that profile. */
|
|
4769
4909
|
function cliFallbackLine(profile, spec) {
|
|
4770
|
-
return `dsh-plugin-shop: dsh's plugin manager
|
|
4910
|
+
return `dsh-plugin-shop: dsh's plugin manager cannot update this package directly (already-installed). Continuing with: \`dsh plugin --profile ${profile} add ${spec}\``;
|
|
4771
4911
|
}
|
|
4772
4912
|
/** How many bytes a release tarball may be at the integrity check. The
|
|
4773
4913
|
* registry already refuses to publish a tarball over 32 MiB, so 64 MiB is
|
|
@@ -4787,10 +4927,10 @@ async function verifyTarballSha256(fetchTarball, url, recordedSha256, maxBytes =
|
|
|
4787
4927
|
try {
|
|
4788
4928
|
response = await fetchTarball(url);
|
|
4789
4929
|
} catch (error) {
|
|
4790
|
-
return `dsh-plugin-shop: the release
|
|
4930
|
+
return `dsh-plugin-shop: could not download the release archive. Installation stopped. Network error: ${error.message}`;
|
|
4791
4931
|
}
|
|
4792
|
-
if (!response.ok) return `dsh-plugin-shop:
|
|
4793
|
-
if (response.body === null) return "dsh-plugin-shop: the release
|
|
4932
|
+
if (!response.ok) return `dsh-plugin-shop: could not download the release archive (HTTP ${response.status}). Installation stopped.`;
|
|
4933
|
+
if (response.body === null) return "dsh-plugin-shop: the release archive response was empty or unreadable. Installation stopped.";
|
|
4794
4934
|
const hash = createHash("sha256");
|
|
4795
4935
|
let bytes = 0;
|
|
4796
4936
|
const reader = response.body.getReader();
|
|
@@ -4803,14 +4943,14 @@ async function verifyTarballSha256(fetchTarball, url, recordedSha256, maxBytes =
|
|
|
4803
4943
|
try {
|
|
4804
4944
|
await reader.cancel();
|
|
4805
4945
|
} catch {}
|
|
4806
|
-
return `dsh-plugin-shop: the release
|
|
4946
|
+
return `dsh-plugin-shop: the release archive exceeds the size limit (${maxBytes} bytes). Installation stopped.`;
|
|
4807
4947
|
}
|
|
4808
4948
|
hash.update(value);
|
|
4809
4949
|
}
|
|
4810
4950
|
} catch (error) {
|
|
4811
|
-
return `dsh-plugin-shop: the release
|
|
4951
|
+
return `dsh-plugin-shop: the release archive download failed. Installation stopped. Error: ${error.message}`;
|
|
4812
4952
|
}
|
|
4813
|
-
if (hash.digest("hex") !== recordedSha256) return "dsh-plugin-shop: the release
|
|
4953
|
+
if (hash.digest("hex") !== recordedSha256) return "dsh-plugin-shop: the release archive does not match the catalog checksum (SHA-256). Installation stopped.";
|
|
4814
4954
|
return null;
|
|
4815
4955
|
}
|
|
4816
4956
|
/** An own-property read of a dependency map parsed from the profile manifest.
|
|
@@ -5381,7 +5521,7 @@ let ShopGateway = (() => {
|
|
|
5381
5521
|
async setEnabled(args) {
|
|
5382
5522
|
if (args.name === "dsh-plugin-shop" || args.name.startsWith("@deepseek-ai/")) return {
|
|
5383
5523
|
ok: false,
|
|
5384
|
-
detail: `dsh-plugin-shop: ${args.name} is part of
|
|
5524
|
+
detail: `dsh-plugin-shop: ${args.name} is part of dsh itself and cannot be enabled or disabled from the shop.`
|
|
5385
5525
|
};
|
|
5386
5526
|
const manager = this.pluginManager();
|
|
5387
5527
|
const profileDir = this.profileDirResolved();
|
|
@@ -5399,12 +5539,12 @@ let ShopGateway = (() => {
|
|
|
5399
5539
|
} catch (error) {
|
|
5400
5540
|
return {
|
|
5401
5541
|
ok: false,
|
|
5402
|
-
detail: `dsh-plugin-shop:
|
|
5542
|
+
detail: `dsh-plugin-shop: could not read the plugin configuration for ${args.name}: ${String(error)}`
|
|
5403
5543
|
};
|
|
5404
5544
|
}
|
|
5405
5545
|
if (owned.length === 0) return {
|
|
5406
5546
|
ok: false,
|
|
5407
|
-
detail: `dsh-plugin-shop: ${args.name}
|
|
5547
|
+
detail: `dsh-plugin-shop: ${args.name} has no plugin entries to enable or disable.`
|
|
5408
5548
|
};
|
|
5409
5549
|
const selected = this.runningProfileBundles(profileDir);
|
|
5410
5550
|
if (selected !== null && !selected.includes(args.name)) return this.setDeselectedEnabled(args.name, args.enabled, profileDir, owned, manager);
|
|
@@ -5412,7 +5552,7 @@ let ShopGateway = (() => {
|
|
|
5412
5552
|
const live = (await this.listInventory()).filter((entry) => ownsEntryId(ownedSet, entry.entryId));
|
|
5413
5553
|
if (live.length === 0) return {
|
|
5414
5554
|
ok: false,
|
|
5415
|
-
detail: `dsh-plugin-shop: ${args.name} is installed but
|
|
5555
|
+
detail: `dsh-plugin-shop: ${args.name} is installed but has not been loaded. Restart dsh to load it.`
|
|
5416
5556
|
};
|
|
5417
5557
|
if (manager !== null) {
|
|
5418
5558
|
if (this.hasRunningCommand()) return {
|
|
@@ -5508,7 +5648,7 @@ let ShopGateway = (() => {
|
|
|
5508
5648
|
}
|
|
5509
5649
|
if (manager === null) return {
|
|
5510
5650
|
ok: false,
|
|
5511
|
-
detail: `dsh-plugin-shop: ${name} is installed but
|
|
5651
|
+
detail: `dsh-plugin-shop: ${name} is installed but disabled. It is missing from dsh.profile.bundles in ${join(profileDir, "package.json")}, so restarting alone will not enable it. This version of dsh cannot enable the bundle from the shop. Add "${name}" back to that list and restart dsh, or uninstall it and install it again from the shop.`
|
|
5512
5652
|
};
|
|
5513
5653
|
if (this.hasRunningCommand()) return {
|
|
5514
5654
|
ok: false,
|
|
@@ -5520,7 +5660,7 @@ let ShopGateway = (() => {
|
|
|
5520
5660
|
} catch (error) {
|
|
5521
5661
|
return {
|
|
5522
5662
|
ok: false,
|
|
5523
|
-
detail: `dsh-plugin-shop: dsh could not
|
|
5663
|
+
detail: `dsh-plugin-shop: dsh could not enable ${name} again${thrownTail(messageOf(error), isDesktopProfile(this.profile))}`
|
|
5524
5664
|
};
|
|
5525
5665
|
}
|
|
5526
5666
|
const { change, failure } = this.readManagerAnswer(raw, {
|
|
@@ -5579,7 +5719,7 @@ let ShopGateway = (() => {
|
|
|
5579
5719
|
}
|
|
5580
5720
|
if (change.application === "overridden") return {
|
|
5581
5721
|
change,
|
|
5582
|
-
failure: `dsh-plugin-shop: dsh saved ${words.request}, but a home or
|
|
5722
|
+
failure: `dsh-plugin-shop: dsh saved ${words.request}, but a home or startup configuration overrides it. The plugin remains ${words.stays}.`
|
|
5583
5723
|
};
|
|
5584
5724
|
if (change.application === "applied" || change.application === "restart-required") return {
|
|
5585
5725
|
change,
|
|
@@ -5587,7 +5727,7 @@ let ShopGateway = (() => {
|
|
|
5587
5727
|
};
|
|
5588
5728
|
return {
|
|
5589
5729
|
change,
|
|
5590
|
-
failure: `dsh-plugin-shop:
|
|
5730
|
+
failure: `dsh-plugin-shop: could not confirm ${words.request}. dsh returned an unrecognized status: "${change.application ?? "nothing"}".`
|
|
5591
5731
|
};
|
|
5592
5732
|
}
|
|
5593
5733
|
rowConfig() {
|
|
@@ -5756,6 +5896,7 @@ let ShopGateway = (() => {
|
|
|
5756
5896
|
denied: snapshot.denied,
|
|
5757
5897
|
notAShop: snapshot.notAShop ?? [],
|
|
5758
5898
|
stars: snapshot.stars,
|
|
5899
|
+
icons: snapshot.icons ?? {},
|
|
5759
5900
|
incompatible,
|
|
5760
5901
|
incompatibleHarness
|
|
5761
5902
|
};
|
|
@@ -5915,14 +6056,14 @@ let ShopGateway = (() => {
|
|
|
5915
6056
|
packageName: name,
|
|
5916
6057
|
dependencies: Object.keys(this.profileDependenciesOrNone() ?? {})
|
|
5917
6058
|
});
|
|
5918
|
-
if (clash !== null) return `dsh-plugin-shop: ${name}
|
|
6059
|
+
if (clash !== null) return `dsh-plugin-shop: ${name} and ${clash.holder} use the same plugin entry ID, "${clash.id}". dsh cannot start this profile while both plugins are installed.` + undo;
|
|
5919
6060
|
switch (patchDeclarationHazard(declaredBundlePatch({
|
|
5920
6061
|
profileDir: this.profileDirResolved(),
|
|
5921
6062
|
packageName: name
|
|
5922
6063
|
}), harness.patchLists)) {
|
|
5923
6064
|
case null: return null;
|
|
5924
|
-
case "list-unsupported": return `dsh-plugin-shop: ${name}
|
|
5925
|
-
case "malformed": return `dsh-plugin-shop: ${name}
|
|
6065
|
+
case "list-unsupported": return `dsh-plugin-shop: ${name} uses multiple configuration files, which require dsh 0.1.7 or later. Your dsh${harness.dshVersion === null ? "" : ` (${harness.dshVersion})`} only supports one file and cannot start with this plugin installed.` + undo;
|
|
6066
|
+
case "malformed": return `dsh-plugin-shop: ${name} has an invalid bundle patch declaration; it must be a file path or a list of paths. dsh cannot start this profile with the plugin installed.` + undo;
|
|
5926
6067
|
}
|
|
5927
6068
|
}
|
|
5928
6069
|
/** Remember a github entry's pinned commit: the manifest records only
|
|
@@ -6030,7 +6171,7 @@ let ShopGateway = (() => {
|
|
|
6030
6171
|
found: false,
|
|
6031
6172
|
state: "failed",
|
|
6032
6173
|
log: [],
|
|
6033
|
-
detail: `
|
|
6174
|
+
detail: `The installation record (${args.installId}) is no longer available. Refresh the shop to check whether the plugin was installed.`
|
|
6034
6175
|
};
|
|
6035
6176
|
return {
|
|
6036
6177
|
found: true,
|
|
@@ -6238,7 +6379,7 @@ let ShopGateway = (() => {
|
|
|
6238
6379
|
if (this.committedRestart !== null) return this.committedRestart;
|
|
6239
6380
|
if (this.hasRunningCommand()) return {
|
|
6240
6381
|
ok: false,
|
|
6241
|
-
detail: "dsh-plugin-shop: an install is still running in this profile
|
|
6382
|
+
detail: "dsh-plugin-shop: an install is still running in this profile. Wait for it to finish before restarting so the configuration can be saved completely."
|
|
6242
6383
|
};
|
|
6243
6384
|
const blocked = this.staticRestartBlock();
|
|
6244
6385
|
if (blocked !== null) return {
|
|
@@ -6266,7 +6407,7 @@ let ShopGateway = (() => {
|
|
|
6266
6407
|
} catch (error) {
|
|
6267
6408
|
return {
|
|
6268
6409
|
ok: false,
|
|
6269
|
-
detail: `dsh-plugin-shop:
|
|
6410
|
+
detail: `dsh-plugin-shop: could not start the restart process: ${error.message}`
|
|
6270
6411
|
};
|
|
6271
6412
|
}
|
|
6272
6413
|
this.committedRestart = {
|