rman 2.19.1 → 2.20.0
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/constants.js +1 -1
- package/core/classes/progress-panel.d.ts +2 -0
- package/core/classes/progress-panel.js +12 -2
- package/core/config/config-paths.js +9 -0
- package/core/interfaces/run-step.d.ts +21 -0
- package/package.json +1 -1
- package/services/run.service.d.ts +6 -2
- package/services/run.service.js +42 -16
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.20.0';
|
|
@@ -81,6 +81,8 @@ export declare class ProgressPanel implements TerminalRegion {
|
|
|
81
81
|
* `TerminalRegion` half of the takeover, so a `runBin` child's output goes somewhere sane while
|
|
82
82
|
* a panel is up. */
|
|
83
83
|
passThrough(text: string): void;
|
|
84
|
+
/** Every line of a replayed log moved two columns right - an empty line left empty. */
|
|
85
|
+
protected indentLog(text: string): string;
|
|
84
86
|
private render;
|
|
85
87
|
/** Stops the redraw loop and erases the panel, leaving the cursor where it started. */
|
|
86
88
|
stop(): void;
|
|
@@ -79,6 +79,13 @@ export class ProgressPanel {
|
|
|
79
79
|
process.stdout.write(text);
|
|
80
80
|
this.render();
|
|
81
81
|
}
|
|
82
|
+
/** Every line of a replayed log moved two columns right - an empty line left empty. */
|
|
83
|
+
indentLog(text) {
|
|
84
|
+
return text
|
|
85
|
+
.split('\n')
|
|
86
|
+
.map(line => (line ? ` ${line}` : line))
|
|
87
|
+
.join('\n');
|
|
88
|
+
}
|
|
82
89
|
render() {
|
|
83
90
|
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
84
91
|
const spinner = colors.cyan(SPINNER_FRAMES[this.spinnerFrame]);
|
|
@@ -273,9 +280,12 @@ export class ProgressPanel {
|
|
|
273
280
|
* the comma after it red.
|
|
274
281
|
*
|
|
275
282
|
* The `X` above already says the item failed, and the step that failed printed its own
|
|
276
|
-
* error.
|
|
283
|
+
* error.
|
|
284
|
+
*
|
|
285
|
+
* **Indented two columns**, so the log reads as belonging to the `X` line above it - level
|
|
286
|
+
* with the recap's own lines it ran together with them, a nested rman's recap most of all. */
|
|
277
287
|
if (item.log.length)
|
|
278
|
-
console.log(item.log.join('\n'));
|
|
288
|
+
console.log(this.indentLog(item.log.join('\n')));
|
|
279
289
|
}
|
|
280
290
|
}
|
|
281
291
|
else if (this.live.enabled) {
|
|
@@ -89,6 +89,15 @@ export const STEP_PATHS = [
|
|
|
89
89
|
'version.before.command',
|
|
90
90
|
'version.exec.command',
|
|
91
91
|
'version.after.command',
|
|
92
|
+
/** A step object's own `if`, for the reason `run.*.if` is here: called while the config loads it
|
|
93
|
+
* would collapse to a boolean and stop being a condition. `run.*.if` already covers the
|
|
94
|
+
* shorthand's (`run: { build: [{ if, command }] }`), which arrives at the same path. */
|
|
95
|
+
'run.*.before.if',
|
|
96
|
+
'run.*.exec.if',
|
|
97
|
+
'run.*.after.if',
|
|
98
|
+
'version.before.if',
|
|
99
|
+
'version.exec.if',
|
|
100
|
+
'version.after.if',
|
|
92
101
|
];
|
|
93
102
|
/**
|
|
94
103
|
* Config paths left untouched when a repository's config is first resolved, and evaluated only by
|
|
@@ -47,6 +47,16 @@ export interface RunStepContext {
|
|
|
47
47
|
* `ctx.runBin` is already bound to this directory, so a binary run through it needs no such care.
|
|
48
48
|
*/
|
|
49
49
|
cwd: string;
|
|
50
|
+
/**
|
|
51
|
+
* The package the run was narrowed to because rman was started inside it, or `undefined` when it
|
|
52
|
+
* covers the repository - started at the root, or with `--from-root`.
|
|
53
|
+
*
|
|
54
|
+
* What a step asks to tell "the whole repository is building" from "just this package is":
|
|
55
|
+
* `repository.currentPackage` answers where rman was started and cannot see `--from-root`. Set by
|
|
56
|
+
* `run` and the commands built on it (`build`, `test`); `undefined` in a `version` hook, which
|
|
57
|
+
* always covers the repository.
|
|
58
|
+
*/
|
|
59
|
+
scopedTo?: Package;
|
|
50
60
|
/**
|
|
51
61
|
* The repository's locally installed binaries, already carrying this run's `cwd` and log level -
|
|
52
62
|
* handed over rather than imported, for the reason `CommandContext.runBin` is.
|
|
@@ -107,6 +117,17 @@ export interface RunStepObject {
|
|
|
107
117
|
* with no package graph to join, and the key is refused there rather than quietly doing nothing.
|
|
108
118
|
*/
|
|
109
119
|
topo?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Whether this one step runs, asked when its turn comes - the same two forms
|
|
122
|
+
* `run.<script>.if` takes: a condition (`changed`, `dirty and not committed`) or a function
|
|
123
|
+
* handed the step's context. A step that says no is passed over, and the ones after it still
|
|
124
|
+
* run.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // lint the package only when the build was started inside it
|
|
128
|
+
* { if: ({ scopedTo }) => !!scopedTo, command: 'rman lint' }
|
|
129
|
+
*/
|
|
130
|
+
if?: string | RunConditionFn;
|
|
110
131
|
}
|
|
111
132
|
/** One entry of a `before`/`exec`/`after` slot: a shell command, a function, or a
|
|
112
133
|
* [`RunStepObject`](#RunStepObject) saying something about the step. A list of them runs in
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ import type { Package } from '../core/classes/package.js';
|
|
|
4
4
|
import { ProgressPanel } from '../core/classes/progress-panel.js';
|
|
5
5
|
import type { Repository } from '../core/classes/repository.js';
|
|
6
6
|
import { Service } from '../core/classes/service.js';
|
|
7
|
-
import type { RunStepContext, RunStepFn, RunStepValue } from '../core/interfaces/run-step.js';
|
|
7
|
+
import type { RunConditionFn, RunStepContext, RunStepFn, RunStepValue } from '../core/interfaces/run-step.js';
|
|
8
8
|
import { type PackageFilterOptions } from '../utils/package-filter.js';
|
|
9
9
|
/**
|
|
10
10
|
* A service class - see `ListService` for the shape and `Service` for the three measured
|
|
@@ -160,6 +160,8 @@ export declare namespace RunService {
|
|
|
160
160
|
steps: ScriptStep[];
|
|
161
161
|
/** The directory the steps run in - the package's own, or the repository root for a bookend. */
|
|
162
162
|
cwd: string;
|
|
163
|
+
/** The package the run was narrowed to by the directory it started in - `RunStepContext.scopedTo`. */
|
|
164
|
+
scopedTo?: Package;
|
|
163
165
|
topo: boolean;
|
|
164
166
|
bail: boolean;
|
|
165
167
|
logLevel: LogLevel;
|
|
@@ -210,6 +212,8 @@ export declare namespace RunService {
|
|
|
210
212
|
* and the three are three different answers, which is why this is not a plain boolean. See
|
|
211
213
|
* `RunStepObject.topo`. */
|
|
212
214
|
topo?: boolean;
|
|
215
|
+
/** The step's own condition, from its object form - see `RunStepObject.if`. */
|
|
216
|
+
if?: string | RunConditionFn;
|
|
213
217
|
}
|
|
214
218
|
export interface CommandStep extends StepBase {
|
|
215
219
|
command: string;
|
|
@@ -306,7 +310,7 @@ export declare namespace RunService {
|
|
|
306
310
|
* `onCommand` is told what the step is spawning, for as long as it runs - the argv while a child
|
|
307
311
|
* is up, `undefined` when it returns.
|
|
308
312
|
*/
|
|
309
|
-
export function createStepContext(pkg: Package, cwd: string, onCommand?: (command: string | undefined) => void, onLine?: (line: string, stream?: 'stdout' | 'stderr') => void): RunStepContext;
|
|
313
|
+
export function createStepContext(pkg: Package, cwd: string, onCommand?: (command: string | undefined) => void, onLine?: (line: string, stream?: 'stdout' | 'stderr') => void, scopedTo?: Package): RunStepContext;
|
|
310
314
|
/**
|
|
311
315
|
* `.rmanrc` conditional execution, GitHub Actions-`if`-flavored but a small closed grammar
|
|
312
316
|
* instead of a full expression language (less to get wrong, still covers what's asked for) -
|
package/services/run.service.js
CHANGED
|
@@ -60,7 +60,7 @@ export class RunService extends Service {
|
|
|
60
60
|
const rootSkipped = !!cwdScope ||
|
|
61
61
|
!repository.monorepo ||
|
|
62
62
|
rootCfg.skip === true ||
|
|
63
|
-
!(await passesIf(repository, repository.rootPackage, rootCfg.if, repository.dirname, ifStatusCache));
|
|
63
|
+
!(await passesIf(repository, repository.rootPackage, rootCfg.if, repository.dirname, ifStatusCache, cwdScope));
|
|
64
64
|
const rootSteps = rootSkipped ? [] : getScriptSteps(repository.rootPackage, script);
|
|
65
65
|
/** Filtered on the slot, not on `'pre' + script`: the step labels are `before`/`exec`/`after`
|
|
66
66
|
* now - the same words the config uses - rather than npm's `pre<script>` naming, which moved
|
|
@@ -75,6 +75,7 @@ export class RunService extends Service {
|
|
|
75
75
|
label: 'root',
|
|
76
76
|
steps,
|
|
77
77
|
cwd: repository.dirname,
|
|
78
|
+
scopedTo: cwdScope,
|
|
78
79
|
topo: false,
|
|
79
80
|
bail: resolveBail(options.bail, repository.rootPackage, script, true),
|
|
80
81
|
logLevel: resolveLogLevel(options.logLevel, repository.rootPackage, script, logLevelDefault),
|
|
@@ -87,7 +88,7 @@ export class RunService extends Service {
|
|
|
87
88
|
const pkgCfg = RunService.getConfig(pkg, script);
|
|
88
89
|
if (pkgCfg.skip === true)
|
|
89
90
|
continue;
|
|
90
|
-
if (!(await passesIf(repository, pkg, pkgCfg.if, pkg.dirname, ifStatusCache)))
|
|
91
|
+
if (!(await passesIf(repository, pkg, pkgCfg.if, pkg.dirname, ifStatusCache, cwdScope)))
|
|
91
92
|
continue;
|
|
92
93
|
const steps = getScriptSteps(pkg, script);
|
|
93
94
|
if (!steps.length)
|
|
@@ -98,6 +99,7 @@ export class RunService extends Service {
|
|
|
98
99
|
label: pkg.name,
|
|
99
100
|
steps,
|
|
100
101
|
cwd: pkg.dirname,
|
|
102
|
+
scopedTo: cwdScope,
|
|
101
103
|
topo: resolveBool(options.topo, pkg, script, 'topo', topo),
|
|
102
104
|
bail: resolveBail(options.bail, pkg, script, true),
|
|
103
105
|
logLevel: resolveLogLevel(options.logLevel, pkg, script, logLevelDefault),
|
|
@@ -162,6 +164,8 @@ export class RunService extends Service {
|
|
|
162
164
|
* abort using *its own* resolved bail setting (see `runSteps` below) - power-tasks' own
|
|
163
165
|
* `bail` is a single blanket policy for the whole batch, it can't vary per package. */
|
|
164
166
|
let rootTask;
|
|
167
|
+
/** One `git` query per reference for every step's `if: changed`, as the scripts' share one. */
|
|
168
|
+
const stepIfCache = new Map();
|
|
165
169
|
const runSteps = async (ctx, plan, steps,
|
|
166
170
|
/**
|
|
167
171
|
* Which slice of the package's steps this call runs, when a `topo` step split it in two.
|
|
@@ -170,7 +174,7 @@ export class RunService extends Service {
|
|
|
170
174
|
* one must not mark the package finished.
|
|
171
175
|
*/
|
|
172
176
|
part) => {
|
|
173
|
-
const { pkg, cwd, bail: pkgBail, logLevel: pkgLogLevel, label: pkgLabel } = plan;
|
|
177
|
+
const { pkg, cwd, bail: pkgBail, logLevel: pkgLogLevel, label: pkgLabel, scopedTo } = plan;
|
|
174
178
|
const from = part?.from ?? 0;
|
|
175
179
|
/** Only the first part starts the clock: the row's elapsed time is the package's, and a
|
|
176
180
|
* package waiting at a `topo` barrier is waiting as part of its own run. */
|
|
@@ -181,6 +185,12 @@ export class RunService extends Service {
|
|
|
181
185
|
try {
|
|
182
186
|
for (let i = from; i < steps.length; i++) {
|
|
183
187
|
const step = steps[i];
|
|
188
|
+
/** Asked when the step's turn comes, not when the plan is made - a condition may read
|
|
189
|
+
* what the steps before it left behind. A step that says no is passed over in silence:
|
|
190
|
+
* it is not a failure, and announcing it on every package would bury the steps that ran. */
|
|
191
|
+
if (step.if !== undefined && !(await passesIf(pkg.repository, pkg, step.if, cwd, stepIfCache, scopedTo))) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
184
194
|
ctx.currentStep = step.name;
|
|
185
195
|
/** The command for a shell step, the function's own name for a JS one - and a bare
|
|
186
196
|
* marker for an anonymous function, which is still worth saying: the slot label alone
|
|
@@ -217,7 +227,7 @@ export class RunService extends Service {
|
|
|
217
227
|
* run step should.
|
|
218
228
|
*/
|
|
219
229
|
if (step.run)
|
|
220
|
-
await runFunctionStep(step.run, pkg, cwd, onLine, onCommand);
|
|
230
|
+
await runFunctionStep(step.run, pkg, cwd, onLine, onCommand, scopedTo);
|
|
221
231
|
else {
|
|
222
232
|
await exec(step.command, {
|
|
223
233
|
cwd,
|
|
@@ -583,6 +593,8 @@ export class RunService extends Service {
|
|
|
583
593
|
* (`{ command }` / `{ run }`) reaches here too and only one place knows the three spellings.
|
|
584
594
|
* `topo` cannot be set on this path - `normalizeScriptValue` refuses it for a version hook. */
|
|
585
595
|
const step = toStep(slot, value);
|
|
596
|
+
if (step.if !== undefined && !(await passesIf(pkg.repository, pkg, step.if, pkg.dirname, new Map())))
|
|
597
|
+
continue;
|
|
586
598
|
if (step.run) {
|
|
587
599
|
await step.run(createStepContext(pkg, pkg.dirname));
|
|
588
600
|
continue;
|
|
@@ -720,12 +732,13 @@ export class RunService extends Service {
|
|
|
720
732
|
* whole of a build, because that is all the slot knows; what a reader wants is the `tsc -b
|
|
721
733
|
* <tsconfig>` it is sitting in. A function step is the shape every shared config uses, so this is
|
|
722
734
|
* the common case rather than an edge one. */
|
|
723
|
-
function createStepContext(pkg, cwd, onCommand, onLine) {
|
|
735
|
+
function createStepContext(pkg, cwd, onCommand, onLine, scopedTo) {
|
|
724
736
|
const logLevel = resolveRootLogLevel(pkg.repository);
|
|
725
737
|
return {
|
|
726
738
|
pkg,
|
|
727
739
|
repository: pkg.repository,
|
|
728
740
|
cwd,
|
|
741
|
+
scopedTo,
|
|
729
742
|
runBin: async (bin, argv, opts) => {
|
|
730
743
|
onCommand?.([bin, ...argv].join(' '));
|
|
731
744
|
const result = await runBin(bin, argv, { cwd, logLevel, app: pkg.repository.app, onLine, ...opts });
|
|
@@ -830,10 +843,12 @@ function describeValue(value) {
|
|
|
830
843
|
* the slot's own word for one passed inline, which has no name at all. */
|
|
831
844
|
function toStep(slot, value) {
|
|
832
845
|
const topo = typeof value === 'object' ? value.topo : undefined;
|
|
846
|
+
const condition = typeof value === 'object' ? value.if : undefined;
|
|
833
847
|
const step = typeof value === 'object' ? value.command : value;
|
|
834
|
-
if (typeof step === 'function')
|
|
835
|
-
return { name: slot, label: step.name || `${slot} (js)`, run: step, topo };
|
|
836
|
-
|
|
848
|
+
if (typeof step === 'function') {
|
|
849
|
+
return { name: slot, label: step.name || `${slot} (js)`, run: step, topo, if: condition };
|
|
850
|
+
}
|
|
851
|
+
return { name: slot, label: step, command: step, topo, if: condition };
|
|
837
852
|
}
|
|
838
853
|
/**
|
|
839
854
|
* Checks a step written as an object and hands it back - a `command` that is a shell command or a
|
|
@@ -873,10 +888,20 @@ function assertStepObject(item, where, allowTopo) {
|
|
|
873
888
|
throw new Error(`"${where}" must set "topo" to true or false, but it is ${describeValue(item.topo)}.`);
|
|
874
889
|
}
|
|
875
890
|
}
|
|
876
|
-
|
|
891
|
+
/** The same two forms `run.<script>.if` takes. An empty string is refused rather than read as
|
|
892
|
+
* "no condition": a key written and left blank is more likely a mistake than a decision. */
|
|
893
|
+
if (item.if !== undefined && typeof item.if !== 'function' && (typeof item.if !== 'string' || !item.if.trim())) {
|
|
894
|
+
throw new Error(`"${where}" must set "if" to a condition (\`changed\`, \`dirty and not committed\`) or a function, ` +
|
|
895
|
+
`but it is ${describeValue(item.if)}.`);
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
command: item.command,
|
|
899
|
+
topo: item.topo,
|
|
900
|
+
...(item.if !== undefined && { if: item.if }),
|
|
901
|
+
};
|
|
877
902
|
}
|
|
878
|
-
/** What a step object may hold - the step itself, and the
|
|
879
|
-
const STEP_OBJECT_KEYS = ['command', 'topo'];
|
|
903
|
+
/** What a step object may hold - the step itself, and the markers beside it. */
|
|
904
|
+
const STEP_OBJECT_KEYS = ['command', 'topo', 'if'];
|
|
880
905
|
/**
|
|
881
906
|
* Runs a function step, with its `console` captured into `onLine` - the step's own lines, routed to
|
|
882
907
|
* whichever reporters the run has, beside those of the children it spawns.
|
|
@@ -894,8 +919,8 @@ const STEP_OBJECT_KEYS = ['command', 'topo'];
|
|
|
894
919
|
* nowhere at all. Written to `onLine` on stderr, ahead of the step's `end` - the order a shell step
|
|
895
920
|
* already produces.
|
|
896
921
|
*/
|
|
897
|
-
async function runFunctionStep(run, pkg, cwd, onLine, onCommand) {
|
|
898
|
-
const context = RunService.createStepContext(pkg, cwd, onCommand, onLine);
|
|
922
|
+
async function runFunctionStep(run, pkg, cwd, onLine, onCommand, scopedTo) {
|
|
923
|
+
const context = RunService.createStepContext(pkg, cwd, onCommand, onLine, scopedTo);
|
|
899
924
|
try {
|
|
900
925
|
await RunService.withCapturedConsole(onLine, () => run(context));
|
|
901
926
|
}
|
|
@@ -920,15 +945,16 @@ function format(args) {
|
|
|
920
945
|
return args.map(arg => (typeof arg === 'string' ? arg : inspect(arg))).join(' ');
|
|
921
946
|
}
|
|
922
947
|
/**
|
|
923
|
-
* Whether a script runs for `pkg` at all - `run.<script>.if
|
|
948
|
+
* Whether a script runs for `pkg` at all - `run.<script>.if` - or a single step does (a step
|
|
949
|
+
* object's own `if`), in either of their two forms.
|
|
924
950
|
*
|
|
925
951
|
* The function form is checked **first**: `parseIfExpr` answers `undefined` for anything that is
|
|
926
952
|
* not a string, which the caller reads as "no condition given", so a function reaching it would be
|
|
927
953
|
* a condition that silently always passed.
|
|
928
954
|
*/
|
|
929
|
-
async function passesIf(repository, pkg, raw, cwd, statusCache) {
|
|
955
|
+
async function passesIf(repository, pkg, raw, cwd, statusCache, scopedTo) {
|
|
930
956
|
if (typeof raw === 'function') {
|
|
931
|
-
return !!(await raw(RunService.createStepContext(pkg, cwd)));
|
|
957
|
+
return !!(await raw(RunService.createStepContext(pkg, cwd, undefined, undefined, scopedTo)));
|
|
932
958
|
}
|
|
933
959
|
const node = RunService.parseIfExpr(raw);
|
|
934
960
|
return node ? RunService.evaluateIf(repository, pkg, node, statusCache) : true;
|