@sparkelf/dsh-plus 0.1.0-rc.33 → 0.1.0-rc.34
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/bin.js
CHANGED
|
@@ -426,6 +426,78 @@ function ensureProfile(paths, consumerDirectory) {
|
|
|
426
426
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
427
427
|
return true;
|
|
428
428
|
}
|
|
429
|
+
/** Run git in one directory, returning undefined instead of throwing when asked to. */
|
|
430
|
+
function git(root, args, acceptFailure = false) {
|
|
431
|
+
const result = spawnSync("git", args, {
|
|
432
|
+
cwd: root,
|
|
433
|
+
encoding: "utf8"
|
|
434
|
+
});
|
|
435
|
+
if (result.status === 0) return result.stdout.trim();
|
|
436
|
+
if (acceptFailure) return void 0;
|
|
437
|
+
const detail = result.stderr.trim();
|
|
438
|
+
throw new Error("git " + args.join(" ") + " failed" + (detail === "" ? "" : ": " + detail));
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Apply the reviewed npm-target patches to the profile's installed packages.
|
|
442
|
+
*
|
|
443
|
+
* A standalone installation runs no `apply` step: it installs the distribution from
|
|
444
|
+
* the registry, links the consumer's packages, and starts. The npm patches a
|
|
445
|
+
* distribution declares therefore need an owner that does not require an official
|
|
446
|
+
* source checkout — the source half of the apply step needs one, this does not.
|
|
447
|
+
*
|
|
448
|
+
* The work is idempotent: a patch whose reverse already applies is left alone, so a
|
|
449
|
+
* second start neither re-applies nor fails. A reinstall restores the published bytes,
|
|
450
|
+
* which is why this runs on every start rather than once.
|
|
451
|
+
*
|
|
452
|
+
* @param distributionDirectory - the installed `@sparkelf/dsh-plus` directory.
|
|
453
|
+
* @param profileDirectory - the standalone profile directory.
|
|
454
|
+
* @returns the labels of the patches that were applied.
|
|
455
|
+
*/
|
|
456
|
+
function applyProfileNpmPatches(distributionDirectory, profileDirectory) {
|
|
457
|
+
const names = requireRecord(requireRecord(JSON.parse(readFileSync(join(distributionDirectory, "package.json"), "utf8")), "Plus distribution manifest").dshPlus, "dshPlus").patchPackages;
|
|
458
|
+
if (!Array.isArray(names)) throw new Error("dshPlus.patchPackages must be an array");
|
|
459
|
+
const applied = [];
|
|
460
|
+
for (const value of names) {
|
|
461
|
+
if (typeof value !== "string" || value === "") throw new Error("dshPlus.patchPackages entries must be non-empty strings");
|
|
462
|
+
const patchPackage = resolveInstalledPackage(distributionDirectory, value);
|
|
463
|
+
const variants = requireRecord(patchPackage.manifest.dshPatch, value + " dshPatch").variants;
|
|
464
|
+
if (!Array.isArray(variants)) throw new Error(value + " dshPatch.variants must be an array");
|
|
465
|
+
for (const entry of variants) {
|
|
466
|
+
const variant = requireRecord(entry, value + " variant");
|
|
467
|
+
const target = requireRecord(variant.target, value + " variant target");
|
|
468
|
+
if (target.kind !== "npm") continue;
|
|
469
|
+
const targetName = String(target.name);
|
|
470
|
+
const patched = resolveInstalledPackage(profileDirectory, targetName);
|
|
471
|
+
const file = resolve(patchPackage.directory, String(variant.file));
|
|
472
|
+
if (git(patched.directory, [
|
|
473
|
+
"apply",
|
|
474
|
+
"--reverse",
|
|
475
|
+
"--check",
|
|
476
|
+
file
|
|
477
|
+
], true) !== void 0) continue;
|
|
478
|
+
git(patched.directory, ["apply", file]);
|
|
479
|
+
applied.push(value + " -> " + targetName);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return applied;
|
|
483
|
+
}
|
|
484
|
+
/** Resolve one installed package's manifest from a requiring directory. */
|
|
485
|
+
function resolveInstalledPackage(from, packageName) {
|
|
486
|
+
const requireFrom = createRequire(join(from, "package.json"));
|
|
487
|
+
let manifestPath;
|
|
488
|
+
try {
|
|
489
|
+
manifestPath = requireFrom.resolve(packageName + "/package.json");
|
|
490
|
+
} catch {
|
|
491
|
+
throw new Error(packageName + " is not installed under " + from);
|
|
492
|
+
}
|
|
493
|
+
const manifest = requireRecord(JSON.parse(readFileSync(manifestPath, "utf8")), packageName + " manifest");
|
|
494
|
+
return {
|
|
495
|
+
name: packageName,
|
|
496
|
+
version: String(manifest.version),
|
|
497
|
+
directory: dirname(manifestPath),
|
|
498
|
+
manifest
|
|
499
|
+
};
|
|
500
|
+
}
|
|
429
501
|
//#endregion
|
|
430
502
|
//#region lib/types/standalone-cli.js
|
|
431
503
|
/**
|
|
@@ -529,6 +601,7 @@ async function start(argv) {
|
|
|
529
601
|
const paths = resolvePaths(anchor);
|
|
530
602
|
const created = ensureProfile(paths, installationRoot());
|
|
531
603
|
console.log(created ? "Created the plus profile at " + paths.profileDirectory : "Using the existing plus profile");
|
|
604
|
+
for (const label of applyProfileNpmPatches(paths.distributionDirectory, paths.profileDirectory)) console.log("Applied the reviewed patch " + label);
|
|
532
605
|
const entry = launcherEntry(anchor);
|
|
533
606
|
if (options.foreground) return runForeground(entry, options.port, options.host, options.open);
|
|
534
607
|
const existing = readState(paths.home);
|
|
@@ -14,7 +14,7 @@ import { dirname, join } from 'node:path';
|
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
15
|
import { newerVersion } from "./registry-versions.js";
|
|
16
16
|
import { DEFAULT_PORT, STOP_GRACE_MILLISECONDS, choosePort, clearState, isRunning, portAvailable, readState, spawnServer, stateDirectory, waitForAuthenticatedUrl, waitForServer, writeState, } from "./standalone-server.js";
|
|
17
|
-
import { STANDALONE_PROFILE, ensureProfile, readDistributionProfile, resolvePaths, } from "./standalone-profile.js";
|
|
17
|
+
import { STANDALONE_PROFILE, applyProfileNpmPatches, ensureProfile, readDistributionProfile, resolvePaths, } from "./standalone-profile.js";
|
|
18
18
|
/** Milliseconds a start waits for the server to answer before reporting failure. */
|
|
19
19
|
const READY_TIMEOUT_MILLISECONDS = 90_000;
|
|
20
20
|
function parseStartOptions(argv) {
|
|
@@ -104,6 +104,12 @@ async function start(argv) {
|
|
|
104
104
|
console.log(created
|
|
105
105
|
? 'Created the ' + STANDALONE_PROFILE + ' profile at ' + paths.profileDirectory
|
|
106
106
|
: 'Using the existing ' + STANDALONE_PROFILE + ' profile');
|
|
107
|
+
// The profile symlinks the consumer's packages, so a patch lands on the installed
|
|
108
|
+
// copy the launcher loads. A reinstall restores the published bytes, which is why
|
|
109
|
+
// this runs on every start rather than only when the profile was created.
|
|
110
|
+
for (const label of applyProfileNpmPatches(paths.distributionDirectory, paths.profileDirectory)) {
|
|
111
|
+
console.log('Applied the reviewed patch ' + label);
|
|
112
|
+
}
|
|
107
113
|
const entry = launcherEntry(anchor);
|
|
108
114
|
if (options.foreground)
|
|
109
115
|
return runForeground(entry, options.port, options.host, options.open);
|
|
@@ -91,4 +91,21 @@ export declare function resolvePaths(anchor: string, env?: NodeJS.ProcessEnv): S
|
|
|
91
91
|
* @returns whether this call created the manifest.
|
|
92
92
|
*/
|
|
93
93
|
export declare function ensureProfile(paths: StandalonePaths, consumerDirectory: string): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Apply the reviewed npm-target patches to the profile's installed packages.
|
|
96
|
+
*
|
|
97
|
+
* A standalone installation runs no `apply` step: it installs the distribution from
|
|
98
|
+
* the registry, links the consumer's packages, and starts. The npm patches a
|
|
99
|
+
* distribution declares therefore need an owner that does not require an official
|
|
100
|
+
* source checkout — the source half of the apply step needs one, this does not.
|
|
101
|
+
*
|
|
102
|
+
* The work is idempotent: a patch whose reverse already applies is left alone, so a
|
|
103
|
+
* second start neither re-applies nor fails. A reinstall restores the published bytes,
|
|
104
|
+
* which is why this runs on every start rather than once.
|
|
105
|
+
*
|
|
106
|
+
* @param distributionDirectory - the installed `@sparkelf/dsh-plus` directory.
|
|
107
|
+
* @param profileDirectory - the standalone profile directory.
|
|
108
|
+
* @returns the labels of the patches that were applied.
|
|
109
|
+
*/
|
|
110
|
+
export declare function applyProfileNpmPatches(distributionDirectory: string, profileDirectory: string): string[];
|
|
94
111
|
//# sourceMappingURL=standalone-profile.d.ts.map
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* the launcher mounts exactly the bundles the profile names and nothing expands a
|
|
9
9
|
* bundle's own list.
|
|
10
10
|
*/
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
11
12
|
import { existsSync, mkdirSync, readdirSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
12
13
|
import { createRequire } from 'node:module';
|
|
13
14
|
import { homedir } from 'node:os';
|
|
@@ -223,4 +224,76 @@ export function ensureProfile(paths, consumerDirectory) {
|
|
|
223
224
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
224
225
|
return true;
|
|
225
226
|
}
|
|
227
|
+
/** Run git in one directory, returning undefined instead of throwing when asked to. */
|
|
228
|
+
function git(root, args, acceptFailure = false) {
|
|
229
|
+
const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
|
|
230
|
+
if (result.status === 0)
|
|
231
|
+
return result.stdout.trim();
|
|
232
|
+
if (acceptFailure)
|
|
233
|
+
return undefined;
|
|
234
|
+
const detail = result.stderr.trim();
|
|
235
|
+
throw new Error('git ' + args.join(' ') + ' failed' + (detail === '' ? '' : ': ' + detail));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Apply the reviewed npm-target patches to the profile's installed packages.
|
|
239
|
+
*
|
|
240
|
+
* A standalone installation runs no `apply` step: it installs the distribution from
|
|
241
|
+
* the registry, links the consumer's packages, and starts. The npm patches a
|
|
242
|
+
* distribution declares therefore need an owner that does not require an official
|
|
243
|
+
* source checkout — the source half of the apply step needs one, this does not.
|
|
244
|
+
*
|
|
245
|
+
* The work is idempotent: a patch whose reverse already applies is left alone, so a
|
|
246
|
+
* second start neither re-applies nor fails. A reinstall restores the published bytes,
|
|
247
|
+
* which is why this runs on every start rather than once.
|
|
248
|
+
*
|
|
249
|
+
* @param distributionDirectory - the installed `@sparkelf/dsh-plus` directory.
|
|
250
|
+
* @param profileDirectory - the standalone profile directory.
|
|
251
|
+
* @returns the labels of the patches that were applied.
|
|
252
|
+
*/
|
|
253
|
+
export function applyProfileNpmPatches(distributionDirectory, profileDirectory) {
|
|
254
|
+
const manifest = requireRecord(JSON.parse(readFileSync(join(distributionDirectory, 'package.json'), 'utf8')), 'Plus distribution manifest');
|
|
255
|
+
const plus = requireRecord(manifest.dshPlus, 'dshPlus');
|
|
256
|
+
const names = plus.patchPackages;
|
|
257
|
+
if (!Array.isArray(names))
|
|
258
|
+
throw new Error('dshPlus.patchPackages must be an array');
|
|
259
|
+
const applied = [];
|
|
260
|
+
for (const value of names) {
|
|
261
|
+
if (typeof value !== 'string' || value === '')
|
|
262
|
+
throw new Error('dshPlus.patchPackages entries must be non-empty strings');
|
|
263
|
+
const patchPackage = resolveInstalledPackage(distributionDirectory, value);
|
|
264
|
+
const declaration = requireRecord(patchPackage.manifest.dshPatch, value + ' dshPatch');
|
|
265
|
+
const variants = declaration.variants;
|
|
266
|
+
if (!Array.isArray(variants))
|
|
267
|
+
throw new Error(value + ' dshPatch.variants must be an array');
|
|
268
|
+
for (const entry of variants) {
|
|
269
|
+
const variant = requireRecord(entry, value + ' variant');
|
|
270
|
+
const target = requireRecord(variant.target, value + ' variant target');
|
|
271
|
+
// Only npm targets reach an installed package; a source target needs the
|
|
272
|
+
// official checkout, which a standalone installation does not have.
|
|
273
|
+
if (target.kind !== 'npm')
|
|
274
|
+
continue;
|
|
275
|
+
const targetName = String(target.name);
|
|
276
|
+
const patched = resolveInstalledPackage(profileDirectory, targetName);
|
|
277
|
+
const file = resolve(patchPackage.directory, String(variant.file));
|
|
278
|
+
if (git(patched.directory, ['apply', '--reverse', '--check', file], true) !== undefined)
|
|
279
|
+
continue;
|
|
280
|
+
git(patched.directory, ['apply', file]);
|
|
281
|
+
applied.push(value + ' -> ' + targetName);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return applied;
|
|
285
|
+
}
|
|
286
|
+
/** Resolve one installed package's manifest from a requiring directory. */
|
|
287
|
+
function resolveInstalledPackage(from, packageName) {
|
|
288
|
+
const requireFrom = createRequire(join(from, 'package.json'));
|
|
289
|
+
let manifestPath;
|
|
290
|
+
try {
|
|
291
|
+
manifestPath = requireFrom.resolve(packageName + '/package.json');
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
throw new Error(packageName + ' is not installed under ' + from);
|
|
295
|
+
}
|
|
296
|
+
const manifest = requireRecord(JSON.parse(readFileSync(manifestPath, 'utf8')), packageName + ' manifest');
|
|
297
|
+
return { name: packageName, version: String(manifest.version), directory: dirname(manifestPath), manifest };
|
|
298
|
+
}
|
|
226
299
|
//# sourceMappingURL=standalone-profile.js.map
|
package/package.json
CHANGED