rman 1.1.1 → 1.2.1
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/cli.js +3 -2
- package/commands/config.command.js +3 -2
- package/constants.js +1 -1
- package/core/config.d.ts +125 -6
- package/core/config.js +402 -50
- package/core/merge-config.d.ts +13 -0
- package/core/merge-config.js +47 -0
- package/core/repository.d.ts +15 -1
- package/core/repository.js +40 -8
- package/core/run-step.d.ts +75 -0
- package/core/run-step.js +1 -0
- package/index.d.ts +1 -0
- package/interfaces/rman-config.interface.d.ts +58 -17
- package/package.json +2 -1
- package/services/run.service.d.ts +66 -20
- package/services/run.service.js +171 -39
- package/services/version.service.d.ts +13 -3
- package/services/version.service.js +17 -11
- package/utils/printable-config.d.ts +15 -0
- package/utils/printable-config.js +42 -0
package/services/run.service.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { inspect } from 'node:util';
|
|
2
3
|
import colors from 'ansi-colors';
|
|
3
4
|
import { tokenize } from 'fast-tokenizer';
|
|
4
5
|
import { Task } from 'power-tasks';
|
|
5
6
|
import { exec } from '../utils/exec.js';
|
|
6
|
-
import { LOG_LEVELS, resolveRootLogLevel } from '../utils/logger.js';
|
|
7
|
+
import { LOG_LEVELS, Logger, resolveRootLogLevel } from '../utils/logger.js';
|
|
7
8
|
import { filterPackages } from '../utils/package-filter.js';
|
|
8
9
|
import { ProgressPanel } from '../utils/progress-panel.js';
|
|
10
|
+
import { runBin } from '../utils/run-bin.js';
|
|
9
11
|
export var RunService;
|
|
10
12
|
(function (RunService) {
|
|
11
13
|
/**
|
|
@@ -53,26 +55,88 @@ export var RunService;
|
|
|
53
55
|
* the copies would sit in the file that writes versions - which now runs no command of its own at
|
|
54
56
|
* all.
|
|
55
57
|
*
|
|
56
|
-
* `fallback` is the caller's own configured
|
|
58
|
+
* `fallback` is the caller's own configured step(s), **already evaluated**: `version`'s three
|
|
57
59
|
* paths are in `DEFERRED_PATHS` precisely because only the caller can bind
|
|
58
60
|
* `${{ pkg.targetVersion }}`, so interpolating here would either be too early or need a scope this
|
|
59
61
|
* service has no business holding.
|
|
62
|
+
*
|
|
63
|
+
* **A list, not one joined string.** `VersionService` used to `join(' && ')` an array into a
|
|
64
|
+
* single shell line, which a function step cannot be part of - and which quietly changed the
|
|
65
|
+
* semantics of the shell case too, since `cd x && y` in one process is not the same as two.
|
|
60
66
|
*/
|
|
61
67
|
async function runLifecycleSlot(pkg, script, slot, fallback) {
|
|
62
68
|
const own = contributedSlots(pkg, script)?.[slot] ?? [];
|
|
63
|
-
const
|
|
64
|
-
for (const
|
|
65
|
-
|
|
69
|
+
const values = own.length ? own : (fallback ?? []);
|
|
70
|
+
for (const value of values) {
|
|
71
|
+
if (typeof value === 'function') {
|
|
72
|
+
await value(createStepContext(pkg, pkg.dirname));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
await exec(value, { cwd: pkg.dirname, stdio: 'inherit' });
|
|
76
|
+
}
|
|
66
77
|
}
|
|
67
78
|
RunService.runLifecycleSlot = runLifecycleSlot;
|
|
68
79
|
function getConfig(pkg, script) {
|
|
69
80
|
const runCfg = pkg.config?.run;
|
|
70
81
|
const cfg = runCfg && typeof runCfg === 'object' ? runCfg[script] : undefined;
|
|
71
|
-
|
|
82
|
+
/** The bare-value shorthand. A function is `typeof 'function'`, not `'object'`, so without
|
|
83
|
+
* naming it here `run: { build: myFn }` fell through to the `{}` below - the long form would
|
|
84
|
+
* have worked and the short one silently done nothing, an arbitrary difference. */
|
|
85
|
+
if (typeof cfg === 'string' || typeof cfg === 'function' || Array.isArray(cfg))
|
|
72
86
|
return { exec: cfg };
|
|
73
87
|
return cfg && typeof cfg === 'object' ? cfg : {};
|
|
74
88
|
}
|
|
75
89
|
RunService.getConfig = getConfig;
|
|
90
|
+
/**
|
|
91
|
+
* The context a function step or `if` is handed - see [`RunStepContext`](../core/run-step.ts).
|
|
92
|
+
*
|
|
93
|
+
* `runBin` and `logger` are bound to *this run* rather than left to be imported, which is the
|
|
94
|
+
* whole reason they are handed over: an imported `runBin` knows neither the cwd nor the resolved
|
|
95
|
+
* log level.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* A `run.<script>.before`/`.exec`/`.after` (or `version.<slot>`) value: one step, or several to
|
|
99
|
+
* run in sequence. A shell command or a function, and a list may mix them.
|
|
100
|
+
*
|
|
101
|
+
* **Anything else throws, naming the path.** It used to `return []`, which meant a value rman did
|
|
102
|
+
* not recognize was dropped with no trace: writing a function here - the obvious guess, and now
|
|
103
|
+
* the supported form - produced `1 succeeded, 0 failed` with the step never run (measured). A
|
|
104
|
+
* configuration mistake has to be loud; silence here reads as success.
|
|
105
|
+
*
|
|
106
|
+
* Exported, and the only implementation: `VersionService` used to carry a second one that behaved
|
|
107
|
+
* differently, which is how `version.<slot>` came to join its array with `' && '`.
|
|
108
|
+
*/
|
|
109
|
+
function normalizeScriptValue(value, at) {
|
|
110
|
+
const items = Array.isArray(value) ? value : [value];
|
|
111
|
+
const steps = [];
|
|
112
|
+
for (let i = 0; i < items.length; i++) {
|
|
113
|
+
const item = items[i];
|
|
114
|
+
/** An empty string and an absent value are both "nothing here", which is how a `"[*]"` block
|
|
115
|
+
* declaring a slot some packages don't use has always behaved. */
|
|
116
|
+
if (item === undefined || item === null || item === '')
|
|
117
|
+
continue;
|
|
118
|
+
if (typeof item === 'string' || typeof item === 'function') {
|
|
119
|
+
steps.push(item);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const where = Array.isArray(value) ? `${at}[${i}]` : at;
|
|
123
|
+
throw new Error(`"${where}" must be a shell command or a function, but it is ${describeValue(item)}.\n` +
|
|
124
|
+
` A list of either (or both) runs them in sequence.`);
|
|
125
|
+
}
|
|
126
|
+
return steps;
|
|
127
|
+
}
|
|
128
|
+
RunService.normalizeScriptValue = normalizeScriptValue;
|
|
129
|
+
function createStepContext(pkg, cwd) {
|
|
130
|
+
const logLevel = resolveRootLogLevel(pkg.repository);
|
|
131
|
+
return {
|
|
132
|
+
pkg,
|
|
133
|
+
repository: pkg.repository,
|
|
134
|
+
cwd,
|
|
135
|
+
runBin: (bin, argv, opts) => runBin(bin, argv, { cwd, logLevel, ...opts }),
|
|
136
|
+
logger: new Logger(logLevel),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
RunService.createStepContext = createStepContext;
|
|
76
140
|
function resolveEnvPlaceholders(value) {
|
|
77
141
|
return value.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => process.env[name] ?? '');
|
|
78
142
|
}
|
|
@@ -180,7 +244,7 @@ export var RunService;
|
|
|
180
244
|
* abort using *its own* resolved bail setting (see `runSteps` below) - power-tasks' own
|
|
181
245
|
* `bail` is a single blanket policy for the whole batch, it can't vary per package. */
|
|
182
246
|
let rootTask;
|
|
183
|
-
const runSteps = async (ctx, pkgLabel, steps, cwd, pkgBail, pkgLogLevel) => {
|
|
247
|
+
const runSteps = async (ctx, pkg, pkgLabel, steps, cwd, pkgBail, pkgLogLevel) => {
|
|
184
248
|
ctx.status = 'running';
|
|
185
249
|
ctx.startedAt = Date.now();
|
|
186
250
|
try {
|
|
@@ -189,15 +253,14 @@ export var RunService;
|
|
|
189
253
|
ctx.currentStep = step.name;
|
|
190
254
|
ctx.stepIndex = i;
|
|
191
255
|
if (panel.enabled) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
});
|
|
256
|
+
const onLine = (line) => {
|
|
257
|
+
ctx.log.push(line);
|
|
258
|
+
ctx.lastLine = line;
|
|
259
|
+
};
|
|
260
|
+
if (step.run)
|
|
261
|
+
await runFunctionStep(step.run, pkg, cwd, onLine);
|
|
262
|
+
else
|
|
263
|
+
await exec(step.command, { cwd, stdio: 'pipe', onLine });
|
|
201
264
|
}
|
|
202
265
|
else {
|
|
203
266
|
/** Match the classic rman output: raw command output streams straight through
|
|
@@ -206,7 +269,12 @@ export var RunService;
|
|
|
206
269
|
const stepStart = Date.now();
|
|
207
270
|
let stepError;
|
|
208
271
|
try {
|
|
209
|
-
|
|
272
|
+
/** No capture with the panel off: the step owns the terminal, exactly as a shell
|
|
273
|
+
* step's `stdio: 'inherit'` does. */
|
|
274
|
+
if (step.run)
|
|
275
|
+
await runFunctionStep(step.run, pkg, cwd);
|
|
276
|
+
else
|
|
277
|
+
await exec(step.command, { cwd, stdio: 'inherit' });
|
|
210
278
|
}
|
|
211
279
|
catch (e) {
|
|
212
280
|
stepError = e;
|
|
@@ -238,9 +306,12 @@ export var RunService;
|
|
|
238
306
|
* Only in a monorepo. Without one the root *is* the single package, already in the loop below
|
|
239
307
|
* with the same hooks and the same directory - a bookend would simply run each of them a
|
|
240
308
|
* second time. */
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const rootSkipped = !!cwdScope ||
|
|
309
|
+
/** Short-circuited deliberately: a root already out of the run for a structural reason must not
|
|
310
|
+
* have its `if` evaluated, now that evaluating one can mean calling the repository's own code. */
|
|
311
|
+
const rootSkipped = !!cwdScope ||
|
|
312
|
+
!repository.monorepo ||
|
|
313
|
+
rootCfg.skip === true ||
|
|
314
|
+
!(await passesIf(repository, repository.rootPackage, rootCfg.if, repository.dirname, ifStatusCache));
|
|
244
315
|
const rootSteps = rootSkipped ? [] : getScriptSteps(repository.rootPackage, script);
|
|
245
316
|
/** Filtered on the slot, not on `'pre' + script`: the step labels are `before`/`exec`/`after`
|
|
246
317
|
* now - the same words the config uses - rather than npm's `pre<script>` naming, which moved
|
|
@@ -253,7 +324,7 @@ export var RunService;
|
|
|
253
324
|
const ctx = panel.addItem(rootPreName, rootPre.length);
|
|
254
325
|
const pkgBail = resolveBail(options.bail, repository.rootPackage, script, true);
|
|
255
326
|
const pkgLogLevel = resolveLogLevel(options.logLevel, repository.rootPackage, script, logLevelDefault);
|
|
256
|
-
children.push(new Task(() => runSteps(ctx, 'root', rootPre, repository.dirname, pkgBail, pkgLogLevel), {
|
|
327
|
+
children.push(new Task(() => runSteps(ctx, repository.rootPackage, 'root', rootPre, repository.dirname, pkgBail, pkgLogLevel), {
|
|
257
328
|
name: ctx.name,
|
|
258
329
|
exclusive: true,
|
|
259
330
|
}));
|
|
@@ -263,8 +334,7 @@ export var RunService;
|
|
|
263
334
|
const pkgCfg = getConfig(pkg, script);
|
|
264
335
|
if (pkgCfg.skip === true)
|
|
265
336
|
continue;
|
|
266
|
-
|
|
267
|
-
if (pkgIf && !(await evaluateIf(repository, pkg, pkgIf, ifStatusCache)))
|
|
337
|
+
if (!(await passesIf(repository, pkg, pkgCfg.if, pkg.dirname, ifStatusCache)))
|
|
268
338
|
continue;
|
|
269
339
|
const steps = getScriptSteps(pkg, script);
|
|
270
340
|
if (steps.length)
|
|
@@ -283,7 +353,7 @@ export var RunService;
|
|
|
283
353
|
const dependencies = pkgTopo ? pkg.dependencies.filter(d => stepsByPackage.has(d.name)).map(d => d.name) : [];
|
|
284
354
|
if (rootPre.length)
|
|
285
355
|
dependencies.push(rootPreName);
|
|
286
|
-
children.push(new Task(() => runSteps(ctx, pkg.name, steps, pkg.dirname, pkgBail, pkgLogLevel), {
|
|
356
|
+
children.push(new Task(() => runSteps(ctx, pkg, pkg.name, steps, pkg.dirname, pkgBail, pkgLogLevel), {
|
|
287
357
|
name: ctx.name,
|
|
288
358
|
dependencies,
|
|
289
359
|
}));
|
|
@@ -292,7 +362,7 @@ export var RunService;
|
|
|
292
362
|
const ctx = panel.addItem(rootPostName, rootPost.length);
|
|
293
363
|
const pkgBail = resolveBail(options.bail, repository.rootPackage, script, true);
|
|
294
364
|
const pkgLogLevel = resolveLogLevel(options.logLevel, repository.rootPackage, script, logLevelDefault);
|
|
295
|
-
children.push(new Task(() => runSteps(ctx, 'root', rootPost, repository.dirname, pkgBail, pkgLogLevel), {
|
|
365
|
+
children.push(new Task(() => runSteps(ctx, repository.rootPackage, 'root', rootPost, repository.dirname, pkgBail, pkgLogLevel), {
|
|
296
366
|
name: ctx.name,
|
|
297
367
|
exclusive: true,
|
|
298
368
|
/** Must wait for every package task to finish, not just be "exclusive" once it starts. */
|
|
@@ -375,7 +445,7 @@ function printLegacyExecutingLine(commandName, pkgLabel, step, level) {
|
|
|
375
445
|
if (LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf('verbose'))
|
|
376
446
|
return;
|
|
377
447
|
const sep = colors.gray('┆');
|
|
378
|
-
console.log(colors.magenta('verbose'), commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), colors.cyanBright.bold('executing'), sep, step.
|
|
448
|
+
console.log(colors.magenta('verbose'), commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), colors.cyanBright.bold('executing'), sep, step.label);
|
|
379
449
|
}
|
|
380
450
|
function printLegacyStepLine(commandName, pkgLabel, step, durationMs, level, error) {
|
|
381
451
|
if (!error && LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf('info'))
|
|
@@ -385,15 +455,77 @@ function printLegacyStepLine(commandName, pkgLabel, step, durationMs, level, err
|
|
|
385
455
|
const sep = colors.gray('┆');
|
|
386
456
|
const levelLabel = error ? colors.red('error') : colors.green('info');
|
|
387
457
|
const status = error ? colors.red.bold('failed') : colors.green.bold('success');
|
|
388
|
-
console.log(levelLabel, commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), status, sep, step.
|
|
458
|
+
console.log(levelLabel, commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), status, sep, step.label, colors.yellow(` (${durationMs} ms)`));
|
|
389
459
|
}
|
|
390
|
-
|
|
391
|
-
function normalizeScriptValue(value) {
|
|
392
|
-
if (typeof value === 'string')
|
|
393
|
-
return value ? [value] : [];
|
|
460
|
+
function describeValue(value) {
|
|
394
461
|
if (Array.isArray(value))
|
|
395
|
-
return
|
|
396
|
-
|
|
462
|
+
return 'a nested array';
|
|
463
|
+
if (value && typeof value === 'object')
|
|
464
|
+
return 'an object';
|
|
465
|
+
return `a ${typeof value} (${JSON.stringify(value)})`;
|
|
466
|
+
}
|
|
467
|
+
/** One step, with the label the panel and the per-step log show. A function's own name - so
|
|
468
|
+
* `function copyDocs()` and `const copyDocs = () => {}` both read as `copyDocs` - falling back to
|
|
469
|
+
* the slot's own word for one passed inline, which has no name at all. */
|
|
470
|
+
function toStep(slot, value) {
|
|
471
|
+
if (typeof value === 'function')
|
|
472
|
+
return { name: slot, label: value.name || `${slot} (js)`, run: value };
|
|
473
|
+
return { name: slot, label: value, command: value };
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Runs a function step.
|
|
477
|
+
*
|
|
478
|
+
* **`console` is redirected while it runs, but only when the panel is on** - and that is the same
|
|
479
|
+
* split a shell step already makes. `exec` hands the panel its output through `stdio: 'pipe'` and
|
|
480
|
+
* `onLine`, so a function writing straight to the real stdout would print *over* the panel it is
|
|
481
|
+
* being rendered inside. With the panel off, `exec` uses `stdio: 'inherit'` and the step owns the
|
|
482
|
+
* terminal; a function gets the same, untouched.
|
|
483
|
+
*
|
|
484
|
+
* Restored in a `finally`, because a step that throws must not leave the rest of the run writing
|
|
485
|
+
* into a log nobody reads.
|
|
486
|
+
*/
|
|
487
|
+
async function runFunctionStep(run, pkg, cwd, onLine) {
|
|
488
|
+
const context = RunService.createStepContext(pkg, cwd);
|
|
489
|
+
if (!onLine) {
|
|
490
|
+
await run(context);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const console_ = globalThis.console;
|
|
494
|
+
const original = {};
|
|
495
|
+
for (const method of CAPTURED_CONSOLE) {
|
|
496
|
+
original[method] = console_[method];
|
|
497
|
+
console_[method] = (...args) => {
|
|
498
|
+
/** Split, because one `console.log` may carry several lines and the panel's log is a list of
|
|
499
|
+
* them - a multi-line entry would render as one unreadable row. */
|
|
500
|
+
for (const line of format(args).split('\n'))
|
|
501
|
+
onLine(line);
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
await run(context);
|
|
506
|
+
}
|
|
507
|
+
finally {
|
|
508
|
+
for (const method of CAPTURED_CONSOLE)
|
|
509
|
+
console_[method] = original[method];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const CAPTURED_CONSOLE = ['log', 'info', 'warn', 'error', 'debug'];
|
|
513
|
+
function format(args) {
|
|
514
|
+
return args.map(arg => (typeof arg === 'string' ? arg : inspect(arg))).join(' ');
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Whether a script runs for `pkg` at all - `run.<script>.if`, in either of its two forms.
|
|
518
|
+
*
|
|
519
|
+
* The function form is checked **first**: `parseIfExpr` answers `undefined` for anything that is
|
|
520
|
+
* not a string, which the caller reads as "no condition given", so a function reaching it would be
|
|
521
|
+
* a condition that silently always passed.
|
|
522
|
+
*/
|
|
523
|
+
async function passesIf(repository, pkg, raw, cwd, statusCache) {
|
|
524
|
+
if (typeof raw === 'function') {
|
|
525
|
+
return !!(await raw(RunService.createStepContext(pkg, cwd)));
|
|
526
|
+
}
|
|
527
|
+
const node = RunService.parseIfExpr(raw);
|
|
528
|
+
return node ? RunService.evaluateIf(repository, pkg, node, statusCache) : true;
|
|
397
529
|
}
|
|
398
530
|
/**
|
|
399
531
|
* Resolves a package's steps for `script`, from its `.rmanrc` and from whatever sources plugins
|
|
@@ -419,17 +551,17 @@ function getScriptSteps(pkg, script) {
|
|
|
419
551
|
const override = cfg.override === true;
|
|
420
552
|
const contributed = firstContributed(pkg, script);
|
|
421
553
|
const fromConfig = {
|
|
422
|
-
before: normalizeScriptValue(cfg.before),
|
|
423
|
-
exec: normalizeScriptValue(cfg.exec),
|
|
424
|
-
after: normalizeScriptValue(cfg.after),
|
|
554
|
+
before: RunService.normalizeScriptValue(cfg.before, `run.${script}.before`),
|
|
555
|
+
exec: RunService.normalizeScriptValue(cfg.exec, `run.${script}.exec`),
|
|
556
|
+
after: RunService.normalizeScriptValue(cfg.after, `run.${script}.after`),
|
|
425
557
|
};
|
|
426
558
|
const steps = [];
|
|
427
559
|
for (const slot of SCRIPT_SLOTS) {
|
|
428
560
|
const own = contributed?.[slot] ?? [];
|
|
429
561
|
const configured = fromConfig[slot] ?? [];
|
|
430
|
-
const
|
|
431
|
-
for (const
|
|
432
|
-
steps.push(
|
|
562
|
+
const values = override ? (configured.length ? configured : own) : own.length ? own : configured;
|
|
563
|
+
for (const value of values)
|
|
564
|
+
steps.push(toStep(slot, value));
|
|
433
565
|
}
|
|
434
566
|
return steps;
|
|
435
567
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Package } from '../core/package.js';
|
|
2
2
|
import type { Repository } from '../core/repository.js';
|
|
3
|
+
import type { RunStepValue } from '../core/run-step.js';
|
|
3
4
|
import { VersionPlanService } from './version-plan.service.js';
|
|
4
5
|
/**
|
|
5
6
|
* Applying a version plan: the writes. Every manifest edit, dependency-range refresh, stamp,
|
|
@@ -74,9 +75,18 @@ export declare namespace VersionService {
|
|
|
74
75
|
* listing of `name@version` pairs when this particular commit spans different versions (a
|
|
75
76
|
* cross-group ripple can land a lone forced patch in a group that otherwise didn't move). */
|
|
76
77
|
function buildCommitMessage(repository: Repository, entries: VersionPlanService.Entry[], messageOverride?: string): string;
|
|
77
|
-
/**
|
|
78
|
-
*
|
|
79
|
-
|
|
78
|
+
/**
|
|
79
|
+
* A `version.<slot>` value: one step, or several to run in sequence - the same shape, and now the
|
|
80
|
+
* same function, as `run.<script>.before`/`.exec`/`.after`.
|
|
81
|
+
*
|
|
82
|
+
* It used to be a second implementation living here, and it differed in two ways that both had to
|
|
83
|
+
* go. It **joined an array with `' && '`** into one shell line, which a function step cannot be
|
|
84
|
+
* part of and which was not even right for shell steps - `cd x && y` in one process is not two
|
|
85
|
+
* processes. And it **dropped anything it did not recognize**, so a function here was silently
|
|
86
|
+
* never run. (The doc comment also still named `.script`/`.preScript`/`.postScript`, three keys
|
|
87
|
+
* that have been `before`/`exec`/`after` for a long time.)
|
|
88
|
+
*/
|
|
89
|
+
function normalizeScriptValue(value: unknown, at: string): RunStepValue[];
|
|
80
90
|
/**
|
|
81
91
|
* Keeps a package's Dockerfile `org.opencontainers.image.version` label in step with the version
|
|
82
92
|
* just written, returning the absolute path when it actually changed (so the caller can fold it
|
|
@@ -65,7 +65,10 @@ export var VersionService;
|
|
|
65
65
|
* pre-empts it, and running the thing, are `RunService`'s (`runLifecycleSlot`), so nothing
|
|
66
66
|
* here names a script, a file, or a shell.
|
|
67
67
|
*/
|
|
68
|
-
const hook = (slot) => RunService.runLifecycleSlot(pkg, VERSION_LIFECYCLE, slot, normalizeScriptValue(
|
|
68
|
+
const hook = (slot) => RunService.runLifecycleSlot(pkg, VERSION_LIFECYCLE, slot, RunService.normalizeScriptValue(
|
|
69
|
+
/** `at`: the path is what tells a step function from a value one, and this is a fragment -
|
|
70
|
+
* without it a function here was called while the hook was being prepared. */
|
|
71
|
+
interpolateConfig(pkg.config?.version?.[slot], scope, { at: ['version', slot] }), `version.${slot}`));
|
|
69
72
|
await hook('before');
|
|
70
73
|
/** Through the manifest, not through a `package.json` field: where a version is written is
|
|
71
74
|
* the provider's business (see `ManifestProvider`), and this is the one place rman changes
|
|
@@ -198,16 +201,19 @@ export var VersionService;
|
|
|
198
201
|
return `chore(release): ${entries.map(e => `${e.package.name}@${e.to}`).join(', ')}`;
|
|
199
202
|
}
|
|
200
203
|
VersionService.buildCommitMessage = buildCommitMessage;
|
|
201
|
-
/**
|
|
202
|
-
*
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
/**
|
|
205
|
+
* A `version.<slot>` value: one step, or several to run in sequence - the same shape, and now the
|
|
206
|
+
* same function, as `run.<script>.before`/`.exec`/`.after`.
|
|
207
|
+
*
|
|
208
|
+
* It used to be a second implementation living here, and it differed in two ways that both had to
|
|
209
|
+
* go. It **joined an array with `' && '`** into one shell line, which a function step cannot be
|
|
210
|
+
* part of and which was not even right for shell steps - `cd x && y` in one process is not two
|
|
211
|
+
* processes. And it **dropped anything it did not recognize**, so a function here was silently
|
|
212
|
+
* never run. (The doc comment also still named `.script`/`.preScript`/`.postScript`, three keys
|
|
213
|
+
* that have been `before`/`exec`/`after` for a long time.)
|
|
214
|
+
*/
|
|
215
|
+
function normalizeScriptValue(value, at) {
|
|
216
|
+
return RunService.normalizeScriptValue(value, at);
|
|
211
217
|
}
|
|
212
218
|
VersionService.normalizeScriptValue = normalizeScriptValue;
|
|
213
219
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A copy of a resolved config with every value a serializer cannot represent replaced by a short
|
|
3
|
+
* description of it - for `rman config` and `--config`, which exist to be *looked at*.
|
|
4
|
+
*
|
|
5
|
+
* Needed because a config legitimately holds functions now: a `run.<script>` or `version.<slot>`
|
|
6
|
+
* step written as JavaScript, and an `if` written the same way. It was already needed before that,
|
|
7
|
+
* though, which is the better argument for doing it here rather than at one call site - a
|
|
8
|
+
* `plugins` entry given in its object form carries the plugin's seams, and `rman config --root`
|
|
9
|
+
* died on one with `unacceptable kind of an object to dump [object Function]` (measured, on a
|
|
10
|
+
* repository whose shared config did nothing more unusual than `extends` a plugin package).
|
|
11
|
+
*
|
|
12
|
+
* A function prints as `[Function: copyDocs]`, so the output says *which* one - an anonymous step
|
|
13
|
+
* reads as `[Function]`, which is itself worth seeing.
|
|
14
|
+
*/
|
|
15
|
+
export declare function printableConfig<T>(config: T): T;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A copy of a resolved config with every value a serializer cannot represent replaced by a short
|
|
3
|
+
* description of it - for `rman config` and `--config`, which exist to be *looked at*.
|
|
4
|
+
*
|
|
5
|
+
* Needed because a config legitimately holds functions now: a `run.<script>` or `version.<slot>`
|
|
6
|
+
* step written as JavaScript, and an `if` written the same way. It was already needed before that,
|
|
7
|
+
* though, which is the better argument for doing it here rather than at one call site - a
|
|
8
|
+
* `plugins` entry given in its object form carries the plugin's seams, and `rman config --root`
|
|
9
|
+
* died on one with `unacceptable kind of an object to dump [object Function]` (measured, on a
|
|
10
|
+
* repository whose shared config did nothing more unusual than `extends` a plugin package).
|
|
11
|
+
*
|
|
12
|
+
* A function prints as `[Function: copyDocs]`, so the output says *which* one - an anonymous step
|
|
13
|
+
* reads as `[Function]`, which is itself worth seeing.
|
|
14
|
+
*/
|
|
15
|
+
export function printableConfig(config) {
|
|
16
|
+
return walk(config, new WeakSet());
|
|
17
|
+
}
|
|
18
|
+
function walk(value, seen) {
|
|
19
|
+
if (typeof value === 'function')
|
|
20
|
+
return `[Function${value.name ? `: ${value.name}` : ''}]`;
|
|
21
|
+
if (!value || typeof value !== 'object')
|
|
22
|
+
return value;
|
|
23
|
+
/** A resolved config is a tree, but a plugin object is arbitrary code's data and may not be.
|
|
24
|
+
* js-yaml's `noRefs` turns a repeat into a copy rather than an anchor, which on a true cycle
|
|
25
|
+
* never terminates - so the cycle is cut here, where it can be named. */
|
|
26
|
+
if (seen.has(value))
|
|
27
|
+
return '[Circular]';
|
|
28
|
+
seen.add(value);
|
|
29
|
+
try {
|
|
30
|
+
if (Array.isArray(value))
|
|
31
|
+
return value.map(item => walk(item, seen));
|
|
32
|
+
const result = {};
|
|
33
|
+
for (const [key, item] of Object.entries(value))
|
|
34
|
+
result[key] = walk(item, seen);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
/** Released on the way out, so a value that merely appears twice in *different* branches - the
|
|
39
|
+
* ordinary case after merging - is printed both times rather than reported as a cycle. */
|
|
40
|
+
seen.delete(value);
|
|
41
|
+
}
|
|
42
|
+
}
|