@sparkelf/dsh-plus 0.2.1-rc.2.4 → 0.2.2-alpha.2.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.md +7 -5
- package/README.zh.md +7 -5
- package/lib/apply-08huHfLG.js +665 -0
- package/lib/apply-DmICvIGh.js +653 -0
- package/lib/apply.js +1 -1
- package/lib/bin.js +188 -45
- package/lib/types/apply.d.ts +1 -1
- package/lib/types/apply.js +56 -117
- package/lib/types/patch-set.d.ts +59 -0
- package/lib/types/patch-set.js +132 -0
- package/lib/types/standalone-cli.js +93 -2
- package/lib/types/standalone-profile.d.ts +8 -1
- package/lib/types/standalone-profile.js +126 -49
- package/package.json +602 -175
- package/patches/approval-allow-policy.patch +108 -0
- package/patches/atomic-write-zombie-lock-holder.patch +53 -0
- package/patches/bootstrap-exempt-names.patch +32 -0
- package/patches/browser-auth-mode.patch +127 -0
- package/patches/composer-popover-boundaries.patch +337 -0
- package/patches/diagram-preview.patch +562 -0
- package/patches/image-offload-hoist-replay.patch +533 -0
- package/patches/legacy-code-preset.patch +148 -0
- package/patches/llm-pi-ai-transport-truncation.patch +33 -0
- package/patches/mobile-journal-generation.patch +76 -0
- package/patches/office-preview-stable-load.patch +34 -0
- package/patches/officecli-deliverables.patch +42 -0
- package/patches/ptc-mcp-schema-types.patch +156 -0
- package/patches/responses-reasoning-status.patch +289 -0
- package/patches/session-envelope-ignorable.patch +63 -0
- package/patches/session-export-chinese.patch +85 -0
- package/patches/session-format-legacy-restart.patch +35 -0
- package/patches/session-log-trajectory-toolbar.patch +352 -0
- package/patches/state-dot-brand-blue.patch +185 -0
- package/patches/ui-settings-compact-phone.patch +76 -0
- package/patches/web-runtime-compat.patch +83 -0
- package/patches/workspace-storage-restore.patch +186 -0
- package/patches/wsl-native-open.patch +265 -0
package/lib/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as runApply } from "./apply-
|
|
2
|
+
import { i as readPatchSet, r as runApply } from "./apply-08huHfLG.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
5
5
|
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
@@ -858,6 +858,11 @@ function requireStringArray(value, label) {
|
|
|
858
858
|
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(label + " must be a string array");
|
|
859
859
|
return value;
|
|
860
860
|
}
|
|
861
|
+
/** Read one non-empty string field. */
|
|
862
|
+
function requireString(value, label) {
|
|
863
|
+
if (typeof value !== "string" || value.trim() === "") throw new Error(label + " must be a non-empty string");
|
|
864
|
+
return value;
|
|
865
|
+
}
|
|
861
866
|
/** Resolve the DSH home, honouring the environment override the launcher itself reads. */
|
|
862
867
|
function resolveHome(env = process.env) {
|
|
863
868
|
const configured = env.DSH_HOME;
|
|
@@ -895,11 +900,12 @@ function isWithin(candidate, parent, platform = process.platform) {
|
|
|
895
900
|
* @returns absolute path to the installed distribution.
|
|
896
901
|
*/
|
|
897
902
|
function resolveDistributionDirectory(anchor) {
|
|
903
|
+
const ownDirectory = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
898
904
|
let resolved;
|
|
899
905
|
try {
|
|
900
906
|
resolved = dirname(createRequire(anchor).resolve("@sparkelf/dsh-plus/package.json"));
|
|
901
907
|
} catch {
|
|
902
|
-
|
|
908
|
+
return ownDirectory;
|
|
903
909
|
}
|
|
904
910
|
let current = resolve(dirname(anchor));
|
|
905
911
|
for (;;) {
|
|
@@ -908,20 +914,14 @@ function resolveDistributionDirectory(anchor) {
|
|
|
908
914
|
if (parent === current) break;
|
|
909
915
|
current = parent;
|
|
910
916
|
}
|
|
911
|
-
|
|
917
|
+
return ownDirectory;
|
|
912
918
|
}
|
|
913
|
-
/** The reviewed bundle order and pins the installed distribution declares. */
|
|
914
919
|
function readDistributionProfile(distributionDirectory) {
|
|
915
920
|
const manifest = requireRecord(JSON.parse(readFileSync(join(distributionDirectory, "package.json"), "utf8")), "Plus distribution manifest");
|
|
916
921
|
const plus = requireRecord(manifest.dshPlus, "dshPlus");
|
|
917
922
|
const profile = requireRecord(plus.profile, "dshPlus.profile");
|
|
918
|
-
const
|
|
923
|
+
const dependencies = requireDependencyMap(requireRecord(profile.dependencies, "dshPlus.profile.dependencies"), "dshPlus.profile.dependencies");
|
|
919
924
|
const rawAllowBuilds = requireRecord(profile.allowBuilds, "dshPlus.profile.allowBuilds");
|
|
920
|
-
const dependencies = {};
|
|
921
|
-
for (const [name, spec] of Object.entries(rawDependencies)) {
|
|
922
|
-
if (typeof spec !== "string" || spec === "") throw new Error("dshPlus.profile.dependencies." + name + " must be a non-empty string");
|
|
923
|
-
dependencies[name] = spec;
|
|
924
|
-
}
|
|
925
925
|
const allowBuilds = {};
|
|
926
926
|
for (const [name, allowed] of Object.entries(rawAllowBuilds)) {
|
|
927
927
|
if (typeof allowed !== "boolean") throw new Error("dshPlus.profile.allowBuilds." + name + " must be a boolean");
|
|
@@ -933,6 +933,16 @@ function readDistributionProfile(distributionDirectory) {
|
|
|
933
933
|
if (typeof spec !== "string" || spec === "") throw new Error("dshPlus.profile.overrides." + name + " must be a non-empty string");
|
|
934
934
|
overrides[name] = spec;
|
|
935
935
|
}
|
|
936
|
+
const editions = {};
|
|
937
|
+
const rawEditions = plus.editions === void 0 ? {} : requireRecord(plus.editions, "dshPlus.editions");
|
|
938
|
+
for (const [id, value] of Object.entries(rawEditions)) {
|
|
939
|
+
const edition = requireRecord(value, "dshPlus.editions." + id);
|
|
940
|
+
editions[id] = {
|
|
941
|
+
label: requireString(edition.label, "dshPlus.editions." + id + ".label"),
|
|
942
|
+
bundles: requireStringArray(edition.bundles, "dshPlus.editions." + id + ".bundles"),
|
|
943
|
+
packages: requireDependencyMap(requireRecord(edition.packages, "dshPlus.editions." + id + ".packages"), "dshPlus.editions." + id + ".packages")
|
|
944
|
+
};
|
|
945
|
+
}
|
|
936
946
|
const compatibility = requireRecord(plus.compatibility, "dshPlus.compatibility");
|
|
937
947
|
return {
|
|
938
948
|
name: String(manifest.name),
|
|
@@ -940,10 +950,20 @@ function readDistributionProfile(distributionDirectory) {
|
|
|
940
950
|
dependencies,
|
|
941
951
|
allowBuilds,
|
|
942
952
|
overrides,
|
|
953
|
+
editions,
|
|
943
954
|
dshRange: String(compatibility.dsh),
|
|
944
955
|
version: String(manifest.version)
|
|
945
956
|
};
|
|
946
957
|
}
|
|
958
|
+
/** Read one package-name-to-spec map, rejecting empty entries. */
|
|
959
|
+
function requireDependencyMap(value, label) {
|
|
960
|
+
const dependencies = {};
|
|
961
|
+
for (const [name, spec] of Object.entries(value)) {
|
|
962
|
+
if (typeof spec !== "string" || spec === "") throw new Error(label + "." + name + " must be a non-empty string");
|
|
963
|
+
dependencies[name] = spec;
|
|
964
|
+
}
|
|
965
|
+
return dependencies;
|
|
966
|
+
}
|
|
947
967
|
/**
|
|
948
968
|
* Give the profile its own installed tree, built from the distribution's declarations.
|
|
949
969
|
*
|
|
@@ -963,7 +983,11 @@ function readDistributionProfile(distributionDirectory) {
|
|
|
963
983
|
* @param consumerDirectory - directory whose `node_modules` holds the installation.
|
|
964
984
|
*/
|
|
965
985
|
function installProfilePackages(paths, consumerDirectory) {
|
|
966
|
-
if (!existsSync(join(consumerDirectory, "node_modules")))
|
|
986
|
+
if (!existsSync(join(consumerDirectory, "node_modules"))) {
|
|
987
|
+
installWithRegistryFallback(paths);
|
|
988
|
+
alignReplacedPackageNames(join(paths.profileDirectory, "node_modules"));
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
967
991
|
const profileModules = join(paths.profileDirectory, "node_modules");
|
|
968
992
|
if (existsSync(profileModules)) {
|
|
969
993
|
linkNestedBundles(paths, profileModules);
|
|
@@ -993,7 +1017,8 @@ function installWithRegistryFallback(paths) {
|
|
|
993
1017
|
], {
|
|
994
1018
|
cwd: paths.profileDirectory,
|
|
995
1019
|
stdio: "inherit",
|
|
996
|
-
shell: process.platform === "win32"
|
|
1020
|
+
shell: process.platform === "win32",
|
|
1021
|
+
timeout: 5 * 6e4
|
|
997
1022
|
});
|
|
998
1023
|
if (result.error !== void 0) throw result.error;
|
|
999
1024
|
if (result.status === 0) return;
|
|
@@ -1153,12 +1178,47 @@ function resolvePaths(anchor, env = process.env) {
|
|
|
1153
1178
|
*
|
|
1154
1179
|
* @param paths - resolved standalone paths.
|
|
1155
1180
|
* @param consumerDirectory - directory whose `node_modules` holds the installed packages.
|
|
1181
|
+
* @param edition - edition id whose package list to install, or undefined for the default.
|
|
1156
1182
|
* @returns whether this call created the manifest.
|
|
1157
1183
|
*/
|
|
1158
|
-
function ensureProfile(paths, consumerDirectory) {
|
|
1184
|
+
function ensureProfile(paths, consumerDirectory, edition) {
|
|
1159
1185
|
const manifestPath = join(paths.profileDirectory, "package.json");
|
|
1160
1186
|
const distribution = readDistributionProfile(paths.distributionDirectory);
|
|
1187
|
+
const composition = edition === void 0 ? void 0 : distribution.editions[edition];
|
|
1188
|
+
if (edition !== void 0 && composition === void 0) {
|
|
1189
|
+
const offered = Object.keys(distribution.editions);
|
|
1190
|
+
throw new Error("unknown edition \"" + edition + "\"; this release offers " + (offered.length === 0 ? "(none)" : offered.join(", ")));
|
|
1191
|
+
}
|
|
1192
|
+
for (const name of Object.keys(composition?.packages ?? {})) if (name.startsWith("@deepseek-ai/")) throw new Error("edition \"" + String(edition) + "\" lists the official package " + name + "; editions contain only third-party or self-developed plugins");
|
|
1193
|
+
const runtime = distribution.dshRange.replace(/^>=?/u, "");
|
|
1194
|
+
const baseBundles = composition === void 0 ? distribution.bundles : distribution.bundles.filter((bundle) => bundle.startsWith("@deepseek-ai/") || bundle === "@sparkelf/dsh-plus");
|
|
1195
|
+
const basePackages = composition === void 0 ? distribution.dependencies : {
|
|
1196
|
+
"@deepseek-ai/dsh": distribution.dshRange,
|
|
1197
|
+
"@sparkelf/dsh-plus": distribution.version,
|
|
1198
|
+
...Object.fromEntries(baseBundles.filter((bundle) => bundle.startsWith("@deepseek-ai/dsh-")).map((bundle) => [bundle, runtime]))
|
|
1199
|
+
};
|
|
1200
|
+
const compositionPackages = composition === void 0 ? basePackages : {
|
|
1201
|
+
...basePackages,
|
|
1202
|
+
...composition.packages
|
|
1203
|
+
};
|
|
1204
|
+
const compositionBundles = composition === void 0 ? baseBundles : [...baseBundles, ...composition.bundles];
|
|
1161
1205
|
if (existsSync(manifestPath)) {
|
|
1206
|
+
if (composition !== void 0) {
|
|
1207
|
+
const manifest = requireRecord(JSON.parse(readFileSync(manifestPath, "utf8")), "Plus profile manifest");
|
|
1208
|
+
manifest.dependencies = {
|
|
1209
|
+
...manifest.dependencies,
|
|
1210
|
+
...compositionPackages
|
|
1211
|
+
};
|
|
1212
|
+
manifest.dsh = {
|
|
1213
|
+
...manifest.dsh,
|
|
1214
|
+
profile: {
|
|
1215
|
+
...manifest.dsh?.profile,
|
|
1216
|
+
bundles: [...compositionBundles],
|
|
1217
|
+
...edition === void 0 ? {} : { plusEdition: edition }
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
1221
|
+
}
|
|
1162
1222
|
writeProfileOverrides(paths.profileDirectory, distribution.overrides, distribution.allowBuilds, paths.omittedPackages);
|
|
1163
1223
|
installProfilePackages(paths, consumerDirectory);
|
|
1164
1224
|
return false;
|
|
@@ -1168,14 +1228,11 @@ function ensureProfile(paths, consumerDirectory) {
|
|
|
1168
1228
|
name: "dsh-profile-plus",
|
|
1169
1229
|
private: true,
|
|
1170
1230
|
type: "module",
|
|
1171
|
-
dependencies:
|
|
1172
|
-
"@sparkelf/dsh-plus": distribution.version,
|
|
1173
|
-
"@deepseek-ai/dsh": distribution.dshRange,
|
|
1174
|
-
...distribution.dependencies
|
|
1175
|
-
},
|
|
1231
|
+
dependencies: compositionPackages,
|
|
1176
1232
|
dsh: { profile: {
|
|
1177
|
-
bundles:
|
|
1178
|
-
patchReload: "live"
|
|
1233
|
+
bundles: [...compositionBundles],
|
|
1234
|
+
patchReload: "live",
|
|
1235
|
+
...edition === void 0 ? {} : { plusEdition: edition }
|
|
1179
1236
|
} }
|
|
1180
1237
|
};
|
|
1181
1238
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
@@ -1234,30 +1291,21 @@ function git(root, args, acceptFailure = false) {
|
|
|
1234
1291
|
* @returns the labels of the patches that were applied.
|
|
1235
1292
|
*/
|
|
1236
1293
|
function applyProfileNpmPatches(distributionDirectory, profileDirectory) {
|
|
1237
|
-
const
|
|
1238
|
-
if (!Array.isArray(names)) throw new Error("dshPlus.patchPackages must be an array");
|
|
1294
|
+
const patchSet = readPatchSet(distributionDirectory, requireRecord(JSON.parse(readFileSync(join(distributionDirectory, "package.json"), "utf8")), "Plus distribution manifest"));
|
|
1239
1295
|
const applied = [];
|
|
1240
|
-
for (const
|
|
1241
|
-
if (
|
|
1242
|
-
const
|
|
1243
|
-
const
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
"apply",
|
|
1254
|
-
"--reverse",
|
|
1255
|
-
"--check",
|
|
1256
|
-
file
|
|
1257
|
-
], true) !== void 0) continue;
|
|
1258
|
-
git(patched.directory, ["apply", file]);
|
|
1259
|
-
applied.push(value + " -> " + targetName);
|
|
1260
|
-
}
|
|
1296
|
+
for (const entry of [...patchSet.entries, ...patchSet.thirdPartyPatches]) {
|
|
1297
|
+
if (entry.target.kind !== "npm") continue;
|
|
1298
|
+
const targetName = entry.target.name;
|
|
1299
|
+
const patched = resolveInstalledPackage(profileDirectory, targetName);
|
|
1300
|
+
const file = resolve(distributionDirectory, entry.file);
|
|
1301
|
+
if (git(patched.directory, [
|
|
1302
|
+
"apply",
|
|
1303
|
+
"--reverse",
|
|
1304
|
+
"--check",
|
|
1305
|
+
file
|
|
1306
|
+
], true) !== void 0) continue;
|
|
1307
|
+
git(patched.directory, ["apply", file]);
|
|
1308
|
+
applied.push(entry.id + " -> " + targetName);
|
|
1261
1309
|
}
|
|
1262
1310
|
return applied;
|
|
1263
1311
|
}
|
|
@@ -1268,7 +1316,18 @@ function resolveInstalledPackage(from, packageName) {
|
|
|
1268
1316
|
try {
|
|
1269
1317
|
manifestPath = requireFrom.resolve(packageName + "/package.json");
|
|
1270
1318
|
} catch {
|
|
1271
|
-
|
|
1319
|
+
let directory;
|
|
1320
|
+
try {
|
|
1321
|
+
directory = dirname(requireFrom.resolve(packageName));
|
|
1322
|
+
} catch {
|
|
1323
|
+
throw new Error(packageName + " is not installed under " + from);
|
|
1324
|
+
}
|
|
1325
|
+
while (!existsSync(resolve(directory, "package.json"))) {
|
|
1326
|
+
const parent = dirname(directory);
|
|
1327
|
+
if (parent === directory) throw new Error(packageName + " has no resolvable package manifest under " + from);
|
|
1328
|
+
directory = parent;
|
|
1329
|
+
}
|
|
1330
|
+
manifestPath = resolve(directory, "package.json");
|
|
1272
1331
|
}
|
|
1273
1332
|
const manifest = requireRecord(JSON.parse(readFileSync(manifestPath, "utf8")), packageName + " manifest");
|
|
1274
1333
|
return {
|
|
@@ -1298,6 +1357,7 @@ function parseStartOptions(argv) {
|
|
|
1298
1357
|
let host = "127.0.0.1";
|
|
1299
1358
|
let open = true;
|
|
1300
1359
|
let foreground = false;
|
|
1360
|
+
let edition;
|
|
1301
1361
|
let capabilities;
|
|
1302
1362
|
for (let index = 0; index < argv.length; index += 1) {
|
|
1303
1363
|
const token = argv[index];
|
|
@@ -1323,6 +1383,13 @@ function parseStartOptions(argv) {
|
|
|
1323
1383
|
foreground = true;
|
|
1324
1384
|
continue;
|
|
1325
1385
|
}
|
|
1386
|
+
if (token === "--edition") {
|
|
1387
|
+
const value = argv[index + 1];
|
|
1388
|
+
if (value === void 0 || value === "") throw new Error("--edition requires a value");
|
|
1389
|
+
edition = value;
|
|
1390
|
+
index += 1;
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1326
1393
|
if (token === "--capabilities") {
|
|
1327
1394
|
const value = argv[index + 1];
|
|
1328
1395
|
if (value === void 0) throw new Error("--capabilities requires a comma-separated list, or an empty string for none");
|
|
@@ -1340,6 +1407,7 @@ function parseStartOptions(argv) {
|
|
|
1340
1407
|
host,
|
|
1341
1408
|
open,
|
|
1342
1409
|
foreground,
|
|
1410
|
+
edition,
|
|
1343
1411
|
capabilities
|
|
1344
1412
|
};
|
|
1345
1413
|
}
|
|
@@ -1425,6 +1493,56 @@ function runForeground(entry, profileName, port, host, open) {
|
|
|
1425
1493
|
if (!open) args.push("--no-open");
|
|
1426
1494
|
return spawnSync(process.execPath, args, { stdio: "inherit" }).status ?? 1;
|
|
1427
1495
|
}
|
|
1496
|
+
/** Parse the install command's edition selection. */
|
|
1497
|
+
function parseInstallOptions(argv) {
|
|
1498
|
+
let edition;
|
|
1499
|
+
let profile;
|
|
1500
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
1501
|
+
const token = argv[index];
|
|
1502
|
+
if (token === "--edition") {
|
|
1503
|
+
const value = argv[index + 1];
|
|
1504
|
+
if (value === void 0 || value === "") throw new Error("--edition requires a value");
|
|
1505
|
+
edition = value;
|
|
1506
|
+
index += 1;
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
if (token === "--profile") {
|
|
1510
|
+
const value = argv[index + 1];
|
|
1511
|
+
if (value === void 0 || value === "") throw new Error("--profile requires a value");
|
|
1512
|
+
profile = value;
|
|
1513
|
+
index += 1;
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
throw new Error("unknown option: " + String(token));
|
|
1517
|
+
}
|
|
1518
|
+
if (edition === void 0) throw new Error("install requires --edition personal or --edition team");
|
|
1519
|
+
return {
|
|
1520
|
+
edition,
|
|
1521
|
+
profile
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Install one edition's package manifest into the profile.
|
|
1526
|
+
*
|
|
1527
|
+
* The profile's `package.json` is the install artifact; there is no separate standalone
|
|
1528
|
+
* package. A later `start` uses the same manifest, so changing the edition is a new
|
|
1529
|
+
* install rather than a second product.
|
|
1530
|
+
*
|
|
1531
|
+
* @param argv - arguments after `install`.
|
|
1532
|
+
* @returns the process exit code.
|
|
1533
|
+
*/
|
|
1534
|
+
function install(argv) {
|
|
1535
|
+
const { edition, profile } = parseInstallOptions(argv);
|
|
1536
|
+
const base = resolvePaths(declarationAnchor());
|
|
1537
|
+
const paths = profile === void 0 ? base : {
|
|
1538
|
+
...base,
|
|
1539
|
+
profileName: profile,
|
|
1540
|
+
profileDirectory: join(base.home, "profiles", profile)
|
|
1541
|
+
};
|
|
1542
|
+
ensureProfile(paths, installationRoot(), edition);
|
|
1543
|
+
console.log("Installed the " + edition + " edition into " + paths.profileDirectory);
|
|
1544
|
+
return 0;
|
|
1545
|
+
}
|
|
1428
1546
|
async function start(argv) {
|
|
1429
1547
|
const options = parseStartOptions(argv);
|
|
1430
1548
|
const anchor = installationAnchor();
|
|
@@ -1458,8 +1576,9 @@ async function start(argv) {
|
|
|
1458
1576
|
}
|
|
1459
1577
|
console.log("pnpm installed.");
|
|
1460
1578
|
}
|
|
1461
|
-
const created = ensureProfile(paths, installationRoot());
|
|
1579
|
+
const created = ensureProfile(paths, installationRoot(), options.edition);
|
|
1462
1580
|
console.log(created ? "Created the " + paths.profileName + " profile at " + paths.profileDirectory : "Using the existing " + paths.profileName + " profile");
|
|
1581
|
+
if (options.edition !== void 0) console.log("Edition: " + options.edition);
|
|
1463
1582
|
for (const label of applyProfileNpmPatches(paths.distributionDirectory, paths.profileDirectory)) console.log("Applied the reviewed patch " + label);
|
|
1464
1583
|
const answers = created || !existsSync(join(paths.home, "capabilities.json")) ? options.capabilities === void 0 ? await interviewCapabilities(true) : selectCapabilities(options.capabilities) : void 0;
|
|
1465
1584
|
if (answers !== void 0) {
|
|
@@ -1802,6 +1921,23 @@ async function doctor() {
|
|
|
1802
1921
|
const record = readCapabilityRecord(paths.home);
|
|
1803
1922
|
const drifted = record === void 0 ? [] : CAPABILITIES.filter((capability) => record.enabled.includes(capability.id) !== actual.mounted.includes(capability.id));
|
|
1804
1923
|
check(drifted.length === 0, drifted.length === 0 ? "capabilities: " + (actual.mounted.length === 0 ? "(none enabled)" : actual.mounted.join(", ")) : "capabilities disagree with capabilities.json: " + drifted.map((capability) => capability.id).join(", ") + " (run dsh-plus capabilities)");
|
|
1924
|
+
const profileManifestPath = join(paths.profileDirectory, "package.json");
|
|
1925
|
+
if (existsSync(profileManifestPath)) {
|
|
1926
|
+
const profileManifest = JSON.parse(readFileSync(profileManifestPath, "utf8"));
|
|
1927
|
+
const editionId = profileManifest.dsh?.profile?.plusEdition;
|
|
1928
|
+
if (editionId !== void 0) {
|
|
1929
|
+
const edition = readDistributionProfile(paths.distributionDirectory).editions[editionId];
|
|
1930
|
+
if (edition === void 0) check(false, "edition \"" + editionId + "\" is not declared by this distribution");
|
|
1931
|
+
else {
|
|
1932
|
+
const bundles = new Set(profileManifest.dsh?.profile?.bundles ?? []);
|
|
1933
|
+
const missingBundles = edition.bundles.filter((bundle) => !bundles.has(bundle));
|
|
1934
|
+
const packageNames = Object.keys(edition.packages);
|
|
1935
|
+
const missingPackages = packageNames.filter((name) => !existsSync(join(paths.profileDirectory, "node_modules", ...name.split("/"), "package.json")));
|
|
1936
|
+
check(missingBundles.length === 0, "edition " + editionId + ": " + String(edition.bundles.length - missingBundles.length) + "/" + String(edition.bundles.length) + " bundles mounted" + (missingBundles.length === 0 ? "" : " (missing " + missingBundles.join(", ") + ")"));
|
|
1937
|
+
check(missingPackages.length === 0, "edition " + editionId + ": " + String(packageNames.length - missingPackages.length) + "/" + String(packageNames.length) + " packages installed" + (missingPackages.length === 0 ? "" : " (missing " + missingPackages.join(", ") + ")"));
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1805
1941
|
console.log(failures === 0 ? "No problems found." : String(failures) + " problem(s) found.");
|
|
1806
1942
|
return failures === 0 ? 0 : 1;
|
|
1807
1943
|
}
|
|
@@ -1809,6 +1945,7 @@ const USAGE = [
|
|
|
1809
1945
|
"Usage: dsh-plus <command> [options]",
|
|
1810
1946
|
"",
|
|
1811
1947
|
"Commands:",
|
|
1948
|
+
" install install an edition package manifest into the profile",
|
|
1812
1949
|
" start start the server (first run creates the profile)",
|
|
1813
1950
|
" stop stop the server",
|
|
1814
1951
|
" status report whether the server is running",
|
|
@@ -1821,6 +1958,11 @@ const USAGE = [
|
|
|
1821
1958
|
" --host <h> interface to bind (default 127.0.0.1)",
|
|
1822
1959
|
" --no-open do not open a browser",
|
|
1823
1960
|
" --foreground run in this terminal instead of in the background",
|
|
1961
|
+
" --edition <id> install the named edition manifest (personal or team)",
|
|
1962
|
+
"",
|
|
1963
|
+
"install options:",
|
|
1964
|
+
" --edition <id> package manifest to install (personal or team)",
|
|
1965
|
+
" --profile <n> profile name to install into (default: this installation)",
|
|
1824
1966
|
"",
|
|
1825
1967
|
"capabilities options:",
|
|
1826
1968
|
" --enable <ids> enable the comma-separated capability ids",
|
|
@@ -1843,6 +1985,7 @@ async function runStandaloneCli(argv) {
|
|
|
1843
1985
|
console.log(USAGE);
|
|
1844
1986
|
return 0;
|
|
1845
1987
|
}
|
|
1988
|
+
if (command === "install") return install(rest);
|
|
1846
1989
|
if (command === "start") return await start(rest);
|
|
1847
1990
|
if (command === "stop") return stop();
|
|
1848
1991
|
if (command === "status") return status();
|
package/lib/types/apply.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Materialize the Plus distribution's
|
|
1
|
+
/** Materialize the Plus distribution's embedded patch set without Desktop. */
|
|
2
2
|
/** One official workspace package available as a profile peer dependency. */
|
|
3
3
|
export interface OfficialWorkspacePackage {
|
|
4
4
|
name: string;
|
package/lib/types/apply.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
/** Materialize the Plus distribution's
|
|
1
|
+
/** Materialize the Plus distribution's embedded patch set without Desktop. */
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from 'node:fs';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
|
-
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
5
|
+
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { satisfies, valid, validRange } from 'semver';
|
|
8
8
|
import { parseDocument } from 'yaml';
|
|
9
|
+
import { readPatchSet } from "./patch-set.js";
|
|
9
10
|
const FORMAT_VERSION = 1;
|
|
10
11
|
function isSelectedNpmPatch(patch) {
|
|
11
12
|
return patch.target.kind === 'npm';
|
|
@@ -50,7 +51,22 @@ function requireStringArray(value, label) {
|
|
|
50
51
|
}
|
|
51
52
|
function resolveInstalledPackage(distributionDirectory, packageName) {
|
|
52
53
|
const requireFromDistribution = createRequire(resolve(distributionDirectory, 'package.json'));
|
|
53
|
-
|
|
54
|
+
let manifestPath;
|
|
55
|
+
try {
|
|
56
|
+
manifestPath = requireFromDistribution.resolve(packageName + '/package.json');
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Some packages do not export `./package.json`; resolve the entry module instead and walk
|
|
60
|
+
// up to the directory that holds the manifest.
|
|
61
|
+
let directory = dirname(requireFromDistribution.resolve(packageName));
|
|
62
|
+
while (!existsSync(resolve(directory, 'package.json'))) {
|
|
63
|
+
const parent = dirname(directory);
|
|
64
|
+
if (parent === directory)
|
|
65
|
+
throw new Error(packageName + ' has no resolvable package manifest');
|
|
66
|
+
directory = parent;
|
|
67
|
+
}
|
|
68
|
+
manifestPath = resolve(directory, 'package.json');
|
|
69
|
+
}
|
|
54
70
|
const manifest = readJsonObject(manifestPath, packageName + ' manifest');
|
|
55
71
|
return {
|
|
56
72
|
name: packageName,
|
|
@@ -59,48 +75,6 @@ function resolveInstalledPackage(distributionDirectory, packageName) {
|
|
|
59
75
|
manifest,
|
|
60
76
|
};
|
|
61
77
|
}
|
|
62
|
-
function resolvePatchFile(packageDirectory, declared) {
|
|
63
|
-
const absoluteFile = resolve(packageDirectory, declared);
|
|
64
|
-
const local = relative(packageDirectory, absoluteFile);
|
|
65
|
-
if (isAbsolute(local) || local === '..' || local.startsWith('../')) {
|
|
66
|
-
throw new Error('patch file must stay inside its patch package: ' + declared);
|
|
67
|
-
}
|
|
68
|
-
if (!existsSync(absoluteFile))
|
|
69
|
-
throw new Error('patch file does not exist: ' + absoluteFile);
|
|
70
|
-
return { relativeFile: local.replaceAll('\\', '/'), absoluteFile };
|
|
71
|
-
}
|
|
72
|
-
function parseVariant(value, index, patchPackage) {
|
|
73
|
-
const label = patchPackage.name + ' dshPatch.variants[' + String(index) + ']';
|
|
74
|
-
const variant = requireObject(value, label);
|
|
75
|
-
const target = requireObject(variant.target, label + '.target');
|
|
76
|
-
const kind = requireString(target.kind, label + '.target.kind');
|
|
77
|
-
const common = {
|
|
78
|
-
id: requireString(variant.id, label + '.id'),
|
|
79
|
-
dsh: requireRange(variant.dsh, label + '.dsh'),
|
|
80
|
-
file: requireString(variant.file, label + '.file'),
|
|
81
|
-
patchPackage,
|
|
82
|
-
};
|
|
83
|
-
if (kind === 'npm') {
|
|
84
|
-
return {
|
|
85
|
-
...common,
|
|
86
|
-
target: {
|
|
87
|
-
kind,
|
|
88
|
-
name: requireString(target.name, label + '.target.name'),
|
|
89
|
-
range: requireRange(target.range, label + '.target.range'),
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
if (kind === 'dsh-source') {
|
|
94
|
-
return {
|
|
95
|
-
...common,
|
|
96
|
-
target: {
|
|
97
|
-
kind,
|
|
98
|
-
baseRevision: requireString(target.baseRevision, label + '.target.baseRevision'),
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
throw new Error('unsupported patch target kind: ' + kind);
|
|
103
|
-
}
|
|
104
78
|
function git(root, args, acceptFailure = false) {
|
|
105
79
|
const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
|
|
106
80
|
if (result.status === 0)
|
|
@@ -148,23 +122,6 @@ function requireSourceBase(root, baseRevision) {
|
|
|
148
122
|
}
|
|
149
123
|
return checkoutRevision;
|
|
150
124
|
}
|
|
151
|
-
function patchPackageVariants(distributionDirectory, names) {
|
|
152
|
-
const variants = [];
|
|
153
|
-
for (const name of names) {
|
|
154
|
-
const patchPackage = resolveInstalledPackage(distributionDirectory, name);
|
|
155
|
-
const declaration = requireObject(patchPackage.manifest.dshPatch, name + ' dshPatch');
|
|
156
|
-
if (declaration.formatVersion !== FORMAT_VERSION) {
|
|
157
|
-
throw new Error('unsupported dshPatch formatVersion in ' + name + ': ' + String(declaration.formatVersion));
|
|
158
|
-
}
|
|
159
|
-
if (!Array.isArray(declaration.variants) || declaration.variants.length === 0) {
|
|
160
|
-
throw new Error(name + ' dshPatch.variants must be a non-empty array');
|
|
161
|
-
}
|
|
162
|
-
for (const [index, value] of declaration.variants.entries()) {
|
|
163
|
-
variants.push(parseVariant(value, index, patchPackage));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return variants;
|
|
167
|
-
}
|
|
168
125
|
// 先验证official source,再物化Plus声明的profile依赖,避免source不匹配时改动用户profile。
|
|
169
126
|
function prepareSelection(distributionDirectory, profileDirectory, dshRoot) {
|
|
170
127
|
const distributionManifest = readJsonObject(resolve(distributionDirectory, 'package.json'), 'Plus distribution manifest');
|
|
@@ -192,31 +149,24 @@ function prepareSelection(distributionDirectory, profileDirectory, dshRoot) {
|
|
|
192
149
|
throw new Error('dshPlus.profile.allowBuilds.' + name + ' must be a boolean');
|
|
193
150
|
allowBuilds[name] = allowed;
|
|
194
151
|
}
|
|
195
|
-
const
|
|
152
|
+
const patchSet = readPatchSet(distributionDirectory, distributionManifest);
|
|
196
153
|
const dependencies = requireObject(distributionManifest.dependencies, 'Plus distribution dependencies');
|
|
197
154
|
const dshManifest = readJsonObject(resolve(dshRoot, 'package.json'), 'DSH source manifest');
|
|
198
155
|
const dshVersion = requireVersion(dshManifest.version, 'DSH source version');
|
|
199
156
|
if (!satisfies(dshVersion, dshRange, { includePrerelease: true })) {
|
|
200
157
|
throw new Error(distributionName + '@' + distributionVersion + ' requires DSH ' + dshRange + ', found ' + dshVersion);
|
|
201
158
|
}
|
|
202
|
-
const checkoutRevision = requireSourceBase(dshRoot,
|
|
203
|
-
const
|
|
204
|
-
for (const variant of patchPackageVariants(distributionDirectory, patchPackageNames)) {
|
|
205
|
-
const entries = groups.get(variant.id) ?? [];
|
|
206
|
-
entries.push(variant);
|
|
207
|
-
groups.set(variant.id, entries);
|
|
208
|
-
}
|
|
159
|
+
const checkoutRevision = requireSourceBase(dshRoot, patchSet.sourceBaseRevision);
|
|
160
|
+
const patchEntries = [...patchSet.entries, ...patchSet.thirdPartyPatches];
|
|
209
161
|
const sourceStates = new Map();
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (variant.target.kind !== 'dsh-source'
|
|
214
|
-
|| variant.target.baseRevision !== baseRevision
|
|
215
|
-
|| !satisfies(dshVersion, variant.dsh, { includePrerelease: true }))
|
|
216
|
-
continue;
|
|
217
|
-
const file = resolvePatchFile(variant.patchPackage.directory, variant.file);
|
|
218
|
-
sourceStates.set(file.absoluteFile, sourcePatchState(dshRoot, file.absoluteFile));
|
|
162
|
+
for (const item of patchEntries) {
|
|
163
|
+
if (!satisfies(dshVersion, item.dsh, { includePrerelease: true })) {
|
|
164
|
+
throw new Error('patch ' + item.id + ' declares DSH ' + item.dsh + ', found ' + dshVersion);
|
|
219
165
|
}
|
|
166
|
+
if (item.target.kind !== 'dsh-source')
|
|
167
|
+
continue;
|
|
168
|
+
const absoluteFile = resolve(distributionDirectory, item.file);
|
|
169
|
+
sourceStates.set(absoluteFile, sourcePatchState(dshRoot, absoluteFile));
|
|
220
170
|
}
|
|
221
171
|
const pendingSourceFiles = [...sourceStates].flatMap(([file, state]) => state === 'pending' ? [file] : []);
|
|
222
172
|
preflightSourcePatches(dshRoot, pendingSourceFiles);
|
|
@@ -227,49 +177,38 @@ function prepareSelection(distributionDirectory, profileDirectory, dshRoot) {
|
|
|
227
177
|
return { name, version: installed.version };
|
|
228
178
|
});
|
|
229
179
|
const runtimePackages = [
|
|
230
|
-
...Object.keys(dependencies).
|
|
180
|
+
...Object.keys(dependencies).sort().map((name) => {
|
|
231
181
|
const installed = resolveInstalledPackage(distributionDirectory, name);
|
|
232
182
|
return { name, version: installed.version };
|
|
233
183
|
}),
|
|
234
184
|
...profileDependencies,
|
|
235
185
|
].sort((left, right) => left.name.localeCompare(right.name));
|
|
236
186
|
const selected = [];
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
patchPackage: { name: variant.patchPackage.name, version: variant.patchPackage.version },
|
|
262
|
-
file: file.relativeFile,
|
|
263
|
-
absoluteFile: file.absoluteFile,
|
|
264
|
-
target: { kind: 'npm', name: variant.target.name, version: targetVersion },
|
|
265
|
-
}];
|
|
187
|
+
for (const item of patchEntries) {
|
|
188
|
+
const absoluteFile = resolve(distributionDirectory, item.file);
|
|
189
|
+
if (item.target.kind === 'dsh-source') {
|
|
190
|
+
selected.push({
|
|
191
|
+
id: item.id,
|
|
192
|
+
scope: item.scope,
|
|
193
|
+
file: item.file,
|
|
194
|
+
absoluteFile,
|
|
195
|
+
target: { kind: 'dsh-source', baseRevision: patchSet.sourceBaseRevision },
|
|
196
|
+
alreadyApplied: sourceStates.get(absoluteFile) === 'applied',
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const targetDirectory = item.target.name in profileDependencySpecs ? profileDirectory : distributionDirectory;
|
|
201
|
+
const targetVersion = resolveInstalledPackage(targetDirectory, item.target.name).version;
|
|
202
|
+
if (!satisfies(targetVersion, item.target.range, { includePrerelease: true })) {
|
|
203
|
+
throw new Error('patch ' + item.id + ' targets ' + item.target.name + ' ' + item.target.range + ', found ' + targetVersion);
|
|
204
|
+
}
|
|
205
|
+
selected.push({
|
|
206
|
+
id: item.id,
|
|
207
|
+
scope: item.scope,
|
|
208
|
+
file: item.file,
|
|
209
|
+
absoluteFile,
|
|
210
|
+
target: { kind: 'npm', name: item.target.name, version: targetVersion },
|
|
266
211
|
});
|
|
267
|
-
const [candidate] = candidates;
|
|
268
|
-
if (candidate === undefined)
|
|
269
|
-
throw new Error('no compatible patch variant for ' + id + ' on DSH ' + dshVersion);
|
|
270
|
-
if (candidates.length > 1)
|
|
271
|
-
throw new Error('multiple compatible patch variants for ' + id + ' on DSH ' + dshVersion);
|
|
272
|
-
selected.push(candidate);
|
|
273
212
|
}
|
|
274
213
|
const lockPatches = selected.map((patch) => {
|
|
275
214
|
if ('alreadyApplied' in patch) {
|
|
@@ -299,7 +238,7 @@ function materializePatchFiles(profileDirectory, patches) {
|
|
|
299
238
|
const root = resolve(profileDirectory, '.dsh-plus', 'patches');
|
|
300
239
|
rmSync(root, { recursive: true, force: true });
|
|
301
240
|
const stablePatches = patches.map((patch) => {
|
|
302
|
-
const destination = resolve(root, patch.
|
|
241
|
+
const destination = resolve(root, patch.scope, patch.id, basename(patch.file));
|
|
303
242
|
mkdirSync(dirname(destination), { recursive: true });
|
|
304
243
|
copyFileSync(patch.absoluteFile, destination);
|
|
305
244
|
return { ...patch, absoluteFile: destination };
|
|
@@ -323,7 +262,7 @@ function materializePatchFiles(profileDirectory, patches) {
|
|
|
323
262
|
continue;
|
|
324
263
|
}
|
|
325
264
|
// 成员或版本变化必须改变workspace路径,使pnpm重新应用新的组合payload。
|
|
326
|
-
const members = group.map(patch => encodeURIComponent(patch.id
|
|
265
|
+
const members = group.map(patch => encodeURIComponent(patch.id)).join('+');
|
|
327
266
|
const destination = resolve(root, 'combined', encodeURIComponent(key), members + '.patch');
|
|
328
267
|
mkdirSync(dirname(destination), { recursive: true });
|
|
329
268
|
const contents = group.map((patch) => {
|