@deepseek-ai/dsh-plugin-manager 0.1.6-alpha.2 → 0.1.7-alpha.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/README.i18n.yaml +2 -2
- package/README.md +12 -2
- package/README.zh.md +12 -2
- package/lib/index.js +371 -74
- package/lib/typert.host.js +207 -33
- package/lib/typert.remote-client.d.ts +7 -3
- package/lib/typert.remote-client.js +159 -19
- package/lib/types/index.d.ts +30 -8
- package/lib/types/index.js +210 -70
- package/lib/types/install-failure.js +1 -1
- package/lib/types/install-spec.d.ts +7 -1
- package/lib/types/install-spec.js +16 -2
- package/lib/types/operations.d.ts +27 -3
- package/lib/types/operations.js +35 -5
- package/lib/types/registry.d.ts +47 -0
- package/lib/types/registry.js +95 -0
- package/lib/types/tools.js +5 -1
- package/lib/types/types.d.ts +46 -4
- package/package.json +37 -32
package/lib/index.js
CHANGED
|
@@ -2,11 +2,12 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { mkdir, mkdtemp, open, readFile, rm } from "node:fs/promises";
|
|
4
4
|
import { isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
6
7
|
import z from "@deepseek-ai/schemastery";
|
|
7
8
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
8
9
|
import { pluginEntryId, readPluginInventory } from "@deepseek-ai/dsh-host-plugin-inventory";
|
|
9
|
-
import { OPTIONAL_BUNDLES, composeEntries, loadOptionalPatches, loadOverlayPatches, readProfileManifest, readProfilePatches, reconcileProfilePatches, resolveBundleDir, resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
|
|
10
|
+
import { OPTIONAL_BUNDLES, bundlePatchPaths, composeEntries, loadOptionalPatches, loadOverlayPatches, readPluginMeta, readProfileManifest, readProfilePatches, reconcileProfilePatches, resolveBundleDir, resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
|
|
10
11
|
import { execa } from "execa";
|
|
11
12
|
import { scrubbedParentEnv } from "@deepseek-ai/dsh-subprocess";
|
|
12
13
|
import { isAlias, isMap, isNode, isScalar, isSeq, parseDocument, visit } from "yaml";
|
|
@@ -55,7 +56,7 @@ async function reconcile(before, dir, anchor, options) {
|
|
|
55
56
|
options.onOutput?.(`dsh: warning: ${name} declares no dsh.bundle — installed as a plain dependency, not a profile layer\n`, "stderr");
|
|
56
57
|
continue;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
for (const file of bundlePatchPaths(resolveBundleDir("dsh", name, anchor, dir), metadata.dsh.bundle)) loadOverlayPatches("dsh", file);
|
|
59
60
|
if (!bundles.includes(name)) bundles.push(name);
|
|
60
61
|
}
|
|
61
62
|
if (JSON.stringify(previous) === JSON.stringify(bundles)) return;
|
|
@@ -151,11 +152,49 @@ async function runProfilePnpm(context, args, options) {
|
|
|
151
152
|
};
|
|
152
153
|
}
|
|
153
154
|
/**
|
|
155
|
+
* Read the registry pnpm's own configuration names in the profile: its `.npmrc` chain and workspace settings,
|
|
156
|
+
* as `pnpm config get registry` resolves them.
|
|
157
|
+
* @param dir Profile directory.
|
|
158
|
+
* @param options The pnpm executable and the time bound.
|
|
159
|
+
* @returns The registry URL as pnpm printed it, or null when pnpm did not answer with one.
|
|
160
|
+
*/
|
|
161
|
+
async function readProfileRegistry(dir, options) {
|
|
162
|
+
const result = await execa(options.command ?? "pnpm", [
|
|
163
|
+
...options.args ?? [],
|
|
164
|
+
"config",
|
|
165
|
+
"get",
|
|
166
|
+
"registry"
|
|
167
|
+
], {
|
|
168
|
+
cwd: dir,
|
|
169
|
+
env: {
|
|
170
|
+
...scrubbedParentEnv(),
|
|
171
|
+
...options.env
|
|
172
|
+
},
|
|
173
|
+
extendEnv: false,
|
|
174
|
+
reject: false,
|
|
175
|
+
stdin: "ignore",
|
|
176
|
+
timeout: options.timeoutMs
|
|
177
|
+
});
|
|
178
|
+
const answer = result.exitCode === 0 ? result.stdout.trim().replace(/^[\s\S]*\n/, "").trim() : "";
|
|
179
|
+
return /^https?:\/\/\S+$/.test(answer) ? answer : null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The argument that sends one pnpm command to a registry.
|
|
183
|
+
* @param registry - the registry, or null for the one pnpm's own configuration names.
|
|
184
|
+
* @returns `--registry=<url>` for a URL; nothing for null.
|
|
185
|
+
*/
|
|
186
|
+
function registryArguments(registry) {
|
|
187
|
+
return registry === null ? [] : [`--registry=${registry}`];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
154
190
|
* Ask the registry what a spec names through `pnpm view`, run in the profile
|
|
155
|
-
* directory so the registry, proxy, and authentication settings of an install
|
|
191
|
+
* directory so the registry, proxy, and authentication settings of an install
|
|
192
|
+
* apply. The lookup makes one request without pnpm's own retries: a registry
|
|
193
|
+
* that does not answer is reported within `timeoutMs`, and the registries
|
|
194
|
+
* configured after it are the retry.
|
|
156
195
|
* @param dir Profile directory.
|
|
157
196
|
* @param spec One registry spec: a package name with an optional range.
|
|
158
|
-
* @param options
|
|
197
|
+
* @param options The registry, cancellation, and the time bound.
|
|
159
198
|
* @returns pnpm's exit, output, and how the lookup ended.
|
|
160
199
|
*/
|
|
161
200
|
async function viewProfilePackage(dir, spec, options) {
|
|
@@ -167,7 +206,9 @@ async function viewProfilePackage(dir, spec, options) {
|
|
|
167
206
|
"version",
|
|
168
207
|
"description",
|
|
169
208
|
"dsh",
|
|
170
|
-
"--json"
|
|
209
|
+
"--json",
|
|
210
|
+
...registryArguments(options.registry ?? null),
|
|
211
|
+
"--config.fetch-retries=0"
|
|
171
212
|
], {
|
|
172
213
|
cwd: dir,
|
|
173
214
|
env: {
|
|
@@ -205,7 +246,7 @@ const LOG_KINDS = [
|
|
|
205
246
|
["disk-full", /\bENOSPC\b|no space left on device/i],
|
|
206
247
|
["permission", /\bEACCES\b|\bEPERM\b|permission denied/i],
|
|
207
248
|
["integrity", /ERR_PNPM_TARBALL_INTEGRITY|ERR_PNPM_BAD_TARBALL_SIZE|\bEINTEGRITY\b/],
|
|
208
|
-
["network", /\bENOTFOUND\b|\bECONNRESET\b|\bETIMEDOUT\b|\bECONNREFUSED\b|\bEAI_AGAIN\b|ERR_PNPM_META_FETCH_FAIL|ERR_PNPM_FETCH_5\d\d|ERR_PNPM_FETCH_TIMEOUT|socket hang up|Could not resolve host|unable to access/]
|
|
249
|
+
["network", /\bENOTFOUND\b|\bECONNRESET\b|\bETIMEDOUT\b|\bECONNREFUSED\b|\bEAI_AGAIN\b|ERR_PNPM_META_FETCH_FAIL|ERR_PNPM_FETCH_5\d\d|ERR_PNPM_FETCH_TIMEOUT|\bFETCH_ERROR\b|socket hang up|Could not resolve host|unable to access/]
|
|
209
250
|
];
|
|
210
251
|
/**
|
|
211
252
|
* Classify a failed run.
|
|
@@ -229,6 +270,13 @@ function classifyInstallFailure(facts) {
|
|
|
229
270
|
const GIT_SHORTHAND = /^(?:github|gitlab|bitbucket|gist):/i;
|
|
230
271
|
const GIT_URL = /^git(?:\+[a-z]+)?:\/\/|^git@[^:]+:/i;
|
|
231
272
|
const HOSTED_REPOSITORY_URL = /^https?:\/\/[^/]+\/[^/]+\/[^/#]+(?:\.git)?(?:#.*)?$/i;
|
|
273
|
+
/** The hosts pnpm's shorthands stand for. */
|
|
274
|
+
const GIT_SHORTHAND_HOSTS = {
|
|
275
|
+
github: "github.com",
|
|
276
|
+
gitlab: "gitlab.com",
|
|
277
|
+
bitbucket: "bitbucket.org",
|
|
278
|
+
gist: "gist.github.com"
|
|
279
|
+
};
|
|
232
280
|
/** A tarball, on disk or over HTTP. */
|
|
233
281
|
const TARBALL_SPEC = /\.(?:tgz|tar\.gz)(?:#.*)?$/i;
|
|
234
282
|
/** An npm package name: lowercase URL-safe segments, an optional scope, no leading dot or underscore. */
|
|
@@ -252,6 +300,14 @@ var InvalidInstallSpecError = class extends Error {
|
|
|
252
300
|
function invalid(spec, reason) {
|
|
253
301
|
return new InvalidInstallSpecError(spec, reason);
|
|
254
302
|
}
|
|
303
|
+
/** The host a git spec is cloned from: the shorthand's host, the scp-like user@host, or the URL's host with its port. */
|
|
304
|
+
function gitHost(spec) {
|
|
305
|
+
const shorthand = /^([a-z]+):/i.exec(spec)?.[1]?.toLowerCase();
|
|
306
|
+
if (shorthand !== void 0 && Object.hasOwn(GIT_SHORTHAND_HOSTS, shorthand)) return GIT_SHORTHAND_HOSTS[shorthand];
|
|
307
|
+
const scp = /^git@([^:]+):/i.exec(spec)?.[1];
|
|
308
|
+
if (scp !== void 0) return scp.toLowerCase();
|
|
309
|
+
return new URL(spec.replace(/^git\+/i, "")).host;
|
|
310
|
+
}
|
|
255
311
|
/**
|
|
256
312
|
* Read a spec into its form. A path must be absolute: the Host's working
|
|
257
313
|
* directory means nothing to the person typing into a browser, and a
|
|
@@ -280,12 +336,14 @@ function parseInstallSpec(raw) {
|
|
|
280
336
|
if (/^\.{1,2}(?:[\\/]|$)/.test(spec)) throw invalid(spec, "a local path must be absolute");
|
|
281
337
|
if ((GIT_SHORTHAND.test(spec) || GIT_URL.test(spec) || HOSTED_REPOSITORY_URL.test(spec)) && !TARBALL_SPEC.test(spec)) return {
|
|
282
338
|
kind: "git",
|
|
283
|
-
spec
|
|
339
|
+
spec,
|
|
340
|
+
host: gitHost(spec)
|
|
284
341
|
};
|
|
285
342
|
if (/^https?:\/\//i.test(spec)) {
|
|
286
343
|
if (TARBALL_SPEC.test(spec)) return {
|
|
287
344
|
kind: "tarball",
|
|
288
|
-
spec
|
|
345
|
+
spec,
|
|
346
|
+
host: new URL(spec).host
|
|
289
347
|
};
|
|
290
348
|
throw invalid(spec, "a URL must point at a git repository or a tarball");
|
|
291
349
|
}
|
|
@@ -305,6 +363,80 @@ function parseInstallSpec(raw) {
|
|
|
305
363
|
range
|
|
306
364
|
};
|
|
307
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Parse a registry URL into the form pnpm compares registries in: lower-case host, trailing slash.
|
|
368
|
+
* @param url - the registry as configured or requested.
|
|
369
|
+
* @returns the normalized URL.
|
|
370
|
+
* @throws {Error} for anything but an http(s) URL.
|
|
371
|
+
*/
|
|
372
|
+
function normalizeRegistry(url) {
|
|
373
|
+
let parsed;
|
|
374
|
+
try {
|
|
375
|
+
parsed = new URL(url);
|
|
376
|
+
} catch {}
|
|
377
|
+
if (parsed === void 0 || parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`a registry must be an http(s) URL: ${url}`);
|
|
378
|
+
if (!parsed.pathname.endsWith("/")) parsed.pathname += "/";
|
|
379
|
+
return parsed.href;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* The registries one operation asks, first to last.
|
|
383
|
+
*
|
|
384
|
+
* The configured set is the configured first registry and the fallbacks; a
|
|
385
|
+
* requested registry is asked first when it is one of them, alone otherwise,
|
|
386
|
+
* so a private registry never falls through to a public one. pnpm's own
|
|
387
|
+
* registry (`null`) belongs to the set only when what it names is known to
|
|
388
|
+
* be public: npm's own registry or one of the configured fallbacks. While it
|
|
389
|
+
* names anything else, or is unknown, it is asked alone, and a public
|
|
390
|
+
* registry asked instead never falls back into it. A registry pnpm's own
|
|
391
|
+
* configuration already names is asked once.
|
|
392
|
+
* @param requested - the caller's registry; undefined defers to the configured first one.
|
|
393
|
+
* @param configured - the configured first registry, the fallbacks after it, and what pnpm's own configuration names.
|
|
394
|
+
* @returns the registries to ask, in order; never empty.
|
|
395
|
+
*/
|
|
396
|
+
function registryPlan(requested, configured) {
|
|
397
|
+
const own = configured.resolved === null ? null : normalizeRegistry(configured.resolved);
|
|
398
|
+
const fallbacks = configured.fallbackRegistries.map(normalizeRegistry);
|
|
399
|
+
const ownIsPublic = own !== null && (own === "https://registry.npmjs.org/" || fallbacks.includes(own));
|
|
400
|
+
const keyOf = (registry) => registry === null ? own : normalizeRegistry(registry);
|
|
401
|
+
const known = [];
|
|
402
|
+
const keys = [];
|
|
403
|
+
for (const registry of [configured.registry, ...configured.fallbackRegistries]) {
|
|
404
|
+
if (registry === null && !ownIsPublic) continue;
|
|
405
|
+
const key = keyOf(registry);
|
|
406
|
+
if (keys.includes(key)) continue;
|
|
407
|
+
known.push(registry === null ? null : normalizeRegistry(registry));
|
|
408
|
+
keys.push(key);
|
|
409
|
+
}
|
|
410
|
+
const first = requested === void 0 ? configured.registry : requested;
|
|
411
|
+
const firstKey = keyOf(first);
|
|
412
|
+
const normalizedFirst = first === null ? null : normalizeRegistry(first);
|
|
413
|
+
if ((first === null || firstKey === own) && !ownIsPublic) return [normalizedFirst];
|
|
414
|
+
if (!keys.includes(firstKey)) return [normalizedFirst];
|
|
415
|
+
return [normalizedFirst, ...known.filter((_registry, index) => keys[index] !== firstKey)];
|
|
416
|
+
}
|
|
417
|
+
/** The failures after which another registry can answer differently: this one was unreachable, or its copy may be stale. */
|
|
418
|
+
const NEXT_REGISTRY_KINDS = new Set([
|
|
419
|
+
"network",
|
|
420
|
+
"timeout",
|
|
421
|
+
"not-found",
|
|
422
|
+
"no-matching-version"
|
|
423
|
+
]);
|
|
424
|
+
/** A line of pnpm or git output that reports a failure, as opposed to a warning or progress line. */
|
|
425
|
+
const ERROR_LINE = /ERR_|ERROR|\berror\b|fatal:|Could not resolve|unable to access|ssh:|\bE[A-Z]{4,}\b/;
|
|
426
|
+
/**
|
|
427
|
+
* What a failed attempt could not reach or get an answer from.
|
|
428
|
+
* @param kind - how the attempt failed.
|
|
429
|
+
* @param log - what the attempt printed.
|
|
430
|
+
* @param spec - the spec the attempt installed.
|
|
431
|
+
* @returns `registry` for a failure another registry can change; `spec-host` when an error line names the host a git
|
|
432
|
+
* or tarball spec is fetched from, which no registry stands in for; `other` for a failure neither explains.
|
|
433
|
+
*/
|
|
434
|
+
function attributeFailure(kind, log, spec) {
|
|
435
|
+
if (!NEXT_REGISTRY_KINDS.has(kind)) return "other";
|
|
436
|
+
const host = spec.kind === "git" || spec.kind === "tarball" ? spec.host?.toLowerCase() : void 0;
|
|
437
|
+
if (host === void 0) return "registry";
|
|
438
|
+
return log.split("\n").some((line) => ERROR_LINE.test(line) && line.toLowerCase().includes(host)) ? "spec-host" : "registry";
|
|
439
|
+
}
|
|
308
440
|
//#endregion
|
|
309
441
|
//#region lib/types/patch.js
|
|
310
442
|
/** Comment-preserving profile plugin enablement edits. */
|
|
@@ -443,6 +575,8 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
443
575
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
444
576
|
done = true;
|
|
445
577
|
};
|
|
578
|
+
/** An http(s) URL, as pnpm's `--registry` takes it. */
|
|
579
|
+
const REGISTRY_URL = /^https?:\/\/\S+$/;
|
|
446
580
|
const protectedModules = new Set([
|
|
447
581
|
"@deepseek-ai/dsh-plugin-manager",
|
|
448
582
|
"@deepseek-ai/cordis-plugin-loader",
|
|
@@ -493,7 +627,7 @@ function stringField(manifest, field) {
|
|
|
493
627
|
return typeof value === "string" ? value : void 0;
|
|
494
628
|
}
|
|
495
629
|
/** What a package manifest says about the package: identity, one-liner, and whether it is a bundle. */
|
|
496
|
-
function inspectionOf(kind, manifest) {
|
|
630
|
+
function inspectionOf(kind, manifest, registry) {
|
|
497
631
|
const dsh = manifest.dsh;
|
|
498
632
|
const declared = typeof dsh === "object" && dsh !== null ? dsh : void 0;
|
|
499
633
|
const bundle = declared !== void 0 && typeof declared.bundle === "object" && declared.bundle !== null;
|
|
@@ -504,6 +638,7 @@ function inspectionOf(kind, manifest) {
|
|
|
504
638
|
status: "accepted",
|
|
505
639
|
kind,
|
|
506
640
|
bundle,
|
|
641
|
+
registry,
|
|
507
642
|
...name === void 0 ? {} : { name },
|
|
508
643
|
...version === void 0 ? {} : { version },
|
|
509
644
|
...description === void 0 || description === "" ? {} : { description }
|
|
@@ -516,16 +651,45 @@ function refused(problem, reason) {
|
|
|
516
651
|
reason
|
|
517
652
|
};
|
|
518
653
|
}
|
|
654
|
+
/** The refusal `pnpm view --json` prints on stdout, `{ error: { code, message } }`, as one log line; empty for anything else. */
|
|
655
|
+
function printedError(printed) {
|
|
656
|
+
let parsed;
|
|
657
|
+
try {
|
|
658
|
+
parsed = JSON.parse(printed || "null");
|
|
659
|
+
} catch {
|
|
660
|
+
return "";
|
|
661
|
+
}
|
|
662
|
+
const error = typeof parsed === "object" && parsed !== null ? parsed.error : void 0;
|
|
663
|
+
if (typeof error !== "object" || error === null) return "";
|
|
664
|
+
const { code, message } = error;
|
|
665
|
+
return [code, message].filter((part) => typeof part === "string").join(" ");
|
|
666
|
+
}
|
|
667
|
+
/** The spec's form, for deciding whether a failed attempt was the registry's; a form the parser refuses has no host of its own. */
|
|
668
|
+
function parsedForRegistry(spec) {
|
|
669
|
+
try {
|
|
670
|
+
return parseInstallSpec(spec);
|
|
671
|
+
} catch (error) {
|
|
672
|
+
/* v8 ignore next -- parseInstallSpec throws nothing but its own refusal */
|
|
673
|
+
if (!(error instanceof InvalidInstallSpecError)) throw error;
|
|
674
|
+
return {
|
|
675
|
+
kind: "registry",
|
|
676
|
+
spec,
|
|
677
|
+
name: spec
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
519
681
|
/** Manage profile files and apply their declared reload lifecycle. */
|
|
520
682
|
let PluginManager = (() => {
|
|
521
683
|
let _classSuper = TypertRemoteService;
|
|
522
684
|
let _instanceExtraInitializers = [];
|
|
523
685
|
let _listPlugins_decorators;
|
|
524
686
|
let _listBundles_decorators;
|
|
687
|
+
let _registries_decorators;
|
|
525
688
|
let _inspect_decorators;
|
|
526
689
|
let _setPluginEnabled_decorators;
|
|
527
690
|
let _setBundleEnabled_decorators;
|
|
528
691
|
let _installBundle_decorators;
|
|
692
|
+
let _waitForInstall_decorators;
|
|
529
693
|
let _cancelInstall_decorators;
|
|
530
694
|
let _removeBundle_decorators;
|
|
531
695
|
return class PluginManager extends _classSuper {
|
|
@@ -533,10 +697,12 @@ let PluginManager = (() => {
|
|
|
533
697
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
534
698
|
_listPlugins_decorators = [Remote];
|
|
535
699
|
_listBundles_decorators = [Remote];
|
|
700
|
+
_registries_decorators = [Remote];
|
|
536
701
|
_inspect_decorators = [Remote];
|
|
537
702
|
_setPluginEnabled_decorators = [Remote];
|
|
538
703
|
_setBundleEnabled_decorators = [Remote];
|
|
539
704
|
_installBundle_decorators = [Remote];
|
|
705
|
+
_waitForInstall_decorators = [Remote];
|
|
540
706
|
_cancelInstall_decorators = [Remote];
|
|
541
707
|
_removeBundle_decorators = [Remote];
|
|
542
708
|
__esDecorate(this, null, _listPlugins_decorators, {
|
|
@@ -561,6 +727,17 @@ let PluginManager = (() => {
|
|
|
561
727
|
},
|
|
562
728
|
metadata: _metadata
|
|
563
729
|
}, null, _instanceExtraInitializers);
|
|
730
|
+
__esDecorate(this, null, _registries_decorators, {
|
|
731
|
+
kind: "method",
|
|
732
|
+
name: "registries",
|
|
733
|
+
static: false,
|
|
734
|
+
private: false,
|
|
735
|
+
access: {
|
|
736
|
+
has: (obj) => "registries" in obj,
|
|
737
|
+
get: (obj) => obj.registries
|
|
738
|
+
},
|
|
739
|
+
metadata: _metadata
|
|
740
|
+
}, null, _instanceExtraInitializers);
|
|
564
741
|
__esDecorate(this, null, _inspect_decorators, {
|
|
565
742
|
kind: "method",
|
|
566
743
|
name: "inspect",
|
|
@@ -605,6 +782,17 @@ let PluginManager = (() => {
|
|
|
605
782
|
},
|
|
606
783
|
metadata: _metadata
|
|
607
784
|
}, null, _instanceExtraInitializers);
|
|
785
|
+
__esDecorate(this, null, _waitForInstall_decorators, {
|
|
786
|
+
kind: "method",
|
|
787
|
+
name: "waitForInstall",
|
|
788
|
+
static: false,
|
|
789
|
+
private: false,
|
|
790
|
+
access: {
|
|
791
|
+
has: (obj) => "waitForInstall" in obj,
|
|
792
|
+
get: (obj) => obj.waitForInstall
|
|
793
|
+
},
|
|
794
|
+
metadata: _metadata
|
|
795
|
+
}, null, _instanceExtraInitializers);
|
|
608
796
|
__esDecorate(this, null, _cancelInstall_decorators, {
|
|
609
797
|
kind: "method",
|
|
610
798
|
name: "cancelInstall",
|
|
@@ -639,15 +827,20 @@ let PluginManager = (() => {
|
|
|
639
827
|
pnpmCommand: z.string().default("pnpm"),
|
|
640
828
|
outputBytes: z.number().step(1).min(1).default(16384),
|
|
641
829
|
lockWaitMs: z.number().step(1).min(0).default(12e4),
|
|
642
|
-
inspectTimeoutMs: z.number().step(1).min(1e3).default(2e4)
|
|
830
|
+
inspectTimeoutMs: z.number().step(1).min(1e3).default(2e4),
|
|
831
|
+
registry: z.string().pattern(REGISTRY_URL),
|
|
832
|
+
fallbackRegistries: z.array(z.string().pattern(REGISTRY_URL)).default(["https://registry.npmmirror.com/"])
|
|
643
833
|
});
|
|
644
|
-
|
|
834
|
+
/** Management bundles remain protected if their files become unreadable. */
|
|
835
|
+
managementBundles = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Set());
|
|
836
|
+
ownerEntryId;
|
|
645
837
|
packageOperations = /* @__PURE__ */ new Set();
|
|
646
838
|
profile;
|
|
647
839
|
outputBytes;
|
|
648
840
|
lockWaitMs;
|
|
649
841
|
inspectTimeoutMs;
|
|
650
842
|
pnpmCommand;
|
|
843
|
+
configuredRegistries;
|
|
651
844
|
ownerContext;
|
|
652
845
|
abort = new AbortController();
|
|
653
846
|
/** Installations by request id, from their call until it settles. */
|
|
@@ -657,10 +850,15 @@ let PluginManager = (() => {
|
|
|
657
850
|
this.ownerEntryId = ctx.fiber.entry?.id;
|
|
658
851
|
this.ownerContext = ctx;
|
|
659
852
|
this.profile = ctx.profileContext;
|
|
853
|
+
for (const name of this.profile.startedBundles) this.protectsManager(name);
|
|
660
854
|
this.outputBytes = config.outputBytes;
|
|
661
855
|
this.lockWaitMs = config.lockWaitMs;
|
|
662
856
|
this.inspectTimeoutMs = config.inspectTimeoutMs;
|
|
663
857
|
this.pnpmCommand = config.pnpmCommand;
|
|
858
|
+
this.configuredRegistries = {
|
|
859
|
+
registry: config.registry === void 0 ? null : normalizeRegistry(config.registry),
|
|
860
|
+
fallbackRegistries: config.fallbackRegistries.map(normalizeRegistry)
|
|
861
|
+
};
|
|
664
862
|
ctx.effect(() => async () => {
|
|
665
863
|
this.abort.abort();
|
|
666
864
|
await Promise.allSettled([...this.packageOperations]);
|
|
@@ -691,8 +889,8 @@ let PluginManager = (() => {
|
|
|
691
889
|
}
|
|
692
890
|
/** Read the profile's installed bundles, the bundles this dsh installation supplies, and the selected names that are not bundles.
|
|
693
891
|
* A dependency without a bundle patch is listed, as a `not-bundle` problem, only while it is selected.
|
|
694
|
-
* @returns Package versions,
|
|
695
|
-
* bundle, and removal availability.
|
|
892
|
+
* @returns Package versions, manifest descriptions, rows, optional display metadata, activation selections,
|
|
893
|
+
* whether the installation offers the bundle, and removal availability.
|
|
696
894
|
*/
|
|
697
895
|
listBundles() {
|
|
698
896
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
@@ -710,6 +908,7 @@ let PluginManager = (() => {
|
|
|
710
908
|
const optional = OPTIONAL_BUNDLES.includes(name);
|
|
711
909
|
const removable = installed && !Object.hasOwn(installation.dependencies ?? {}, name);
|
|
712
910
|
const enabled = selected.includes(name);
|
|
911
|
+
const readOnlyReason = this.protectsManager(name) ? "management-required" : void 0;
|
|
713
912
|
try {
|
|
714
913
|
const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
715
914
|
if (info === void 0) {
|
|
@@ -718,18 +917,21 @@ let PluginManager = (() => {
|
|
|
718
917
|
enabled,
|
|
719
918
|
installed,
|
|
720
919
|
optional,
|
|
721
|
-
removable,
|
|
920
|
+
removable: removable && readOnlyReason === void 0,
|
|
921
|
+
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
722
922
|
error: { code: "not-bundle" },
|
|
723
923
|
rows: [],
|
|
724
924
|
overrides: []
|
|
725
925
|
});
|
|
726
926
|
continue;
|
|
727
927
|
}
|
|
728
|
-
const
|
|
928
|
+
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
929
|
+
const meta = readPluginMeta(info.name ?? name, pathToFileURL(join(dir, "package.json")).href);
|
|
729
930
|
bundles.push({
|
|
730
931
|
name,
|
|
731
932
|
...info.version === void 0 ? {} : { version: info.version },
|
|
732
933
|
...info.description === void 0 || info.description === "" ? {} : { description: info.description },
|
|
934
|
+
...meta === void 0 ? {} : { meta },
|
|
733
935
|
enabled,
|
|
734
936
|
installed,
|
|
735
937
|
optional,
|
|
@@ -743,7 +945,8 @@ let PluginManager = (() => {
|
|
|
743
945
|
enabled,
|
|
744
946
|
installed,
|
|
745
947
|
optional,
|
|
746
|
-
removable,
|
|
948
|
+
removable: removable && readOnlyReason === void 0,
|
|
949
|
+
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
747
950
|
error: managementError(error),
|
|
748
951
|
rows: [],
|
|
749
952
|
overrides: []
|
|
@@ -752,12 +955,26 @@ let PluginManager = (() => {
|
|
|
752
955
|
}
|
|
753
956
|
return Promise.resolve(bundles);
|
|
754
957
|
}
|
|
958
|
+
/** Read the registries this manager asks: the configured first one, its fallbacks in order, and what pnpm's own configuration names.
|
|
959
|
+
* @returns The registries in pnpm's comparison form; null is the one pnpm's own configuration names, `resolved` as pnpm reads it now.
|
|
960
|
+
*/
|
|
961
|
+
async registries() {
|
|
962
|
+
return {
|
|
963
|
+
...this.configuredRegistries,
|
|
964
|
+
fallbackRegistries: [...this.configuredRegistries.fallbackRegistries],
|
|
965
|
+
resolved: await readProfileRegistry(this.profile.dir, {
|
|
966
|
+
...this.profile.packageManager ?? { command: this.pnpmCommand },
|
|
967
|
+
timeoutMs: this.inspectTimeoutMs
|
|
968
|
+
})
|
|
969
|
+
};
|
|
970
|
+
}
|
|
755
971
|
/** Read what a spec names before installing it.
|
|
756
972
|
* @param spec One package spec: a registry name, an absolute path, a git address, or a tarball.
|
|
973
|
+
* @param options The registry asked first.
|
|
757
974
|
* @param signal Ends a registry lookup early.
|
|
758
975
|
* @returns The package the spec names, or why it is refused.
|
|
759
976
|
*/
|
|
760
|
-
async inspect(spec, signal) {
|
|
977
|
+
async inspect(spec, options, signal) {
|
|
761
978
|
let parsed;
|
|
762
979
|
try {
|
|
763
980
|
parsed = parseInstallSpec(spec);
|
|
@@ -773,18 +990,24 @@ let PluginManager = (() => {
|
|
|
773
990
|
...Object.keys(manifest.dependencies ?? {}),
|
|
774
991
|
...Object.keys(installation.dependencies ?? {})
|
|
775
992
|
]);
|
|
993
|
+
const plan = registryPlan(options?.registry, await this.registries());
|
|
994
|
+
const registry = plan[0];
|
|
776
995
|
switch (parsed.kind) {
|
|
777
996
|
case "git": return {
|
|
778
997
|
status: "accepted",
|
|
779
998
|
kind: "git",
|
|
780
|
-
bundle: null
|
|
999
|
+
bundle: null,
|
|
1000
|
+
registry,
|
|
1001
|
+
host: parsed.host
|
|
781
1002
|
};
|
|
782
1003
|
case "tarball":
|
|
783
1004
|
if (parsed.path !== void 0 && !existsSync(parsed.path)) return refused("not-a-package", "the tarball does not exist");
|
|
784
1005
|
return {
|
|
785
1006
|
status: "accepted",
|
|
786
1007
|
kind: "tarball",
|
|
787
|
-
bundle: null
|
|
1008
|
+
bundle: null,
|
|
1009
|
+
registry,
|
|
1010
|
+
...parsed.host === void 0 ? {} : { host: parsed.host }
|
|
788
1011
|
};
|
|
789
1012
|
case "path": {
|
|
790
1013
|
if (!existsSync(parsed.path)) return refused("not-a-package", "the path does not exist");
|
|
@@ -794,7 +1017,7 @@ let PluginManager = (() => {
|
|
|
794
1017
|
} catch (error) {
|
|
795
1018
|
return refused("not-a-package", `no readable package.json at the path: ${messageOf(error)}`);
|
|
796
1019
|
}
|
|
797
|
-
const inspection = inspectionOf("path", read);
|
|
1020
|
+
const inspection = inspectionOf("path", read, registry);
|
|
798
1021
|
if (inspection.name === void 0) return refused("not-a-package", "the package.json names no package");
|
|
799
1022
|
if (known.has(inspection.name)) return refused("already-installed", `${inspection.name} is already installed`);
|
|
800
1023
|
if (!inspection.bundle) return refused("not-a-bundle", `${inspection.name} declares no dsh.bundle`);
|
|
@@ -802,38 +1025,57 @@ let PluginManager = (() => {
|
|
|
802
1025
|
}
|
|
803
1026
|
case "registry": {
|
|
804
1027
|
if (known.has(parsed.name)) return refused("already-installed", `${parsed.name} is already installed`);
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1028
|
+
const registries = [];
|
|
1029
|
+
const refusedBy = (problem, reason) => ({
|
|
1030
|
+
status: "refused",
|
|
1031
|
+
problem,
|
|
1032
|
+
reason,
|
|
1033
|
+
registries
|
|
809
1034
|
});
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
...
|
|
1035
|
+
for (const current of plan) {
|
|
1036
|
+
registries.push(current);
|
|
1037
|
+
const view = await viewProfilePackage(this.profile.dir, spec.trim(), {
|
|
1038
|
+
...this.profile.packageManager ?? { command: this.pnpmCommand },
|
|
1039
|
+
timeoutMs: this.inspectTimeoutMs,
|
|
1040
|
+
...signal === void 0 ? {} : { signal },
|
|
1041
|
+
registry: current
|
|
816
1042
|
});
|
|
817
|
-
const
|
|
818
|
-
if (
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1043
|
+
const printed = view.stdout.replace(ANSI_SEQUENCE, "").trim();
|
|
1044
|
+
if (view.exitCode !== 0 || view.cause !== void 0 || view.timedOut) {
|
|
1045
|
+
const log = [
|
|
1046
|
+
view.stderr.trim(),
|
|
1047
|
+
printedError(printed),
|
|
1048
|
+
view.cause === void 0 ? "" : messageOf(view.cause)
|
|
1049
|
+
].filter(Boolean).join("\n");
|
|
1050
|
+
const kind = classifyInstallFailure({
|
|
1051
|
+
log,
|
|
1052
|
+
timedOut: view.timedOut,
|
|
1053
|
+
...view.cause === void 0 ? {} : { cause: view.cause }
|
|
1054
|
+
});
|
|
1055
|
+
if (registries.length < plan.length && signal?.aborted !== true && attributeFailure(kind, log, parsed) === "registry") continue;
|
|
1056
|
+
const reason = view.timedOut ? `pnpm view timed out after ${String(this.inspectTimeoutMs)}ms` : log || printed || `pnpm view exited with ${String(view.exitCode)}`;
|
|
1057
|
+
if (kind === "not-found" || kind === "no-matching-version") return refusedBy("not-found", reason);
|
|
1058
|
+
if (kind === "network" || kind === "timeout") return refusedBy("network", reason);
|
|
1059
|
+
return refusedBy("unknown", reason);
|
|
1060
|
+
}
|
|
1061
|
+
let answer;
|
|
1062
|
+
try {
|
|
1063
|
+
answer = JSON.parse(printed || "null");
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
return refusedBy("unknown", `unreadable pnpm view output: ${messageOf(error)}`);
|
|
1066
|
+
}
|
|
1067
|
+
const latest = Array.isArray(answer) ? answer.at(-1) : answer;
|
|
1068
|
+
if (typeof latest !== "object" || latest === null) return refusedBy("unknown", "pnpm view answered no package");
|
|
1069
|
+
const inspection = inspectionOf("registry", latest, current);
|
|
1070
|
+
const named = inspection.name === void 0 ? {
|
|
1071
|
+
...inspection,
|
|
1072
|
+
name: parsed.name
|
|
1073
|
+
} : inspection;
|
|
1074
|
+
if (!named.bundle) return refusedBy("not-a-bundle", `${named.name} declares no dsh.bundle`);
|
|
1075
|
+
return named;
|
|
827
1076
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
const inspection = inspectionOf("registry", latest);
|
|
831
|
-
const named = inspection.name === void 0 ? {
|
|
832
|
-
...inspection,
|
|
833
|
-
name: parsed.name
|
|
834
|
-
} : inspection;
|
|
835
|
-
if (!named.bundle) return refused("not-a-bundle", `${named.name} declares no dsh.bundle`);
|
|
836
|
-
return named;
|
|
1077
|
+
/* v8 ignore next -- the plan is never empty: every attempt returns or continues to the next */
|
|
1078
|
+
throw new Error("no registry was asked");
|
|
837
1079
|
}
|
|
838
1080
|
}
|
|
839
1081
|
}
|
|
@@ -876,23 +1118,24 @@ let PluginManager = (() => {
|
|
|
876
1118
|
* that fails, is cancelled, or adds a package without a bundle patch restores
|
|
877
1119
|
* `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay.
|
|
878
1120
|
* @param spec One package spec, including local paths relative to the invocation directory.
|
|
879
|
-
* @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names,
|
|
880
|
-
* the pending build scripts to allow for this profile before pnpm runs.
|
|
881
|
-
* @returns Package-manager diagnostics and observed activation outcome.
|
|
1121
|
+
* @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names,
|
|
1122
|
+
* the pending build scripts to allow for this profile before pnpm runs, and the registry asked first.
|
|
1123
|
+
* @returns Package-manager diagnostics, the registries asked, and the observed activation outcome.
|
|
882
1124
|
*/
|
|
883
1125
|
installBundle(spec, options) {
|
|
884
1126
|
const requestId = options?.requestId;
|
|
885
1127
|
const control = {
|
|
886
1128
|
abort: new AbortController(),
|
|
887
1129
|
phase: "installing",
|
|
888
|
-
|
|
1130
|
+
result: Promise.resolve(null)
|
|
889
1131
|
};
|
|
890
1132
|
const stopped = () => control.abort.signal.aborted;
|
|
891
1133
|
if (requestId !== void 0) this.installs.set(requestId, control);
|
|
892
|
-
const announce = (phase) => {
|
|
1134
|
+
const announce = (phase, attempt) => {
|
|
893
1135
|
if (requestId !== void 0) this.ownerContext.emit("plugin-manager/install-state", {
|
|
894
1136
|
requestId,
|
|
895
|
-
phase
|
|
1137
|
+
phase,
|
|
1138
|
+
...attempt === void 0 ? {} : { attempt }
|
|
896
1139
|
});
|
|
897
1140
|
};
|
|
898
1141
|
const result = this.change(async (result) => {
|
|
@@ -904,18 +1147,44 @@ let PluginManager = (() => {
|
|
|
904
1147
|
}
|
|
905
1148
|
const files = await this.readRestoredFiles();
|
|
906
1149
|
const before = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
907
|
-
|
|
1150
|
+
const plan = registryPlan(options?.registry, await this.registries());
|
|
908
1151
|
let name;
|
|
909
1152
|
try {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1153
|
+
let run;
|
|
1154
|
+
result.registries = [];
|
|
1155
|
+
for (const [index, registry] of plan.entries()) {
|
|
1156
|
+
if (index > 0) await this.restoreFiles(files);
|
|
1157
|
+
if (stopped()) throw new InstallCancelledError();
|
|
1158
|
+
result.registries.push(registry);
|
|
1159
|
+
announce("installing", {
|
|
1160
|
+
registry,
|
|
1161
|
+
index: index + 1,
|
|
1162
|
+
total: plan.length
|
|
1163
|
+
});
|
|
1164
|
+
run = await this.runPnpm([
|
|
1165
|
+
"add",
|
|
1166
|
+
spec,
|
|
1167
|
+
...registryArguments(registry)
|
|
1168
|
+
], control.abort.signal, requestId);
|
|
1169
|
+
result.packageResult = run;
|
|
1170
|
+
if (stopped()) throw new InstallCancelledError();
|
|
1171
|
+
/* v8 ignore next 2 -- runPnpm classifies every failed run, so kind is never absent here */
|
|
1172
|
+
if (run.exitCode === 0 || run.kind === void 0) break;
|
|
1173
|
+
const failedAt = attributeFailure(run.kind, run.output, parsedForRegistry(spec));
|
|
1174
|
+
delete result.failedAt;
|
|
1175
|
+
if (failedAt !== "other") result.failedAt = failedAt;
|
|
1176
|
+
if (failedAt !== "registry" || index === plan.length - 1) break;
|
|
1177
|
+
}
|
|
1178
|
+
/* v8 ignore next -- the plan is never empty, so a run always settled */
|
|
1179
|
+
if (run === void 0) throw new Error("no registry was asked");
|
|
1180
|
+
if (run.exitCode === 0) delete result.failedAt;
|
|
1181
|
+
if (run.exitCode !== 0) {
|
|
913
1182
|
try {
|
|
914
1183
|
result.pendingBuilds = await readPendingBuilds(this.profile.dir);
|
|
915
1184
|
} catch (error) {
|
|
916
1185
|
this.ownerContext.logger.warn("Could not read pending build approvals after pnpm failed", error);
|
|
917
1186
|
}
|
|
918
|
-
throw new Error(
|
|
1187
|
+
throw new Error(run.output);
|
|
919
1188
|
}
|
|
920
1189
|
const after = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
921
1190
|
const installed = Object.keys(after).filter((name) => before[name] !== after[name]);
|
|
@@ -925,8 +1194,8 @@ let PluginManager = (() => {
|
|
|
925
1194
|
name = target;
|
|
926
1195
|
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
927
1196
|
const manifest = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
928
|
-
if (manifest?.dsh?.bundle
|
|
929
|
-
|
|
1197
|
+
if (manifest?.dsh?.bundle === void 0) throw new ManagementFailure("not-bundle");
|
|
1198
|
+
for (const file of bundlePatchPaths(dir, manifest.dsh.bundle)) loadOverlayPatches("dsh", file);
|
|
930
1199
|
} catch (error) {
|
|
931
1200
|
await this.restoreFiles(files);
|
|
932
1201
|
throw error;
|
|
@@ -946,12 +1215,19 @@ let PluginManager = (() => {
|
|
|
946
1215
|
target: spec,
|
|
947
1216
|
enabled: options?.enabled !== false
|
|
948
1217
|
}, "install");
|
|
949
|
-
|
|
950
|
-
control.settled = result.then(() => void 0, () => void 0);
|
|
1218
|
+
control.result = result;
|
|
951
1219
|
return result.finally(() => {
|
|
952
1220
|
if (requestId !== void 0) this.installs.delete(requestId);
|
|
953
1221
|
});
|
|
954
1222
|
}
|
|
1223
|
+
/** Recover the result of an active installation without cancelling it.
|
|
1224
|
+
* @param requestId The id supplied when installation started.
|
|
1225
|
+
* @returns The installation's outcome after it settles, or null if no active request has that id.
|
|
1226
|
+
* Completed results are not retained; null establishes neither success nor cancellation.
|
|
1227
|
+
*/
|
|
1228
|
+
async waitForInstall(requestId) {
|
|
1229
|
+
return this.installs.get(requestId)?.result ?? null;
|
|
1230
|
+
}
|
|
955
1231
|
/** Stop an installation this manager owns and wait until its files are back.
|
|
956
1232
|
* @param requestId The id the installation was started with.
|
|
957
1233
|
* @returns `cancelled` once pnpm exited and the files are restored, `too-late` once the bundle is being
|
|
@@ -966,7 +1242,8 @@ let PluginManager = (() => {
|
|
|
966
1242
|
phase: "cancelling"
|
|
967
1243
|
});
|
|
968
1244
|
control.abort.abort();
|
|
969
|
-
|
|
1245
|
+
/* v8 ignore next -- change() folds every failure into its result; only a lock or disposal error rejects */
|
|
1246
|
+
await control.result.then(() => void 0, () => void 0);
|
|
970
1247
|
return { status: "cancelled" };
|
|
971
1248
|
}
|
|
972
1249
|
/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path.
|
|
@@ -978,7 +1255,7 @@ let PluginManager = (() => {
|
|
|
978
1255
|
await this.configure(async () => {
|
|
979
1256
|
const bundle = (await this.listBundles()).find((item) => item.name === name);
|
|
980
1257
|
if (bundle === void 0 || !bundle.removable) throw new ManagementFailure("not-removable");
|
|
981
|
-
if (this.ownerContext.get("hmr") === void 0 && (this.profile.startedBundles.includes(name) || this.bundleRows(name).some((row) => [...this.ctx.loader.entries()].some((entry) => entry.options.id === row.id && entry.fiber !== void 0)))) throw new ManagementFailure("stop-profile");
|
|
1258
|
+
if (this.ownerContext.get("hmr") === void 0 && (this.profile.startedBundles.includes(name) || bundle.error === void 0 && this.bundleRows(name).some((row) => [...this.ctx.loader.entries()].some((entry) => entry.options.id === row.id && entry.fiber !== void 0)))) throw new ManagementFailure("stop-profile");
|
|
982
1259
|
const contributions = bundle.error === void 0 ? this.bundleRows(name) : [];
|
|
983
1260
|
if (bundle.enabled) {
|
|
984
1261
|
await this.selectBundle(name, false);
|
|
@@ -995,25 +1272,34 @@ let PluginManager = (() => {
|
|
|
995
1272
|
}
|
|
996
1273
|
/** The rows a bundle's patch inserts and the existing rows it changes; an unreadable patch throws. */
|
|
997
1274
|
declaredRows(name, info) {
|
|
998
|
-
const
|
|
1275
|
+
const bundle = info.dsh?.bundle;
|
|
999
1276
|
/* v8 ignore next -- bundleManifest answers only manifests that declare a patch */
|
|
1000
|
-
if (
|
|
1277
|
+
if (bundle === void 0) return {
|
|
1001
1278
|
rows: [],
|
|
1002
1279
|
overrides: []
|
|
1003
1280
|
};
|
|
1004
|
-
const
|
|
1281
|
+
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
1282
|
+
const patches = bundlePatchPaths(dir, bundle).flatMap((file) => loadOverlayPatches("dsh", file));
|
|
1005
1283
|
const live = /* @__PURE__ */ new Map();
|
|
1006
1284
|
for (const entry of this.ctx.loader.entries())
|
|
1007
1285
|
/* v8 ignore next -- the Loader gives every entry an id before it is listed */
|
|
1008
|
-
if (typeof entry.options.id === "string") live.set(entry.options.id,
|
|
1286
|
+
if (typeof entry.options.id === "string") live.set(entry.options.id, {
|
|
1287
|
+
entryId: pluginEntryId(entry.id),
|
|
1288
|
+
baseUrl: entry.parent.tree.ctx.baseUrl
|
|
1289
|
+
});
|
|
1009
1290
|
const rows = [];
|
|
1291
|
+
const packages = this.ctx.get("pluginPackages");
|
|
1010
1292
|
for (const row of flatten(composeEntries([patches.filter((item) => item.insert !== void 0)]))) {
|
|
1011
1293
|
if (typeof row.id !== "string" || typeof row.name !== "string") continue;
|
|
1012
|
-
const
|
|
1294
|
+
const active = live.get(row.id);
|
|
1295
|
+
const entryId = active?.entryId;
|
|
1296
|
+
const base = active?.baseUrl ?? pathToFileURL(join(dir, "package.json")).href;
|
|
1297
|
+
const meta = packages?.metaOf(row.name, base);
|
|
1013
1298
|
rows.push({
|
|
1014
1299
|
rowId: row.id,
|
|
1015
1300
|
moduleName: row.name,
|
|
1016
|
-
...entryId === void 0 ? {} : { entryId }
|
|
1301
|
+
...entryId === void 0 ? {} : { entryId },
|
|
1302
|
+
...meta === void 0 ? {} : { meta }
|
|
1017
1303
|
});
|
|
1018
1304
|
}
|
|
1019
1305
|
const declared = new Set(rows.map((row) => row.rowId));
|
|
@@ -1097,6 +1383,7 @@ let PluginManager = (() => {
|
|
|
1097
1383
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
1098
1384
|
const previous = manifest.dsh?.profile?.bundles ?? [];
|
|
1099
1385
|
if ((enabled || !previous.includes(name)) && bundleManifest(name, this.profile.dir, this.profile.installAnchor) === void 0) throw new ManagementFailure("not-bundle");
|
|
1386
|
+
if (enabled) this.bundleRows(name);
|
|
1100
1387
|
if (!enabled && previous.includes(name)) {
|
|
1101
1388
|
if (this.protectsManager(name)) throw new ManagementFailure("management-required");
|
|
1102
1389
|
}
|
|
@@ -1110,14 +1397,24 @@ let PluginManager = (() => {
|
|
|
1110
1397
|
}
|
|
1111
1398
|
};
|
|
1112
1399
|
await saveManifest(this.profile.dir, manifest);
|
|
1400
|
+
if (enabled) this.protectsManager(name);
|
|
1113
1401
|
}
|
|
1114
1402
|
bundleRows(name) {
|
|
1115
1403
|
const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
1116
1404
|
if (info?.dsh?.bundle === void 0) return [];
|
|
1117
|
-
return flatten(composeEntries([
|
|
1405
|
+
return flatten(composeEntries([bundlePatchPaths(resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir), info.dsh.bundle).flatMap((file) => loadOverlayPatches("dsh", file))]));
|
|
1118
1406
|
}
|
|
1119
1407
|
protectsManager(name) {
|
|
1120
|
-
|
|
1408
|
+
if (this.managementBundles.has(name)) return true;
|
|
1409
|
+
let rows;
|
|
1410
|
+
try {
|
|
1411
|
+
rows = this.bundleRows(name);
|
|
1412
|
+
} catch (_error) {
|
|
1413
|
+
return false;
|
|
1414
|
+
}
|
|
1415
|
+
const protectedBundle = rows.some((row) => protectedModules.has(row.name) || `include:${row.id}` === this.ownerEntryId);
|
|
1416
|
+
if (protectedBundle) this.managementBundles.add(name);
|
|
1417
|
+
return protectedBundle;
|
|
1121
1418
|
}
|
|
1122
1419
|
configure(operation) {
|
|
1123
1420
|
const hmr = this.ownerContext.get("hmr");
|