@deepseek-ai/dsh-plugin-manager 0.2.1-alpha.1 → 0.2.1-alpha.3
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 +10 -10
- package/README.md +14 -7
- package/README.zh.md +15 -8
- package/lib/index.js +585 -292
- package/lib/typert.host.js +407 -242
- package/lib/typert.remote-client.d.ts +16 -2
- package/lib/typert.remote-client.js +390 -234
- package/lib/types/build-approval.d.ts +14 -2
- package/lib/types/build-approval.js +35 -5
- package/lib/types/index.d.ts +22 -2
- package/lib/types/index.js +172 -62
- package/lib/types/official-install-target.d.ts +15 -0
- package/lib/types/official-install-target.js +54 -0
- package/lib/types/operations.d.ts +1 -0
- package/lib/types/operations.js +320 -255
- package/lib/types/patch.d.ts +12 -3
- package/lib/types/patch.js +47 -9
- package/lib/types/tools.js +70 -8
- package/lib/types/types.d.ts +24 -3
- package/locale/en.json +5 -0
- package/locale/tools/en.json +5 -0
- package/locale/tools/zh.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +40 -36
package/lib/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, globSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { mkdir, mkdtemp, open, readFile, rm } from "node:fs/promises";
|
|
4
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
|
|
7
7
|
import z from "@deepseek-ai/schemastery";
|
|
8
8
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
9
9
|
import { pluginEntryId, readPluginInventory } from "@deepseek-ai/dsh-host-plugin-inventory";
|
|
10
|
-
import { OPTIONAL_BUNDLES, PROFILE_COMPATIBILITY_FILENAME, bundlePatchPaths, composeEntries, evaluatePluginCompatibility, loadOptionalPatches, loadOverlayPatches, pluginCompatibilityWarning, readPluginMeta, readProfileCompatibility, readProfileManifest, readProfilePatches, readProfileVersionExemptions,
|
|
10
|
+
import { OFFICIAL_ON_DEMAND_CATALOG, OPTIONAL_BUNDLES, PROFILE_COMPATIBILITY_FILENAME, ResolutionRestartRequired, bundlePatchPaths, composeEntries, evaluatePluginCompatibility, getDshRuntimeVersion, loadOptionalPatches, loadOverlayPatches, pluginCompatibilityWarning, readBundlePatches, readPluginMeta, readProfileCompatibility, readProfileManifest, readProfilePatches, readProfileVersionExemptions, resolveBundleDir, resolveProfileDir, setProfileVersionExemption } from "@deepseek-ai/dsh-app-boot";
|
|
11
11
|
import { once } from "node:events";
|
|
12
12
|
import { execa } from "execa";
|
|
13
13
|
import { scrubbedParentEnv } from "@deepseek-ai/dsh-subprocess";
|
|
@@ -234,6 +234,64 @@ function incompatiblePlugin(issue) {
|
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region lib/types/operations.js
|
|
236
236
|
/** Shared profile package operations used by dsh plugin and the running manager. */
|
|
237
|
+
var __addDisposableResource = function(env, value, async) {
|
|
238
|
+
if (value !== null && value !== void 0) {
|
|
239
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
240
|
+
var dispose, inner;
|
|
241
|
+
if (async) {
|
|
242
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
243
|
+
dispose = value[Symbol.asyncDispose];
|
|
244
|
+
}
|
|
245
|
+
if (dispose === void 0) {
|
|
246
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
247
|
+
dispose = value[Symbol.dispose];
|
|
248
|
+
if (async) inner = dispose;
|
|
249
|
+
}
|
|
250
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
251
|
+
if (inner) dispose = function() {
|
|
252
|
+
try {
|
|
253
|
+
inner.call(this);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return Promise.reject(e);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
env.stack.push({
|
|
259
|
+
value,
|
|
260
|
+
dispose,
|
|
261
|
+
async
|
|
262
|
+
});
|
|
263
|
+
} else if (async) env.stack.push({ async: true });
|
|
264
|
+
return value;
|
|
265
|
+
};
|
|
266
|
+
var __disposeResources = (function(SuppressedError) {
|
|
267
|
+
return function(env) {
|
|
268
|
+
function fail(e) {
|
|
269
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
270
|
+
env.hasError = true;
|
|
271
|
+
}
|
|
272
|
+
var r, s = 0;
|
|
273
|
+
function next() {
|
|
274
|
+
while (r = env.stack.pop()) try {
|
|
275
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
276
|
+
if (r.dispose) {
|
|
277
|
+
var result = r.dispose.call(r.value);
|
|
278
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
279
|
+
fail(e);
|
|
280
|
+
return next();
|
|
281
|
+
});
|
|
282
|
+
} else s |= 1;
|
|
283
|
+
} catch (e) {
|
|
284
|
+
fail(e);
|
|
285
|
+
}
|
|
286
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
287
|
+
if (env.hasError) throw env.error;
|
|
288
|
+
}
|
|
289
|
+
return next();
|
|
290
|
+
};
|
|
291
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
292
|
+
var e = new Error(message);
|
|
293
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
294
|
+
});
|
|
237
295
|
/** Resolve relative package specs against the caller's directory.
|
|
238
296
|
* @param argument One pnpm argument.
|
|
239
297
|
* @param cwd Invocation directory, never the profile directory.
|
|
@@ -461,254 +519,268 @@ function bundleComponentManifests(manifest, dir, anchor) {
|
|
|
461
519
|
* @param options Output, activation and cancellation policy.
|
|
462
520
|
* @returns Exit status, whether the silence bound stopped the run, and the diagnostic path.
|
|
463
521
|
* A compatibility denial returns exit code 1. Service output is bounded; CLI output uses inherited descriptors.
|
|
522
|
+
* The diagnostic file is closed on return or rejection, including preparation and spawn failures.
|
|
464
523
|
*/
|
|
465
524
|
async function runProfilePnpm(context, args, options) {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
recursive: true,
|
|
471
|
-
mode: 448
|
|
472
|
-
});
|
|
473
|
-
const logPath = join(await mkdtemp(join(logRoot, "operation-")), "pnpm.log");
|
|
474
|
-
const log = await open(logPath, "wx", 384);
|
|
475
|
-
if (active !== void 0) {
|
|
476
|
-
await log.write(active);
|
|
477
|
-
await log.close();
|
|
478
|
-
options.onOutput?.(active, "stderr");
|
|
479
|
-
const bytes = Buffer.from(active);
|
|
480
|
-
return {
|
|
481
|
-
exitCode: 1,
|
|
482
|
-
output: bytes.subarray(Math.max(0, bytes.length - options.outputBytes)).toString("utf8"),
|
|
483
|
-
truncated: bytes.length > options.outputBytes,
|
|
484
|
-
logPath
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
const before = readProfileManifest("dsh", dir);
|
|
488
|
-
const savedFiles = ["package.json", "pnpm-lock.yaml"].map((name) => ({
|
|
489
|
-
path: join(dir, name),
|
|
490
|
-
text: optionalFile(join(dir, name))
|
|
491
|
-
}));
|
|
492
|
-
const beforeDependencies = directDependencies(before);
|
|
493
|
-
const installedBefore = new Map(Object.keys(beforeDependencies).map((name) => [name, optionalFile(join(dir, "node_modules", name, "package.json"))]));
|
|
494
|
-
let output = Buffer.alloc(0);
|
|
495
|
-
let truncated = false;
|
|
496
|
-
const append = (bytes) => {
|
|
497
|
-
output = Buffer.concat([output, bytes]);
|
|
498
|
-
if (output.length > options.outputBytes) {
|
|
499
|
-
truncated = true;
|
|
500
|
-
output = output.subarray(output.length - options.outputBytes);
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
const environment = {
|
|
504
|
-
...options.execution === "cli" ? process.env : scrubbedParentEnv(),
|
|
505
|
-
...options.env
|
|
506
|
-
};
|
|
507
|
-
const restore = async () => {
|
|
508
|
-
for (const file of savedFiles) if (file.text === void 0) await rm(file.path, { force: true });
|
|
509
|
-
else await writeFileAtomic(file.path, file.text, { mode: 384 });
|
|
510
|
-
};
|
|
511
|
-
/** Packages a compatibility check refused; callers render them for their own surface. */
|
|
512
|
-
const incompatible = [];
|
|
513
|
-
const rejected = async (warnings, restoration) => {
|
|
514
|
-
const diagnostic = `\ndsh: installation rejected: ${warnings.join("\n")}\ndsh: ${restoration}.\n`;
|
|
515
|
-
await log.write(diagnostic);
|
|
516
|
-
options.onOutput?.(diagnostic, "stderr");
|
|
517
|
-
append(Buffer.from(diagnostic));
|
|
518
|
-
await log.close();
|
|
519
|
-
return {
|
|
520
|
-
exitCode: 1,
|
|
521
|
-
output: output.toString("utf8"),
|
|
522
|
-
truncated,
|
|
523
|
-
logPath,
|
|
524
|
-
incompatible
|
|
525
|
-
};
|
|
526
|
-
};
|
|
527
|
-
const preflight = [];
|
|
528
|
-
const exemptions = readProfileVersionExemptions(dir);
|
|
529
|
-
const registryFlags = args.filter((argument) => argument.startsWith("--registry="));
|
|
530
|
-
for (const raw of namedSpecs(args)) try {
|
|
531
|
-
const manifest = await namedSpecManifest(dir, anchorPathSpec(raw, context.cwd), options, environment, registryFlags);
|
|
532
|
-
if (manifest === void 0) continue;
|
|
533
|
-
const issue = evaluatePluginCompatibility(manifest, exemptions);
|
|
534
|
-
if (issue !== void 0 && !issue.exempted) {
|
|
535
|
-
preflight.push(pluginCompatibilityWarning(issue));
|
|
536
|
-
incompatible.push(incompatiblePlugin(issue));
|
|
537
|
-
}
|
|
538
|
-
} catch (error) {
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
if (preflight.length > 0) return rejected(preflight, "nothing was installed");
|
|
542
|
-
const cancellation = new AbortController();
|
|
543
|
-
const grouped = leadsOwnGroup(options.execution);
|
|
544
|
-
const child = execa(options.command ?? "pnpm", [...options.args ?? [], ...args.map((arg) => anchorPathSpec(arg, context.cwd))], {
|
|
545
|
-
cwd: dir,
|
|
546
|
-
env: environment,
|
|
547
|
-
extendEnv: false,
|
|
548
|
-
reject: false,
|
|
549
|
-
stdout: options.execution === "cli" ? "inherit" : "pipe",
|
|
550
|
-
stderr: options.execution === "cli" ? "inherit" : "pipe",
|
|
551
|
-
killDescendants: options.execution === "service",
|
|
552
|
-
buffer: false,
|
|
553
|
-
stdin: options.execution === "cli" ? "inherit" : "ignore",
|
|
554
|
-
cancelSignal: options.signal === void 0 ? cancellation.signal : AbortSignal.any([cancellation.signal, options.signal])
|
|
555
|
-
});
|
|
556
|
-
const exited = once(child.nodeChildProcess, "exit").catch(() => void 0);
|
|
557
|
-
let writes = Promise.resolve();
|
|
558
|
-
/** `settled` records that the process outcome is known; `stalled` that the silence bound stopped the run. */
|
|
559
|
-
const control = {
|
|
560
|
-
settled: false,
|
|
561
|
-
stalled: false
|
|
562
|
-
};
|
|
563
|
-
/** Set once this call cuts the reading short itself, so the close it causes is not read as a run failure. */
|
|
564
|
-
let cut = false;
|
|
565
|
-
/** The first failure a reading hit before that cut, which the run still reports. */
|
|
566
|
-
let failure;
|
|
567
|
-
let idleTimer;
|
|
568
|
-
/** The silence bound: a captured run that stops printing without exiting is terminated, never awaited. */
|
|
569
|
-
const armIdle = () => {
|
|
570
|
-
if (control.settled || options.idleTimeoutMs === void 0) return;
|
|
571
|
-
clearTimeout(idleTimer);
|
|
572
|
-
idleTimer = setTimeout(() => {
|
|
573
|
-
control.stalled = true;
|
|
574
|
-
child.kill();
|
|
575
|
-
}, options.idleTimeoutMs);
|
|
576
|
-
};
|
|
577
|
-
const collect = async (stream, kind) => {
|
|
578
|
-
try {
|
|
579
|
-
for await (const chunk of stream) {
|
|
580
|
-
armIdle();
|
|
581
|
-
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
582
|
-
writes = writes.then(async () => {
|
|
583
|
-
await log.write(bytes);
|
|
584
|
-
});
|
|
585
|
-
await writes;
|
|
586
|
-
options.onOutput?.(bytes.toString("utf8"), kind);
|
|
587
|
-
append(bytes);
|
|
588
|
-
}
|
|
589
|
-
} catch (error) {
|
|
590
|
-
if (!cut) {
|
|
591
|
-
failure ??= error instanceof Error ? error : new Error(String(error));
|
|
592
|
-
cancellation.abort();
|
|
593
|
-
}
|
|
594
|
-
throw error;
|
|
595
|
-
}
|
|
525
|
+
const env_1 = {
|
|
526
|
+
stack: [],
|
|
527
|
+
error: void 0,
|
|
528
|
+
hasError: false
|
|
596
529
|
};
|
|
597
|
-
const collectors = [...child.stdout === null ? [] : [collect(child.stdout, "stdout")], ...child.stderr === null ? [] : [collect(child.stderr, "stderr")]];
|
|
598
|
-
for (const collector of collectors) collector.catch(() => {});
|
|
599
|
-
if (collectors.length > 0) armIdle();
|
|
600
|
-
let exitCode;
|
|
601
530
|
try {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
control.settled = true;
|
|
609
|
-
clearTimeout(idleTimer);
|
|
610
|
-
if (control.stalled) await awaitTreeGone({
|
|
611
|
-
pid: child.pid,
|
|
612
|
-
grouped
|
|
531
|
+
const dir = context.dir ?? resolveProfileDir(context.profile, context.home);
|
|
532
|
+
const active = await activeRecordedRun(dir);
|
|
533
|
+
const logRoot = join(dir, ".plugin-manager", "logs");
|
|
534
|
+
await mkdir(logRoot, {
|
|
535
|
+
recursive: true,
|
|
536
|
+
mode: 448
|
|
613
537
|
});
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if (completion.status === "rejected") throw completion.reason;
|
|
628
|
-
const result = completion.value;
|
|
629
|
-
exitCode = result.exitCode ?? (result.code === "ENOENT" ? 127 : 1);
|
|
630
|
-
if (control.stalled) {
|
|
631
|
-
const notice = `dsh: pnpm printed nothing for ${String(options.idleTimeoutMs)}ms and was terminated\n`;
|
|
632
|
-
await log.write(notice);
|
|
633
|
-
options.onOutput?.(notice, "stderr");
|
|
634
|
-
append(Buffer.from(notice));
|
|
538
|
+
const logPath = join(await mkdtemp(join(logRoot, "operation-")), "pnpm.log");
|
|
539
|
+
const log = __addDisposableResource(env_1, await open(logPath, "wx", 384), true);
|
|
540
|
+
if (active !== void 0) {
|
|
541
|
+
await log.write(active);
|
|
542
|
+
await log.close();
|
|
543
|
+
options.onOutput?.(active, "stderr");
|
|
544
|
+
const bytes = Buffer.from(active);
|
|
545
|
+
return {
|
|
546
|
+
exitCode: 1,
|
|
547
|
+
output: bytes.subarray(Math.max(0, bytes.length - options.outputBytes)).toString("utf8"),
|
|
548
|
+
truncated: bytes.length > options.outputBytes,
|
|
549
|
+
logPath
|
|
550
|
+
};
|
|
635
551
|
}
|
|
636
|
-
|
|
637
|
-
|
|
552
|
+
const before = readProfileManifest("dsh", dir);
|
|
553
|
+
const savedFiles = ["package.json", "pnpm-lock.yaml"].map((name) => ({
|
|
554
|
+
path: join(dir, name),
|
|
555
|
+
text: optionalFile(join(dir, name))
|
|
556
|
+
}));
|
|
557
|
+
const beforeDependencies = directDependencies(before);
|
|
558
|
+
const installedBefore = new Map(Object.keys(beforeDependencies).map((name) => [name, optionalFile(join(dir, "node_modules", name, "package.json"))]));
|
|
559
|
+
let output = Buffer.alloc(0);
|
|
560
|
+
let truncated = false;
|
|
561
|
+
const append = (bytes) => {
|
|
562
|
+
output = Buffer.concat([output, bytes]);
|
|
563
|
+
if (output.length > options.outputBytes) {
|
|
564
|
+
truncated = true;
|
|
565
|
+
output = output.subarray(output.length - options.outputBytes);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
const environment = {
|
|
569
|
+
...options.execution === "cli" ? process.env : scrubbedParentEnv(),
|
|
570
|
+
...options.env
|
|
571
|
+
};
|
|
572
|
+
const restore = async () => {
|
|
573
|
+
for (const file of savedFiles) if (file.text === void 0) await rm(file.path, { force: true });
|
|
574
|
+
else await writeFileAtomic(file.path, file.text, { mode: 384 });
|
|
575
|
+
};
|
|
576
|
+
/** Packages a compatibility check refused; callers render them for their own surface. */
|
|
577
|
+
const incompatible = [];
|
|
578
|
+
const rejected = async (warnings, restoration) => {
|
|
579
|
+
const diagnostic = `\ndsh: installation rejected: ${warnings.join("\n")}\ndsh: ${restoration}.\n`;
|
|
638
580
|
await log.write(diagnostic);
|
|
639
|
-
|
|
640
|
-
|
|
581
|
+
options.onOutput?.(diagnostic, "stderr");
|
|
582
|
+
append(Buffer.from(diagnostic));
|
|
583
|
+
await log.close();
|
|
584
|
+
return {
|
|
585
|
+
exitCode: 1,
|
|
586
|
+
output: output.toString("utf8"),
|
|
587
|
+
truncated,
|
|
588
|
+
logPath,
|
|
589
|
+
incompatible
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
const preflight = [];
|
|
593
|
+
const exemptions = readProfileVersionExemptions(dir);
|
|
594
|
+
const registryFlags = args.filter((argument) => argument.startsWith("--registry="));
|
|
595
|
+
for (const raw of namedSpecs(args)) try {
|
|
596
|
+
const manifest = await namedSpecManifest(dir, anchorPathSpec(raw, context.cwd), options, environment, registryFlags);
|
|
597
|
+
if (manifest === void 0) continue;
|
|
598
|
+
const issue = evaluatePluginCompatibility(manifest, exemptions);
|
|
599
|
+
if (issue !== void 0 && !issue.exempted) {
|
|
600
|
+
preflight.push(pluginCompatibilityWarning(issue));
|
|
601
|
+
incompatible.push(incompatiblePlugin(issue));
|
|
602
|
+
}
|
|
603
|
+
} catch (error) {
|
|
604
|
+
continue;
|
|
641
605
|
}
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
606
|
+
if (preflight.length > 0) return await rejected(preflight, "nothing was installed");
|
|
607
|
+
const cancellation = new AbortController();
|
|
608
|
+
const grouped = leadsOwnGroup(options.execution);
|
|
609
|
+
const child = execa(options.command ?? "pnpm", [...options.args ?? [], ...args.map((arg) => anchorPathSpec(arg, context.cwd))], {
|
|
610
|
+
cwd: dir,
|
|
611
|
+
env: environment,
|
|
612
|
+
extendEnv: false,
|
|
613
|
+
reject: false,
|
|
614
|
+
stdout: options.execution === "cli" ? "inherit" : "pipe",
|
|
615
|
+
stderr: options.execution === "cli" ? "inherit" : "pipe",
|
|
616
|
+
killDescendants: options.execution === "service",
|
|
617
|
+
buffer: false,
|
|
618
|
+
stdin: options.execution === "cli" ? "inherit" : "ignore",
|
|
619
|
+
cancelSignal: options.signal === void 0 ? cancellation.signal : AbortSignal.any([cancellation.signal, options.signal])
|
|
620
|
+
});
|
|
621
|
+
const exited = once(child.nodeChildProcess, "exit").catch(() => void 0);
|
|
622
|
+
let writes = Promise.resolve();
|
|
623
|
+
/** `settled` records that the process outcome is known; `stalled` that the silence bound stopped the run. */
|
|
624
|
+
const control = {
|
|
625
|
+
settled: false,
|
|
626
|
+
stalled: false
|
|
627
|
+
};
|
|
628
|
+
/** Set once this call cuts the reading short itself, so the close it causes is not read as a run failure. */
|
|
629
|
+
let cut = false;
|
|
630
|
+
/** The first failure a reading hit before that cut, which the run still reports. */
|
|
631
|
+
let failure;
|
|
632
|
+
let idleTimer;
|
|
633
|
+
/** The silence bound: a captured run that stops printing without exiting is terminated, never awaited. */
|
|
634
|
+
const armIdle = () => {
|
|
635
|
+
if (control.settled || options.idleTimeoutMs === void 0) return;
|
|
636
|
+
clearTimeout(idleTimer);
|
|
637
|
+
idleTimer = setTimeout(() => {
|
|
638
|
+
control.stalled = true;
|
|
639
|
+
child.kill();
|
|
640
|
+
}, options.idleTimeoutMs);
|
|
641
|
+
};
|
|
642
|
+
const collect = async (stream, kind) => {
|
|
643
|
+
try {
|
|
644
|
+
for await (const chunk of stream) {
|
|
645
|
+
armIdle();
|
|
646
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
647
|
+
writes = writes.then(async () => {
|
|
648
|
+
await log.write(bytes);
|
|
649
|
+
});
|
|
650
|
+
await writes;
|
|
651
|
+
options.onOutput?.(bytes.toString("utf8"), kind);
|
|
652
|
+
append(bytes);
|
|
663
653
|
}
|
|
664
|
-
|
|
665
|
-
if (!
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
} else {
|
|
669
|
-
const notice = `\ndsh: warning: ${found.join("\n")}\ndsh: it stays installed but profile startup denies it until you grant an exemption for those exact versions.\n`;
|
|
670
|
-
await log.write(notice);
|
|
671
|
-
options.onOutput?.(notice, "stderr");
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if (!cut) {
|
|
656
|
+
failure ??= error instanceof Error ? error : new Error(String(error));
|
|
657
|
+
cancellation.abort();
|
|
672
658
|
}
|
|
659
|
+
throw error;
|
|
673
660
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
661
|
+
};
|
|
662
|
+
const collectors = [...child.stdout === null ? [] : [collect(child.stdout, "stdout")], ...child.stderr === null ? [] : [collect(child.stderr, "stderr")]];
|
|
663
|
+
for (const collector of collectors) collector.catch(() => {});
|
|
664
|
+
if (collectors.length > 0) armIdle();
|
|
665
|
+
let exitCode;
|
|
666
|
+
try {
|
|
667
|
+
await recordRun(dir, {
|
|
668
|
+
pid: child.pid,
|
|
669
|
+
grouped
|
|
670
|
+
});
|
|
671
|
+
const settled = Promise.allSettled([child]);
|
|
672
|
+
await Promise.race([exited, settled]);
|
|
673
|
+
control.settled = true;
|
|
674
|
+
clearTimeout(idleTimer);
|
|
675
|
+
if (control.stalled) await awaitTreeGone({
|
|
676
|
+
pid: child.pid,
|
|
677
|
+
grouped
|
|
678
|
+
});
|
|
679
|
+
if (await drainWithin(collectors, DRAIN_AFTER_EXIT_MS)) {
|
|
680
|
+
for (const stream of await Promise.allSettled(collectors)) if (stream.status === "rejected") throw stream.reason;
|
|
681
|
+
} else {
|
|
682
|
+
cut = true;
|
|
683
|
+
child.stdout?.destroy();
|
|
684
|
+
child.stderr?.destroy();
|
|
685
|
+
const notice = "dsh: pnpm output was cut short after its process exited\n";
|
|
686
|
+
await log.write(notice);
|
|
687
|
+
if (failure !== void 0) throw failure;
|
|
688
|
+
options.onOutput?.(notice, "stderr");
|
|
689
|
+
append(Buffer.from(notice));
|
|
690
|
+
}
|
|
691
|
+
const [completion] = await settled;
|
|
692
|
+
if (completion.status === "rejected") throw completion.reason;
|
|
693
|
+
const result = completion.value;
|
|
694
|
+
exitCode = result.exitCode ?? (result.code === "ENOENT" ? 127 : 1);
|
|
695
|
+
if (control.stalled) {
|
|
696
|
+
const notice = `dsh: pnpm printed nothing for ${String(options.idleTimeoutMs)}ms and was terminated\n`;
|
|
697
|
+
await log.write(notice);
|
|
698
|
+
options.onOutput?.(notice, "stderr");
|
|
699
|
+
append(Buffer.from(notice));
|
|
700
|
+
}
|
|
701
|
+
if (result.failed && output.length === 0) {
|
|
702
|
+
const diagnostic = result.shortMessage ?? "pnpm failed";
|
|
693
703
|
await log.write(diagnostic);
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
704
|
+
truncated = Buffer.byteLength(diagnostic) > options.outputBytes;
|
|
705
|
+
output = Buffer.from(diagnostic).subarray(0, options.outputBytes);
|
|
706
|
+
}
|
|
707
|
+
if (exitCode === 0 && !control.stalled) {
|
|
708
|
+
const after = readProfileManifest("dsh", dir);
|
|
709
|
+
const warnings = [];
|
|
710
|
+
for (const [name, spec] of Object.entries(directDependencies(after))) {
|
|
711
|
+
const packageDir = join(dir, "node_modules", name);
|
|
712
|
+
const installed = optionalFile(join(packageDir, "package.json"));
|
|
713
|
+
if (installed === void 0) continue;
|
|
714
|
+
const untouched = beforeDependencies[name] === spec && installedBefore.get(name) === installed;
|
|
715
|
+
const found = [];
|
|
716
|
+
const issues = [];
|
|
717
|
+
try {
|
|
718
|
+
const manifest = readProfileManifest("dsh", packageDir);
|
|
719
|
+
for (const candidate of [manifest, ...bundleComponentManifests(manifest, packageDir, context.installAnchor)]) {
|
|
720
|
+
const issue = evaluatePluginCompatibility(candidate, readProfileVersionExemptions(dir));
|
|
721
|
+
if (issue !== void 0 && !issue.exempted) {
|
|
722
|
+
found.push(pluginCompatibilityWarning(issue));
|
|
723
|
+
issues.push(incompatiblePlugin(issue));
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} catch (error) {
|
|
727
|
+
found.push(`Cannot validate installed package ${name}: ${String(error)}`);
|
|
728
|
+
}
|
|
729
|
+
if (found.length === 0) continue;
|
|
730
|
+
if (!untouched) {
|
|
731
|
+
warnings.push(...found);
|
|
732
|
+
incompatible.push(...issues);
|
|
733
|
+
} else {
|
|
734
|
+
const notice = `\ndsh: warning: ${found.join("\n")}\ndsh: it stays installed but profile startup denies it until you grant an exemption for those exact versions.\n`;
|
|
735
|
+
await log.write(notice);
|
|
736
|
+
options.onOutput?.(notice, "stderr");
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (warnings.length > 0) {
|
|
740
|
+
await restore();
|
|
741
|
+
const repair = ["install", savedFiles.some((file) => file.path.endsWith("pnpm-lock.yaml") && file.text !== void 0) ? "--frozen-lockfile" : "--config.lockfile=false"];
|
|
742
|
+
const repairing = execa(options.command ?? "pnpm", [...options.args ?? [], ...repair], {
|
|
743
|
+
cwd: dir,
|
|
744
|
+
env: environment,
|
|
745
|
+
extendEnv: false,
|
|
746
|
+
reject: false,
|
|
747
|
+
stdin: "ignore",
|
|
748
|
+
...options.idleTimeoutMs === void 0 ? {} : { timeout: options.idleTimeoutMs }
|
|
749
|
+
});
|
|
750
|
+
await recordRun(dir, {
|
|
751
|
+
pid: repairing.pid,
|
|
752
|
+
grouped: false
|
|
753
|
+
});
|
|
754
|
+
const repaired = await repairing;
|
|
755
|
+
exitCode = 1;
|
|
756
|
+
const restoration = repaired.exitCode === 0 ? "restored package.json, pnpm-lock.yaml, and node_modules" : "restored package.json and pnpm-lock.yaml, but node_modules could not be reinstalled; run 'dsh plugin install'";
|
|
757
|
+
const diagnostic = `\ndsh: installation rejected: ${warnings.join("\n")}\ndsh: ${restoration}.\n`;
|
|
758
|
+
await log.write(diagnostic);
|
|
759
|
+
options.onOutput?.(diagnostic, "stderr");
|
|
760
|
+
append(Buffer.from(diagnostic));
|
|
761
|
+
} else if (options.activateNewBundles !== false) await reconcile(before, dir, context.installAnchor, options);
|
|
762
|
+
}
|
|
763
|
+
} finally {
|
|
764
|
+
control.settled = true;
|
|
765
|
+
clearTimeout(idleTimer);
|
|
766
|
+
await rm(runRecordPath(dir), { force: true });
|
|
767
|
+
await log.close();
|
|
697
768
|
}
|
|
769
|
+
return {
|
|
770
|
+
exitCode,
|
|
771
|
+
output: output.toString("utf8"),
|
|
772
|
+
truncated,
|
|
773
|
+
logPath,
|
|
774
|
+
...control.stalled ? { timedOut: true } : {},
|
|
775
|
+
...incompatible.length > 0 ? { incompatible } : {}
|
|
776
|
+
};
|
|
777
|
+
} catch (e_1) {
|
|
778
|
+
env_1.error = e_1;
|
|
779
|
+
env_1.hasError = true;
|
|
698
780
|
} finally {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
await rm(runRecordPath(dir), { force: true });
|
|
702
|
-
await log.close();
|
|
781
|
+
const result_1 = __disposeResources(env_1);
|
|
782
|
+
if (result_1) await result_1;
|
|
703
783
|
}
|
|
704
|
-
return {
|
|
705
|
-
exitCode,
|
|
706
|
-
output: output.toString("utf8"),
|
|
707
|
-
truncated,
|
|
708
|
-
logPath,
|
|
709
|
-
...control.stalled ? { timedOut: true } : {},
|
|
710
|
-
...incompatible.length > 0 ? { incompatible } : {}
|
|
711
|
-
};
|
|
712
784
|
}
|
|
713
785
|
/**
|
|
714
786
|
* Read the registry pnpm's own configuration names in the profile: its `.npmrc` chain and workspace settings,
|
|
@@ -819,6 +891,48 @@ function classifyInstallFailure(facts) {
|
|
|
819
891
|
for (const [kind, pattern] of LOG_KINDS) if (pattern.test(facts.log)) return kind;
|
|
820
892
|
return "unknown";
|
|
821
893
|
}
|
|
894
|
+
//#endregion
|
|
895
|
+
//#region lib/types/official-install-target.js
|
|
896
|
+
/** Select Official bundle packages from the running installation without importing provider code. */
|
|
897
|
+
function manifestAt(file) {
|
|
898
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Offer a source checkout link or an exact published package version. Source installations are
|
|
902
|
+
* identified by the real CLI manifest and its unpacked workspace dependencies, including Desktop's
|
|
903
|
+
* development symlink. An incomplete checkout is an error, never a registry fallback.
|
|
904
|
+
* @param name - Official catalog package name.
|
|
905
|
+
* @param installAnchor - Running application's DSH package.json, possibly reached through a symlink.
|
|
906
|
+
* @param version - Running DSH version used by its release family.
|
|
907
|
+
* @returns An ordinary package-manager spec and its package version.
|
|
908
|
+
* @throws when a source installation lacks its workspace package, bundle patch, or built runtime.
|
|
909
|
+
*/
|
|
910
|
+
function officialBundleInstallTarget(name, installAnchor, version) {
|
|
911
|
+
const anchor = realpathSync(installAnchor);
|
|
912
|
+
const installation = manifestAt(anchor);
|
|
913
|
+
if (!(installation.name === "@deepseek-ai/dsh" && Object.values(installation.dependencies ?? {}).some((value) => value.startsWith("workspace:")))) return {
|
|
914
|
+
spec: `${name}@${version}`,
|
|
915
|
+
version
|
|
916
|
+
};
|
|
917
|
+
const root = resolve(dirname(anchor), "../..");
|
|
918
|
+
if (anchor !== join(root, "apps", "cli", "package.json") || !existsSync(join(root, "package.json")) || manifestAt(join(root, "package.json")).name !== "@deepseek-ai/dsh-root") throw new Error(`Official bundle ${name}: source DSH installation ${anchor} requires its complete workspace checkout`);
|
|
919
|
+
const matches = globSync("packages/*/*/package.json", { cwd: root }).map((file) => ({
|
|
920
|
+
file: join(root, file),
|
|
921
|
+
manifest: manifestAt(join(root, file))
|
|
922
|
+
})).filter((entry) => entry.manifest.name === name);
|
|
923
|
+
const selected = matches[0];
|
|
924
|
+
if (matches.length !== 1 || selected === void 0) throw new Error(`Official bundle ${name}: expected one workspace package in ${root}, found ${matches.length}; restore the checkout and run pnpm install`);
|
|
925
|
+
const { file, manifest } = selected;
|
|
926
|
+
const directory = realpathSync(dirname(file));
|
|
927
|
+
if (manifest.version !== version || manifest.private === true || manifest.dsh?.bundle === void 0) throw new Error(`Official bundle ${name}: ${file} must declare a public bundle at DSH version ${version}`);
|
|
928
|
+
const main = typeof manifest.main === "string" ? resolve(directory, manifest.main) : void 0;
|
|
929
|
+
if (main === void 0 || !existsSync(main) || !statSync(main).isFile()) throw new Error(`Official bundle ${name}: built runtime is missing in ${directory}; run pnpm install and pnpm run build in ${root}`);
|
|
930
|
+
for (const patch of bundlePatchPaths(directory, manifest.dsh.bundle)) if (!existsSync(patch) || !statSync(patch).isFile()) throw new Error(`Official bundle ${name}: bundle patch ${patch} is missing; restore the checkout`);
|
|
931
|
+
return {
|
|
932
|
+
spec: `link:${directory}`,
|
|
933
|
+
version
|
|
934
|
+
};
|
|
935
|
+
}
|
|
822
936
|
/** Public npmmirror URL shared by the fallback configuration and public-registry comparison. */
|
|
823
937
|
const NPMMIRROR_REGISTRY = "https://registry.npmmirror.com/";
|
|
824
938
|
/**
|
|
@@ -897,15 +1011,25 @@ function attributeFailure(kind, log, spec) {
|
|
|
897
1011
|
}
|
|
898
1012
|
//#endregion
|
|
899
1013
|
//#region lib/types/patch.js
|
|
900
|
-
/** Comment-preserving profile plugin enablement edits. */
|
|
901
|
-
/**
|
|
902
|
-
* @param
|
|
1014
|
+
/** Comment-preserving profile plugin enablement edits and the entry flattening they compare against. */
|
|
1015
|
+
/** Flatten only the groups addressable by the profile's patch composer.
|
|
1016
|
+
* @param rows Composed top-level entries.
|
|
1017
|
+
* @returns Entries in depth-first order.
|
|
1018
|
+
*/
|
|
1019
|
+
function flatten(rows) {
|
|
1020
|
+
return rows.flatMap((row) => [row, ...row.group && Array.isArray(row.config) ? flatten(row.config) : []]);
|
|
1021
|
+
}
|
|
1022
|
+
/** Replace the last matching override or append one after existing insertions. An override that holds only `id` and
|
|
1023
|
+
* `disabled`, without anchors, is removed, or not appended, when the bundle layers and the remaining profile patches
|
|
1024
|
+
* already give the entry the requested literal `disabled` value; home patches and launch overlays are not consulted.
|
|
1025
|
+
* @param profile Profile whose patch file is edited and whose selected bundles supply the compared composition.
|
|
903
1026
|
* @param id Unique composition entry id.
|
|
904
1027
|
* @param name Module name used to match name-qualified overrides.
|
|
905
1028
|
* @param enabled Desired entry enablement.
|
|
906
1029
|
* @returns Whether the file changed.
|
|
907
1030
|
*/
|
|
908
|
-
async function writePluginEnabled(
|
|
1031
|
+
async function writePluginEnabled(profile, id, name, enabled) {
|
|
1032
|
+
const filename = profile.patchPath;
|
|
909
1033
|
let text;
|
|
910
1034
|
try {
|
|
911
1035
|
text = await readFile(filename, "utf8");
|
|
@@ -920,26 +1044,46 @@ async function writePluginEnabled(filename, id, name, enabled) {
|
|
|
920
1044
|
const error = document.errors[0];
|
|
921
1045
|
if (error !== void 0) throw error;
|
|
922
1046
|
if (!isSeq(document.contents)) throw new Error("Profile patch must be a YAML sequence");
|
|
923
|
-
loadOptionalPatches("dsh", filename);
|
|
1047
|
+
const patches = loadOptionalPatches("dsh", filename) ?? [];
|
|
1048
|
+
const bundlePatches = readBundlePatches("dsh", profile);
|
|
924
1049
|
const items = document.contents.items;
|
|
925
1050
|
const target = items.findLast((item, index) => {
|
|
926
1051
|
if (!isMap(item) || document.getIn([index, "id"]) !== id || item.has("insert")) return false;
|
|
927
1052
|
const expectedName = document.getIn([index, "name"]);
|
|
928
1053
|
return !expectedName || expectedName === name;
|
|
929
1054
|
});
|
|
1055
|
+
const implied = (index) => {
|
|
1056
|
+
const row = flatten(composeEntries([[...bundlePatches, ...patches.toSpliced(index, 1)]])).findLast((entry) => entry.id === id);
|
|
1057
|
+
return row !== void 0 && (row.disabled ?? false) === !enabled;
|
|
1058
|
+
};
|
|
930
1059
|
if (isMap(target)) {
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1060
|
+
const index = items.indexOf(target);
|
|
1061
|
+
if (target.anchor === void 0 && target.items.every((pair) => isScalar(pair.key) && isScalar(pair.value) && pair.key.anchor === void 0 && pair.value.anchor === void 0 && (pair.key.value === "id" || pair.key.value === "disabled")) && implied(index)) {
|
|
1062
|
+
document.deleteIn([index]);
|
|
1063
|
+
const comment = target.commentBefore;
|
|
1064
|
+
const next = items[index];
|
|
1065
|
+
if (comment && isNode(next)) next.commentBefore = next.commentBefore ? `${comment}\n${next.commentBefore}` : comment;
|
|
1066
|
+
else if (comment && items.length === 0) document.contents.commentBefore = comment;
|
|
1067
|
+
else if (comment) document.comment = document.comment ? `${comment}\n${document.comment}` : comment;
|
|
1068
|
+
} else if (document.getIn([index, "disabled"]) === !enabled) return false;
|
|
1069
|
+
else document.setIn([index, "disabled"], !enabled);
|
|
1070
|
+
} else {
|
|
1071
|
+
if (implied(items.length)) return false;
|
|
1072
|
+
document.add({
|
|
1073
|
+
id,
|
|
1074
|
+
disabled: !enabled
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
937
1077
|
await writeFileAtomic(filename, String(document), { mode: 384 });
|
|
938
1078
|
return true;
|
|
939
1079
|
}
|
|
940
1080
|
//#endregion
|
|
941
1081
|
//#region lib/types/build-approval.js
|
|
942
1082
|
/** Approve pnpm's pending dependency scripts in the current profile's workspace settings. */
|
|
1083
|
+
/** Value pnpm leaves for a build script that still needs a decision. */
|
|
1084
|
+
const UNDECIDED_BUILD = "set this to true or false";
|
|
1085
|
+
/** Where a non-interactive run names the build scripts it skipped. */
|
|
1086
|
+
const IGNORED_BUILDS = /\bIgnored build scripts: (.+)/;
|
|
943
1087
|
async function readPolicy(dir) {
|
|
944
1088
|
let text;
|
|
945
1089
|
try {
|
|
@@ -956,17 +1100,41 @@ async function readPolicy(dir) {
|
|
|
956
1100
|
visit(builds ?? null, (_key, node) => {
|
|
957
1101
|
if (isAlias(node) || isNode(node) && "anchor" in node && node.anchor) throw new Error("allowBuilds must not contain YAML anchors or aliases");
|
|
958
1102
|
});
|
|
1103
|
+
const keys = isMap(builds) ? builds.items.flatMap(({ key }) => isScalar(key) && typeof key.value === "string" ? [key.value] : []) : [];
|
|
959
1104
|
return {
|
|
960
1105
|
document,
|
|
961
|
-
pending: isMap(builds) ? builds.items.flatMap(({ key, value }) => isScalar(key) && typeof key.value === "string" && !/[*?]/.test(key.value) && isScalar(value) && value.value ===
|
|
1106
|
+
pending: isMap(builds) ? builds.items.flatMap(({ key, value }) => isScalar(key) && typeof key.value === "string" && !/[*?]/.test(key.value) && isScalar(value) && value.value === UNDECIDED_BUILD ? [key.value] : []) : [],
|
|
1107
|
+
keys
|
|
962
1108
|
};
|
|
963
1109
|
}
|
|
964
|
-
/**
|
|
1110
|
+
/**
|
|
1111
|
+
* Record the build scripts a failed run reported as ignored, then list every name awaiting a decision.
|
|
1112
|
+
*
|
|
1113
|
+
* pnpm writes those policy entries itself only when it can prompt, and the
|
|
1114
|
+
* manager always runs it non-interactively, so the run's own report is the
|
|
1115
|
+
* record that survives.
|
|
965
1116
|
* @param dir Current profile directory.
|
|
1117
|
+
* @param output Captured output of the failed run.
|
|
966
1118
|
* @returns Exact package names awaiting a build decision; wildcard rules are excluded.
|
|
967
1119
|
*/
|
|
968
|
-
async function
|
|
969
|
-
|
|
1120
|
+
async function recordPendingBuilds(dir, output) {
|
|
1121
|
+
const { document, pending, keys } = await readPolicy(dir);
|
|
1122
|
+
const known = new Set(keys);
|
|
1123
|
+
const added = ignoredBuildNames(output).filter((name) => !known.has(name));
|
|
1124
|
+
if (added.length === 0) return pending;
|
|
1125
|
+
for (const name of added) document.setIn(["allowBuilds", name], UNDECIDED_BUILD);
|
|
1126
|
+
await writeFileAtomic(join(dir, "pnpm-workspace.yaml"), String(document), { mode: 384 });
|
|
1127
|
+
return [...pending, ...added];
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Read the package names a pnpm run reported as ignored build scripts.
|
|
1131
|
+
* @param output Captured output of the run.
|
|
1132
|
+
* @returns Exact names in report order; wildcard patterns are excluded.
|
|
1133
|
+
*/
|
|
1134
|
+
function ignoredBuildNames(output) {
|
|
1135
|
+
const reported = IGNORED_BUILDS.exec(output)?.[1];
|
|
1136
|
+
if (reported === void 0) return [];
|
|
1137
|
+
return reported.split(",").map((name) => name.trim()).filter((name) => name !== "" && !/[*?]/.test(name));
|
|
970
1138
|
}
|
|
971
1139
|
/** Persist approval without running scripts; the caller holds the profile manifest lock.
|
|
972
1140
|
* @param dir Current profile directory.
|
|
@@ -1113,6 +1281,7 @@ const protectedModules = new Set([
|
|
|
1113
1281
|
"@deepseek-ai/dsh-host-plugin-inventory",
|
|
1114
1282
|
"@deepseek-ai/dsh-typert-registry",
|
|
1115
1283
|
"@deepseek-ai/dsh-api-remotes",
|
|
1284
|
+
"@deepseek-ai/dsh-api-remote-access",
|
|
1116
1285
|
"@deepseek-ai/cordis-plugin-timer",
|
|
1117
1286
|
"@deepseek-ai/dsh-client-connection",
|
|
1118
1287
|
"@deepseek-ai/dsh-host-frontend-static",
|
|
@@ -1123,10 +1292,6 @@ const protectedModules = new Set([
|
|
|
1123
1292
|
const RESTORED_FILES = ["package.json", "pnpm-lock.yaml"];
|
|
1124
1293
|
/** pnpm's colour escapes, which a JSON answer may be wrapped in. */
|
|
1125
1294
|
const ANSI_SEQUENCE = /\x1b\[[0-9;]*m/g;
|
|
1126
|
-
/** Flatten only the groups addressable by the profile's patch composer. */
|
|
1127
|
-
function flatten(rows) {
|
|
1128
|
-
return rows.flatMap((row) => [row, ...row.group && Array.isArray(row.config) ? flatten(row.config) : []]);
|
|
1129
|
-
}
|
|
1130
1295
|
/** Preserve the exact observed diagnostic, including non-Error failures. */
|
|
1131
1296
|
function messageOf(error) {
|
|
1132
1297
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -1192,6 +1357,33 @@ function printedError(printed) {
|
|
|
1192
1357
|
const { code, message } = error;
|
|
1193
1358
|
return [code, message].filter((part) => typeof part === "string").join(" ");
|
|
1194
1359
|
}
|
|
1360
|
+
/** The bundles a profile manifest selects. */
|
|
1361
|
+
function bundleSelection(manifest) {
|
|
1362
|
+
return manifest.dsh?.profile?.bundles ?? [];
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* The package an installation spec names before pnpm runs: a registry name, a local directory's manifest name, or the
|
|
1366
|
+
* dependency saved with this git or tarball spec, compared as the plugin list shows saved specs.
|
|
1367
|
+
* @param spec The installation spec.
|
|
1368
|
+
* @param dependencies The profile's saved dependencies.
|
|
1369
|
+
* @param profileDir The profile directory saved local paths are relative to.
|
|
1370
|
+
* @returns The package name, or `undefined` when only the installation tells it.
|
|
1371
|
+
*/
|
|
1372
|
+
function specPackageName(spec, dependencies, profileDir) {
|
|
1373
|
+
const parsed = parsedForRegistry(spec);
|
|
1374
|
+
if (parsed.kind === "registry") return parsed.name;
|
|
1375
|
+
if (parsed.kind === "path") return packageName(parsed.path);
|
|
1376
|
+
const specs = parsed.kind === "tarball" && parsed.path !== void 0 ? [spec, `file:${parsed.path}`] : [spec];
|
|
1377
|
+
return Object.entries(dependencies).find(([dependency, saved]) => specs.includes(dependencySpec(dependency, saved, profileDir)))?.[0];
|
|
1378
|
+
}
|
|
1379
|
+
/** The package name a local directory declares, or `undefined` without a readable manifest. */
|
|
1380
|
+
function packageName(dir) {
|
|
1381
|
+
try {
|
|
1382
|
+
return readProfileManifest("dsh", dir).name;
|
|
1383
|
+
} catch (_error) {
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1195
1387
|
/** 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. */
|
|
1196
1388
|
function parsedForRegistry(spec) {
|
|
1197
1389
|
try {
|
|
@@ -1389,6 +1581,11 @@ let PluginManager = (() => {
|
|
|
1389
1581
|
});
|
|
1390
1582
|
/** Management bundles remain protected if their files become unreadable. */
|
|
1391
1583
|
managementBundles = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Set());
|
|
1584
|
+
/**
|
|
1585
|
+
* The registry each package was last installed from by this manager in this process. pnpm verifies lockfile tarball
|
|
1586
|
+
* URLs against its default registry unless they belong to a named registry, so a later removal names it again.
|
|
1587
|
+
*/
|
|
1588
|
+
installedFrom = /* @__PURE__ */ new Map();
|
|
1392
1589
|
ownerEntryId;
|
|
1393
1590
|
packageOperations = /* @__PURE__ */ new Set();
|
|
1394
1591
|
profile;
|
|
@@ -1445,7 +1642,7 @@ let PluginManager = (() => {
|
|
|
1445
1642
|
setVersionExemption(packageVersion, runtimeVersion, enabled, acceptRisk) {
|
|
1446
1643
|
return this.change((result) => this.configure(async () => {
|
|
1447
1644
|
await setProfileVersionExemption(this.profile.dir, packageVersion, runtimeVersion, enabled, acceptRisk === true);
|
|
1448
|
-
result
|
|
1645
|
+
Object.assign(result, await this.reload());
|
|
1449
1646
|
}), {
|
|
1450
1647
|
stage: "enable",
|
|
1451
1648
|
target: packageVersion,
|
|
@@ -1475,7 +1672,7 @@ let PluginManager = (() => {
|
|
|
1475
1672
|
};
|
|
1476
1673
|
});
|
|
1477
1674
|
}
|
|
1478
|
-
/** Read
|
|
1675
|
+
/** Read installed, installation-provided, and offline Official catalog bundles, plus selected non-bundle names.
|
|
1479
1676
|
* A dependency without a bundle patch is listed, as a `not-bundle` problem, only while it is selected.
|
|
1480
1677
|
* @returns Package versions, manifest descriptions, the installable spec of profile dependencies, rows, optional
|
|
1481
1678
|
* display metadata, activation selections, whether the installation offers the bundle, and removal availability.
|
|
@@ -1483,49 +1680,90 @@ let PluginManager = (() => {
|
|
|
1483
1680
|
listBundles() {
|
|
1484
1681
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
1485
1682
|
const exemptions = readProfileVersionExemptions(this.profile.dir);
|
|
1486
|
-
const selected = manifest
|
|
1683
|
+
const selected = bundleSelection(manifest);
|
|
1487
1684
|
const recorded = manifest.dependencies ?? {};
|
|
1488
1685
|
const dependencies = Object.keys(recorded);
|
|
1489
1686
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, "utf8"));
|
|
1687
|
+
const catalog = new Map(OFFICIAL_ON_DEMAND_CATALOG.map((entry) => [entry.packageName, entry]));
|
|
1490
1688
|
const names = [...new Set([
|
|
1491
1689
|
...selected,
|
|
1492
1690
|
...dependencies,
|
|
1493
|
-
...Object.keys(installation.dependencies ?? {})
|
|
1691
|
+
...Object.keys(installation.dependencies ?? {}),
|
|
1692
|
+
...catalog.keys()
|
|
1494
1693
|
])];
|
|
1495
1694
|
const bundles = [];
|
|
1695
|
+
const runtimeVersion = getDshRuntimeVersion();
|
|
1496
1696
|
for (const name of names) {
|
|
1497
1697
|
const installed = dependencies.includes(name);
|
|
1498
1698
|
const optional = OPTIONAL_BUNDLES.includes(name);
|
|
1499
1699
|
const enabled = selected.includes(name);
|
|
1500
1700
|
const shipped = Object.hasOwn(installation.dependencies ?? {}, name);
|
|
1701
|
+
const offered = catalog.get(name);
|
|
1702
|
+
const official = optional || offered !== void 0;
|
|
1703
|
+
let target = {};
|
|
1704
|
+
let targetError;
|
|
1705
|
+
if (offered !== void 0) try {
|
|
1706
|
+
target = { installTarget: officialBundleInstallTarget(name, this.profile.installAnchor, runtimeVersion) };
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
targetError = managementError(error);
|
|
1709
|
+
}
|
|
1710
|
+
const catalogMeta = offered === void 0 ? {} : { meta: offered.meta };
|
|
1711
|
+
if (offered !== void 0 && !installed && !shipped) {
|
|
1712
|
+
bundles.push({
|
|
1713
|
+
name,
|
|
1714
|
+
official,
|
|
1715
|
+
availability: "missing",
|
|
1716
|
+
...catalogMeta,
|
|
1717
|
+
...target,
|
|
1718
|
+
enabled,
|
|
1719
|
+
installed,
|
|
1720
|
+
optional,
|
|
1721
|
+
removable: enabled,
|
|
1722
|
+
rows: [],
|
|
1723
|
+
overrides: [],
|
|
1724
|
+
...targetError !== void 0 ? { error: targetError } : enabled ? { error: { code: "not-bundle" } } : {}
|
|
1725
|
+
});
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1501
1728
|
const owned = installed && !shipped;
|
|
1502
1729
|
const removable = owned || enabled && !installed && !shipped;
|
|
1503
1730
|
const sourceOf = (packageName) => owned ? { source: dependencySpec(name, recorded[name], this.profile.dir, packageName) } : {};
|
|
1504
1731
|
const readOnlyReason = this.protectsManager(name) ? "management-required" : void 0;
|
|
1732
|
+
let availability = "missing";
|
|
1733
|
+
let version;
|
|
1505
1734
|
try {
|
|
1506
1735
|
const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
1736
|
+
availability = shipped ? "installation" : "profile";
|
|
1507
1737
|
if (info === void 0) {
|
|
1508
|
-
if (enabled) bundles.push({
|
|
1738
|
+
if (enabled || offered !== void 0) bundles.push({
|
|
1509
1739
|
name,
|
|
1740
|
+
official,
|
|
1741
|
+
availability,
|
|
1742
|
+
...catalogMeta,
|
|
1743
|
+
...target,
|
|
1510
1744
|
...sourceOf(),
|
|
1511
1745
|
enabled,
|
|
1512
1746
|
installed,
|
|
1513
1747
|
optional,
|
|
1514
1748
|
removable: removable && readOnlyReason === void 0,
|
|
1515
1749
|
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
1516
|
-
error: { code: "not-bundle" },
|
|
1750
|
+
error: targetError ?? { code: "not-bundle" },
|
|
1517
1751
|
rows: [],
|
|
1518
1752
|
overrides: []
|
|
1519
1753
|
});
|
|
1520
1754
|
continue;
|
|
1521
1755
|
}
|
|
1756
|
+
version = info.version;
|
|
1522
1757
|
const compatibility = evaluatePluginCompatibility(info, exemptions);
|
|
1523
1758
|
if (compatibility !== void 0 && !compatibility.exempted) throw new ManagementFailure("incompatible-version", [incompatiblePlugin(compatibility)]);
|
|
1524
1759
|
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
1525
|
-
const meta = readPluginMeta(info.name ?? name, pathToFileURL(join(dir, "package.json")).href);
|
|
1760
|
+
const meta = offered?.meta ?? readPluginMeta(info.name ?? name, pathToFileURL(join(dir, "package.json")).href);
|
|
1526
1761
|
bundles.push({
|
|
1527
1762
|
name,
|
|
1528
|
-
|
|
1763
|
+
official,
|
|
1764
|
+
availability,
|
|
1765
|
+
...target,
|
|
1766
|
+
...version === void 0 ? {} : { version },
|
|
1529
1767
|
...info.description === void 0 || info.description === "" ? {} : { description: info.description },
|
|
1530
1768
|
...meta === void 0 ? {} : { meta },
|
|
1531
1769
|
...sourceOf(info.name),
|
|
@@ -1534,18 +1772,24 @@ let PluginManager = (() => {
|
|
|
1534
1772
|
optional,
|
|
1535
1773
|
removable: removable && readOnlyReason === void 0,
|
|
1536
1774
|
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
1537
|
-
...this.declaredRows(name, info)
|
|
1775
|
+
...this.declaredRows(name, info),
|
|
1776
|
+
...targetError === void 0 ? {} : { error: targetError }
|
|
1538
1777
|
});
|
|
1539
1778
|
} catch (error) {
|
|
1540
|
-
if (enabled || installed) bundles.push({
|
|
1779
|
+
if (enabled || installed || optional || offered !== void 0) bundles.push({
|
|
1541
1780
|
name,
|
|
1781
|
+
official,
|
|
1782
|
+
availability,
|
|
1783
|
+
...catalogMeta,
|
|
1784
|
+
...target,
|
|
1785
|
+
...version === void 0 ? {} : { version },
|
|
1542
1786
|
...sourceOf(),
|
|
1543
1787
|
enabled,
|
|
1544
1788
|
installed,
|
|
1545
1789
|
optional,
|
|
1546
1790
|
removable: removable && readOnlyReason === void 0,
|
|
1547
1791
|
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
1548
|
-
error: managementError(error),
|
|
1792
|
+
error: targetError ?? managementError(error),
|
|
1549
1793
|
rows: [],
|
|
1550
1794
|
overrides: []
|
|
1551
1795
|
});
|
|
@@ -1584,7 +1828,7 @@ let PluginManager = (() => {
|
|
|
1584
1828
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
1585
1829
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, "utf8"));
|
|
1586
1830
|
const known = new Set([
|
|
1587
|
-
...manifest
|
|
1831
|
+
...bundleSelection(manifest),
|
|
1588
1832
|
...Object.keys(manifest.dependencies ?? {}),
|
|
1589
1833
|
...Object.keys(installation.dependencies ?? {})
|
|
1590
1834
|
]);
|
|
@@ -1687,8 +1931,8 @@ let PluginManager = (() => {
|
|
|
1687
1931
|
const row = (await this.listPlugins()).find((item) => item.entryId === id);
|
|
1688
1932
|
if (row === void 0) throw new ManagementFailure("unknown-plugin");
|
|
1689
1933
|
if (row.readOnlyReason !== void 0) throw new ManagementFailure(row.readOnlyReason);
|
|
1690
|
-
await writePluginEnabled(this.profile
|
|
1691
|
-
result
|
|
1934
|
+
await writePluginEnabled(this.profile, row.patchId, row.moduleName, enabled);
|
|
1935
|
+
Object.assign(result, await this.reload(enabled ? [row.patchId] : []));
|
|
1692
1936
|
return (await this.listPlugins()).find((item) => item.entryId === id)?.enabled !== enabled && this.ownerContext.get("hmr") !== void 0 ? "overridden" : void 0;
|
|
1693
1937
|
}), {
|
|
1694
1938
|
stage: "enable",
|
|
@@ -1704,9 +1948,7 @@ let PluginManager = (() => {
|
|
|
1704
1948
|
setBundleEnabled(name, enabled) {
|
|
1705
1949
|
return this.change((result) => this.configure(async () => {
|
|
1706
1950
|
await this.selectBundle(name, enabled);
|
|
1707
|
-
|
|
1708
|
-
result.warnings = await this.reload(enabled ? this.bundleRows(name).map((row) => row.id) : []);
|
|
1709
|
-
if (!enabled && this.ownerContext.get("hmr") !== void 0) await this.refreshPackages();
|
|
1951
|
+
Object.assign(result, enabled ? await this.publishAndReload(this.bundleRows(name).map((row) => row.id)) : await this.reload());
|
|
1710
1952
|
}), {
|
|
1711
1953
|
stage: "enable",
|
|
1712
1954
|
target: name,
|
|
@@ -1721,7 +1963,7 @@ let PluginManager = (() => {
|
|
|
1721
1963
|
* `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay.
|
|
1722
1964
|
* @param spec One package spec, including local paths relative to the invocation directory.
|
|
1723
1965
|
* @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names,
|
|
1724
|
-
* the pending build scripts to allow
|
|
1966
|
+
* the pending build scripts to allow, whether to save an exact dependency, and the registry asked first.
|
|
1725
1967
|
* @returns Package-manager diagnostics, the registries asked, and the observed activation outcome.
|
|
1726
1968
|
*/
|
|
1727
1969
|
installBundle(spec, options) {
|
|
@@ -1743,12 +1985,14 @@ let PluginManager = (() => {
|
|
|
1743
1985
|
const result = this.change(async (result) => {
|
|
1744
1986
|
if (spec.trim() === "" || spec.startsWith("-")) throw new ManagementFailure("invalid-spec");
|
|
1745
1987
|
if (stopped()) throw new InstallCancelledError();
|
|
1988
|
+
const before = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1989
|
+
const known = specPackageName(spec, before, this.profile.dir);
|
|
1990
|
+
if (this.ownerContext.get("hmr") !== void 0 && known !== void 0 && Object.hasOwn(before, known)) throw new ManagementFailure("already-installed");
|
|
1746
1991
|
if (options?.approvedBuilds !== void 0) {
|
|
1747
1992
|
await approveBuilds(this.profile.dir, options.approvedBuilds);
|
|
1748
1993
|
result.approvedBuilds = options.approvedBuilds;
|
|
1749
1994
|
}
|
|
1750
1995
|
const files = await this.readRestoredFiles();
|
|
1751
|
-
const before = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1752
1996
|
let name;
|
|
1753
1997
|
let version;
|
|
1754
1998
|
try {
|
|
@@ -1774,10 +2018,12 @@ let PluginManager = (() => {
|
|
|
1774
2018
|
}
|
|
1775
2019
|
const plan = registryPlan(options?.registry, await this.registries());
|
|
1776
2020
|
let run;
|
|
2021
|
+
let used = null;
|
|
1777
2022
|
for (const [index, registry] of plan.entries()) {
|
|
1778
2023
|
if (index > 0) await this.restoreFiles(files);
|
|
1779
2024
|
if (stopped()) throw new InstallCancelledError();
|
|
1780
2025
|
result.registries.push(registry);
|
|
2026
|
+
used = registry;
|
|
1781
2027
|
announce("installing", {
|
|
1782
2028
|
registry,
|
|
1783
2029
|
index: index + 1,
|
|
@@ -1786,6 +2032,7 @@ let PluginManager = (() => {
|
|
|
1786
2032
|
run = await this.runPnpm([
|
|
1787
2033
|
"add",
|
|
1788
2034
|
spec,
|
|
2035
|
+
...options?.saveExact === true ? ["--save-exact"] : [],
|
|
1789
2036
|
...registryArguments(registry)
|
|
1790
2037
|
], control.abort.signal, requestId);
|
|
1791
2038
|
result.packageResult = run;
|
|
@@ -1806,7 +2053,7 @@ let PluginManager = (() => {
|
|
|
1806
2053
|
if (succeeded) delete result.failedAt;
|
|
1807
2054
|
if (!succeeded) {
|
|
1808
2055
|
try {
|
|
1809
|
-
result.pendingBuilds = await
|
|
2056
|
+
result.pendingBuilds = await recordPendingBuilds(this.profile.dir, run.output);
|
|
1810
2057
|
} catch (error) {
|
|
1811
2058
|
this.ownerContext.logger.warn("Could not read pending build approvals after pnpm failed", error);
|
|
1812
2059
|
}
|
|
@@ -1814,7 +2061,10 @@ let PluginManager = (() => {
|
|
|
1814
2061
|
}
|
|
1815
2062
|
const after = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1816
2063
|
const installed = Object.keys(after).filter((name) => before[name] !== after[name]);
|
|
1817
|
-
if (installed.length === 0)
|
|
2064
|
+
if (installed.length === 0) {
|
|
2065
|
+
const local = specPackageName(spec, after, this.profile.dir);
|
|
2066
|
+
if (local !== void 0 && Object.hasOwn(after, local)) installed.push(local);
|
|
2067
|
+
}
|
|
1818
2068
|
const target = installed[0];
|
|
1819
2069
|
if (installed.length !== 1 || target === void 0) throw new ManagementFailure("ambiguous-install");
|
|
1820
2070
|
name = target;
|
|
@@ -1825,6 +2075,7 @@ let PluginManager = (() => {
|
|
|
1825
2075
|
if (compatibility !== void 0 && !compatibility.exempted) throw new ManagementFailure("incompatible-version", [incompatiblePlugin(compatibility)]);
|
|
1826
2076
|
for (const file of bundlePatchPaths(dir, manifest.dsh.bundle)) loadOverlayPatches("dsh", file);
|
|
1827
2077
|
version = manifest.version;
|
|
2078
|
+
this.installedFrom.set(name, used);
|
|
1828
2079
|
} catch (error) {
|
|
1829
2080
|
await this.restoreFiles(files);
|
|
1830
2081
|
throw error;
|
|
@@ -1837,9 +2088,9 @@ let PluginManager = (() => {
|
|
|
1837
2088
|
result.stage = "enable";
|
|
1838
2089
|
return this.configure(async () => {
|
|
1839
2090
|
if (options?.enabled !== false) await this.selectBundle(name, true);
|
|
1840
|
-
if (Object.hasOwn(before, name)
|
|
1841
|
-
|
|
1842
|
-
|
|
2091
|
+
if (Object.hasOwn(before, name) && this.ownerContext.get("hmr") === void 0) return;
|
|
2092
|
+
const selected = bundleSelection(readProfileManifest("dsh", this.profile.dir)).includes(name);
|
|
2093
|
+
Object.assign(result, await this.publishAndReload(selected ? this.bundleRows(name).map((row) => row.id) : []));
|
|
1843
2094
|
});
|
|
1844
2095
|
}, {
|
|
1845
2096
|
stage: "install",
|
|
@@ -1891,14 +2142,20 @@ let PluginManager = (() => {
|
|
|
1891
2142
|
const contributions = bundle.error === void 0 ? this.bundleRows(name) : [];
|
|
1892
2143
|
if (bundle.enabled) {
|
|
1893
2144
|
await this.selectBundle(name, false);
|
|
1894
|
-
result
|
|
2145
|
+
Object.assign(result, await this.reload());
|
|
1895
2146
|
}
|
|
1896
2147
|
if ([...this.ctx.loader.entries()].some((entry) => entry.fiber?.uid != null && contributions.some((row) => row.id === entry.options.id && row.name === entry.options.name))) throw new ManagementFailure("bundle-in-use");
|
|
1897
2148
|
return bundle.installed;
|
|
1898
2149
|
})) return;
|
|
1899
|
-
|
|
2150
|
+
const registry = this.installedFrom.has(name) ? this.installedFrom.get(name) : this.configuredRegistries.registry;
|
|
2151
|
+
result.packageResult = await this.runPnpm([
|
|
2152
|
+
"remove",
|
|
2153
|
+
name,
|
|
2154
|
+
...typeof registry === "string" ? [`--config.registry=${registry}`] : []
|
|
2155
|
+
]);
|
|
1900
2156
|
if (result.packageResult.exitCode !== 0 || result.packageResult.timedOut === true) throw new Error(result.packageResult.output);
|
|
1901
|
-
|
|
2157
|
+
this.installedFrom.delete(name);
|
|
2158
|
+
Object.assign(result, await this.configure(() => this.publishAndReload()));
|
|
1902
2159
|
}, {
|
|
1903
2160
|
stage: "remove",
|
|
1904
2161
|
target: name
|
|
@@ -1924,7 +2181,7 @@ let PluginManager = (() => {
|
|
|
1924
2181
|
const rows = [];
|
|
1925
2182
|
const packages = this.ctx.get("pluginPackages");
|
|
1926
2183
|
for (const row of flatten(composeEntries([patches.filter((item) => item.insert !== void 0)]))) {
|
|
1927
|
-
if (typeof row.id !== "string" || typeof row.name !== "string") continue;
|
|
2184
|
+
if (typeof row.id !== "string" || typeof row.name !== "string" || row.group === true) continue;
|
|
1928
2185
|
const active = live.get(row.id);
|
|
1929
2186
|
const entryId = active?.entryId;
|
|
1930
2187
|
const base = active?.baseUrl ?? pathToFileURL(join(dir, "package.json")).href;
|
|
@@ -2021,7 +2278,7 @@ let PluginManager = (() => {
|
|
|
2021
2278
|
}
|
|
2022
2279
|
async selectBundle(name, enabled) {
|
|
2023
2280
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
2024
|
-
const previous = manifest
|
|
2281
|
+
const previous = bundleSelection(manifest);
|
|
2025
2282
|
if (enabled || !previous.includes(name)) {
|
|
2026
2283
|
const metadata = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
2027
2284
|
if (metadata === void 0) throw new ManagementFailure("not-bundle");
|
|
@@ -2071,16 +2328,46 @@ let PluginManager = (() => {
|
|
|
2071
2328
|
};
|
|
2072
2329
|
return hmr === void 0 ? apply() : hmr.runExclusive(apply);
|
|
2073
2330
|
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Publish the profile's runtime resolution without HMR, unless a deselected startup bundle still runs and needs the
|
|
2333
|
+
* current table. A publication that needs a restart waits for the restart the result already reports.
|
|
2334
|
+
*/
|
|
2074
2335
|
async refreshPackages() {
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2336
|
+
const selected = bundleSelection(readProfileManifest("dsh", this.profile.dir));
|
|
2337
|
+
if (this.profile.startedBundles.some((name) => !selected.includes(name))) return;
|
|
2338
|
+
try {
|
|
2339
|
+
const packages = this.ownerContext.get("pluginPackages");
|
|
2340
|
+
if (packages?.resolution !== void 0) await packages.refresh();
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
if (!(error instanceof ResolutionRestartRequired)) throw error;
|
|
2078
2343
|
}
|
|
2079
|
-
await this.ownerContext.get("pluginPackages")?.refresh();
|
|
2080
2344
|
}
|
|
2345
|
+
/**
|
|
2346
|
+
* Apply a package change: without HMR, publish the runtime resolution when that is safe, then {@link reload}.
|
|
2347
|
+
* @param requiredIds Entry ids that must start.
|
|
2348
|
+
* @returns The reload's outcome.
|
|
2349
|
+
*/
|
|
2350
|
+
async publishAndReload(requiredIds = []) {
|
|
2351
|
+
if (this.ownerContext.get("hmr") === void 0) await this.refreshPackages();
|
|
2352
|
+
return this.reload(requiredIds);
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Apply the saved profile; with HMR it recomposes live, including the runtime resolution.
|
|
2356
|
+
* @param requiredIds Entry ids that must start.
|
|
2357
|
+
* @returns Warnings about entries that were already inactive, and why the change waits for a restart, if it does.
|
|
2358
|
+
*/
|
|
2081
2359
|
async reload(requiredIds = []) {
|
|
2082
|
-
|
|
2083
|
-
|
|
2360
|
+
const hmr = this.ownerContext.get("hmr");
|
|
2361
|
+
if (hmr === void 0) {
|
|
2362
|
+
composeEntries([readProfilePatches("dsh", this.profile)]);
|
|
2363
|
+
return { warnings: [] };
|
|
2364
|
+
}
|
|
2365
|
+
const { warnings, pending, restartReason } = await hmr.reconcileProfile(requiredIds);
|
|
2366
|
+
const restart = restartReason ?? pending[0]?.reason;
|
|
2367
|
+
return restart === void 0 ? { warnings } : {
|
|
2368
|
+
warnings,
|
|
2369
|
+
restartReason: restart
|
|
2370
|
+
};
|
|
2084
2371
|
}
|
|
2085
2372
|
async change(operation, request, reason) {
|
|
2086
2373
|
return withFileLock(join(this.profile.dir, "package.json"), async () => {
|
|
@@ -2089,10 +2376,15 @@ let PluginManager = (() => {
|
|
|
2089
2376
|
const result = {
|
|
2090
2377
|
...request,
|
|
2091
2378
|
changed: false,
|
|
2092
|
-
|
|
2379
|
+
...this.ownerContext.get("hmr") !== void 0 ? { application: "applied" } : {
|
|
2380
|
+
application: "restart-required",
|
|
2381
|
+
restartReason: "no-hmr"
|
|
2382
|
+
}
|
|
2093
2383
|
};
|
|
2094
2384
|
try {
|
|
2095
|
-
|
|
2385
|
+
const outcome = await operation(result);
|
|
2386
|
+
if (outcome !== void 0) result.application = outcome;
|
|
2387
|
+
else if (result.application === "applied" && result.restartReason !== void 0) result.application = "restart-required";
|
|
2096
2388
|
} catch (error) {
|
|
2097
2389
|
if (error instanceof InstallCancelledError) result.application = "cancelled";
|
|
2098
2390
|
else {
|
|
@@ -2100,6 +2392,7 @@ let PluginManager = (() => {
|
|
|
2100
2392
|
result.error = managementError(error);
|
|
2101
2393
|
}
|
|
2102
2394
|
}
|
|
2395
|
+
if (result.application !== "restart-required") delete result.restartReason;
|
|
2103
2396
|
result.changed = before !== this.diskState();
|
|
2104
2397
|
this.ownerContext.emit("plugin-manager/changed", { reason });
|
|
2105
2398
|
return result;
|