@deepseek-ai/dsh-plugin-manager 0.2.1-alpha.2 → 0.2.1-alpha.4
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 +12 -12
- package/README.md +10 -9
- package/README.zh.md +11 -10
- package/lib/index.js +451 -279
- package/lib/typert.host.js +400 -242
- package/lib/typert.remote-client.d.ts +16 -2
- package/lib/typert.remote-client.js +387 -238
- package/lib/types/failure.d.ts +1 -1
- package/lib/types/failure.js +2 -2
- package/lib/types/index.d.ts +20 -0
- package/lib/types/index.js +121 -42
- package/lib/types/operations.d.ts +1 -0
- package/lib/types/operations.js +322 -257
- package/lib/types/patch.d.ts +12 -3
- package/lib/types/patch.js +47 -9
- package/lib/types/tools.js +69 -7
- package/lib/types/types.d.ts +14 -4
- 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 +39 -35
package/lib/index.js
CHANGED
|
@@ -7,7 +7,7 @@ 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 { OFFICIAL_ON_DEMAND_CATALOG, OPTIONAL_BUNDLES, PROFILE_COMPATIBILITY_FILENAME, bundlePatchPaths, composeEntries, evaluatePluginCompatibility, getDshRuntimeVersion, 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";
|
|
@@ -221,19 +221,77 @@ var ManagementFailure = class extends Error {
|
|
|
221
221
|
/**
|
|
222
222
|
* Drop the exemption status from an unexempted compatibility result.
|
|
223
223
|
* @param issue Result whose exemption is not active.
|
|
224
|
-
* @returns The package, runtime, and
|
|
224
|
+
* @returns The package, runtime, and unmet DSH requirements.
|
|
225
225
|
*/
|
|
226
226
|
function incompatiblePlugin(issue) {
|
|
227
227
|
return {
|
|
228
228
|
name: issue.name,
|
|
229
229
|
version: issue.version,
|
|
230
230
|
runtimeVersion: issue.runtimeVersion,
|
|
231
|
-
|
|
231
|
+
requirements: issue.requirements
|
|
232
232
|
};
|
|
233
233
|
}
|
|
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.
|
|
@@ -332,7 +390,7 @@ function namedSpecs(args) {
|
|
|
332
390
|
}
|
|
333
391
|
/** The manifest a named spec would install, read without installing it.
|
|
334
392
|
* A path spec is read from disk. A registry spec asks pnpm's own configuration for the version the
|
|
335
|
-
* range selects and its
|
|
393
|
+
* range selects and its dsh requirements (`engines` and peerDependencies). A git or tarball spec needs the fetch itself, so the
|
|
336
394
|
* check after installation is what judges it.
|
|
337
395
|
* @param dir Profile directory the lookup runs in.
|
|
338
396
|
* @param spec Anchored install spec.
|
|
@@ -354,6 +412,7 @@ async function namedSpecManifest(dir, spec, options, environment, flags) {
|
|
|
354
412
|
parsed.spec,
|
|
355
413
|
"name",
|
|
356
414
|
"version",
|
|
415
|
+
"engines",
|
|
357
416
|
"peerDependencies",
|
|
358
417
|
"--json",
|
|
359
418
|
...flags,
|
|
@@ -461,254 +520,268 @@ function bundleComponentManifests(manifest, dir, anchor) {
|
|
|
461
520
|
* @param options Output, activation and cancellation policy.
|
|
462
521
|
* @returns Exit status, whether the silence bound stopped the run, and the diagnostic path.
|
|
463
522
|
* A compatibility denial returns exit code 1. Service output is bounded; CLI output uses inherited descriptors.
|
|
523
|
+
* The diagnostic file is closed on return or rejection, including preparation and spawn failures.
|
|
464
524
|
*/
|
|
465
525
|
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
|
+
const env_1 = {
|
|
527
|
+
stack: [],
|
|
528
|
+
error: void 0,
|
|
529
|
+
hasError: false
|
|
526
530
|
};
|
|
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
|
-
}
|
|
596
|
-
};
|
|
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
531
|
try {
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
532
|
+
const dir = context.dir ?? resolveProfileDir(context.profile, context.home);
|
|
533
|
+
const active = await activeRecordedRun(dir);
|
|
534
|
+
const logRoot = join(dir, ".plugin-manager", "logs");
|
|
535
|
+
await mkdir(logRoot, {
|
|
536
|
+
recursive: true,
|
|
537
|
+
mode: 448
|
|
605
538
|
});
|
|
606
|
-
const
|
|
607
|
-
await
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
child.stderr?.destroy();
|
|
620
|
-
const notice = "dsh: pnpm output was cut short after its process exited\n";
|
|
621
|
-
await log.write(notice);
|
|
622
|
-
if (failure !== void 0) throw failure;
|
|
623
|
-
options.onOutput?.(notice, "stderr");
|
|
624
|
-
append(Buffer.from(notice));
|
|
625
|
-
}
|
|
626
|
-
const [completion] = await settled;
|
|
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));
|
|
539
|
+
const logPath = join(await mkdtemp(join(logRoot, "operation-")), "pnpm.log");
|
|
540
|
+
const log = __addDisposableResource(env_1, await open(logPath, "wx", 384), true);
|
|
541
|
+
if (active !== void 0) {
|
|
542
|
+
await log.write(active);
|
|
543
|
+
await log.close();
|
|
544
|
+
options.onOutput?.(active, "stderr");
|
|
545
|
+
const bytes = Buffer.from(active);
|
|
546
|
+
return {
|
|
547
|
+
exitCode: 1,
|
|
548
|
+
output: bytes.subarray(Math.max(0, bytes.length - options.outputBytes)).toString("utf8"),
|
|
549
|
+
truncated: bytes.length > options.outputBytes,
|
|
550
|
+
logPath
|
|
551
|
+
};
|
|
635
552
|
}
|
|
636
|
-
|
|
637
|
-
|
|
553
|
+
const before = readProfileManifest("dsh", dir);
|
|
554
|
+
const savedFiles = ["package.json", "pnpm-lock.yaml"].map((name) => ({
|
|
555
|
+
path: join(dir, name),
|
|
556
|
+
text: optionalFile(join(dir, name))
|
|
557
|
+
}));
|
|
558
|
+
const beforeDependencies = directDependencies(before);
|
|
559
|
+
const installedBefore = new Map(Object.keys(beforeDependencies).map((name) => [name, optionalFile(join(dir, "node_modules", name, "package.json"))]));
|
|
560
|
+
let output = Buffer.alloc(0);
|
|
561
|
+
let truncated = false;
|
|
562
|
+
const append = (bytes) => {
|
|
563
|
+
output = Buffer.concat([output, bytes]);
|
|
564
|
+
if (output.length > options.outputBytes) {
|
|
565
|
+
truncated = true;
|
|
566
|
+
output = output.subarray(output.length - options.outputBytes);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
const environment = {
|
|
570
|
+
...options.execution === "cli" ? process.env : scrubbedParentEnv(),
|
|
571
|
+
...options.env
|
|
572
|
+
};
|
|
573
|
+
const restore = async () => {
|
|
574
|
+
for (const file of savedFiles) if (file.text === void 0) await rm(file.path, { force: true });
|
|
575
|
+
else await writeFileAtomic(file.path, file.text, { mode: 384 });
|
|
576
|
+
};
|
|
577
|
+
/** Packages a compatibility check refused; callers render them for their own surface. */
|
|
578
|
+
const incompatible = [];
|
|
579
|
+
const rejected = async (warnings, restoration) => {
|
|
580
|
+
const diagnostic = `\ndsh: installation rejected: ${warnings.join("\n")}\ndsh: ${restoration}.\n`;
|
|
638
581
|
await log.write(diagnostic);
|
|
639
|
-
|
|
640
|
-
|
|
582
|
+
options.onOutput?.(diagnostic, "stderr");
|
|
583
|
+
append(Buffer.from(diagnostic));
|
|
584
|
+
await log.close();
|
|
585
|
+
return {
|
|
586
|
+
exitCode: 1,
|
|
587
|
+
output: output.toString("utf8"),
|
|
588
|
+
truncated,
|
|
589
|
+
logPath,
|
|
590
|
+
incompatible
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
const preflight = [];
|
|
594
|
+
const exemptions = readProfileVersionExemptions(dir);
|
|
595
|
+
const registryFlags = args.filter((argument) => argument.startsWith("--registry="));
|
|
596
|
+
for (const raw of namedSpecs(args)) try {
|
|
597
|
+
const manifest = await namedSpecManifest(dir, anchorPathSpec(raw, context.cwd), options, environment, registryFlags);
|
|
598
|
+
if (manifest === void 0) continue;
|
|
599
|
+
const issue = evaluatePluginCompatibility(manifest, exemptions);
|
|
600
|
+
if (issue !== void 0 && !issue.exempted) {
|
|
601
|
+
preflight.push(pluginCompatibilityWarning(issue));
|
|
602
|
+
incompatible.push(incompatiblePlugin(issue));
|
|
603
|
+
}
|
|
604
|
+
} catch (error) {
|
|
605
|
+
continue;
|
|
641
606
|
}
|
|
642
|
-
if (
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
607
|
+
if (preflight.length > 0) return await rejected(preflight, "nothing was installed");
|
|
608
|
+
const cancellation = new AbortController();
|
|
609
|
+
const grouped = leadsOwnGroup(options.execution);
|
|
610
|
+
const child = execa(options.command ?? "pnpm", [...options.args ?? [], ...args.map((arg) => anchorPathSpec(arg, context.cwd))], {
|
|
611
|
+
cwd: dir,
|
|
612
|
+
env: environment,
|
|
613
|
+
extendEnv: false,
|
|
614
|
+
reject: false,
|
|
615
|
+
stdout: options.execution === "cli" ? "inherit" : "pipe",
|
|
616
|
+
stderr: options.execution === "cli" ? "inherit" : "pipe",
|
|
617
|
+
killDescendants: options.execution === "service",
|
|
618
|
+
buffer: false,
|
|
619
|
+
stdin: options.execution === "cli" ? "inherit" : "ignore",
|
|
620
|
+
cancelSignal: options.signal === void 0 ? cancellation.signal : AbortSignal.any([cancellation.signal, options.signal])
|
|
621
|
+
});
|
|
622
|
+
const exited = once(child.nodeChildProcess, "exit").catch(() => void 0);
|
|
623
|
+
let writes = Promise.resolve();
|
|
624
|
+
/** `settled` records that the process outcome is known; `stalled` that the silence bound stopped the run. */
|
|
625
|
+
const control = {
|
|
626
|
+
settled: false,
|
|
627
|
+
stalled: false
|
|
628
|
+
};
|
|
629
|
+
/** Set once this call cuts the reading short itself, so the close it causes is not read as a run failure. */
|
|
630
|
+
let cut = false;
|
|
631
|
+
/** The first failure a reading hit before that cut, which the run still reports. */
|
|
632
|
+
let failure;
|
|
633
|
+
let idleTimer;
|
|
634
|
+
/** The silence bound: a captured run that stops printing without exiting is terminated, never awaited. */
|
|
635
|
+
const armIdle = () => {
|
|
636
|
+
if (control.settled || options.idleTimeoutMs === void 0) return;
|
|
637
|
+
clearTimeout(idleTimer);
|
|
638
|
+
idleTimer = setTimeout(() => {
|
|
639
|
+
control.stalled = true;
|
|
640
|
+
child.kill();
|
|
641
|
+
}, options.idleTimeoutMs);
|
|
642
|
+
};
|
|
643
|
+
const collect = async (stream, kind) => {
|
|
644
|
+
try {
|
|
645
|
+
for await (const chunk of stream) {
|
|
646
|
+
armIdle();
|
|
647
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
648
|
+
writes = writes.then(async () => {
|
|
649
|
+
await log.write(bytes);
|
|
650
|
+
});
|
|
651
|
+
await writes;
|
|
652
|
+
options.onOutput?.(bytes.toString("utf8"), kind);
|
|
653
|
+
append(bytes);
|
|
663
654
|
}
|
|
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");
|
|
655
|
+
} catch (error) {
|
|
656
|
+
if (!cut) {
|
|
657
|
+
failure ??= error instanceof Error ? error : new Error(String(error));
|
|
658
|
+
cancellation.abort();
|
|
672
659
|
}
|
|
660
|
+
throw error;
|
|
673
661
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
662
|
+
};
|
|
663
|
+
const collectors = [...child.stdout === null ? [] : [collect(child.stdout, "stdout")], ...child.stderr === null ? [] : [collect(child.stderr, "stderr")]];
|
|
664
|
+
for (const collector of collectors) collector.catch(() => {});
|
|
665
|
+
if (collectors.length > 0) armIdle();
|
|
666
|
+
let exitCode;
|
|
667
|
+
try {
|
|
668
|
+
await recordRun(dir, {
|
|
669
|
+
pid: child.pid,
|
|
670
|
+
grouped
|
|
671
|
+
});
|
|
672
|
+
const settled = Promise.allSettled([child]);
|
|
673
|
+
await Promise.race([exited, settled]);
|
|
674
|
+
control.settled = true;
|
|
675
|
+
clearTimeout(idleTimer);
|
|
676
|
+
if (control.stalled) await awaitTreeGone({
|
|
677
|
+
pid: child.pid,
|
|
678
|
+
grouped
|
|
679
|
+
});
|
|
680
|
+
if (await drainWithin(collectors, DRAIN_AFTER_EXIT_MS)) {
|
|
681
|
+
for (const stream of await Promise.allSettled(collectors)) if (stream.status === "rejected") throw stream.reason;
|
|
682
|
+
} else {
|
|
683
|
+
cut = true;
|
|
684
|
+
child.stdout?.destroy();
|
|
685
|
+
child.stderr?.destroy();
|
|
686
|
+
const notice = "dsh: pnpm output was cut short after its process exited\n";
|
|
687
|
+
await log.write(notice);
|
|
688
|
+
if (failure !== void 0) throw failure;
|
|
689
|
+
options.onOutput?.(notice, "stderr");
|
|
690
|
+
append(Buffer.from(notice));
|
|
691
|
+
}
|
|
692
|
+
const [completion] = await settled;
|
|
693
|
+
if (completion.status === "rejected") throw completion.reason;
|
|
694
|
+
const result = completion.value;
|
|
695
|
+
exitCode = result.exitCode ?? (result.code === "ENOENT" ? 127 : 1);
|
|
696
|
+
if (control.stalled) {
|
|
697
|
+
const notice = `dsh: pnpm printed nothing for ${String(options.idleTimeoutMs)}ms and was terminated\n`;
|
|
698
|
+
await log.write(notice);
|
|
699
|
+
options.onOutput?.(notice, "stderr");
|
|
700
|
+
append(Buffer.from(notice));
|
|
701
|
+
}
|
|
702
|
+
if (result.failed && output.length === 0) {
|
|
703
|
+
const diagnostic = result.shortMessage ?? "pnpm failed";
|
|
693
704
|
await log.write(diagnostic);
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
705
|
+
truncated = Buffer.byteLength(diagnostic) > options.outputBytes;
|
|
706
|
+
output = Buffer.from(diagnostic).subarray(0, options.outputBytes);
|
|
707
|
+
}
|
|
708
|
+
if (exitCode === 0 && !control.stalled) {
|
|
709
|
+
const after = readProfileManifest("dsh", dir);
|
|
710
|
+
const warnings = [];
|
|
711
|
+
for (const [name, spec] of Object.entries(directDependencies(after))) {
|
|
712
|
+
const packageDir = join(dir, "node_modules", name);
|
|
713
|
+
const installed = optionalFile(join(packageDir, "package.json"));
|
|
714
|
+
if (installed === void 0) continue;
|
|
715
|
+
const untouched = beforeDependencies[name] === spec && installedBefore.get(name) === installed;
|
|
716
|
+
const found = [];
|
|
717
|
+
const issues = [];
|
|
718
|
+
try {
|
|
719
|
+
const manifest = readProfileManifest("dsh", packageDir);
|
|
720
|
+
for (const candidate of [manifest, ...bundleComponentManifests(manifest, packageDir, context.installAnchor)]) {
|
|
721
|
+
const issue = evaluatePluginCompatibility(candidate, readProfileVersionExemptions(dir));
|
|
722
|
+
if (issue !== void 0 && !issue.exempted) {
|
|
723
|
+
found.push(pluginCompatibilityWarning(issue));
|
|
724
|
+
issues.push(incompatiblePlugin(issue));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} catch (error) {
|
|
728
|
+
found.push(`Cannot validate installed package ${name}: ${String(error)}`);
|
|
729
|
+
}
|
|
730
|
+
if (found.length === 0) continue;
|
|
731
|
+
if (!untouched) {
|
|
732
|
+
warnings.push(...found);
|
|
733
|
+
incompatible.push(...issues);
|
|
734
|
+
} else {
|
|
735
|
+
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`;
|
|
736
|
+
await log.write(notice);
|
|
737
|
+
options.onOutput?.(notice, "stderr");
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (warnings.length > 0) {
|
|
741
|
+
await restore();
|
|
742
|
+
const repair = ["install", savedFiles.some((file) => file.path.endsWith("pnpm-lock.yaml") && file.text !== void 0) ? "--frozen-lockfile" : "--config.lockfile=false"];
|
|
743
|
+
const repairing = execa(options.command ?? "pnpm", [...options.args ?? [], ...repair], {
|
|
744
|
+
cwd: dir,
|
|
745
|
+
env: environment,
|
|
746
|
+
extendEnv: false,
|
|
747
|
+
reject: false,
|
|
748
|
+
stdin: "ignore",
|
|
749
|
+
...options.idleTimeoutMs === void 0 ? {} : { timeout: options.idleTimeoutMs }
|
|
750
|
+
});
|
|
751
|
+
await recordRun(dir, {
|
|
752
|
+
pid: repairing.pid,
|
|
753
|
+
grouped: false
|
|
754
|
+
});
|
|
755
|
+
const repaired = await repairing;
|
|
756
|
+
exitCode = 1;
|
|
757
|
+
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'";
|
|
758
|
+
const diagnostic = `\ndsh: installation rejected: ${warnings.join("\n")}\ndsh: ${restoration}.\n`;
|
|
759
|
+
await log.write(diagnostic);
|
|
760
|
+
options.onOutput?.(diagnostic, "stderr");
|
|
761
|
+
append(Buffer.from(diagnostic));
|
|
762
|
+
} else if (options.activateNewBundles !== false) await reconcile(before, dir, context.installAnchor, options);
|
|
763
|
+
}
|
|
764
|
+
} finally {
|
|
765
|
+
control.settled = true;
|
|
766
|
+
clearTimeout(idleTimer);
|
|
767
|
+
await rm(runRecordPath(dir), { force: true });
|
|
768
|
+
await log.close();
|
|
697
769
|
}
|
|
770
|
+
return {
|
|
771
|
+
exitCode,
|
|
772
|
+
output: output.toString("utf8"),
|
|
773
|
+
truncated,
|
|
774
|
+
logPath,
|
|
775
|
+
...control.stalled ? { timedOut: true } : {},
|
|
776
|
+
...incompatible.length > 0 ? { incompatible } : {}
|
|
777
|
+
};
|
|
778
|
+
} catch (e_1) {
|
|
779
|
+
env_1.error = e_1;
|
|
780
|
+
env_1.hasError = true;
|
|
698
781
|
} finally {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
await rm(runRecordPath(dir), { force: true });
|
|
702
|
-
await log.close();
|
|
782
|
+
const result_1 = __disposeResources(env_1);
|
|
783
|
+
if (result_1) await result_1;
|
|
703
784
|
}
|
|
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
785
|
}
|
|
713
786
|
/**
|
|
714
787
|
* Read the registry pnpm's own configuration names in the profile: its `.npmrc` chain and workspace settings,
|
|
@@ -939,15 +1012,25 @@ function attributeFailure(kind, log, spec) {
|
|
|
939
1012
|
}
|
|
940
1013
|
//#endregion
|
|
941
1014
|
//#region lib/types/patch.js
|
|
942
|
-
/** Comment-preserving profile plugin enablement edits. */
|
|
943
|
-
/**
|
|
944
|
-
* @param
|
|
1015
|
+
/** Comment-preserving profile plugin enablement edits and the entry flattening they compare against. */
|
|
1016
|
+
/** Flatten only the groups addressable by the profile's patch composer.
|
|
1017
|
+
* @param rows Composed top-level entries.
|
|
1018
|
+
* @returns Entries in depth-first order.
|
|
1019
|
+
*/
|
|
1020
|
+
function flatten(rows) {
|
|
1021
|
+
return rows.flatMap((row) => [row, ...row.group && Array.isArray(row.config) ? flatten(row.config) : []]);
|
|
1022
|
+
}
|
|
1023
|
+
/** Replace the last matching override or append one after existing insertions. An override that holds only `id` and
|
|
1024
|
+
* `disabled`, without anchors, is removed, or not appended, when the bundle layers and the remaining profile patches
|
|
1025
|
+
* already give the entry the requested literal `disabled` value; home patches and launch overlays are not consulted.
|
|
1026
|
+
* @param profile Profile whose patch file is edited and whose selected bundles supply the compared composition.
|
|
945
1027
|
* @param id Unique composition entry id.
|
|
946
1028
|
* @param name Module name used to match name-qualified overrides.
|
|
947
1029
|
* @param enabled Desired entry enablement.
|
|
948
1030
|
* @returns Whether the file changed.
|
|
949
1031
|
*/
|
|
950
|
-
async function writePluginEnabled(
|
|
1032
|
+
async function writePluginEnabled(profile, id, name, enabled) {
|
|
1033
|
+
const filename = profile.patchPath;
|
|
951
1034
|
let text;
|
|
952
1035
|
try {
|
|
953
1036
|
text = await readFile(filename, "utf8");
|
|
@@ -962,20 +1045,36 @@ async function writePluginEnabled(filename, id, name, enabled) {
|
|
|
962
1045
|
const error = document.errors[0];
|
|
963
1046
|
if (error !== void 0) throw error;
|
|
964
1047
|
if (!isSeq(document.contents)) throw new Error("Profile patch must be a YAML sequence");
|
|
965
|
-
loadOptionalPatches("dsh", filename);
|
|
1048
|
+
const patches = loadOptionalPatches("dsh", filename) ?? [];
|
|
1049
|
+
const bundlePatches = readBundlePatches("dsh", profile);
|
|
966
1050
|
const items = document.contents.items;
|
|
967
1051
|
const target = items.findLast((item, index) => {
|
|
968
1052
|
if (!isMap(item) || document.getIn([index, "id"]) !== id || item.has("insert")) return false;
|
|
969
1053
|
const expectedName = document.getIn([index, "name"]);
|
|
970
1054
|
return !expectedName || expectedName === name;
|
|
971
1055
|
});
|
|
1056
|
+
const implied = (index) => {
|
|
1057
|
+
const row = flatten(composeEntries([[...bundlePatches, ...patches.toSpliced(index, 1)]])).findLast((entry) => entry.id === id);
|
|
1058
|
+
return row !== void 0 && (row.disabled ?? false) === !enabled;
|
|
1059
|
+
};
|
|
972
1060
|
if (isMap(target)) {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1061
|
+
const index = items.indexOf(target);
|
|
1062
|
+
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)) {
|
|
1063
|
+
document.deleteIn([index]);
|
|
1064
|
+
const comment = target.commentBefore;
|
|
1065
|
+
const next = items[index];
|
|
1066
|
+
if (comment && isNode(next)) next.commentBefore = next.commentBefore ? `${comment}\n${next.commentBefore}` : comment;
|
|
1067
|
+
else if (comment && items.length === 0) document.contents.commentBefore = comment;
|
|
1068
|
+
else if (comment) document.comment = document.comment ? `${comment}\n${document.comment}` : comment;
|
|
1069
|
+
} else if (document.getIn([index, "disabled"]) === !enabled) return false;
|
|
1070
|
+
else document.setIn([index, "disabled"], !enabled);
|
|
1071
|
+
} else {
|
|
1072
|
+
if (implied(items.length)) return false;
|
|
1073
|
+
document.add({
|
|
1074
|
+
id,
|
|
1075
|
+
disabled: !enabled
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
979
1078
|
await writeFileAtomic(filename, String(document), { mode: 384 });
|
|
980
1079
|
return true;
|
|
981
1080
|
}
|
|
@@ -1183,6 +1282,7 @@ const protectedModules = new Set([
|
|
|
1183
1282
|
"@deepseek-ai/dsh-host-plugin-inventory",
|
|
1184
1283
|
"@deepseek-ai/dsh-typert-registry",
|
|
1185
1284
|
"@deepseek-ai/dsh-api-remotes",
|
|
1285
|
+
"@deepseek-ai/dsh-api-remote-access",
|
|
1186
1286
|
"@deepseek-ai/cordis-plugin-timer",
|
|
1187
1287
|
"@deepseek-ai/dsh-client-connection",
|
|
1188
1288
|
"@deepseek-ai/dsh-host-frontend-static",
|
|
@@ -1193,10 +1293,6 @@ const protectedModules = new Set([
|
|
|
1193
1293
|
const RESTORED_FILES = ["package.json", "pnpm-lock.yaml"];
|
|
1194
1294
|
/** pnpm's colour escapes, which a JSON answer may be wrapped in. */
|
|
1195
1295
|
const ANSI_SEQUENCE = /\x1b\[[0-9;]*m/g;
|
|
1196
|
-
/** Flatten only the groups addressable by the profile's patch composer. */
|
|
1197
|
-
function flatten(rows) {
|
|
1198
|
-
return rows.flatMap((row) => [row, ...row.group && Array.isArray(row.config) ? flatten(row.config) : []]);
|
|
1199
|
-
}
|
|
1200
1296
|
/** Preserve the exact observed diagnostic, including non-Error failures. */
|
|
1201
1297
|
function messageOf(error) {
|
|
1202
1298
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -1262,6 +1358,33 @@ function printedError(printed) {
|
|
|
1262
1358
|
const { code, message } = error;
|
|
1263
1359
|
return [code, message].filter((part) => typeof part === "string").join(" ");
|
|
1264
1360
|
}
|
|
1361
|
+
/** The bundles a profile manifest selects. */
|
|
1362
|
+
function bundleSelection(manifest) {
|
|
1363
|
+
return manifest.dsh?.profile?.bundles ?? [];
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* The package an installation spec names before pnpm runs: a registry name, a local directory's manifest name, or the
|
|
1367
|
+
* dependency saved with this git or tarball spec, compared as the plugin list shows saved specs.
|
|
1368
|
+
* @param spec The installation spec.
|
|
1369
|
+
* @param dependencies The profile's saved dependencies.
|
|
1370
|
+
* @param profileDir The profile directory saved local paths are relative to.
|
|
1371
|
+
* @returns The package name, or `undefined` when only the installation tells it.
|
|
1372
|
+
*/
|
|
1373
|
+
function specPackageName(spec, dependencies, profileDir) {
|
|
1374
|
+
const parsed = parsedForRegistry(spec);
|
|
1375
|
+
if (parsed.kind === "registry") return parsed.name;
|
|
1376
|
+
if (parsed.kind === "path") return packageName(parsed.path);
|
|
1377
|
+
const specs = parsed.kind === "tarball" && parsed.path !== void 0 ? [spec, `file:${parsed.path}`] : [spec];
|
|
1378
|
+
return Object.entries(dependencies).find(([dependency, saved]) => specs.includes(dependencySpec(dependency, saved, profileDir)))?.[0];
|
|
1379
|
+
}
|
|
1380
|
+
/** The package name a local directory declares, or `undefined` without a readable manifest. */
|
|
1381
|
+
function packageName(dir) {
|
|
1382
|
+
try {
|
|
1383
|
+
return readProfileManifest("dsh", dir).name;
|
|
1384
|
+
} catch (_error) {
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1265
1388
|
/** 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. */
|
|
1266
1389
|
function parsedForRegistry(spec) {
|
|
1267
1390
|
try {
|
|
@@ -1459,6 +1582,11 @@ let PluginManager = (() => {
|
|
|
1459
1582
|
});
|
|
1460
1583
|
/** Management bundles remain protected if their files become unreadable. */
|
|
1461
1584
|
managementBundles = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Set());
|
|
1585
|
+
/**
|
|
1586
|
+
* The registry each package was last installed from by this manager in this process. pnpm verifies lockfile tarball
|
|
1587
|
+
* URLs against its default registry unless they belong to a named registry, so a later removal names it again.
|
|
1588
|
+
*/
|
|
1589
|
+
installedFrom = /* @__PURE__ */ new Map();
|
|
1462
1590
|
ownerEntryId;
|
|
1463
1591
|
packageOperations = /* @__PURE__ */ new Set();
|
|
1464
1592
|
profile;
|
|
@@ -1515,7 +1643,7 @@ let PluginManager = (() => {
|
|
|
1515
1643
|
setVersionExemption(packageVersion, runtimeVersion, enabled, acceptRisk) {
|
|
1516
1644
|
return this.change((result) => this.configure(async () => {
|
|
1517
1645
|
await setProfileVersionExemption(this.profile.dir, packageVersion, runtimeVersion, enabled, acceptRisk === true);
|
|
1518
|
-
result
|
|
1646
|
+
Object.assign(result, await this.reload());
|
|
1519
1647
|
}), {
|
|
1520
1648
|
stage: "enable",
|
|
1521
1649
|
target: packageVersion,
|
|
@@ -1553,7 +1681,7 @@ let PluginManager = (() => {
|
|
|
1553
1681
|
listBundles() {
|
|
1554
1682
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
1555
1683
|
const exemptions = readProfileVersionExemptions(this.profile.dir);
|
|
1556
|
-
const selected = manifest
|
|
1684
|
+
const selected = bundleSelection(manifest);
|
|
1557
1685
|
const recorded = manifest.dependencies ?? {};
|
|
1558
1686
|
const dependencies = Object.keys(recorded);
|
|
1559
1687
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, "utf8"));
|
|
@@ -1701,7 +1829,7 @@ let PluginManager = (() => {
|
|
|
1701
1829
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
1702
1830
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, "utf8"));
|
|
1703
1831
|
const known = new Set([
|
|
1704
|
-
...manifest
|
|
1832
|
+
...bundleSelection(manifest),
|
|
1705
1833
|
...Object.keys(manifest.dependencies ?? {}),
|
|
1706
1834
|
...Object.keys(installation.dependencies ?? {})
|
|
1707
1835
|
]);
|
|
@@ -1804,8 +1932,8 @@ let PluginManager = (() => {
|
|
|
1804
1932
|
const row = (await this.listPlugins()).find((item) => item.entryId === id);
|
|
1805
1933
|
if (row === void 0) throw new ManagementFailure("unknown-plugin");
|
|
1806
1934
|
if (row.readOnlyReason !== void 0) throw new ManagementFailure(row.readOnlyReason);
|
|
1807
|
-
await writePluginEnabled(this.profile
|
|
1808
|
-
result
|
|
1935
|
+
await writePluginEnabled(this.profile, row.patchId, row.moduleName, enabled);
|
|
1936
|
+
Object.assign(result, await this.reload(enabled ? [row.patchId] : []));
|
|
1809
1937
|
return (await this.listPlugins()).find((item) => item.entryId === id)?.enabled !== enabled && this.ownerContext.get("hmr") !== void 0 ? "overridden" : void 0;
|
|
1810
1938
|
}), {
|
|
1811
1939
|
stage: "enable",
|
|
@@ -1821,9 +1949,7 @@ let PluginManager = (() => {
|
|
|
1821
1949
|
setBundleEnabled(name, enabled) {
|
|
1822
1950
|
return this.change((result) => this.configure(async () => {
|
|
1823
1951
|
await this.selectBundle(name, enabled);
|
|
1824
|
-
|
|
1825
|
-
result.warnings = await this.reload(enabled ? this.bundleRows(name).map((row) => row.id) : []);
|
|
1826
|
-
if (!enabled && this.ownerContext.get("hmr") !== void 0) await this.refreshPackages();
|
|
1952
|
+
Object.assign(result, enabled ? await this.publishAndReload(this.bundleRows(name).map((row) => row.id)) : await this.reload());
|
|
1827
1953
|
}), {
|
|
1828
1954
|
stage: "enable",
|
|
1829
1955
|
target: name,
|
|
@@ -1860,12 +1986,14 @@ let PluginManager = (() => {
|
|
|
1860
1986
|
const result = this.change(async (result) => {
|
|
1861
1987
|
if (spec.trim() === "" || spec.startsWith("-")) throw new ManagementFailure("invalid-spec");
|
|
1862
1988
|
if (stopped()) throw new InstallCancelledError();
|
|
1989
|
+
const before = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1990
|
+
const known = specPackageName(spec, before, this.profile.dir);
|
|
1991
|
+
if (this.ownerContext.get("hmr") !== void 0 && known !== void 0 && Object.hasOwn(before, known)) throw new ManagementFailure("already-installed");
|
|
1863
1992
|
if (options?.approvedBuilds !== void 0) {
|
|
1864
1993
|
await approveBuilds(this.profile.dir, options.approvedBuilds);
|
|
1865
1994
|
result.approvedBuilds = options.approvedBuilds;
|
|
1866
1995
|
}
|
|
1867
1996
|
const files = await this.readRestoredFiles();
|
|
1868
|
-
const before = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1869
1997
|
let name;
|
|
1870
1998
|
let version;
|
|
1871
1999
|
try {
|
|
@@ -1891,10 +2019,12 @@ let PluginManager = (() => {
|
|
|
1891
2019
|
}
|
|
1892
2020
|
const plan = registryPlan(options?.registry, await this.registries());
|
|
1893
2021
|
let run;
|
|
2022
|
+
let used = null;
|
|
1894
2023
|
for (const [index, registry] of plan.entries()) {
|
|
1895
2024
|
if (index > 0) await this.restoreFiles(files);
|
|
1896
2025
|
if (stopped()) throw new InstallCancelledError();
|
|
1897
2026
|
result.registries.push(registry);
|
|
2027
|
+
used = registry;
|
|
1898
2028
|
announce("installing", {
|
|
1899
2029
|
registry,
|
|
1900
2030
|
index: index + 1,
|
|
@@ -1933,9 +2063,8 @@ let PluginManager = (() => {
|
|
|
1933
2063
|
const after = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1934
2064
|
const installed = Object.keys(after).filter((name) => before[name] !== after[name]);
|
|
1935
2065
|
if (installed.length === 0) {
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1938
|
-
installed.push(...Object.keys(after).filter((name) => name === localName || spec === name || spec.startsWith(`${name}@`)));
|
|
2066
|
+
const local = specPackageName(spec, after, this.profile.dir);
|
|
2067
|
+
if (local !== void 0 && Object.hasOwn(after, local)) installed.push(local);
|
|
1939
2068
|
}
|
|
1940
2069
|
const target = installed[0];
|
|
1941
2070
|
if (installed.length !== 1 || target === void 0) throw new ManagementFailure("ambiguous-install");
|
|
@@ -1947,6 +2076,7 @@ let PluginManager = (() => {
|
|
|
1947
2076
|
if (compatibility !== void 0 && !compatibility.exempted) throw new ManagementFailure("incompatible-version", [incompatiblePlugin(compatibility)]);
|
|
1948
2077
|
for (const file of bundlePatchPaths(dir, manifest.dsh.bundle)) loadOverlayPatches("dsh", file);
|
|
1949
2078
|
version = manifest.version;
|
|
2079
|
+
this.installedFrom.set(name, used);
|
|
1950
2080
|
} catch (error) {
|
|
1951
2081
|
await this.restoreFiles(files);
|
|
1952
2082
|
throw error;
|
|
@@ -1959,9 +2089,9 @@ let PluginManager = (() => {
|
|
|
1959
2089
|
result.stage = "enable";
|
|
1960
2090
|
return this.configure(async () => {
|
|
1961
2091
|
if (options?.enabled !== false) await this.selectBundle(name, true);
|
|
1962
|
-
if (Object.hasOwn(before, name)
|
|
1963
|
-
|
|
1964
|
-
|
|
2092
|
+
if (Object.hasOwn(before, name) && this.ownerContext.get("hmr") === void 0) return;
|
|
2093
|
+
const selected = bundleSelection(readProfileManifest("dsh", this.profile.dir)).includes(name);
|
|
2094
|
+
Object.assign(result, await this.publishAndReload(selected ? this.bundleRows(name).map((row) => row.id) : []));
|
|
1965
2095
|
});
|
|
1966
2096
|
}, {
|
|
1967
2097
|
stage: "install",
|
|
@@ -2013,14 +2143,20 @@ let PluginManager = (() => {
|
|
|
2013
2143
|
const contributions = bundle.error === void 0 ? this.bundleRows(name) : [];
|
|
2014
2144
|
if (bundle.enabled) {
|
|
2015
2145
|
await this.selectBundle(name, false);
|
|
2016
|
-
result
|
|
2146
|
+
Object.assign(result, await this.reload());
|
|
2017
2147
|
}
|
|
2018
2148
|
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");
|
|
2019
2149
|
return bundle.installed;
|
|
2020
2150
|
})) return;
|
|
2021
|
-
|
|
2151
|
+
const registry = this.installedFrom.has(name) ? this.installedFrom.get(name) : this.configuredRegistries.registry;
|
|
2152
|
+
result.packageResult = await this.runPnpm([
|
|
2153
|
+
"remove",
|
|
2154
|
+
name,
|
|
2155
|
+
...typeof registry === "string" ? [`--config.registry=${registry}`] : []
|
|
2156
|
+
]);
|
|
2022
2157
|
if (result.packageResult.exitCode !== 0 || result.packageResult.timedOut === true) throw new Error(result.packageResult.output);
|
|
2023
|
-
|
|
2158
|
+
this.installedFrom.delete(name);
|
|
2159
|
+
Object.assign(result, await this.configure(() => this.publishAndReload()));
|
|
2024
2160
|
}, {
|
|
2025
2161
|
stage: "remove",
|
|
2026
2162
|
target: name
|
|
@@ -2143,7 +2279,7 @@ let PluginManager = (() => {
|
|
|
2143
2279
|
}
|
|
2144
2280
|
async selectBundle(name, enabled) {
|
|
2145
2281
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
2146
|
-
const previous = manifest
|
|
2282
|
+
const previous = bundleSelection(manifest);
|
|
2147
2283
|
if (enabled || !previous.includes(name)) {
|
|
2148
2284
|
const metadata = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
2149
2285
|
if (metadata === void 0) throw new ManagementFailure("not-bundle");
|
|
@@ -2193,16 +2329,46 @@ let PluginManager = (() => {
|
|
|
2193
2329
|
};
|
|
2194
2330
|
return hmr === void 0 ? apply() : hmr.runExclusive(apply);
|
|
2195
2331
|
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Publish the profile's runtime resolution without HMR, unless a deselected startup bundle still runs and needs the
|
|
2334
|
+
* current table. A publication that needs a restart waits for the restart the result already reports.
|
|
2335
|
+
*/
|
|
2196
2336
|
async refreshPackages() {
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2337
|
+
const selected = bundleSelection(readProfileManifest("dsh", this.profile.dir));
|
|
2338
|
+
if (this.profile.startedBundles.some((name) => !selected.includes(name))) return;
|
|
2339
|
+
try {
|
|
2340
|
+
const packages = this.ownerContext.get("pluginPackages");
|
|
2341
|
+
if (packages?.resolution !== void 0) await packages.refresh();
|
|
2342
|
+
} catch (error) {
|
|
2343
|
+
if (!(error instanceof ResolutionRestartRequired)) throw error;
|
|
2200
2344
|
}
|
|
2201
|
-
await this.ownerContext.get("pluginPackages")?.refresh();
|
|
2202
2345
|
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Apply a package change: without HMR, publish the runtime resolution when that is safe, then {@link reload}.
|
|
2348
|
+
* @param requiredIds Entry ids that must start.
|
|
2349
|
+
* @returns The reload's outcome.
|
|
2350
|
+
*/
|
|
2351
|
+
async publishAndReload(requiredIds = []) {
|
|
2352
|
+
if (this.ownerContext.get("hmr") === void 0) await this.refreshPackages();
|
|
2353
|
+
return this.reload(requiredIds);
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Apply the saved profile; with HMR it recomposes live, including the runtime resolution.
|
|
2357
|
+
* @param requiredIds Entry ids that must start.
|
|
2358
|
+
* @returns Warnings about entries that were already inactive, and why the change waits for a restart, if it does.
|
|
2359
|
+
*/
|
|
2203
2360
|
async reload(requiredIds = []) {
|
|
2204
|
-
|
|
2205
|
-
|
|
2361
|
+
const hmr = this.ownerContext.get("hmr");
|
|
2362
|
+
if (hmr === void 0) {
|
|
2363
|
+
composeEntries([readProfilePatches("dsh", this.profile)]);
|
|
2364
|
+
return { warnings: [] };
|
|
2365
|
+
}
|
|
2366
|
+
const { warnings, pending, restartReason } = await hmr.reconcileProfile(requiredIds);
|
|
2367
|
+
const restart = restartReason ?? pending[0]?.reason;
|
|
2368
|
+
return restart === void 0 ? { warnings } : {
|
|
2369
|
+
warnings,
|
|
2370
|
+
restartReason: restart
|
|
2371
|
+
};
|
|
2206
2372
|
}
|
|
2207
2373
|
async change(operation, request, reason) {
|
|
2208
2374
|
return withFileLock(join(this.profile.dir, "package.json"), async () => {
|
|
@@ -2211,10 +2377,15 @@ let PluginManager = (() => {
|
|
|
2211
2377
|
const result = {
|
|
2212
2378
|
...request,
|
|
2213
2379
|
changed: false,
|
|
2214
|
-
|
|
2380
|
+
...this.ownerContext.get("hmr") !== void 0 ? { application: "applied" } : {
|
|
2381
|
+
application: "restart-required",
|
|
2382
|
+
restartReason: "no-hmr"
|
|
2383
|
+
}
|
|
2215
2384
|
};
|
|
2216
2385
|
try {
|
|
2217
|
-
|
|
2386
|
+
const outcome = await operation(result);
|
|
2387
|
+
if (outcome !== void 0) result.application = outcome;
|
|
2388
|
+
else if (result.application === "applied" && result.restartReason !== void 0) result.application = "restart-required";
|
|
2218
2389
|
} catch (error) {
|
|
2219
2390
|
if (error instanceof InstallCancelledError) result.application = "cancelled";
|
|
2220
2391
|
else {
|
|
@@ -2222,6 +2393,7 @@ let PluginManager = (() => {
|
|
|
2222
2393
|
result.error = managementError(error);
|
|
2223
2394
|
}
|
|
2224
2395
|
}
|
|
2396
|
+
if (result.application !== "restart-required") delete result.restartReason;
|
|
2225
2397
|
result.changed = before !== this.diskState();
|
|
2226
2398
|
this.ownerContext.emit("plugin-manager/changed", { reason });
|
|
2227
2399
|
return result;
|