@deepseek-ai/dsh-plugin-manager 0.2.0-rc.2 → 0.2.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +6 -6
- package/README.md +17 -6
- package/README.zh.md +17 -6
- package/lib/index.js +200 -28
- package/lib/typert.host.js +34 -20
- package/lib/typert.remote-client.js +26 -12
- package/lib/types/build-approval.d.ts +14 -2
- package/lib/types/build-approval.js +35 -5
- package/lib/types/index.d.ts +8 -6
- package/lib/types/index.js +91 -29
- package/lib/types/install-spec.d.ts +13 -0
- package/lib/types/install-spec.js +33 -1
- package/lib/types/official-install-target.d.ts +15 -0
- package/lib/types/official-install-target.js +54 -0
- package/lib/types/tools.js +1 -1
- package/lib/types/types.d.ts +20 -2
- package/package.json +35 -34
package/lib/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
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, reconcileProfilePatches, resolveBundleDir, resolveProfileDir, setProfileVersionExemption } from "@deepseek-ai/dsh-app-boot";
|
|
10
|
+
import { OFFICIAL_ON_DEMAND_CATALOG, OPTIONAL_BUNDLES, PROFILE_COMPATIBILITY_FILENAME, bundlePatchPaths, composeEntries, evaluatePluginCompatibility, getDshRuntimeVersion, loadOptionalPatches, loadOverlayPatches, pluginCompatibilityWarning, readPluginMeta, readProfileCompatibility, readProfileManifest, readProfilePatches, readProfileVersionExemptions, reconcileProfilePatches, 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";
|
|
14
|
+
import { homedir } from "node:os";
|
|
14
15
|
import { isAlias, isMap, isNode, isScalar, isSeq, parseDocument, visit } from "yaml";
|
|
15
16
|
//#region lib/types/install-spec.js
|
|
16
17
|
/**
|
|
@@ -115,6 +116,32 @@ function parseInstallSpec(raw) {
|
|
|
115
116
|
range
|
|
116
117
|
};
|
|
117
118
|
}
|
|
119
|
+
/** A recorded dependency value pnpm resolves outside a registry: a protocol other than a registry alias, or an scp-like git address. */
|
|
120
|
+
const NON_REGISTRY_VALUE = /^(?!(?:npm|jsr|workspace|catalog):)[a-z][a-z0-9+.-]*:|^[^@/:\s]+@[^:/\s]+:/i;
|
|
121
|
+
/** The user information of an http(s) URL, up to the last `@` before the path, which holds a password or access token when present. */
|
|
122
|
+
const HTTP_USER_INFO = /^((?:git\+)?https?:\/\/)[^/]*@/i;
|
|
123
|
+
/**
|
|
124
|
+
* The spec `pnpm add` accepts for a dependency the profile manifest records. A git address, URL, or other
|
|
125
|
+
* non-registry protocol is the recorded value without the user information of an http(s) URL, whose query string
|
|
126
|
+
* is kept; a `file:` or `link:` path, which pnpm records relative to the profile or with `~` for the home
|
|
127
|
+
* directory, becomes absolute. A registry range, tag, or alias follows the dependency name after `@`, as does a
|
|
128
|
+
* non-registry value installed under a name other than the package's own.
|
|
129
|
+
* @param name - the dependency name.
|
|
130
|
+
* @param recorded - the value the profile manifest records for it.
|
|
131
|
+
* @param profileDir - the profile directory a relative local path is resolved against.
|
|
132
|
+
* @param packageName - the name the installed package's manifest declares, when it could be read.
|
|
133
|
+
* @returns the installable spec.
|
|
134
|
+
*/
|
|
135
|
+
function dependencySpec(name, recorded, profileDir, packageName = name) {
|
|
136
|
+
const local = /^(file|link):(.*)$/s.exec(recorded);
|
|
137
|
+
let spec;
|
|
138
|
+
if (local !== null) {
|
|
139
|
+
const path = local[2].replace(/^~(?=$|[\\/])/, () => homedir());
|
|
140
|
+
spec = `${local[1]}:${resolve(profileDir, path)}`;
|
|
141
|
+
} else if (NON_REGISTRY_VALUE.test(recorded)) spec = recorded.replace(HTTP_USER_INFO, "$1");
|
|
142
|
+
else return `${name}@${recorded}`;
|
|
143
|
+
return packageName === name ? spec : `${name}@${spec}`;
|
|
144
|
+
}
|
|
118
145
|
//#endregion
|
|
119
146
|
//#region lib/types/run-tree.js
|
|
120
147
|
/** Waiting for one package run's process tree to disappear before its caller touches the profile. */
|
|
@@ -792,6 +819,48 @@ function classifyInstallFailure(facts) {
|
|
|
792
819
|
for (const [kind, pattern] of LOG_KINDS) if (pattern.test(facts.log)) return kind;
|
|
793
820
|
return "unknown";
|
|
794
821
|
}
|
|
822
|
+
//#endregion
|
|
823
|
+
//#region lib/types/official-install-target.js
|
|
824
|
+
/** Select Official bundle packages from the running installation without importing provider code. */
|
|
825
|
+
function manifestAt(file) {
|
|
826
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Offer a source checkout link or an exact published package version. Source installations are
|
|
830
|
+
* identified by the real CLI manifest and its unpacked workspace dependencies, including Desktop's
|
|
831
|
+
* development symlink. An incomplete checkout is an error, never a registry fallback.
|
|
832
|
+
* @param name - Official catalog package name.
|
|
833
|
+
* @param installAnchor - Running application's DSH package.json, possibly reached through a symlink.
|
|
834
|
+
* @param version - Running DSH version used by its release family.
|
|
835
|
+
* @returns An ordinary package-manager spec and its package version.
|
|
836
|
+
* @throws when a source installation lacks its workspace package, bundle patch, or built runtime.
|
|
837
|
+
*/
|
|
838
|
+
function officialBundleInstallTarget(name, installAnchor, version) {
|
|
839
|
+
const anchor = realpathSync(installAnchor);
|
|
840
|
+
const installation = manifestAt(anchor);
|
|
841
|
+
if (!(installation.name === "@deepseek-ai/dsh" && Object.values(installation.dependencies ?? {}).some((value) => value.startsWith("workspace:")))) return {
|
|
842
|
+
spec: `${name}@${version}`,
|
|
843
|
+
version
|
|
844
|
+
};
|
|
845
|
+
const root = resolve(dirname(anchor), "../..");
|
|
846
|
+
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`);
|
|
847
|
+
const matches = globSync("packages/*/*/package.json", { cwd: root }).map((file) => ({
|
|
848
|
+
file: join(root, file),
|
|
849
|
+
manifest: manifestAt(join(root, file))
|
|
850
|
+
})).filter((entry) => entry.manifest.name === name);
|
|
851
|
+
const selected = matches[0];
|
|
852
|
+
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`);
|
|
853
|
+
const { file, manifest } = selected;
|
|
854
|
+
const directory = realpathSync(dirname(file));
|
|
855
|
+
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}`);
|
|
856
|
+
const main = typeof manifest.main === "string" ? resolve(directory, manifest.main) : void 0;
|
|
857
|
+
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}`);
|
|
858
|
+
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`);
|
|
859
|
+
return {
|
|
860
|
+
spec: `link:${directory}`,
|
|
861
|
+
version
|
|
862
|
+
};
|
|
863
|
+
}
|
|
795
864
|
/** Public npmmirror URL shared by the fallback configuration and public-registry comparison. */
|
|
796
865
|
const NPMMIRROR_REGISTRY = "https://registry.npmmirror.com/";
|
|
797
866
|
/**
|
|
@@ -913,6 +982,10 @@ async function writePluginEnabled(filename, id, name, enabled) {
|
|
|
913
982
|
//#endregion
|
|
914
983
|
//#region lib/types/build-approval.js
|
|
915
984
|
/** Approve pnpm's pending dependency scripts in the current profile's workspace settings. */
|
|
985
|
+
/** Value pnpm leaves for a build script that still needs a decision. */
|
|
986
|
+
const UNDECIDED_BUILD = "set this to true or false";
|
|
987
|
+
/** Where a non-interactive run names the build scripts it skipped. */
|
|
988
|
+
const IGNORED_BUILDS = /\bIgnored build scripts: (.+)/;
|
|
916
989
|
async function readPolicy(dir) {
|
|
917
990
|
let text;
|
|
918
991
|
try {
|
|
@@ -929,17 +1002,41 @@ async function readPolicy(dir) {
|
|
|
929
1002
|
visit(builds ?? null, (_key, node) => {
|
|
930
1003
|
if (isAlias(node) || isNode(node) && "anchor" in node && node.anchor) throw new Error("allowBuilds must not contain YAML anchors or aliases");
|
|
931
1004
|
});
|
|
1005
|
+
const keys = isMap(builds) ? builds.items.flatMap(({ key }) => isScalar(key) && typeof key.value === "string" ? [key.value] : []) : [];
|
|
932
1006
|
return {
|
|
933
1007
|
document,
|
|
934
|
-
pending: isMap(builds) ? builds.items.flatMap(({ key, value }) => isScalar(key) && typeof key.value === "string" && !/[*?]/.test(key.value) && isScalar(value) && value.value ===
|
|
1008
|
+
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] : []) : [],
|
|
1009
|
+
keys
|
|
935
1010
|
};
|
|
936
1011
|
}
|
|
937
|
-
/**
|
|
1012
|
+
/**
|
|
1013
|
+
* Record the build scripts a failed run reported as ignored, then list every name awaiting a decision.
|
|
1014
|
+
*
|
|
1015
|
+
* pnpm writes those policy entries itself only when it can prompt, and the
|
|
1016
|
+
* manager always runs it non-interactively, so the run's own report is the
|
|
1017
|
+
* record that survives.
|
|
938
1018
|
* @param dir Current profile directory.
|
|
1019
|
+
* @param output Captured output of the failed run.
|
|
939
1020
|
* @returns Exact package names awaiting a build decision; wildcard rules are excluded.
|
|
940
1021
|
*/
|
|
941
|
-
async function
|
|
942
|
-
|
|
1022
|
+
async function recordPendingBuilds(dir, output) {
|
|
1023
|
+
const { document, pending, keys } = await readPolicy(dir);
|
|
1024
|
+
const known = new Set(keys);
|
|
1025
|
+
const added = ignoredBuildNames(output).filter((name) => !known.has(name));
|
|
1026
|
+
if (added.length === 0) return pending;
|
|
1027
|
+
for (const name of added) document.setIn(["allowBuilds", name], UNDECIDED_BUILD);
|
|
1028
|
+
await writeFileAtomic(join(dir, "pnpm-workspace.yaml"), String(document), { mode: 384 });
|
|
1029
|
+
return [...pending, ...added];
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Read the package names a pnpm run reported as ignored build scripts.
|
|
1033
|
+
* @param output Captured output of the run.
|
|
1034
|
+
* @returns Exact names in report order; wildcard patterns are excluded.
|
|
1035
|
+
*/
|
|
1036
|
+
function ignoredBuildNames(output) {
|
|
1037
|
+
const reported = IGNORED_BUILDS.exec(output)?.[1];
|
|
1038
|
+
if (reported === void 0) return [];
|
|
1039
|
+
return reported.split(",").map((name) => name.trim()).filter((name) => name !== "" && !/[*?]/.test(name));
|
|
943
1040
|
}
|
|
944
1041
|
/** Persist approval without running scripts; the caller holds the profile manifest lock.
|
|
945
1042
|
* @param dir Current profile directory.
|
|
@@ -1448,70 +1545,124 @@ let PluginManager = (() => {
|
|
|
1448
1545
|
};
|
|
1449
1546
|
});
|
|
1450
1547
|
}
|
|
1451
|
-
/** Read
|
|
1548
|
+
/** Read installed, installation-provided, and offline Official catalog bundles, plus selected non-bundle names.
|
|
1452
1549
|
* A dependency without a bundle patch is listed, as a `not-bundle` problem, only while it is selected.
|
|
1453
|
-
* @returns Package versions, manifest descriptions,
|
|
1454
|
-
* whether the installation offers the bundle, and removal availability.
|
|
1550
|
+
* @returns Package versions, manifest descriptions, the installable spec of profile dependencies, rows, optional
|
|
1551
|
+
* display metadata, activation selections, whether the installation offers the bundle, and removal availability.
|
|
1455
1552
|
*/
|
|
1456
1553
|
listBundles() {
|
|
1457
1554
|
const manifest = readProfileManifest("dsh", this.profile.dir);
|
|
1458
1555
|
const exemptions = readProfileVersionExemptions(this.profile.dir);
|
|
1459
1556
|
const selected = manifest.dsh?.profile?.bundles ?? [];
|
|
1460
|
-
const
|
|
1557
|
+
const recorded = manifest.dependencies ?? {};
|
|
1558
|
+
const dependencies = Object.keys(recorded);
|
|
1461
1559
|
const installation = JSON.parse(readFileSync(this.profile.installAnchor, "utf8"));
|
|
1560
|
+
const catalog = new Map(OFFICIAL_ON_DEMAND_CATALOG.map((entry) => [entry.packageName, entry]));
|
|
1462
1561
|
const names = [...new Set([
|
|
1463
1562
|
...selected,
|
|
1464
1563
|
...dependencies,
|
|
1465
|
-
...Object.keys(installation.dependencies ?? {})
|
|
1564
|
+
...Object.keys(installation.dependencies ?? {}),
|
|
1565
|
+
...catalog.keys()
|
|
1466
1566
|
])];
|
|
1467
1567
|
const bundles = [];
|
|
1568
|
+
const runtimeVersion = getDshRuntimeVersion();
|
|
1468
1569
|
for (const name of names) {
|
|
1469
1570
|
const installed = dependencies.includes(name);
|
|
1470
1571
|
const optional = OPTIONAL_BUNDLES.includes(name);
|
|
1471
|
-
const removable = installed && !Object.hasOwn(installation.dependencies ?? {}, name);
|
|
1472
1572
|
const enabled = selected.includes(name);
|
|
1573
|
+
const shipped = Object.hasOwn(installation.dependencies ?? {}, name);
|
|
1574
|
+
const offered = catalog.get(name);
|
|
1575
|
+
const official = optional || offered !== void 0;
|
|
1576
|
+
let target = {};
|
|
1577
|
+
let targetError;
|
|
1578
|
+
if (offered !== void 0) try {
|
|
1579
|
+
target = { installTarget: officialBundleInstallTarget(name, this.profile.installAnchor, runtimeVersion) };
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
targetError = managementError(error);
|
|
1582
|
+
}
|
|
1583
|
+
const catalogMeta = offered === void 0 ? {} : { meta: offered.meta };
|
|
1584
|
+
if (offered !== void 0 && !installed && !shipped) {
|
|
1585
|
+
bundles.push({
|
|
1586
|
+
name,
|
|
1587
|
+
official,
|
|
1588
|
+
availability: "missing",
|
|
1589
|
+
...catalogMeta,
|
|
1590
|
+
...target,
|
|
1591
|
+
enabled,
|
|
1592
|
+
installed,
|
|
1593
|
+
optional,
|
|
1594
|
+
removable: enabled,
|
|
1595
|
+
rows: [],
|
|
1596
|
+
overrides: [],
|
|
1597
|
+
...targetError !== void 0 ? { error: targetError } : enabled ? { error: { code: "not-bundle" } } : {}
|
|
1598
|
+
});
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
const owned = installed && !shipped;
|
|
1602
|
+
const removable = owned || enabled && !installed && !shipped;
|
|
1603
|
+
const sourceOf = (packageName) => owned ? { source: dependencySpec(name, recorded[name], this.profile.dir, packageName) } : {};
|
|
1473
1604
|
const readOnlyReason = this.protectsManager(name) ? "management-required" : void 0;
|
|
1605
|
+
let availability = "missing";
|
|
1606
|
+
let version;
|
|
1474
1607
|
try {
|
|
1475
1608
|
const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor);
|
|
1609
|
+
availability = shipped ? "installation" : "profile";
|
|
1476
1610
|
if (info === void 0) {
|
|
1477
|
-
if (enabled) bundles.push({
|
|
1611
|
+
if (enabled || offered !== void 0) bundles.push({
|
|
1478
1612
|
name,
|
|
1613
|
+
official,
|
|
1614
|
+
availability,
|
|
1615
|
+
...catalogMeta,
|
|
1616
|
+
...target,
|
|
1617
|
+
...sourceOf(),
|
|
1479
1618
|
enabled,
|
|
1480
1619
|
installed,
|
|
1481
1620
|
optional,
|
|
1482
1621
|
removable: removable && readOnlyReason === void 0,
|
|
1483
1622
|
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
1484
|
-
error: { code: "not-bundle" },
|
|
1623
|
+
error: targetError ?? { code: "not-bundle" },
|
|
1485
1624
|
rows: [],
|
|
1486
1625
|
overrides: []
|
|
1487
1626
|
});
|
|
1488
1627
|
continue;
|
|
1489
1628
|
}
|
|
1629
|
+
version = info.version;
|
|
1490
1630
|
const compatibility = evaluatePluginCompatibility(info, exemptions);
|
|
1491
1631
|
if (compatibility !== void 0 && !compatibility.exempted) throw new ManagementFailure("incompatible-version", [incompatiblePlugin(compatibility)]);
|
|
1492
1632
|
const dir = resolveBundleDir("dsh", name, this.profile.installAnchor, this.profile.dir);
|
|
1493
|
-
const meta = readPluginMeta(info.name ?? name, pathToFileURL(join(dir, "package.json")).href);
|
|
1633
|
+
const meta = offered?.meta ?? readPluginMeta(info.name ?? name, pathToFileURL(join(dir, "package.json")).href);
|
|
1494
1634
|
bundles.push({
|
|
1495
1635
|
name,
|
|
1496
|
-
|
|
1636
|
+
official,
|
|
1637
|
+
availability,
|
|
1638
|
+
...target,
|
|
1639
|
+
...version === void 0 ? {} : { version },
|
|
1497
1640
|
...info.description === void 0 || info.description === "" ? {} : { description: info.description },
|
|
1498
1641
|
...meta === void 0 ? {} : { meta },
|
|
1642
|
+
...sourceOf(info.name),
|
|
1499
1643
|
enabled,
|
|
1500
1644
|
installed,
|
|
1501
1645
|
optional,
|
|
1502
1646
|
removable: removable && readOnlyReason === void 0,
|
|
1503
1647
|
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
1504
|
-
...this.declaredRows(name, info)
|
|
1648
|
+
...this.declaredRows(name, info),
|
|
1649
|
+
...targetError === void 0 ? {} : { error: targetError }
|
|
1505
1650
|
});
|
|
1506
1651
|
} catch (error) {
|
|
1507
|
-
if (enabled || installed) bundles.push({
|
|
1652
|
+
if (enabled || installed || optional || offered !== void 0) bundles.push({
|
|
1508
1653
|
name,
|
|
1654
|
+
official,
|
|
1655
|
+
availability,
|
|
1656
|
+
...catalogMeta,
|
|
1657
|
+
...target,
|
|
1658
|
+
...version === void 0 ? {} : { version },
|
|
1659
|
+
...sourceOf(),
|
|
1509
1660
|
enabled,
|
|
1510
1661
|
installed,
|
|
1511
1662
|
optional,
|
|
1512
1663
|
removable: removable && readOnlyReason === void 0,
|
|
1513
1664
|
...readOnlyReason === void 0 ? {} : { readOnlyReason },
|
|
1514
|
-
error: managementError(error),
|
|
1665
|
+
error: targetError ?? managementError(error),
|
|
1515
1666
|
rows: [],
|
|
1516
1667
|
overrides: []
|
|
1517
1668
|
});
|
|
@@ -1670,7 +1821,9 @@ let PluginManager = (() => {
|
|
|
1670
1821
|
setBundleEnabled(name, enabled) {
|
|
1671
1822
|
return this.change((result) => this.configure(async () => {
|
|
1672
1823
|
await this.selectBundle(name, enabled);
|
|
1824
|
+
if (enabled) await this.refreshPackages();
|
|
1673
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();
|
|
1674
1827
|
}), {
|
|
1675
1828
|
stage: "enable",
|
|
1676
1829
|
target: name,
|
|
@@ -1685,7 +1838,7 @@ let PluginManager = (() => {
|
|
|
1685
1838
|
* `package.json` and `pnpm-lock.yaml` as they were; downloaded files can stay.
|
|
1686
1839
|
* @param spec One package spec, including local paths relative to the invocation directory.
|
|
1687
1840
|
* @param options Whether to activate the installed bundle (defaults to true), the request id a cancellation names,
|
|
1688
|
-
* the pending build scripts to allow
|
|
1841
|
+
* the pending build scripts to allow, whether to save an exact dependency, and the registry asked first.
|
|
1689
1842
|
* @returns Package-manager diagnostics, the registries asked, and the observed activation outcome.
|
|
1690
1843
|
*/
|
|
1691
1844
|
installBundle(spec, options) {
|
|
@@ -1714,6 +1867,7 @@ let PluginManager = (() => {
|
|
|
1714
1867
|
const files = await this.readRestoredFiles();
|
|
1715
1868
|
const before = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1716
1869
|
let name;
|
|
1870
|
+
let version;
|
|
1717
1871
|
try {
|
|
1718
1872
|
result.registries = [];
|
|
1719
1873
|
const connection = checkGithubConnection(parsedForRegistry(spec), this.profile.dir, {
|
|
@@ -1749,6 +1903,7 @@ let PluginManager = (() => {
|
|
|
1749
1903
|
run = await this.runPnpm([
|
|
1750
1904
|
"add",
|
|
1751
1905
|
spec,
|
|
1906
|
+
...options?.saveExact === true ? ["--save-exact"] : [],
|
|
1752
1907
|
...registryArguments(registry)
|
|
1753
1908
|
], control.abort.signal, requestId);
|
|
1754
1909
|
result.packageResult = run;
|
|
@@ -1769,7 +1924,7 @@ let PluginManager = (() => {
|
|
|
1769
1924
|
if (succeeded) delete result.failedAt;
|
|
1770
1925
|
if (!succeeded) {
|
|
1771
1926
|
try {
|
|
1772
|
-
result.pendingBuilds = await
|
|
1927
|
+
result.pendingBuilds = await recordPendingBuilds(this.profile.dir, run.output);
|
|
1773
1928
|
} catch (error) {
|
|
1774
1929
|
this.ownerContext.logger.warn("Could not read pending build approvals after pnpm failed", error);
|
|
1775
1930
|
}
|
|
@@ -1777,7 +1932,11 @@ let PluginManager = (() => {
|
|
|
1777
1932
|
}
|
|
1778
1933
|
const after = readProfileManifest("dsh", this.profile.dir).dependencies ?? {};
|
|
1779
1934
|
const installed = Object.keys(after).filter((name) => before[name] !== after[name]);
|
|
1780
|
-
if (installed.length === 0)
|
|
1935
|
+
if (installed.length === 0) {
|
|
1936
|
+
const parsed = parsedForRegistry(spec);
|
|
1937
|
+
const localName = parsed.kind === "path" ? readProfileManifest("dsh", parsed.path).name : void 0;
|
|
1938
|
+
installed.push(...Object.keys(after).filter((name) => name === localName || spec === name || spec.startsWith(`${name}@`)));
|
|
1939
|
+
}
|
|
1781
1940
|
const target = installed[0];
|
|
1782
1941
|
if (installed.length !== 1 || target === void 0) throw new ManagementFailure("ambiguous-install");
|
|
1783
1942
|
name = target;
|
|
@@ -1787,6 +1946,7 @@ let PluginManager = (() => {
|
|
|
1787
1946
|
const compatibility = evaluatePluginCompatibility(manifest, readProfileVersionExemptions(this.profile.dir));
|
|
1788
1947
|
if (compatibility !== void 0 && !compatibility.exempted) throw new ManagementFailure("incompatible-version", [incompatiblePlugin(compatibility)]);
|
|
1789
1948
|
for (const file of bundlePatchPaths(dir, manifest.dsh.bundle)) loadOverlayPatches("dsh", file);
|
|
1949
|
+
version = manifest.version;
|
|
1790
1950
|
} catch (error) {
|
|
1791
1951
|
await this.restoreFiles(files);
|
|
1792
1952
|
throw error;
|
|
@@ -1794,11 +1954,13 @@ let PluginManager = (() => {
|
|
|
1794
1954
|
control.phase = "applying";
|
|
1795
1955
|
announce("applying");
|
|
1796
1956
|
result.bundle = name;
|
|
1957
|
+
if (version !== void 0) result.version = version;
|
|
1797
1958
|
result.target = name;
|
|
1798
1959
|
result.stage = "enable";
|
|
1799
1960
|
return this.configure(async () => {
|
|
1800
1961
|
if (options?.enabled !== false) await this.selectBundle(name, true);
|
|
1801
1962
|
if (Object.hasOwn(before, name)) return "restart-required";
|
|
1963
|
+
await this.refreshPackages();
|
|
1802
1964
|
if (options?.enabled !== false) result.warnings = await this.reload();
|
|
1803
1965
|
});
|
|
1804
1966
|
}, {
|
|
@@ -1837,13 +1999,14 @@ let PluginManager = (() => {
|
|
|
1837
1999
|
await control.result.then(() => void 0, () => void 0);
|
|
1838
2000
|
return { status: "cancelled" };
|
|
1839
2001
|
}
|
|
1840
|
-
/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path
|
|
1841
|
-
*
|
|
2002
|
+
/** Unload and remove a profile-owned bundle dependency through dsh plugin's pnpm path; a selected name no
|
|
2003
|
+
* dependency holds is only deselected.
|
|
2004
|
+
* @param name Installed dependency or selected bundle name.
|
|
1842
2005
|
* @returns Removal diagnostics and the remaining profile state.
|
|
1843
2006
|
*/
|
|
1844
2007
|
removeBundle(name) {
|
|
1845
2008
|
return this.change(async (result) => {
|
|
1846
|
-
await this.configure(async () => {
|
|
2009
|
+
if (!await this.configure(async () => {
|
|
1847
2010
|
const bundle = (await this.listBundles()).find((item) => item.name === name);
|
|
1848
2011
|
if (bundle === void 0 || !bundle.removable) throw new ManagementFailure("not-removable");
|
|
1849
2012
|
if (this.ownerContext.get("hmr") === void 0 && (this.profile.startedBundles.includes(name) || bundle.error === void 0 && this.bundleRows(name).some((row) => [...this.ctx.loader.entries()].some((entry) => entry.options.id === row.id && entry.fiber !== void 0)))) throw new ManagementFailure("stop-profile");
|
|
@@ -1853,9 +2016,11 @@ let PluginManager = (() => {
|
|
|
1853
2016
|
result.warnings = await this.reload();
|
|
1854
2017
|
}
|
|
1855
2018
|
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");
|
|
1856
|
-
|
|
2019
|
+
return bundle.installed;
|
|
2020
|
+
})) return;
|
|
1857
2021
|
result.packageResult = await this.runPnpm(["remove", name]);
|
|
1858
2022
|
if (result.packageResult.exitCode !== 0 || result.packageResult.timedOut === true) throw new Error(result.packageResult.output);
|
|
2023
|
+
await this.configure(() => this.refreshPackages());
|
|
1859
2024
|
}, {
|
|
1860
2025
|
stage: "remove",
|
|
1861
2026
|
target: name
|
|
@@ -1881,7 +2046,7 @@ let PluginManager = (() => {
|
|
|
1881
2046
|
const rows = [];
|
|
1882
2047
|
const packages = this.ctx.get("pluginPackages");
|
|
1883
2048
|
for (const row of flatten(composeEntries([patches.filter((item) => item.insert !== void 0)]))) {
|
|
1884
|
-
if (typeof row.id !== "string" || typeof row.name !== "string") continue;
|
|
2049
|
+
if (typeof row.id !== "string" || typeof row.name !== "string" || row.group === true) continue;
|
|
1885
2050
|
const active = live.get(row.id);
|
|
1886
2051
|
const entryId = active?.entryId;
|
|
1887
2052
|
const base = active?.baseUrl ?? pathToFileURL(join(dir, "package.json")).href;
|
|
@@ -2028,6 +2193,13 @@ let PluginManager = (() => {
|
|
|
2028
2193
|
};
|
|
2029
2194
|
return hmr === void 0 ? apply() : hmr.runExclusive(apply);
|
|
2030
2195
|
}
|
|
2196
|
+
async refreshPackages() {
|
|
2197
|
+
if (this.ownerContext.get("hmr") === void 0) {
|
|
2198
|
+
const selected = readProfileManifest("dsh", this.profile.dir).dsh?.profile?.bundles ?? [];
|
|
2199
|
+
if (this.profile.startedBundles.some((name) => !selected.includes(name))) return;
|
|
2200
|
+
}
|
|
2201
|
+
await this.ownerContext.get("pluginPackages")?.refresh();
|
|
2202
|
+
}
|
|
2031
2203
|
async reload(requiredIds = []) {
|
|
2032
2204
|
if (this.ownerContext.get("hmr") === void 0) return [];
|
|
2033
2205
|
return reconcileProfilePatches(this.ownerContext.root, readProfilePatches("dsh", this.profile), "dsh", requiredIds);
|