@sparkelf/dsh-plus 0.1.0-rc.9 → 0.2.0-rc.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/lib/apply.js CHANGED
@@ -1,374 +1,2 @@
1
- import { createRequire } from "node:module";
2
- import { spawnSync } from "node:child_process";
3
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
- import { dirname, isAbsolute, relative, resolve } from "node:path";
5
- import { satisfies, valid, validRange } from "semver";
6
- import { parseDocument } from "yaml";
7
- //#region lib/types/apply.js
8
- /** Materialize the Plus distribution's independent patch packages without Desktop. */
9
- const FORMAT_VERSION = 1;
10
- function readJsonObject(path, label) {
11
- const parsed = JSON.parse(readFileSync(path, "utf8"));
12
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(label + " must be a JSON object");
13
- return parsed;
14
- }
15
- function requireObject(value, label) {
16
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(label + " must be an object");
17
- return value;
18
- }
19
- function requireString(value, label) {
20
- if (typeof value !== "string" || value === "") throw new Error(label + " must be a non-empty string");
21
- return value;
22
- }
23
- function requireRange(value, label) {
24
- const range = requireString(value, label);
25
- if (validRange(range) === null) throw new Error(label + " is not a semantic-version range: " + range);
26
- return range;
27
- }
28
- function requireVersion(value, label) {
29
- const version = requireString(value, label);
30
- if (valid(version) === null) throw new Error(label + " is not a semantic version: " + version);
31
- return version;
32
- }
33
- function requireStringArray(value, label) {
34
- if (!Array.isArray(value) || value.length === 0) throw new Error(label + " must be a non-empty array");
35
- const strings = value.map((entry, index) => requireString(entry, label + "[" + String(index) + "]"));
36
- if (new Set(strings).size !== strings.length) throw new Error(label + " must not contain duplicates");
37
- return strings;
38
- }
39
- function resolveInstalledPackage(distributionDirectory, packageName) {
40
- const manifestPath = createRequire(resolve(distributionDirectory, "package.json")).resolve(packageName + "/package.json");
41
- const manifest = readJsonObject(manifestPath, packageName + " manifest");
42
- return {
43
- name: packageName,
44
- version: requireVersion(manifest.version, packageName + " version"),
45
- directory: dirname(manifestPath),
46
- manifest
47
- };
48
- }
49
- function resolvePatchFile(packageDirectory, declared) {
50
- const absoluteFile = resolve(packageDirectory, declared);
51
- const local = relative(packageDirectory, absoluteFile);
52
- if (isAbsolute(local) || local === ".." || local.startsWith("../")) throw new Error("patch file must stay inside its patch package: " + declared);
53
- if (!existsSync(absoluteFile)) throw new Error("patch file does not exist: " + absoluteFile);
54
- return {
55
- relativeFile: local.replaceAll("\\", "/"),
56
- absoluteFile
57
- };
58
- }
59
- function parseVariant(value, index, patchPackage) {
60
- const label = patchPackage.name + " dshPatch.variants[" + String(index) + "]";
61
- const variant = requireObject(value, label);
62
- const target = requireObject(variant.target, label + ".target");
63
- const kind = requireString(target.kind, label + ".target.kind");
64
- const common = {
65
- id: requireString(variant.id, label + ".id"),
66
- dsh: requireRange(variant.dsh, label + ".dsh"),
67
- file: requireString(variant.file, label + ".file"),
68
- patchPackage
69
- };
70
- if (kind === "npm") return {
71
- ...common,
72
- target: {
73
- kind,
74
- name: requireString(target.name, label + ".target.name"),
75
- range: requireRange(target.range, label + ".target.range")
76
- }
77
- };
78
- if (kind === "dsh-source") return {
79
- ...common,
80
- target: {
81
- kind,
82
- baseRevision: requireString(target.baseRevision, label + ".target.baseRevision")
83
- }
84
- };
85
- throw new Error("unsupported patch target kind: " + kind);
86
- }
87
- function git(root, args, acceptFailure = false) {
88
- const result = spawnSync("git", args, {
89
- cwd: root,
90
- encoding: "utf8"
91
- });
92
- if (result.status === 0) return result.stdout.trim();
93
- if (acceptFailure) return void 0;
94
- const detail = result.stderr.trim();
95
- throw new Error("git " + args.join(" ") + " failed" + (detail === "" ? "" : ": " + detail));
96
- }
97
- function sourcePatchState(root, file) {
98
- if (git(root, [
99
- "apply",
100
- "--check",
101
- file
102
- ], true) !== void 0) return "pending";
103
- if (git(root, [
104
- "apply",
105
- "--reverse",
106
- "--check",
107
- file
108
- ], true) !== void 0) return "applied";
109
- git(root, [
110
- "apply",
111
- "--check",
112
- file
113
- ]);
114
- throw new Error("unreachable");
115
- }
116
- function requireSourceBase(root, baseRevision) {
117
- const checkoutRevision = requireString(git(root, ["rev-parse", "HEAD"]), "DSH checkout revision");
118
- if (checkoutRevision !== baseRevision) throw new Error("DSH checkout must be exact official base " + baseRevision + ", found " + checkoutRevision);
119
- return checkoutRevision;
120
- }
121
- function patchPackageVariants(distributionDirectory, names) {
122
- const variants = [];
123
- for (const name of names) {
124
- const patchPackage = resolveInstalledPackage(distributionDirectory, name);
125
- const declaration = requireObject(patchPackage.manifest.dshPatch, name + " dshPatch");
126
- if (declaration.formatVersion !== FORMAT_VERSION) throw new Error("unsupported dshPatch formatVersion in " + name + ": " + String(declaration.formatVersion));
127
- if (!Array.isArray(declaration.variants) || declaration.variants.length === 0) throw new Error(name + " dshPatch.variants must be a non-empty array");
128
- for (const [index, value] of declaration.variants.entries()) variants.push(parseVariant(value, index, patchPackage));
129
- }
130
- return variants;
131
- }
132
- function selectPatches(distributionDirectory, dshRoot) {
133
- const distributionManifest = readJsonObject(resolve(distributionDirectory, "package.json"), "Plus distribution manifest");
134
- const distributionName = requireString(distributionManifest.name, "Plus distribution name");
135
- const distributionVersion = requireVersion(distributionManifest.version, "Plus distribution version");
136
- const plus = requireObject(distributionManifest.dshPlus, "Plus distribution dshPlus");
137
- if (plus.formatVersion !== FORMAT_VERSION) throw new Error("unsupported dshPlus formatVersion: " + String(plus.formatVersion));
138
- const dshRange = requireRange(requireObject(plus.compatibility, "dshPlus.compatibility").dsh, "dshPlus.compatibility.dsh");
139
- const baseRevision = requireString(requireObject(plus.sourceBase, "dshPlus.sourceBase").revision, "dshPlus.sourceBase.revision");
140
- const bundles = requireStringArray(requireObject(plus.profile, "dshPlus.profile").bundles, "dshPlus.profile.bundles");
141
- const patchPackageNames = requireStringArray(plus.patchPackages, "dshPlus.patchPackages");
142
- const dependencies = requireObject(distributionManifest.dependencies, "Plus distribution dependencies");
143
- const runtimePackages = Object.keys(dependencies).filter((name) => !patchPackageNames.includes(name)).sort().map((name) => {
144
- return {
145
- name,
146
- version: resolveInstalledPackage(distributionDirectory, name).version
147
- };
148
- });
149
- const dshVersion = requireVersion(readJsonObject(resolve(dshRoot, "package.json"), "DSH source manifest").version, "DSH source version");
150
- if (!satisfies(dshVersion, dshRange, { includePrerelease: true })) throw new Error(distributionName + "@" + distributionVersion + " requires DSH " + dshRange + ", found " + dshVersion);
151
- const checkoutRevision = requireSourceBase(dshRoot, baseRevision);
152
- const groups = /* @__PURE__ */ new Map();
153
- for (const variant of patchPackageVariants(distributionDirectory, patchPackageNames)) {
154
- const entries = groups.get(variant.id) ?? [];
155
- entries.push(variant);
156
- groups.set(variant.id, entries);
157
- }
158
- const selected = [];
159
- for (const id of [...groups.keys()].sort()) {
160
- const candidates = (groups.get(id) ?? []).flatMap((variant) => {
161
- if (!satisfies(dshVersion, variant.dsh, { includePrerelease: true })) return [];
162
- const file = resolvePatchFile(variant.patchPackage.directory, variant.file);
163
- if (variant.target.kind === "dsh-source") {
164
- if (variant.target.baseRevision !== baseRevision) return [];
165
- return [{
166
- id,
167
- patchPackage: {
168
- name: variant.patchPackage.name,
169
- version: variant.patchPackage.version
170
- },
171
- file: file.relativeFile,
172
- absoluteFile: file.absoluteFile,
173
- target: {
174
- kind: "dsh-source",
175
- baseRevision
176
- },
177
- alreadyApplied: sourcePatchState(dshRoot, file.absoluteFile) === "applied"
178
- }];
179
- }
180
- const targetVersion = resolveInstalledPackage(distributionDirectory, variant.target.name).version;
181
- if (!satisfies(targetVersion, variant.target.range, { includePrerelease: true })) return [];
182
- return [{
183
- id,
184
- patchPackage: {
185
- name: variant.patchPackage.name,
186
- version: variant.patchPackage.version
187
- },
188
- file: file.relativeFile,
189
- absoluteFile: file.absoluteFile,
190
- target: {
191
- kind: "npm",
192
- name: variant.target.name,
193
- version: targetVersion
194
- }
195
- }];
196
- });
197
- const [candidate] = candidates;
198
- if (candidate === void 0) throw new Error("no compatible patch variant for " + id + " on DSH " + dshVersion);
199
- if (candidates.length > 1) throw new Error("multiple compatible patch variants for " + id + " on DSH " + dshVersion);
200
- selected.push(candidate);
201
- }
202
- const lockPatches = selected.map((patch) => {
203
- if ("alreadyApplied" in patch) {
204
- const { absoluteFile: _absoluteFile, alreadyApplied: _alreadyApplied, ...locked } = patch;
205
- return locked;
206
- }
207
- const { absoluteFile: _absoluteFile, ...locked } = patch;
208
- return locked;
209
- });
210
- return {
211
- patches: selected,
212
- bundles,
213
- runtimePackages,
214
- lock: {
215
- formatVersion: FORMAT_VERSION,
216
- distribution: {
217
- name: distributionName,
218
- version: distributionVersion
219
- },
220
- dsh: {
221
- version: dshVersion,
222
- baseRevision,
223
- checkoutRevision
224
- },
225
- profile: { bundles },
226
- runtimePackages,
227
- patches: lockPatches
228
- }
229
- };
230
- }
231
- function materializePatchFiles(profileDirectory, patches) {
232
- const root = resolve(profileDirectory, ".dsh-plus", "patches");
233
- rmSync(root, {
234
- recursive: true,
235
- force: true
236
- });
237
- return patches.map((patch) => {
238
- const destination = resolve(root, patch.patchPackage.name, patch.patchPackage.version, patch.file);
239
- mkdirSync(dirname(destination), { recursive: true });
240
- copyFileSync(patch.absoluteFile, destination);
241
- return {
242
- ...patch,
243
- absoluteFile: destination
244
- };
245
- });
246
- }
247
- function profilePatchConfiguration(profileDirectory, patches) {
248
- const document = parseDocument(readFileSync(resolve(profileDirectory, "pnpm-workspace.yaml"), "utf8"));
249
- const [documentError] = document.errors;
250
- if (documentError !== void 0) throw new Error("Plus profile workspace is not valid YAML", { cause: documentError });
251
- let changed = false;
252
- const desiredKeys = new Set(patches.flatMap((patch) => patch.target.kind === "npm" ? [patch.target.name + "@" + patch.target.version] : []));
253
- const workspace = requireObject(document.toJS(), "Plus profile workspace");
254
- const configured = workspace.patchedDependencies === void 0 ? {} : requireObject(workspace.patchedDependencies, "Plus profile patchedDependencies");
255
- for (const [key, value] of Object.entries(configured)) if (typeof value === "string" && value.startsWith(".dsh-plus/patches/") && !desiredKeys.has(key)) {
256
- document.deleteIn(["patchedDependencies", key]);
257
- changed = true;
258
- }
259
- for (const patch of patches) {
260
- if (patch.target.kind !== "npm") continue;
261
- const key = patch.target.name + "@" + patch.target.version;
262
- const value = relative(profileDirectory, patch.absoluteFile).replaceAll("\\", "/");
263
- const current = document.getIn(["patchedDependencies", key]);
264
- if (current !== void 0 && current !== value && (typeof current !== "string" || !current.startsWith(".dsh-plus/patches/"))) throw new Error("profile patchedDependencies." + key + " is already owned by another payload");
265
- if (current === value) continue;
266
- document.setIn(["patchedDependencies", key], value);
267
- changed = true;
268
- }
269
- return {
270
- text: String(document),
271
- changed
272
- };
273
- }
274
- function writeProfileComposition(profileDirectory, bundles, runtimePackages) {
275
- const path = resolve(profileDirectory, "package.json");
276
- const manifest = readJsonObject(path, "DSH profile manifest");
277
- const dependencies = requireObject(manifest.dependencies, "DSH profile dependencies");
278
- let changed = false;
279
- for (const runtimePackage of runtimePackages) {
280
- if (dependencies[runtimePackage.name] === void 0) {
281
- dependencies[runtimePackage.name] = runtimePackage.version;
282
- changed = true;
283
- continue;
284
- }
285
- const installed = resolveInstalledPackage(profileDirectory, runtimePackage.name);
286
- if (installed.version !== runtimePackage.version) throw new Error("profile dependency " + runtimePackage.name + " resolves to " + installed.version + ", but the Plus distribution requires " + runtimePackage.version);
287
- }
288
- const dsh = manifest.dsh === void 0 ? {} : requireObject(manifest.dsh, "DSH profile dsh");
289
- const profile = dsh.profile === void 0 ? {} : requireObject(dsh.profile, "DSH profile dsh.profile");
290
- if (JSON.stringify(profile.bundles) !== JSON.stringify(bundles)) changed = true;
291
- manifest.dependencies = dependencies;
292
- manifest.dsh = {
293
- ...dsh,
294
- profile: {
295
- ...profile,
296
- bundles: [...bundles]
297
- }
298
- };
299
- if (changed) writeFileSync(path, JSON.stringify(manifest, void 0, 2) + "\n");
300
- return changed;
301
- }
302
- function runPnpm(profileDirectory, args, label) {
303
- const result = spawnSync("pnpm", args, {
304
- cwd: profileDirectory,
305
- stdio: "inherit",
306
- shell: process.platform === "win32"
307
- });
308
- if (result.error !== void 0) throw result.error;
309
- if (result.status !== 0) throw new Error(label + " failed with exit code " + String(result.status));
310
- }
311
- function pendingSourcePatchFiles(patches) {
312
- return patches.flatMap((patch) => "alreadyApplied" in patch && !patch.alreadyApplied ? [patch.absoluteFile] : []);
313
- }
314
- function applySelectedPatches(profileDirectory, dshRoot, patches, profileChanged) {
315
- const stablePatches = materializePatchFiles(profileDirectory, patches);
316
- const workspacePath = resolve(profileDirectory, "pnpm-workspace.yaml");
317
- const workspace = profilePatchConfiguration(profileDirectory, stablePatches);
318
- if (workspace.changed) writeFileSync(workspacePath, workspace.text);
319
- if (profileChanged || workspace.changed) runPnpm(profileDirectory, ["install"], "pnpm install in Plus profile");
320
- const sourceFiles = pendingSourcePatchFiles(stablePatches);
321
- if (sourceFiles.length > 0) git(dshRoot, ["apply", ...sourceFiles]);
322
- if (stablePatches.some((patch) => patch.target.kind === "dsh-source")) runPnpm(dshRoot, ["run", "build"], "official DSH build after source patches");
323
- }
324
- function writeLock(profileDirectory, lock) {
325
- const directory = resolve(profileDirectory, ".dsh-plus");
326
- mkdirSync(directory, { recursive: true });
327
- const path = resolve(directory, "patchset.lock.json");
328
- writeFileSync(path, JSON.stringify(lock, void 0, 2) + "\n", { mode: 384 });
329
- chmodSync(path, 384);
330
- return path;
331
- }
332
- function parseApplyArguments(argv) {
333
- if (argv[0] !== "apply") throw new Error("usage: dsh-plus apply --dsh-root <official-dsh-root>");
334
- let dshRoot;
335
- for (let index = 1; index < argv.length; index += 2) {
336
- const key = argv[index];
337
- const value = argv[index + 1];
338
- if (key !== "--dsh-root" || value === void 0) throw new Error("usage: dsh-plus apply --dsh-root <official-dsh-root>");
339
- dshRoot = resolve(value);
340
- }
341
- if (dshRoot === void 0) throw new Error("usage: dsh-plus apply --dsh-root <official-dsh-root>");
342
- return {
343
- dshRoot,
344
- profileDirectory: process.cwd()
345
- };
346
- }
347
- /**
348
- * Apply the installed Plus distribution metadata from a DSH profile directory.
349
- * @param argv - Explicit `apply --dsh-root <path>` command arguments.
350
- */
351
- function runApply(argv) {
352
- const { dshRoot, profileDirectory } = parseApplyArguments(argv);
353
- const dependencies = requireObject(readJsonObject(resolve(profileDirectory, "package.json"), "DSH profile manifest").dependencies, "DSH profile dependencies");
354
- if (dependencies["@sparkelf/dsh-plus"] === void 0) throw new Error("current directory is not a DSH profile with @sparkelf/dsh-plus installed");
355
- const selection = selectPatches(dirname(createRequire(resolve(profileDirectory, "package.json")).resolve("@sparkelf/dsh-plus/package.json")), dshRoot);
356
- const sourceFiles = pendingSourcePatchFiles(selection.patches);
357
- if (sourceFiles.length > 0) git(dshRoot, [
358
- "apply",
359
- "--check",
360
- ...sourceFiles
361
- ]);
362
- const profileChanged = writeProfileComposition(profileDirectory, selection.bundles, selection.runtimePackages);
363
- applySelectedPatches(profileDirectory, dshRoot, selection.patches, profileChanged);
364
- selection.lock.profile = { bundles: selection.bundles.map((name) => {
365
- return {
366
- name,
367
- version: (dependencies[name] === void 0 ? resolveInstalledPackage(dshRoot, name) : resolveInstalledPackage(profileDirectory, name)).version
368
- };
369
- }) };
370
- const lock = writeLock(profileDirectory, selection.lock);
371
- process.stdout.write("[dsh-plus] applied " + String(selection.patches.length) + " patches; lock " + lock + "\n");
372
- }
373
- //#endregion
374
- export { runApply };
1
+ import { n as parseOfficialWorkspacePackages, r as runApply, t as materializeOfficialPackageScope } from "./apply-e0aMC4B4.js";
2
+ export { materializeOfficialPackageScope, parseOfficialWorkspacePackages, runApply };