@deepseek-ai/dsh-plugin-manager 0.2.1-alpha.2 → 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 +8 -7
- package/README.zh.md +9 -8
- package/lib/index.js +447 -276
- package/lib/typert.host.js +399 -241
- package/lib/typert.remote-client.d.ts +16 -2
- package/lib/typert.remote-client.js +387 -238
- 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 +320 -255
- 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 +11 -1
- 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/types/index.d.ts
CHANGED
|
@@ -40,6 +40,11 @@ export declare class PluginManager extends TypertRemoteService {
|
|
|
40
40
|
static Config: z<Config>;
|
|
41
41
|
/** Management bundles remain protected if their files become unreadable. */
|
|
42
42
|
private readonly managementBundles;
|
|
43
|
+
/**
|
|
44
|
+
* The registry each package was last installed from by this manager in this process. pnpm verifies lockfile tarball
|
|
45
|
+
* URLs against its default registry unless they belong to a named registry, so a later removal names it again.
|
|
46
|
+
*/
|
|
47
|
+
private readonly installedFrom;
|
|
43
48
|
private readonly ownerEntryId;
|
|
44
49
|
private readonly packageOperations;
|
|
45
50
|
private readonly profile;
|
|
@@ -146,7 +151,22 @@ export declare class PluginManager extends TypertRemoteService {
|
|
|
146
151
|
private bundleRows;
|
|
147
152
|
private protectsManager;
|
|
148
153
|
private configure;
|
|
154
|
+
/**
|
|
155
|
+
* Publish the profile's runtime resolution without HMR, unless a deselected startup bundle still runs and needs the
|
|
156
|
+
* current table. A publication that needs a restart waits for the restart the result already reports.
|
|
157
|
+
*/
|
|
149
158
|
private refreshPackages;
|
|
159
|
+
/**
|
|
160
|
+
* Apply a package change: without HMR, publish the runtime resolution when that is safe, then {@link reload}.
|
|
161
|
+
* @param requiredIds Entry ids that must start.
|
|
162
|
+
* @returns The reload's outcome.
|
|
163
|
+
*/
|
|
164
|
+
private publishAndReload;
|
|
165
|
+
/**
|
|
166
|
+
* Apply the saved profile; with HMR it recomposes live, including the runtime resolution.
|
|
167
|
+
* @param requiredIds Entry ids that must start.
|
|
168
|
+
* @returns Warnings about entries that were already inactive, and why the change waits for a restart, if it does.
|
|
169
|
+
*/
|
|
150
170
|
private reload;
|
|
151
171
|
private change;
|
|
152
172
|
private diskState;
|
package/lib/types/index.js
CHANGED
|
@@ -42,13 +42,13 @@ import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
|
42
42
|
import z from '@deepseek-ai/schemastery';
|
|
43
43
|
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol';
|
|
44
44
|
import { pluginEntryId, readPluginInventory } from '@deepseek-ai/dsh-host-plugin-inventory';
|
|
45
|
-
import { readPluginMeta, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries,
|
|
45
|
+
import { readPluginMeta, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries, readProfilePatches, OPTIONAL_BUNDLES, bundlePatchPaths, evaluatePluginCompatibility, readProfileCompatibility, readProfileVersionExemptions, setProfileVersionExemption, PROFILE_COMPATIBILITY_FILENAME, OFFICIAL_ON_DEMAND_CATALOG, getDshRuntimeVersion, ResolutionRestartRequired, } from '@deepseek-ai/dsh-app-boot';
|
|
46
46
|
import { bundleManifest, readProfileRegistry, registryArguments, runProfilePnpm, saveManifest, viewProfilePackage } from "./operations.js";
|
|
47
47
|
import { classifyInstallFailure } from "./install-failure.js";
|
|
48
48
|
import { officialBundleInstallTarget } from "./official-install-target.js";
|
|
49
49
|
import { dependencySpec, InvalidInstallSpecError, parseInstallSpec } from "./install-spec.js";
|
|
50
50
|
import { attributeFailure, normalizeRegistry, NPMMIRROR_REGISTRY, registryPlan } from "./registry.js";
|
|
51
|
-
import { writePluginEnabled } from "./patch.js";
|
|
51
|
+
import { flatten, writePluginEnabled } from "./patch.js";
|
|
52
52
|
import { incompatiblePlugin, ManagementFailure } from "./failure.js";
|
|
53
53
|
import { approveBuilds, recordPendingBuilds } from "./build-approval.js";
|
|
54
54
|
import { checkGithubConnection } from "./github-connection.js";
|
|
@@ -62,7 +62,7 @@ const protectedModules = new Set([
|
|
|
62
62
|
'@deepseek-ai/dsh-host-webserver', '@deepseek-ai/dsh-client-modules',
|
|
63
63
|
'@deepseek-ai/dsh-client-ui-settings-plugin-inventory', '@deepseek-ai/dsh-client-ui-plugin-manager',
|
|
64
64
|
'@deepseek-ai/dsh-host-plugin-inventory', '@deepseek-ai/dsh-typert-registry',
|
|
65
|
-
'@deepseek-ai/dsh-api-remotes',
|
|
65
|
+
'@deepseek-ai/dsh-api-remotes', '@deepseek-ai/dsh-api-remote-access',
|
|
66
66
|
'@deepseek-ai/cordis-plugin-timer', '@deepseek-ai/dsh-client-connection',
|
|
67
67
|
'@deepseek-ai/dsh-host-frontend-static', '@deepseek-ai/dsh-tools',
|
|
68
68
|
'@deepseek-ai/dsh-hmr',
|
|
@@ -71,10 +71,6 @@ const protectedModules = new Set([
|
|
|
71
71
|
const RESTORED_FILES = ['package.json', 'pnpm-lock.yaml'];
|
|
72
72
|
/** pnpm's colour escapes, which a JSON answer may be wrapped in. */
|
|
73
73
|
const ANSI_SEQUENCE = /\x1b\[[0-9;]*m/g;
|
|
74
|
-
/** Flatten only the groups addressable by the profile's patch composer. */
|
|
75
|
-
function flatten(rows) {
|
|
76
|
-
return rows.flatMap(row => [row, ...(row.group && Array.isArray(row.config) ? flatten(row.config) : [])]);
|
|
77
|
-
}
|
|
78
74
|
/** Preserve the exact observed diagnostic, including non-Error failures. */
|
|
79
75
|
function messageOf(error) { return error instanceof Error ? error.message : String(error); }
|
|
80
76
|
/** An expected refusal keeps its code; anything else becomes an operation error carrying its exact diagnostic. */
|
|
@@ -128,6 +124,37 @@ function printedError(printed) {
|
|
|
128
124
|
const { code, message } = error;
|
|
129
125
|
return [code, message].filter((part) => typeof part === 'string').join(' ');
|
|
130
126
|
}
|
|
127
|
+
/** The bundles a profile manifest selects. */
|
|
128
|
+
function bundleSelection(manifest) {
|
|
129
|
+
return manifest.dsh?.profile?.bundles ?? [];
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The package an installation spec names before pnpm runs: a registry name, a local directory's manifest name, or the
|
|
133
|
+
* dependency saved with this git or tarball spec, compared as the plugin list shows saved specs.
|
|
134
|
+
* @param spec The installation spec.
|
|
135
|
+
* @param dependencies The profile's saved dependencies.
|
|
136
|
+
* @param profileDir The profile directory saved local paths are relative to.
|
|
137
|
+
* @returns The package name, or `undefined` when only the installation tells it.
|
|
138
|
+
*/
|
|
139
|
+
function specPackageName(spec, dependencies, profileDir) {
|
|
140
|
+
const parsed = parsedForRegistry(spec);
|
|
141
|
+
if (parsed.kind === 'registry')
|
|
142
|
+
return parsed.name;
|
|
143
|
+
if (parsed.kind === 'path')
|
|
144
|
+
return packageName(parsed.path);
|
|
145
|
+
const specs = parsed.kind === 'tarball' && parsed.path !== undefined ? [spec, `file:${parsed.path}`] : [spec];
|
|
146
|
+
return Object.entries(dependencies).find(([dependency, saved]) => specs.includes(dependencySpec(dependency, saved, profileDir)))?.[0];
|
|
147
|
+
}
|
|
148
|
+
/** The package name a local directory declares, or `undefined` without a readable manifest. */
|
|
149
|
+
function packageName(dir) {
|
|
150
|
+
try {
|
|
151
|
+
return readProfileManifest('dsh', dir).name;
|
|
152
|
+
}
|
|
153
|
+
catch (_error) {
|
|
154
|
+
// pnpm reports the unreadable directory itself.
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
131
158
|
/** 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. */
|
|
132
159
|
function parsedForRegistry(spec) {
|
|
133
160
|
try {
|
|
@@ -198,6 +225,11 @@ let PluginManager = (() => {
|
|
|
198
225
|
});
|
|
199
226
|
/** Management bundles remain protected if their files become unreadable. */
|
|
200
227
|
managementBundles = (__runInitializers(this, _instanceExtraInitializers), new Set());
|
|
228
|
+
/**
|
|
229
|
+
* The registry each package was last installed from by this manager in this process. pnpm verifies lockfile tarball
|
|
230
|
+
* URLs against its default registry unless they belong to a named registry, so a later removal names it again.
|
|
231
|
+
*/
|
|
232
|
+
installedFrom = new Map();
|
|
201
233
|
ownerEntryId;
|
|
202
234
|
packageOperations = new Set();
|
|
203
235
|
profile;
|
|
@@ -252,7 +284,7 @@ let PluginManager = (() => {
|
|
|
252
284
|
setVersionExemption(packageVersion, runtimeVersion, enabled, acceptRisk) {
|
|
253
285
|
return this.change(result => this.configure(async () => {
|
|
254
286
|
await setProfileVersionExemption(this.profile.dir, packageVersion, runtimeVersion, enabled, acceptRisk === true);
|
|
255
|
-
result
|
|
287
|
+
Object.assign(result, await this.reload());
|
|
256
288
|
}), { stage: 'enable', target: packageVersion, enabled }, 'bundle');
|
|
257
289
|
}
|
|
258
290
|
/** Read current plugins, including why a row cannot be changed through the profile patch.
|
|
@@ -283,7 +315,7 @@ let PluginManager = (() => {
|
|
|
283
315
|
listBundles() {
|
|
284
316
|
const manifest = readProfileManifest('dsh', this.profile.dir);
|
|
285
317
|
const exemptions = readProfileVersionExemptions(this.profile.dir);
|
|
286
|
-
const selected = manifest
|
|
318
|
+
const selected = bundleSelection(manifest);
|
|
287
319
|
const recorded = manifest.dependencies ?? {};
|
|
288
320
|
const dependencies = Object.keys(recorded);
|
|
289
321
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, 'utf8'));
|
|
@@ -388,7 +420,7 @@ let PluginManager = (() => {
|
|
|
388
420
|
const manifest = readProfileManifest('dsh', this.profile.dir);
|
|
389
421
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, 'utf8'));
|
|
390
422
|
const known = new Set([
|
|
391
|
-
...manifest
|
|
423
|
+
...bundleSelection(manifest), ...Object.keys(manifest.dependencies ?? {}), ...Object.keys(installation.dependencies ?? {}),
|
|
392
424
|
]);
|
|
393
425
|
const plan = registryPlan(options?.registry, await this.registries());
|
|
394
426
|
const registry = plan[0];
|
|
@@ -477,8 +509,8 @@ let PluginManager = (() => {
|
|
|
477
509
|
throw new ManagementFailure('unknown-plugin');
|
|
478
510
|
if (row.readOnlyReason !== undefined)
|
|
479
511
|
throw new ManagementFailure(row.readOnlyReason);
|
|
480
|
-
await writePluginEnabled(this.profile
|
|
481
|
-
result
|
|
512
|
+
await writePluginEnabled(this.profile, row.patchId, row.moduleName, enabled);
|
|
513
|
+
Object.assign(result, await this.reload(enabled ? [row.patchId] : []));
|
|
482
514
|
const current = (await this.listPlugins()).find(item => item.entryId === id);
|
|
483
515
|
return current?.enabled !== enabled && this.ownerContext.get('hmr') !== undefined ? 'overridden' : undefined;
|
|
484
516
|
}), { stage: 'enable', target: id, enabled }, 'plugin');
|
|
@@ -491,11 +523,7 @@ let PluginManager = (() => {
|
|
|
491
523
|
setBundleEnabled(name, enabled) {
|
|
492
524
|
return this.change(result => this.configure(async () => {
|
|
493
525
|
await this.selectBundle(name, enabled);
|
|
494
|
-
|
|
495
|
-
await this.refreshPackages();
|
|
496
|
-
result.warnings = await this.reload(enabled ? this.bundleRows(name).map(row => row.id) : []);
|
|
497
|
-
if (!enabled && this.ownerContext.get('hmr') !== undefined)
|
|
498
|
-
await this.refreshPackages();
|
|
526
|
+
Object.assign(result, enabled ? await this.publishAndReload(this.bundleRows(name).map(row => row.id)) : await this.reload());
|
|
499
527
|
}), { stage: 'enable', target: name, enabled }, 'bundle');
|
|
500
528
|
}
|
|
501
529
|
/**
|
|
@@ -524,12 +552,17 @@ let PluginManager = (() => {
|
|
|
524
552
|
throw new ManagementFailure('invalid-spec');
|
|
525
553
|
if (stopped())
|
|
526
554
|
throw new InstallCancelledError();
|
|
555
|
+
const before = readProfileManifest('dsh', this.profile.dir).dependencies ?? {};
|
|
556
|
+
// With HMR, an installed package is removed before another version is installed, so its plugins stop first.
|
|
557
|
+
const known = specPackageName(spec, before, this.profile.dir);
|
|
558
|
+
if (this.ownerContext.get('hmr') !== undefined && known !== undefined && Object.hasOwn(before, known)) {
|
|
559
|
+
throw new ManagementFailure('already-installed');
|
|
560
|
+
}
|
|
527
561
|
if (options?.approvedBuilds !== undefined) {
|
|
528
562
|
await approveBuilds(this.profile.dir, options.approvedBuilds);
|
|
529
563
|
result.approvedBuilds = options.approvedBuilds;
|
|
530
564
|
}
|
|
531
565
|
const files = await this.readRestoredFiles();
|
|
532
|
-
const before = readProfileManifest('dsh', this.profile.dir).dependencies ?? {};
|
|
533
566
|
let name;
|
|
534
567
|
let version;
|
|
535
568
|
try {
|
|
@@ -557,6 +590,7 @@ let PluginManager = (() => {
|
|
|
557
590
|
// The last run is the result's; the registries asked stay listed whatever the outcome.
|
|
558
591
|
const plan = registryPlan(options?.registry, await this.registries());
|
|
559
592
|
let run;
|
|
593
|
+
let used = null;
|
|
560
594
|
for (const [index, registry] of plan.entries()) {
|
|
561
595
|
if (index > 0)
|
|
562
596
|
await this.restoreFiles(files);
|
|
@@ -564,6 +598,7 @@ let PluginManager = (() => {
|
|
|
564
598
|
if (stopped())
|
|
565
599
|
throw new InstallCancelledError();
|
|
566
600
|
result.registries.push(registry);
|
|
601
|
+
used = registry;
|
|
567
602
|
announce('installing', { registry, index: index + 1, total: plan.length });
|
|
568
603
|
run = await this.runPnpm(['add', spec, ...options?.saveExact === true ? ['--save-exact'] : [], ...registryArguments(registry)], control.abort.signal, requestId);
|
|
569
604
|
result.packageResult = run;
|
|
@@ -608,11 +643,11 @@ let PluginManager = (() => {
|
|
|
608
643
|
}
|
|
609
644
|
const after = readProfileManifest('dsh', this.profile.dir).dependencies ?? {};
|
|
610
645
|
const installed = Object.keys(after).filter(name => before[name] !== after[name]);
|
|
611
|
-
//
|
|
646
|
+
// Without HMR a repeated installation can leave the saved dependency as it was.
|
|
612
647
|
if (installed.length === 0) {
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
648
|
+
const local = specPackageName(spec, after, this.profile.dir);
|
|
649
|
+
if (local !== undefined && Object.hasOwn(after, local))
|
|
650
|
+
installed.push(local);
|
|
616
651
|
}
|
|
617
652
|
const target = installed[0];
|
|
618
653
|
if (installed.length !== 1 || target === undefined)
|
|
@@ -628,6 +663,7 @@ let PluginManager = (() => {
|
|
|
628
663
|
for (const file of bundlePatchPaths(dir, manifest.dsh.bundle))
|
|
629
664
|
loadOverlayPatches('dsh', file);
|
|
630
665
|
version = manifest.version;
|
|
666
|
+
this.installedFrom.set(name, used);
|
|
631
667
|
}
|
|
632
668
|
catch (error) {
|
|
633
669
|
// pnpm has exited by now, so the files it rewrote go back as they were.
|
|
@@ -644,11 +680,12 @@ let PluginManager = (() => {
|
|
|
644
680
|
return this.configure(async () => {
|
|
645
681
|
if (options?.enabled !== false)
|
|
646
682
|
await this.selectBundle(name, true);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
683
|
+
// Without HMR the replaced package's plugins keep running until the restart the result already reports.
|
|
684
|
+
if (Object.hasOwn(before, name) && this.ownerContext.get('hmr') === undefined)
|
|
685
|
+
return;
|
|
686
|
+
// A selected bundle's own rows must start.
|
|
687
|
+
const selected = bundleSelection(readProfileManifest('dsh', this.profile.dir)).includes(name);
|
|
688
|
+
Object.assign(result, await this.publishAndReload(selected ? this.bundleRows(name).map(row => row.id) : []));
|
|
652
689
|
});
|
|
653
690
|
}, { stage: 'install', target: spec, enabled: options?.enabled !== false }, 'install');
|
|
654
691
|
control.result = result;
|
|
@@ -699,7 +736,7 @@ let PluginManager = (() => {
|
|
|
699
736
|
const contributions = bundle.error === undefined ? this.bundleRows(name) : [];
|
|
700
737
|
if (bundle.enabled) {
|
|
701
738
|
await this.selectBundle(name, false);
|
|
702
|
-
result
|
|
739
|
+
Object.assign(result, await this.reload());
|
|
703
740
|
}
|
|
704
741
|
if ([...this.ctx.loader.entries()].some(entry => entry.fiber?.uid != null
|
|
705
742
|
&& contributions.some(row => row.id === entry.options.id && row.name === entry.options.name))) {
|
|
@@ -709,11 +746,15 @@ let PluginManager = (() => {
|
|
|
709
746
|
});
|
|
710
747
|
if (!installed)
|
|
711
748
|
return;
|
|
712
|
-
|
|
749
|
+
// `pnpm remove` takes no `--registry` option; the generic configuration flag sets the same setting. A package
|
|
750
|
+
// installed before this process started is removed with the registry an installation asks first.
|
|
751
|
+
const registry = this.installedFrom.has(name) ? this.installedFrom.get(name) : this.configuredRegistries.registry;
|
|
752
|
+
result.packageResult = await this.runPnpm(['remove', name, ...typeof registry === 'string' ? [`--config.registry=${registry}`] : []]);
|
|
713
753
|
if (result.packageResult.exitCode !== 0 || result.packageResult.timedOut === true) {
|
|
714
754
|
throw new Error(result.packageResult.output);
|
|
715
755
|
}
|
|
716
|
-
|
|
756
|
+
this.installedFrom.delete(name);
|
|
757
|
+
Object.assign(result, await this.configure(() => this.publishAndReload()));
|
|
717
758
|
}, { stage: 'remove', target: name }, 'remove');
|
|
718
759
|
}
|
|
719
760
|
/** The rows a bundle's patch inserts and the existing rows it changes; an unreadable patch throws. */
|
|
@@ -807,7 +848,7 @@ let PluginManager = (() => {
|
|
|
807
848
|
}
|
|
808
849
|
async selectBundle(name, enabled) {
|
|
809
850
|
const manifest = readProfileManifest('dsh', this.profile.dir);
|
|
810
|
-
const previous = manifest
|
|
851
|
+
const previous = bundleSelection(manifest);
|
|
811
852
|
if (enabled || !previous.includes(name)) {
|
|
812
853
|
const metadata = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
813
854
|
if (metadata === undefined)
|
|
@@ -859,28 +900,64 @@ let PluginManager = (() => {
|
|
|
859
900
|
const apply = () => { this.abort.signal.throwIfAborted(); return operation(); };
|
|
860
901
|
return hmr === undefined ? apply() : hmr.runExclusive(apply);
|
|
861
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* Publish the profile's runtime resolution without HMR, unless a deselected startup bundle still runs and needs the
|
|
905
|
+
* current table. A publication that needs a restart waits for the restart the result already reports.
|
|
906
|
+
*/
|
|
862
907
|
async refreshPackages() {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
908
|
+
const selected = bundleSelection(readProfileManifest('dsh', this.profile.dir));
|
|
909
|
+
if (this.profile.startedBundles.some(name => !selected.includes(name)))
|
|
910
|
+
return;
|
|
911
|
+
try {
|
|
912
|
+
const packages = this.ownerContext.get('pluginPackages');
|
|
913
|
+
// A dropped mapping's bundle is not running: the guard above keeps the table while a deselected startup bundle runs.
|
|
914
|
+
if (packages?.resolution !== undefined)
|
|
915
|
+
await packages.refresh();
|
|
916
|
+
}
|
|
917
|
+
catch (error) {
|
|
918
|
+
if (!(error instanceof ResolutionRestartRequired))
|
|
919
|
+
throw error;
|
|
868
920
|
}
|
|
869
|
-
await this.ownerContext.get('pluginPackages')?.refresh();
|
|
870
921
|
}
|
|
871
|
-
|
|
922
|
+
/**
|
|
923
|
+
* Apply a package change: without HMR, publish the runtime resolution when that is safe, then {@link reload}.
|
|
924
|
+
* @param requiredIds Entry ids that must start.
|
|
925
|
+
* @returns The reload's outcome.
|
|
926
|
+
*/
|
|
927
|
+
async publishAndReload(requiredIds = []) {
|
|
872
928
|
if (this.ownerContext.get('hmr') === undefined)
|
|
873
|
-
|
|
874
|
-
return
|
|
929
|
+
await this.refreshPackages();
|
|
930
|
+
return this.reload(requiredIds);
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Apply the saved profile; with HMR it recomposes live, including the runtime resolution.
|
|
934
|
+
* @param requiredIds Entry ids that must start.
|
|
935
|
+
* @returns Warnings about entries that were already inactive, and why the change waits for a restart, if it does.
|
|
936
|
+
*/
|
|
937
|
+
async reload(requiredIds = []) {
|
|
938
|
+
const hmr = this.ownerContext.get('hmr');
|
|
939
|
+
if (hmr === undefined) {
|
|
940
|
+
// Startup-only profiles recompose at restart; the saved patches still have to compose.
|
|
941
|
+
composeEntries([readProfilePatches('dsh', this.profile)]);
|
|
942
|
+
return { warnings: [] };
|
|
943
|
+
}
|
|
944
|
+
const { warnings, pending, restartReason } = await hmr.reconcileProfile(requiredIds);
|
|
945
|
+
const restart = restartReason ?? pending[0]?.reason;
|
|
946
|
+
return restart === undefined ? { warnings } : { warnings, restartReason: restart };
|
|
875
947
|
}
|
|
876
948
|
async change(operation, request, reason) {
|
|
877
949
|
return withFileLock(join(this.profile.dir, 'package.json'), async () => {
|
|
878
950
|
this.abort.signal.throwIfAborted();
|
|
879
951
|
const before = this.diskState();
|
|
880
952
|
const result = { ...request, changed: false,
|
|
881
|
-
|
|
953
|
+
...this.ownerContext.get('hmr') !== undefined ? { application: 'applied' } : { application: 'restart-required', restartReason: 'no-hmr' } };
|
|
882
954
|
try {
|
|
883
|
-
|
|
955
|
+
const outcome = await operation(result);
|
|
956
|
+
if (outcome !== undefined)
|
|
957
|
+
result.application = outcome;
|
|
958
|
+
// A reload that waits for a restart leaves the change saved but not applied.
|
|
959
|
+
else if (result.application === 'applied' && result.restartReason !== undefined)
|
|
960
|
+
result.application = 'restart-required';
|
|
884
961
|
}
|
|
885
962
|
catch (error) {
|
|
886
963
|
if (error instanceof InstallCancelledError) {
|
|
@@ -891,6 +968,8 @@ let PluginManager = (() => {
|
|
|
891
968
|
result.error = managementError(error);
|
|
892
969
|
}
|
|
893
970
|
}
|
|
971
|
+
if (result.application !== 'restart-required')
|
|
972
|
+
delete result.restartReason;
|
|
894
973
|
result.changed = before !== this.diskState();
|
|
895
974
|
this.ownerContext.emit('plugin-manager/changed', { reason });
|
|
896
975
|
return result;
|
|
@@ -62,6 +62,7 @@ export declare function saveManifest(dir: string, manifest: ProfileManifest): Pr
|
|
|
62
62
|
* @param options Output, activation and cancellation policy.
|
|
63
63
|
* @returns Exit status, whether the silence bound stopped the run, and the diagnostic path.
|
|
64
64
|
* A compatibility denial returns exit code 1. Service output is bounded; CLI output uses inherited descriptors.
|
|
65
|
+
* The diagnostic file is closed on return or rejection, including preparation and spawn failures.
|
|
65
66
|
*/
|
|
66
67
|
export declare function runProfilePnpm(context: PackageOperationContext, args: readonly string[], options: PackageOperationOptions): Promise<PackageResult>;
|
|
67
68
|
/** Initialize and run the dsh plugin command with the same write lock as the service.
|