rman 2.19.1 → 2.21.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/cli.js +6 -0
- package/constants.js +1 -1
- package/core/classes/progress-panel.d.ts +21 -0
- package/core/classes/progress-panel.js +38 -3
- package/core/classes/run-reporters.d.ts +9 -4
- package/core/classes/run-reporters.js +12 -4
- 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/cli.js
CHANGED
|
@@ -99,6 +99,12 @@ export async function runCli(options) {
|
|
|
99
99
|
? err.message
|
|
100
100
|
: '';
|
|
101
101
|
console.log('\n' + colors.red(text));
|
|
102
|
+
/** The handler's own error is marked too, because it is the one that comes back: yargs
|
|
103
|
+
* rejects `parseAsync` with what the handler threw, not with what this throws, so
|
|
104
|
+
* `runCli`'s catch saw it unmarked and printed the same line again on stderr - every
|
|
105
|
+
* failing command said its reason twice. */
|
|
106
|
+
if (err && typeof err === 'object')
|
|
107
|
+
err.logged = true;
|
|
102
108
|
/** A real `Error`, marked `logged` since the text above just went out: yargs hands the
|
|
103
109
|
* reason over as a bare string, and rethrowing that raw made bad argv indistinguishable
|
|
104
110
|
* from success to anything holding the promise - or the shell. */
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.21.0';
|
|
@@ -24,6 +24,20 @@ export interface ProgressItem {
|
|
|
24
24
|
finishedAt?: number;
|
|
25
25
|
/** Captured output, printed back out if the item ends up failing. */
|
|
26
26
|
log: string[];
|
|
27
|
+
/**
|
|
28
|
+
* The same output split by the step that printed it, when the driver reports steps (`run`'s
|
|
29
|
+
* scheduler does). A failed item's recap then names the steps that passed in a line each and
|
|
30
|
+
* prints the output of the one that failed; without it, `log` is printed whole.
|
|
31
|
+
*/
|
|
32
|
+
steps?: StepLog[];
|
|
33
|
+
}
|
|
34
|
+
/** One step of an item, as the recap shows it - see `ProgressItem.steps`. */
|
|
35
|
+
export interface StepLog {
|
|
36
|
+
/** `before: rman check` - the slot and what ran in it. */
|
|
37
|
+
label: string;
|
|
38
|
+
/** Unset while it runs, and for a step that never got to end. */
|
|
39
|
+
status?: 'success' | 'failed';
|
|
40
|
+
lines: string[];
|
|
27
41
|
}
|
|
28
42
|
export interface ProgressSummary {
|
|
29
43
|
successCount: number;
|
|
@@ -81,6 +95,13 @@ export declare class ProgressPanel implements TerminalRegion {
|
|
|
81
95
|
* `TerminalRegion` half of the takeover, so a `runBin` child's output goes somewhere sane while
|
|
82
96
|
* a panel is up. */
|
|
83
97
|
passThrough(text: string): void;
|
|
98
|
+
/**
|
|
99
|
+
* A failed item's steps: one line for each that passed, and the one that failed - or never got to
|
|
100
|
+
* end - with its whole output under it.
|
|
101
|
+
*/
|
|
102
|
+
protected stepRecap(steps: readonly StepLog[]): string;
|
|
103
|
+
/** Every line of a replayed log moved two columns right - an empty line left empty. */
|
|
104
|
+
protected indentLog(text: string): string;
|
|
84
105
|
private render;
|
|
85
106
|
/** Stops the redraw loop and erases the panel, leaving the cursor where it started. */
|
|
86
107
|
stop(): void;
|
|
@@ -79,6 +79,35 @@ export class ProgressPanel {
|
|
|
79
79
|
process.stdout.write(text);
|
|
80
80
|
this.render();
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* A failed item's steps: one line for each that passed, and the one that failed - or never got to
|
|
84
|
+
* end - with its whole output under it.
|
|
85
|
+
*/
|
|
86
|
+
/* **Only the failing step's output, and the user's call.** The whole log used to be replayed, on
|
|
87
|
+
* the reasoning that an earlier step's output can explain a later failure. In a real build that
|
|
88
|
+
* put `rman check`'s file count and its own recap above every lint error, four lines of success
|
|
89
|
+
* the reader had to skip to reach the one that mattered. The passing steps keep a line each, so
|
|
90
|
+
* the order is still there; `--log-file` keeps every line. */
|
|
91
|
+
stepRecap(steps) {
|
|
92
|
+
const out = [];
|
|
93
|
+
for (const step of steps) {
|
|
94
|
+
if (step.status === 'success') {
|
|
95
|
+
out.push(`${colors.green('✔')} ${colors.gray(step.label)}`);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
out.push(`${colors.red('✖')} ${step.label}`);
|
|
99
|
+
if (step.lines.length)
|
|
100
|
+
out.push(this.indentLog(step.lines.join('\n')));
|
|
101
|
+
}
|
|
102
|
+
return out.join('\n');
|
|
103
|
+
}
|
|
104
|
+
/** Every line of a replayed log moved two columns right - an empty line left empty. */
|
|
105
|
+
indentLog(text) {
|
|
106
|
+
return text
|
|
107
|
+
.split('\n')
|
|
108
|
+
.map(line => (line ? ` ${line}` : line))
|
|
109
|
+
.join('\n');
|
|
110
|
+
}
|
|
82
111
|
render() {
|
|
83
112
|
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
84
113
|
const spinner = colors.cyan(SPINNER_FRAMES[this.spinnerFrame]);
|
|
@@ -273,9 +302,14 @@ export class ProgressPanel {
|
|
|
273
302
|
* the comma after it red.
|
|
274
303
|
*
|
|
275
304
|
* The `X` above already says the item failed, and the step that failed printed its own
|
|
276
|
-
* error.
|
|
277
|
-
|
|
278
|
-
|
|
305
|
+
* error.
|
|
306
|
+
*
|
|
307
|
+
* **Indented two columns**, so the log reads as belonging to the `X` line above it - level
|
|
308
|
+
* with the recap's own lines it ran together with them, a nested rman's recap most of all. */
|
|
309
|
+
if (item.steps?.length)
|
|
310
|
+
console.log(this.indentLog(this.stepRecap(item.steps)));
|
|
311
|
+
else if (item.log.length)
|
|
312
|
+
console.log(this.indentLog(item.log.join('\n')));
|
|
279
313
|
}
|
|
280
314
|
}
|
|
281
315
|
else if (this.live.enabled) {
|
|
@@ -339,6 +373,7 @@ class PanelItem {
|
|
|
339
373
|
startedAt;
|
|
340
374
|
finishedAt;
|
|
341
375
|
log = [];
|
|
376
|
+
steps = [];
|
|
342
377
|
_currentStep;
|
|
343
378
|
_currentCommand;
|
|
344
379
|
constructor(name, stepsTotal) {
|
|
@@ -54,11 +54,16 @@ export declare class PanelReporter extends PlainReporter {
|
|
|
54
54
|
* would only be escape codes counted as text there. */
|
|
55
55
|
readonly childEnv: undefined;
|
|
56
56
|
constructor(commandName: string, panel: ProgressPanel);
|
|
57
|
-
/** The row names the step itself; there is no separate line to print
|
|
58
|
-
|
|
57
|
+
/** The row names the step itself; there is no separate line to print - but the recap needs to
|
|
58
|
+
* know which lines were whose, so a step is opened here. */
|
|
59
|
+
protected started(event: Extract<LogEvent, {
|
|
60
|
+
event: 'start';
|
|
61
|
+
}>, origin: ReportOrigin): void;
|
|
59
62
|
protected output(event: Extract<LogEvent, {
|
|
60
63
|
event: 'output';
|
|
61
64
|
}>, origin: ReportOrigin): void;
|
|
62
|
-
/** The row shows the step's outcome; the recap prints the package's. */
|
|
63
|
-
protected ended(
|
|
65
|
+
/** The row shows the step's outcome; the recap prints the package's - and reads it from here. */
|
|
66
|
+
protected ended(event: Extract<LogEvent, {
|
|
67
|
+
event: 'end';
|
|
68
|
+
}>, origin: ReportOrigin): void;
|
|
64
69
|
}
|
|
@@ -124,12 +124,20 @@ export class PanelReporter extends PlainReporter {
|
|
|
124
124
|
constructor(commandName, panel) {
|
|
125
125
|
super(commandName, panel);
|
|
126
126
|
}
|
|
127
|
-
/** The row names the step itself; there is no separate line to print
|
|
128
|
-
|
|
127
|
+
/** The row names the step itself; there is no separate line to print - but the recap needs to
|
|
128
|
+
* know which lines were whose, so a step is opened here. */
|
|
129
|
+
started(event, origin) {
|
|
130
|
+
origin.item.steps?.push({ label: event.command ? `${event.step}: ${event.command}` : event.step, lines: [] });
|
|
131
|
+
}
|
|
129
132
|
output(event, origin) {
|
|
130
133
|
origin.item.log.push(event.line);
|
|
134
|
+
origin.item.steps?.at(-1)?.lines.push(event.line);
|
|
131
135
|
origin.item.lastLine = event.line;
|
|
132
136
|
}
|
|
133
|
-
/** The row shows the step's outcome; the recap prints the package's. */
|
|
134
|
-
ended() {
|
|
137
|
+
/** The row shows the step's outcome; the recap prints the package's - and reads it from here. */
|
|
138
|
+
ended(event, origin) {
|
|
139
|
+
const step = origin.item.steps?.at(-1);
|
|
140
|
+
if (step)
|
|
141
|
+
step.status = event.status;
|
|
142
|
+
}
|
|
135
143
|
}
|
|
@@ -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;
|