@deepseek-ai/dsh-plugin-manager 0.1.6-alpha.2 → 0.1.7-alpha.2
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 +14 -2
- package/README.zh.md +14 -2
- package/lib/index.js +373 -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 +49 -0
- package/lib/types/registry.js +97 -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,82 @@ function parseInstallSpec(raw) {
|
|
|
305
363
|
range
|
|
306
364
|
};
|
|
307
365
|
}
|
|
366
|
+
/** Public npmmirror URL shared by the fallback configuration and public-registry comparison. */
|
|
367
|
+
const NPMMIRROR_REGISTRY = "https://registry.npmmirror.com/";
|
|
368
|
+
/**
|
|
369
|
+
* Parse a registry URL into the form pnpm compares registries in: lower-case host, trailing slash.
|
|
370
|
+
* @param url - the registry as configured or requested.
|
|
371
|
+
* @returns the normalized URL.
|
|
372
|
+
* @throws {Error} for anything but an http(s) URL.
|
|
373
|
+
*/
|
|
374
|
+
function normalizeRegistry(url) {
|
|
375
|
+
let parsed;
|
|
376
|
+
try {
|
|
377
|
+
parsed = new URL(url);
|
|
378
|
+
} catch {}
|
|
379
|
+
if (parsed === void 0 || parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error(`a registry must be an http(s) URL: ${url}`);
|
|
380
|
+
if (!parsed.pathname.endsWith("/")) parsed.pathname += "/";
|
|
381
|
+
return parsed.href;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* The registries one operation asks, first to last.
|
|
385
|
+
*
|
|
386
|
+
* The configured set is the configured first registry and the fallbacks; a
|
|
387
|
+
* requested registry is asked first when it is one of them, alone otherwise,
|
|
388
|
+
* so a private registry never falls through to a public one. pnpm's own
|
|
389
|
+
* registry (`null`) belongs to the set only when what it names is known to
|
|
390
|
+
* be public: npm's own registry or one of the configured fallbacks. While it
|
|
391
|
+
* names anything else, or is unknown, it is asked alone, and a public
|
|
392
|
+
* registry asked instead never falls back into it. A registry pnpm's own
|
|
393
|
+
* configuration already names is asked once.
|
|
394
|
+
* @param requested - the caller's registry; undefined defers to the configured first one.
|
|
395
|
+
* @param configured - the configured first registry, the fallbacks after it, and what pnpm's own configuration names.
|
|
396
|
+
* @returns the registries to ask, in order; never empty.
|
|
397
|
+
*/
|
|
398
|
+
function registryPlan(requested, configured) {
|
|
399
|
+
const own = configured.resolved === null ? null : normalizeRegistry(configured.resolved);
|
|
400
|
+
const fallbacks = configured.fallbackRegistries.map(normalizeRegistry);
|
|
401
|
+
const ownIsPublic = own !== null && (own === "https://registry.npmjs.org/" || fallbacks.includes(own));
|
|
402
|
+
const keyOf = (registry) => registry === null ? own : normalizeRegistry(registry);
|
|
403
|
+
const known = [];
|
|
404
|
+
const keys = [];
|
|
405
|
+
for (const registry of [configured.registry, ...configured.fallbackRegistries]) {
|
|
406
|
+
if (registry === null && !ownIsPublic) continue;
|
|
407
|
+
const key = keyOf(registry);
|
|
408
|
+
if (keys.includes(key)) continue;
|
|
409
|
+
known.push(registry === null ? null : normalizeRegistry(registry));
|
|
410
|
+
keys.push(key);
|
|
411
|
+
}
|
|
412
|
+
const first = requested === void 0 ? configured.registry : requested;
|
|
413
|
+
const firstKey = keyOf(first);
|
|
414
|
+
const normalizedFirst = first === null ? null : normalizeRegistry(first);
|
|
415
|
+
if ((first === null || firstKey === own) && !ownIsPublic) return [normalizedFirst];
|
|
416
|
+
if (!keys.includes(firstKey)) return [normalizedFirst];
|
|
417
|
+
return [normalizedFirst, ...known.filter((_registry, index) => keys[index] !== firstKey)];
|
|
418
|
+
}
|
|
419
|
+
/** The failures after which another registry can answer differently: this one was unreachable, or its copy may be stale. */
|
|
420
|
+
const NEXT_REGISTRY_KINDS = new Set([
|
|
421
|
+
"network",
|
|
422
|
+
"timeout",
|
|
423
|
+
"not-found",
|
|
424
|
+
"no-matching-version"
|
|
425
|
+
]);
|
|
426
|
+
/** A line of pnpm or git output that reports a failure, as opposed to a warning or progress line. */
|
|
427
|
+
const ERROR_LINE = /ERR_|ERROR|\berror\b|fatal:|Could not resolve|unable to access|ssh:|\bE[A-Z]{4,}\b/;
|
|
428
|
+
/**
|
|
429
|
+
* What a failed attempt could not reach or get an answer from.
|
|
430
|
+
* @param kind - how the attempt failed.
|
|
431
|
+
* @param log - what the attempt printed.
|
|
432
|
+
* @param spec - the spec the attempt installed.
|
|
433
|
+
* @returns `registry` for a failure another registry can change; `spec-host` when an error line names the host a git
|
|
434
|
+
* or tarball spec is fetched from, which no registry stands in for; `other` for a failure neither explains.
|
|
435
|
+
*/
|
|
436
|
+
function attributeFailure(kind, log, spec) {
|
|
437
|
+
if (!NEXT_REGISTRY_KINDS.has(kind)) return "other";
|
|
438
|
+
const host = spec.kind === "git" || spec.kind === "tarball" ? spec.host?.toLowerCase() : void 0;
|
|
439
|
+
if (host === void 0) return "registry";
|
|
440
|
+
return log.split("\n").some((line) => ERROR_LINE.test(line) && line.toLowerCase().includes(host)) ? "spec-host" : "registry";
|
|
441
|
+
}
|
|
308
442
|
//#endregion
|
|
309
443
|
//#region lib/types/patch.js
|
|
310
444
|
/** Comment-preserving profile plugin enablement edits. */
|
|
@@ -443,6 +577,8 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
443
577
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
444
578
|
done = true;
|
|
445
579
|
};
|
|
580
|
+
/** An http(s) URL, as pnpm's `--registry` takes it. */
|
|
581
|
+
const REGISTRY_URL = /^https?:\/\/\S+$/;
|
|
446
582
|
const protectedModules = new Set([
|
|
447
583
|
"@deepseek-ai/dsh-plugin-manager",
|
|
448
584
|
"@deepseek-ai/cordis-plugin-loader",
|
|
@@ -493,7 +629,7 @@ function stringField(manifest, field) {
|
|
|
493
629
|
return typeof value === "string" ? value : void 0;
|
|
494
630
|
}
|
|
495
631
|
/** What a package manifest says about the package: identity, one-liner, and whether it is a bundle. */
|
|
496
|
-
function inspectionOf(kind, manifest) {
|
|
632
|
+
function inspectionOf(kind, manifest, registry) {
|
|
497
633
|
const dsh = manifest.dsh;
|
|
498
634
|
const declared = typeof dsh === "object" && dsh !== null ? dsh : void 0;
|
|
499
635
|
const bundle = declared !== void 0 && typeof declared.bundle === "object" && declared.bundle !== null;
|
|
@@ -504,6 +640,7 @@ function inspectionOf(kind, manifest) {
|
|
|
504
640
|
status: "accepted",
|
|
505
641
|
kind,
|
|
506
642
|
bundle,
|
|
643
|
+
registry,
|
|
507
644
|
...name === void 0 ? {} : { name },
|
|
508
645
|
...version === void 0 ? {} : { version },
|
|
509
646
|
...description === void 0 || description === "" ? {} : { description }
|
|
@@ -516,16 +653,45 @@ function refused(problem, reason) {
|
|
|
516
653
|
reason
|
|
517
654
|
};
|
|
518
655
|
}
|
|
656
|
+
/** The refusal `pnpm view --json` prints on stdout, `{ error: { code, message } }`, as one log line; empty for anything else. */
|
|
657
|
+
function printedError(printed) {
|
|
658
|
+
let parsed;
|
|
659
|
+
try {
|
|
660
|
+
parsed = JSON.parse(printed || "null");
|
|
661
|
+
} catch {
|
|
662
|
+
return "";
|
|
663
|
+
}
|
|
664
|
+
const error = typeof parsed === "object" && parsed !== null ? parsed.error : void 0;
|
|
665
|
+
if (typeof error !== "object" || error === null) return "";
|
|
666
|
+
const { code, message } = error;
|
|
667
|
+
return [code, message].filter((part) => typeof part === "string").join(" ");
|
|
668
|
+
}
|
|
669
|
+
/** 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. */
|
|
670
|
+
function parsedForRegistry(spec) {
|
|
671
|
+
try {
|
|
672
|
+
return parseInstallSpec(spec);
|
|
673
|
+
} catch (error) {
|
|
674
|
+
/* v8 ignore next -- parseInstallSpec throws nothing but its own refusal */
|
|
675
|
+
if (!(error instanceof InvalidInstallSpecError)) throw error;
|
|
676
|
+
return {
|
|
677
|
+
kind: "registry",
|
|
678
|
+
spec,
|
|
679
|
+
name: spec
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
}
|
|
519
683
|
/** Manage profile files and apply their declared reload lifecycle. */
|
|
520
684
|
let PluginManager = (() => {
|
|
521
685
|
let _classSuper = TypertRemoteService;
|
|
522
686
|
let _instanceExtraInitializers = [];
|
|
523
687
|
let _listPlugins_decorators;
|
|
524
688
|
let _listBundles_decorators;
|
|
689
|
+
let _registries_decorators;
|
|
525
690
|
let _inspect_decorators;
|
|
526
691
|
let _setPluginEnabled_decorators;
|
|
527
692
|
let _setBundleEnabled_decorators;
|
|
528
693
|
let _installBundle_decorators;
|
|
694
|
+
let _waitForInstall_decorators;
|
|
529
695
|
let _cancelInstall_decorators;
|
|
530
696
|
let _removeBundle_decorators;
|
|
531
697
|
return class PluginManager extends _classSuper {
|
|
@@ -533,10 +699,12 @@ let PluginManager = (() => {
|
|
|
533
699
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
534
700
|
_listPlugins_decorators = [Remote];
|
|
535
701
|
_listBundles_decorators = [Remote];
|
|
702
|
+
_registries_decorators = [Remote];
|
|
536
703
|
_inspect_decorators = [Remote];
|
|
537
704
|
_setPluginEnabled_decorators = [Remote];
|
|
538
705
|
_setBundleEnabled_decorators = [Remote];
|
|
539
706
|
_installBundle_decorators = [Remote];
|
|
707
|
+
_waitForInstall_decorators = [Remote];
|
|
540
708
|
_cancelInstall_decorators = [Remote];
|
|
541
709
|
_removeBundle_decorators = [Remote];
|
|
542
710
|
__esDecorate(this, null, _listPlugins_decorators, {
|
|
@@ -561,6 +729,17 @@ let PluginManager = (() => {
|
|
|
561
729
|
},
|
|
562
730
|
metadata: _metadata
|
|
563
731
|
}, null, _instanceExtraInitializers);
|
|
732
|
+
__esDecorate(this, null, _registries_decorators, {
|
|
733
|
+
kind: "method",
|
|
734
|
+
name: "registries",
|
|
735
|
+
static: false,
|
|
736
|
+
private: false,
|
|
737
|
+
access: {
|
|
738
|
+
has: (obj) => "registries" in obj,
|
|
739
|
+
get: (obj) => obj.registries
|
|
740
|
+
},
|
|
741
|
+
metadata: _metadata
|
|
742
|
+
}, null, _instanceExtraInitializers);
|
|
564
743
|
__esDecorate(this, null, _inspect_decorators, {
|
|
565
744
|
kind: "method",
|
|
566
745
|
name: "inspect",
|
|
@@ -605,6 +784,17 @@ let PluginManager = (() => {
|
|
|
605
784
|
},
|
|
606
785
|
metadata: _metadata
|
|
607
786
|
}, null, _instanceExtraInitializers);
|
|
787
|
+
__esDecorate(this, null, _waitForInstall_decorators, {
|
|
788
|
+
kind: "method",
|
|
789
|
+
name: "waitForInstall",
|
|
790
|
+
static: false,
|
|
791
|
+
private: false,
|
|
792
|
+
access: {
|
|
793
|
+
has: (obj) => "waitForInstall" in obj,
|
|
794
|
+
get: (obj) => obj.waitForInstall
|
|
795
|
+
},
|
|
796
|
+
metadata: _metadata
|
|
797
|
+
}, null, _instanceExtraInitializers);
|
|
608
798
|
__esDecorate(this, null, _cancelInstall_decorators, {
|
|
609
799
|
kind: "method",
|
|
610
800
|
name: "cancelInstall",
|
|
@@ -639,15 +829,20 @@ let PluginManager = (() => {
|
|
|
639
829
|
pnpmCommand: z.string().default("pnpm"),
|
|
640
830
|
outputBytes: z.number().step(1).min(1).default(16384),
|
|
641
831
|
lockWaitMs: z.number().step(1).min(0).default(12e4),
|
|
642
|
-
inspectTimeoutMs: z.number().step(1).min(1e3).default(2e4)
|
|
832
|
+
inspectTimeoutMs: z.number().step(1).min(1e3).default(2e4),
|
|
833
|
+
registry: z.string().pattern(REGISTRY_URL),
|
|
834
|
+
fallbackRegistries: z.array(z.string().pattern(REGISTRY_URL)).default([NPMMIRROR_REGISTRY])
|
|
643
835
|
});
|
|
644
|
-
|
|
836
|
+
/** Management bundles remain protected if their files become unreadable. */
|
|
837
|
+
managementBundles = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Set());
|
|
838
|
+
ownerEntryId;
|
|
645
839
|
packageOperations = /* @__PURE__ */ new Set();
|
|
646
840
|
profile;
|
|
647
841
|
outputBytes;
|
|
648
842
|
lockWaitMs;
|
|
649
843
|
inspectTimeoutMs;
|
|
650
844
|
pnpmCommand;
|
|
845
|
+
configuredRegistries;
|
|
651
846
|
ownerContext;
|
|
652
847
|
abort = new AbortController();
|
|
653
848
|
/** Installations by request id, from their call until it settles. */
|
|
@@ -657,10 +852,15 @@ let PluginManager = (() => {
|
|
|
657
852
|
this.ownerEntryId = ctx.fiber.entry?.id;
|
|
658
853
|
this.ownerContext = ctx;
|
|
659
854
|
this.profile = ctx.profileContext;
|
|
855
|
+
for (const name of this.profile.startedBundles) this.protectsManager(name);
|
|
660
856
|
this.outputBytes = config.outputBytes;
|
|
661
857
|
this.lockWaitMs = config.lockWaitMs;
|
|
662
858
|
this.inspectTimeoutMs = config.inspectTimeoutMs;
|
|
663
859
|
this.pnpmCommand = config.pnpmCommand;
|
|
860
|
+
this.configuredRegistries = {
|
|
861
|
+
registry: config.registry === void 0 ? null : normalizeRegistry(config.registry),
|
|
862
|
+
fallbackRegistries: config.fallbackRegistries.map(normalizeRegistry)
|
|
863
|
+
};
|
|
664
864
|
ctx.effect(() => async () => {
|
|
665
865
|
this.abort.abort();
|
|
666
866
|
await Promise.allSettled([...this.packageOperations]);
|
|
@@ -691,8 +891,8 @@ let PluginManager = (() => {
|
|
|
691
891
|
}
|
|
692
892
|
/** Read the profile's installed bundles, the bundles this dsh installation supplies, and the selected names that are not bundles.
|
|
693
893
|
* 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.
|
|
894
|
+
* @returns Package versions, manifest descriptions, rows, optional display metadata, activation selections,
|
|
895
|
+
* whether the installation offers the bundle, and removal availability.
|
|
696
896
|
*/
|
|
697
897
|
listBundles() {
|
|
698
898
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
@@ -710,6 +910,7 @@ let PluginManager = (() => {
|
|
|
710
910
|
const optional = OPTIONAL_BUNDLES.includes(name);
|
|
711
911
|
const removable = installed && !Object.hasOwn(installation.dependencies ?? {}, name);
|
|
712
912
|
const enabled = selected.includes(name);
|
|
913
|
+
const readOnlyReason = this.protectsManager(name) ? "management-required" : void 0;
|
|
713
914
|
try {
|
|
714
915
|
const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
715
916
|
if (info === void 0) {
|
|
@@ -718,18 +919,21 @@ let PluginManager = (() => {
|
|
|
718
919
|
enabled,
|
|
719
920
|
installed,
|
|
720
921
|
optional,
|
|
721
|
-
removable,
|
|
922
|
+
removable: removable && readOnlyReason === void 0,
|
|
923
|
+
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
722
924
|
error: { code: "not-bundle" },
|
|
723
925
|
rows: [],
|
|
724
926
|
overrides: []
|
|
725
927
|
});
|
|
726
928
|
continue;
|
|
727
929
|
}
|
|
728
|
-
const
|
|
930
|
+
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
931
|
+
const meta = readPluginMeta(info.name ?? name, pathToFileURL(join(dir, "package.json")).href);
|
|
729
932
|
bundles.push({
|
|
730
933
|
name,
|
|
731
934
|
...info.version === void 0 ? {} : { version: info.version },
|
|
732
935
|
...info.description === void 0 || info.description === "" ? {} : { description: info.description },
|
|
936
|
+
...meta === void 0 ? {} : { meta },
|
|
733
937
|
enabled,
|
|
734
938
|
installed,
|
|
735
939
|
optional,
|
|
@@ -743,7 +947,8 @@ let PluginManager = (() => {
|
|
|
743
947
|
enabled,
|
|
744
948
|
installed,
|
|
745
949
|
optional,
|
|
746
|
-
removable,
|
|
950
|
+
removable: removable && readOnlyReason === void 0,
|
|
951
|
+
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
747
952
|
error: managementError(error),
|
|
748
953
|
rows: [],
|
|
749
954
|
overrides: []
|
|
@@ -752,12 +957,26 @@ let PluginManager = (() => {
|
|
|
752
957
|
}
|
|
753
958
|
return Promise.resolve(bundles);
|
|
754
959
|
}
|
|
960
|
+
/** Read the registries this manager asks: the configured first one, its fallbacks in order, and what pnpm's own configuration names.
|
|
961
|
+
* @returns The registries in pnpm's comparison form; null is the one pnpm's own configuration names, `resolved` as pnpm reads it now.
|
|
962
|
+
*/
|
|
963
|
+
async registries() {
|
|
964
|
+
return {
|
|
965
|
+
...this.configuredRegistries,
|
|
966
|
+
fallbackRegistries: [...this.configuredRegistries.fallbackRegistries],
|
|
967
|
+
resolved: await readProfileRegistry(this.profile.dir, {
|
|
968
|
+
...this.profile.packageManager ?? { command: this.pnpmCommand },
|
|
969
|
+
timeoutMs: this.inspectTimeoutMs
|
|
970
|
+
})
|
|
971
|
+
};
|
|
972
|
+
}
|
|
755
973
|
/** Read what a spec names before installing it.
|
|
756
974
|
* @param spec One package spec: a registry name, an absolute path, a git address, or a tarball.
|
|
975
|
+
* @param options The registry asked first.
|
|
757
976
|
* @param signal Ends a registry lookup early.
|
|
758
977
|
* @returns The package the spec names, or why it is refused.
|
|
759
978
|
*/
|
|
760
|
-
async inspect(spec, signal) {
|
|
979
|
+
async inspect(spec, options, signal) {
|
|
761
980
|
let parsed;
|
|
762
981
|
try {
|
|
763
982
|
parsed = parseInstallSpec(spec);
|
|
@@ -773,18 +992,24 @@ let PluginManager = (() => {
|
|
|
773
992
|
...Object.keys(manifest.dependencies ?? {}),
|
|
774
993
|
...Object.keys(installation.dependencies ?? {})
|
|
775
994
|
]);
|
|
995
|
+
const plan = registryPlan(options?.registry, await this.registries());
|
|
996
|
+
const registry = plan[0];
|
|
776
997
|
switch (parsed.kind) {
|
|
777
998
|
case "git": return {
|
|
778
999
|
status: "accepted",
|
|
779
1000
|
kind: "git",
|
|
780
|
-
bundle: null
|
|
1001
|
+
bundle: null,
|
|
1002
|
+
registry,
|
|
1003
|
+
host: parsed.host
|
|
781
1004
|
};
|
|
782
1005
|
case "tarball":
|
|
783
1006
|
if (parsed.path !== void 0 && !existsSync(parsed.path)) return refused("not-a-package", "the tarball does not exist");
|
|
784
1007
|
return {
|
|
785
1008
|
status: "accepted",
|
|
786
1009
|
kind: "tarball",
|
|
787
|
-
bundle: null
|
|
1010
|
+
bundle: null,
|
|
1011
|
+
registry,
|
|
1012
|
+
...parsed.host === void 0 ? {} : { host: parsed.host }
|
|
788
1013
|
};
|
|
789
1014
|
case "path": {
|
|
790
1015
|
if (!existsSync(parsed.path)) return refused("not-a-package", "the path does not exist");
|
|
@@ -794,7 +1019,7 @@ let PluginManager = (() => {
|
|
|
794
1019
|
} catch (error) {
|
|
795
1020
|
return refused("not-a-package", `no readable package.json at the path: ${messageOf(error)}`);
|
|
796
1021
|
}
|
|
797
|
-
const inspection = inspectionOf("path", read);
|
|
1022
|
+
const inspection = inspectionOf("path", read, registry);
|
|
798
1023
|
if (inspection.name === void 0) return refused("not-a-package", "the package.json names no package");
|
|
799
1024
|
if (known.has(inspection.name)) return refused("already-installed", `${inspection.name} is already installed`);
|
|
800
1025
|
if (!inspection.bundle) return refused("not-a-bundle", `${inspection.name} declares no dsh.bundle`);
|
|
@@ -802,38 +1027,57 @@ let PluginManager = (() => {
|
|
|
802
1027
|
}
|
|
803
1028
|
case "registry": {
|
|
804
1029
|
if (known.has(parsed.name)) return refused("already-installed", `${parsed.name} is already installed`);
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1030
|
+
const registries = [];
|
|
1031
|
+
const refusedBy = (problem, reason) => ({
|
|
1032
|
+
status: "refused",
|
|
1033
|
+
problem,
|
|
1034
|
+
reason,
|
|
1035
|
+
registries
|
|
809
1036
|
});
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
...
|
|
1037
|
+
for (const current of plan) {
|
|
1038
|
+
registries.push(current);
|
|
1039
|
+
const view = await viewProfilePackage(this.profile.dir, spec.trim(), {
|
|
1040
|
+
...this.profile.packageManager ?? { command: this.pnpmCommand },
|
|
1041
|
+
timeoutMs: this.inspectTimeoutMs,
|
|
1042
|
+
...signal === void 0 ? {} : { signal },
|
|
1043
|
+
registry: current
|
|
816
1044
|
});
|
|
817
|
-
const
|
|
818
|
-
if (
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
1045
|
+
const printed = view.stdout.replace(ANSI_SEQUENCE, "").trim();
|
|
1046
|
+
if (view.exitCode !== 0 || view.cause !== void 0 || view.timedOut) {
|
|
1047
|
+
const log = [
|
|
1048
|
+
view.stderr.trim(),
|
|
1049
|
+
printedError(printed),
|
|
1050
|
+
view.cause === void 0 ? "" : messageOf(view.cause)
|
|
1051
|
+
].filter(Boolean).join("\n");
|
|
1052
|
+
const kind = classifyInstallFailure({
|
|
1053
|
+
log,
|
|
1054
|
+
timedOut: view.timedOut,
|
|
1055
|
+
...view.cause === void 0 ? {} : { cause: view.cause }
|
|
1056
|
+
});
|
|
1057
|
+
if (registries.length < plan.length && signal?.aborted !== true && attributeFailure(kind, log, parsed) === "registry") continue;
|
|
1058
|
+
const reason = view.timedOut ? `pnpm view timed out after ${String(this.inspectTimeoutMs)}ms` : log || printed || `pnpm view exited with ${String(view.exitCode)}`;
|
|
1059
|
+
if (kind === "not-found" || kind === "no-matching-version") return refusedBy("not-found", reason);
|
|
1060
|
+
if (kind === "network" || kind === "timeout") return refusedBy("network", reason);
|
|
1061
|
+
return refusedBy("unknown", reason);
|
|
1062
|
+
}
|
|
1063
|
+
let answer;
|
|
1064
|
+
try {
|
|
1065
|
+
answer = JSON.parse(printed || "null");
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
return refusedBy("unknown", `unreadable pnpm view output: ${messageOf(error)}`);
|
|
1068
|
+
}
|
|
1069
|
+
const latest = Array.isArray(answer) ? answer.at(-1) : answer;
|
|
1070
|
+
if (typeof latest !== "object" || latest === null) return refusedBy("unknown", "pnpm view answered no package");
|
|
1071
|
+
const inspection = inspectionOf("registry", latest, current);
|
|
1072
|
+
const named = inspection.name === void 0 ? {
|
|
1073
|
+
...inspection,
|
|
1074
|
+
name: parsed.name
|
|
1075
|
+
} : inspection;
|
|
1076
|
+
if (!named.bundle) return refusedBy("not-a-bundle", `${named.name} declares no dsh.bundle`);
|
|
1077
|
+
return named;
|
|
827
1078
|
}
|
|
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;
|
|
1079
|
+
/* v8 ignore next -- the plan is never empty: every attempt returns or continues to the next */
|
|
1080
|
+
throw new Error("no registry was asked");
|
|
837
1081
|
}
|
|
838
1082
|
}
|
|
839
1083
|
}
|
|
@@ -876,23 +1120,24 @@ let PluginManager = (() => {
|
|
|
876
1120
|
* that fails, is cancelled, or adds a package without a bundle patch restores
|
|
877
1121
|
* `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay.
|
|
878
1122
|
* @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.
|
|
1123
|
+
* @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names,
|
|
1124
|
+
* the pending build scripts to allow for this profile before pnpm runs, and the registry asked first.
|
|
1125
|
+
* @returns Package-manager diagnostics, the registries asked, and the observed activation outcome.
|
|
882
1126
|
*/
|
|
883
1127
|
installBundle(spec, options) {
|
|
884
1128
|
const requestId = options?.requestId;
|
|
885
1129
|
const control = {
|
|
886
1130
|
abort: new AbortController(),
|
|
887
1131
|
phase: "installing",
|
|
888
|
-
|
|
1132
|
+
result: Promise.resolve(null)
|
|
889
1133
|
};
|
|
890
1134
|
const stopped = () => control.abort.signal.aborted;
|
|
891
1135
|
if (requestId !== void 0) this.installs.set(requestId, control);
|
|
892
|
-
const announce = (phase) => {
|
|
1136
|
+
const announce = (phase, attempt) => {
|
|
893
1137
|
if (requestId !== void 0) this.ownerContext.emit("plugin-manager/install-state", {
|
|
894
1138
|
requestId,
|
|
895
|
-
phase
|
|
1139
|
+
phase,
|
|
1140
|
+
...attempt === void 0 ? {} : { attempt }
|
|
896
1141
|
});
|
|
897
1142
|
};
|
|
898
1143
|
const result = this.change(async (result) => {
|
|
@@ -904,18 +1149,44 @@ let PluginManager = (() => {
|
|
|
904
1149
|
}
|
|
905
1150
|
const files = await this.readRestoredFiles();
|
|
906
1151
|
const before = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
907
|
-
|
|
1152
|
+
const plan = registryPlan(options?.registry, await this.registries());
|
|
908
1153
|
let name;
|
|
909
1154
|
try {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1155
|
+
let run;
|
|
1156
|
+
result.registries = [];
|
|
1157
|
+
for (const [index, registry] of plan.entries()) {
|
|
1158
|
+
if (index > 0) await this.restoreFiles(files);
|
|
1159
|
+
if (stopped()) throw new InstallCancelledError();
|
|
1160
|
+
result.registries.push(registry);
|
|
1161
|
+
announce("installing", {
|
|
1162
|
+
registry,
|
|
1163
|
+
index: index + 1,
|
|
1164
|
+
total: plan.length
|
|
1165
|
+
});
|
|
1166
|
+
run = await this.runPnpm([
|
|
1167
|
+
"add",
|
|
1168
|
+
spec,
|
|
1169
|
+
...registryArguments(registry)
|
|
1170
|
+
], control.abort.signal, requestId);
|
|
1171
|
+
result.packageResult = run;
|
|
1172
|
+
if (stopped()) throw new InstallCancelledError();
|
|
1173
|
+
/* v8 ignore next 2 -- runPnpm classifies every failed run, so kind is never absent here */
|
|
1174
|
+
if (run.exitCode === 0 || run.kind === void 0) break;
|
|
1175
|
+
const failedAt = attributeFailure(run.kind, run.output, parsedForRegistry(spec));
|
|
1176
|
+
delete result.failedAt;
|
|
1177
|
+
if (failedAt !== "other") result.failedAt = failedAt;
|
|
1178
|
+
if (failedAt !== "registry" || index === plan.length - 1) break;
|
|
1179
|
+
}
|
|
1180
|
+
/* v8 ignore next -- the plan is never empty, so a run always settled */
|
|
1181
|
+
if (run === void 0) throw new Error("no registry was asked");
|
|
1182
|
+
if (run.exitCode === 0) delete result.failedAt;
|
|
1183
|
+
if (run.exitCode !== 0) {
|
|
913
1184
|
try {
|
|
914
1185
|
result.pendingBuilds = await readPendingBuilds(this.profile.dir);
|
|
915
1186
|
} catch (error) {
|
|
916
1187
|
this.ownerContext.logger.warn("Could not read pending build approvals after pnpm failed", error);
|
|
917
1188
|
}
|
|
918
|
-
throw new Error(
|
|
1189
|
+
throw new Error(run.output);
|
|
919
1190
|
}
|
|
920
1191
|
const after = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
921
1192
|
const installed = Object.keys(after).filter((name) => before[name] !== after[name]);
|
|
@@ -925,8 +1196,8 @@ let PluginManager = (() => {
|
|
|
925
1196
|
name = target;
|
|
926
1197
|
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
927
1198
|
const manifest = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
928
|
-
if (manifest?.dsh?.bundle
|
|
929
|
-
|
|
1199
|
+
if (manifest?.dsh?.bundle === void 0) throw new ManagementFailure("not-bundle");
|
|
1200
|
+
for (const file of bundlePatchPaths(dir, manifest.dsh.bundle)) loadOverlayPatches("dsh", file);
|
|
930
1201
|
} catch (error) {
|
|
931
1202
|
await this.restoreFiles(files);
|
|
932
1203
|
throw error;
|
|
@@ -946,12 +1217,19 @@ let PluginManager = (() => {
|
|
|
946
1217
|
target: spec,
|
|
947
1218
|
enabled: options?.enabled !== false
|
|
948
1219
|
}, "install");
|
|
949
|
-
|
|
950
|
-
control.settled = result.then(() => void 0, () => void 0);
|
|
1220
|
+
control.result = result;
|
|
951
1221
|
return result.finally(() => {
|
|
952
1222
|
if (requestId !== void 0) this.installs.delete(requestId);
|
|
953
1223
|
});
|
|
954
1224
|
}
|
|
1225
|
+
/** Recover the result of an active installation without cancelling it.
|
|
1226
|
+
* @param requestId The id supplied when installation started.
|
|
1227
|
+
* @returns The installation's outcome after it settles, or null if no active request has that id.
|
|
1228
|
+
* Completed results are not retained; null establishes neither success nor cancellation.
|
|
1229
|
+
*/
|
|
1230
|
+
async waitForInstall(requestId) {
|
|
1231
|
+
return this.installs.get(requestId)?.result ?? null;
|
|
1232
|
+
}
|
|
955
1233
|
/** Stop an installation this manager owns and wait until its files are back.
|
|
956
1234
|
* @param requestId The id the installation was started with.
|
|
957
1235
|
* @returns `cancelled` once pnpm exited and the files are restored, `too-late` once the bundle is being
|
|
@@ -966,7 +1244,8 @@ let PluginManager = (() => {
|
|
|
966
1244
|
phase: "cancelling"
|
|
967
1245
|
});
|
|
968
1246
|
control.abort.abort();
|
|
969
|
-
|
|
1247
|
+
/* v8 ignore next -- change() folds every failure into its result; only a lock or disposal error rejects */
|
|
1248
|
+
await control.result.then(() => void 0, () => void 0);
|
|
970
1249
|
return { status: "cancelled" };
|
|
971
1250
|
}
|
|
972
1251
|
/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path.
|
|
@@ -978,7 +1257,7 @@ let PluginManager = (() => {
|
|
|
978
1257
|
await this.configure(async () => {
|
|
979
1258
|
const bundle = (await this.listBundles()).find((item) => item.name === name);
|
|
980
1259
|
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");
|
|
1260
|
+
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
1261
|
const contributions = bundle.error === void 0 ? this.bundleRows(name) : [];
|
|
983
1262
|
if (bundle.enabled) {
|
|
984
1263
|
await this.selectBundle(name, false);
|
|
@@ -995,25 +1274,34 @@ let PluginManager = (() => {
|
|
|
995
1274
|
}
|
|
996
1275
|
/** The rows a bundle's patch inserts and the existing rows it changes; an unreadable patch throws. */
|
|
997
1276
|
declaredRows(name, info) {
|
|
998
|
-
const
|
|
1277
|
+
const bundle = info.dsh?.bundle;
|
|
999
1278
|
/* v8 ignore next -- bundleManifest answers only manifests that declare a patch */
|
|
1000
|
-
if (
|
|
1279
|
+
if (bundle === void 0) return {
|
|
1001
1280
|
rows: [],
|
|
1002
1281
|
overrides: []
|
|
1003
1282
|
};
|
|
1004
|
-
const
|
|
1283
|
+
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
1284
|
+
const patches = bundlePatchPaths(dir, bundle).flatMap((file) => loadOverlayPatches("dsh", file));
|
|
1005
1285
|
const live = /* @__PURE__ */ new Map();
|
|
1006
1286
|
for (const entry of this.ctx.loader.entries())
|
|
1007
1287
|
/* 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,
|
|
1288
|
+
if (typeof entry.options.id === "string") live.set(entry.options.id, {
|
|
1289
|
+
entryId: pluginEntryId(entry.id),
|
|
1290
|
+
baseUrl: entry.parent.tree.ctx.baseUrl
|
|
1291
|
+
});
|
|
1009
1292
|
const rows = [];
|
|
1293
|
+
const packages = this.ctx.get("pluginPackages");
|
|
1010
1294
|
for (const row of flatten(composeEntries([patches.filter((item) => item.insert !== void 0)]))) {
|
|
1011
1295
|
if (typeof row.id !== "string" || typeof row.name !== "string") continue;
|
|
1012
|
-
const
|
|
1296
|
+
const active = live.get(row.id);
|
|
1297
|
+
const entryId = active?.entryId;
|
|
1298
|
+
const base = active?.baseUrl ?? pathToFileURL(join(dir, "package.json")).href;
|
|
1299
|
+
const meta = packages?.metaOf(row.name, base);
|
|
1013
1300
|
rows.push({
|
|
1014
1301
|
rowId: row.id,
|
|
1015
1302
|
moduleName: row.name,
|
|
1016
|
-
...entryId === void 0 ? {} : { entryId }
|
|
1303
|
+
...entryId === void 0 ? {} : { entryId },
|
|
1304
|
+
...meta === void 0 ? {} : { meta }
|
|
1017
1305
|
});
|
|
1018
1306
|
}
|
|
1019
1307
|
const declared = new Set(rows.map((row) => row.rowId));
|
|
@@ -1097,6 +1385,7 @@ let PluginManager = (() => {
|
|
|
1097
1385
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
1098
1386
|
const previous = manifest.dsh?.profile?.bundles ?? [];
|
|
1099
1387
|
if ((enabled || !previous.includes(name)) && bundleManifest(name, this.profile.dir, this.profile.installAnchor) === void 0) throw new ManagementFailure("not-bundle");
|
|
1388
|
+
if (enabled) this.bundleRows(name);
|
|
1100
1389
|
if (!enabled && previous.includes(name)) {
|
|
1101
1390
|
if (this.protectsManager(name)) throw new ManagementFailure("management-required");
|
|
1102
1391
|
}
|
|
@@ -1110,14 +1399,24 @@ let PluginManager = (() => {
|
|
|
1110
1399
|
}
|
|
1111
1400
|
};
|
|
1112
1401
|
await saveManifest(this.profile.dir, manifest);
|
|
1402
|
+
if (enabled) this.protectsManager(name);
|
|
1113
1403
|
}
|
|
1114
1404
|
bundleRows(name) {
|
|
1115
1405
|
const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
1116
1406
|
if (info?.dsh?.bundle === void 0) return [];
|
|
1117
|
-
return flatten(composeEntries([
|
|
1407
|
+
return flatten(composeEntries([bundlePatchPaths(resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir), info.dsh.bundle).flatMap((file) => loadOverlayPatches("dsh", file))]));
|
|
1118
1408
|
}
|
|
1119
1409
|
protectsManager(name) {
|
|
1120
|
-
|
|
1410
|
+
if (this.managementBundles.has(name)) return true;
|
|
1411
|
+
let rows;
|
|
1412
|
+
try {
|
|
1413
|
+
rows = this.bundleRows(name);
|
|
1414
|
+
} catch (_error) {
|
|
1415
|
+
return false;
|
|
1416
|
+
}
|
|
1417
|
+
const protectedBundle = rows.some((row) => protectedModules.has(row.name) || `include:${row.id}` === this.ownerEntryId);
|
|
1418
|
+
if (protectedBundle) this.managementBundles.add(name);
|
|
1419
|
+
return protectedBundle;
|
|
1121
1420
|
}
|
|
1122
1421
|
configure(operation) {
|
|
1123
1422
|
const hmr = this.ownerContext.get("hmr");
|