rman 2.10.1 → 2.11.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/builtins/platforms/node/services/ci.service.d.ts +1 -1
- package/builtins/platforms/node/services/ci.service.js +1 -1
- package/builtins/platforms/node/services/clean.service.d.ts +1 -1
- package/builtins/platforms/node/services/clean.service.js +1 -1
- package/cli.js +81 -23
- package/commands/changelog.command.js +1 -1
- package/commands/version.command.js +42 -29
- package/constants.js +1 -1
- package/core/application.d.ts +8 -1
- package/core/application.js +7 -1
- package/core/classes/log-sink.d.ts +99 -0
- package/core/classes/log-sink.js +101 -0
- package/{utils → core/classes}/logger.d.ts +1 -1
- package/core/classes/run-reporters.d.ts +64 -0
- package/core/classes/run-reporters.js +135 -0
- package/core/classes/service.d.ts +1 -1
- package/core/command-builder.js +3 -0
- package/core/interfaces/custom-command.d.ts +5 -1
- package/core/interfaces/run-step.d.ts +5 -5
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/package.json +1 -1
- package/services/exec.service.d.ts +1 -1
- package/services/exec.service.js +1 -1
- package/services/run.service.d.ts +22 -3
- package/services/run.service.js +190 -125
- package/services/version-plan.service.js +3 -3
- package/utils/progress-panel.d.ts +6 -0
- package/utils/progress-panel.js +22 -7
- package/utils/run-bin.d.ts +10 -2
- package/utils/run-bin.js +57 -11
- package/utils/version-group.d.ts +9 -0
- package/utils/version-group.js +11 -0
- /package/{utils → core/classes}/logger.js +0 -0
package/services/run.service.js
CHANGED
|
@@ -4,9 +4,10 @@ import { inspect } from 'node:util';
|
|
|
4
4
|
import colors from 'ansi-colors';
|
|
5
5
|
import { tokenize } from 'fast-tokenizer';
|
|
6
6
|
import { Task } from 'power-tasks';
|
|
7
|
+
import { LOG_LEVELS, Logger, resolveRootLogLevel } from '../core/classes/logger.js';
|
|
8
|
+
import { PanelReporter, PlainReporter } from '../core/classes/run-reporters.js';
|
|
7
9
|
import { Service } from '../core/classes/service.js';
|
|
8
10
|
import { exec } from '../utils/exec.js';
|
|
9
|
-
import { LOG_LEVELS, Logger, resolveRootLogLevel } from '../utils/logger.js';
|
|
10
11
|
import { filterPackages } from '../utils/package-filter.js';
|
|
11
12
|
import { ProgressPanel } from '../utils/progress-panel.js';
|
|
12
13
|
import { runBin } from '../utils/run-bin.js';
|
|
@@ -44,7 +45,7 @@ export class RunService extends Service {
|
|
|
44
45
|
}
|
|
45
46
|
const concurrency = resolveConcurrency(options.parallel, resolveNumber(undefined, repository.rootPackage, script, 'concurrency', os.cpus().length));
|
|
46
47
|
const progress = resolveBool(options.progress, repository.rootPackage, script, 'progress', true);
|
|
47
|
-
const panel = new ProgressPanel(`RUN ${script}`, !!process.stdout.isTTY && progress);
|
|
48
|
+
const panel = new ProgressPanel(`RUN ${script}`, !!process.stdout.isTTY && progress && !repository.app.logSink?.json);
|
|
48
49
|
/** Shared across all `if: changed[ = hash]` evaluations so the same reference is only `git`-queried once. */
|
|
49
50
|
const ifStatusCache = new Map();
|
|
50
51
|
/** Repo-wide bookend: root's own pre/post hooks run once each, exclusively, around every package
|
|
@@ -123,11 +124,11 @@ export class RunService extends Service {
|
|
|
123
124
|
* repository the root *is* the one package, and is covered. */
|
|
124
125
|
const definedSomewhere = repository.getPackages().some(pkg => getScriptSteps(pkg, script).length > 0);
|
|
125
126
|
if (definedSomewhere) {
|
|
126
|
-
|
|
127
|
+
this.say('info', `Nothing to run - every package was filtered out of "${script}".`);
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
130
|
const message = `No package defines a "${script}" script.`;
|
|
130
|
-
|
|
131
|
+
this.say('error', message);
|
|
131
132
|
const err = new Error(message);
|
|
132
133
|
err.logged = true;
|
|
133
134
|
throw err;
|
|
@@ -147,6 +148,16 @@ export class RunService extends Service {
|
|
|
147
148
|
*/
|
|
148
149
|
async schedule(options) {
|
|
149
150
|
const { panel, concurrency, commandName } = options;
|
|
151
|
+
/** Every event of the run is produced once, here, and rendered by each reporter - the screen
|
|
152
|
+
* (panel or plain lines, or neither under `--json`) and the log. See `Reporter`. */
|
|
153
|
+
const reporters = this.reportersFor(commandName, panel);
|
|
154
|
+
const emit = (event, origin) => {
|
|
155
|
+
for (const reporter of reporters)
|
|
156
|
+
reporter.report(event, origin);
|
|
157
|
+
};
|
|
158
|
+
/** What a child is started with so the reporters can render it - colour where one prints it. */
|
|
159
|
+
const childEnv = Object.assign({}, ...reporters.map(r => r.childEnv ?? {}));
|
|
160
|
+
const runStarted = Date.now();
|
|
150
161
|
/** Set once the aggregate Task exists, so a package's own failure can trigger a manual
|
|
151
162
|
* abort using *its own* resolved bail setting (see `runSteps` below) - power-tasks' own
|
|
152
163
|
* `bail` is a single blanket policy for the whole batch, it can't vary per package. */
|
|
@@ -184,50 +195,60 @@ export class RunService extends Service {
|
|
|
184
195
|
ctx.currentCommand = command ?? stepLabel;
|
|
185
196
|
};
|
|
186
197
|
ctx.stepIndex = i;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
const stepStart = Date.now();
|
|
199
|
+
const origin = { item: ctx, logLevel: pkgLogLevel, label: step.label };
|
|
200
|
+
/** Every step is bracketed - `start` before, `end` after whatever happened - and every line
|
|
201
|
+
* it produces is an `output` event, whichever reporters render them. */
|
|
202
|
+
emit({ event: 'start', package: pkgLabel, step: step.name, command: stepLabel }, origin);
|
|
203
|
+
const onLine = (line, stream = 'stdout', level) => emit({ event: 'output', package: pkgLabel, stream, line, ...(level && { level }) }, origin);
|
|
204
|
+
let stepError;
|
|
205
|
+
try {
|
|
206
|
+
/**
|
|
207
|
+
* **Piped, never inherited - the child never sees a terminal**, whether or not a panel is
|
|
208
|
+
* drawing. Every line comes back as an event and the reporters print or store it. A child
|
|
209
|
+
* that finds a TTY draws its own live output, and a build is mostly other CLIs: the
|
|
210
|
+
* shared preset's `run.build` is `rman check`, `rman lint`, `rman clean` and `tsc`.
|
|
211
|
+
* Reported as `rman build --no-progress` printing progress and losing its logs: a nested
|
|
212
|
+
* `rman check` drew its own panel inside a run asked not to draw one.
|
|
213
|
+
*
|
|
214
|
+
* **No TTY is the convention every well-behaved CLI already honours**, which is why it
|
|
215
|
+
* beats telling children about `--no-progress`: an environment variable would reach
|
|
216
|
+
* nested rman and nothing else. The cost, stated: a child cannot prompt. Nothing in a
|
|
217
|
+
* run step should.
|
|
218
|
+
*/
|
|
192
219
|
if (step.run)
|
|
193
220
|
await runFunctionStep(step.run, pkg, cwd, onLine, onCommand);
|
|
194
|
-
else
|
|
195
|
-
await exec(step.command, {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
let stepError;
|
|
203
|
-
try {
|
|
204
|
-
/** No capture with the panel off: the step owns the terminal, exactly as a shell
|
|
205
|
-
* step's `stdio: 'inherit'` does. */
|
|
206
|
-
if (step.run)
|
|
207
|
-
await runFunctionStep(step.run, pkg, cwd, undefined, onCommand);
|
|
208
|
-
else
|
|
209
|
-
await exec(step.command, { cwd, stdio: 'inherit', app: pkg.repository.app });
|
|
210
|
-
}
|
|
211
|
-
catch (e) {
|
|
212
|
-
/**
|
|
213
|
-
* **Normalized to an `Error`, because a *falsy* throw was indistinguishable from no
|
|
214
|
-
* failure at all.** This was `let stepError: any` with `if (stepError) throw
|
|
215
|
-
* stepError` below, so a step doing `throw undefined` - legal JavaScript, and what a
|
|
216
|
-
* rejected promise carrying nothing gives you - left `stepError` falsy: the step
|
|
217
|
-
* line printed **success**, nothing was rethrown, and the run exited 0. Found by the
|
|
218
|
-
* spec written for the message-reporting fix above, which is the only reason it is
|
|
219
|
-
* not still there. A step that fails while reporting success is the one outcome this
|
|
220
|
-
* slot exists to rule out.
|
|
221
|
-
*
|
|
222
|
-
* Only the panel-off path had it: with the panel on there is no local catch, and the
|
|
223
|
-
* outer one runs whatever was thrown.
|
|
224
|
-
*/
|
|
225
|
-
stepError = e instanceof Error ? e : new Error(messageOf(e));
|
|
221
|
+
else {
|
|
222
|
+
await exec(step.command, {
|
|
223
|
+
cwd,
|
|
224
|
+
stdio: 'pipe',
|
|
225
|
+
onLine,
|
|
226
|
+
env: Object.keys(childEnv).length ? childEnv : undefined,
|
|
227
|
+
app: pkg.repository.app,
|
|
228
|
+
});
|
|
226
229
|
}
|
|
227
|
-
printLegacyStepLine(commandName, pkgLabel, step, Date.now() - stepStart, pkgLogLevel, stepError);
|
|
228
|
-
if (stepError)
|
|
229
|
-
throw stepError;
|
|
230
230
|
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
/**
|
|
233
|
+
* **Normalized to an `Error`, because a *falsy* throw was indistinguishable from no
|
|
234
|
+
* failure at all.** This was `let stepError: any` with `if (stepError) throw
|
|
235
|
+
* stepError` below, so a step doing `throw undefined` - legal JavaScript, and what a
|
|
236
|
+
* rejected promise carrying nothing gives you - left `stepError` falsy: the step
|
|
237
|
+
* line printed **success**, nothing was rethrown, and the run exited 0. A step that
|
|
238
|
+
* fails while reporting success is the one outcome this slot exists to rule out.
|
|
239
|
+
*/
|
|
240
|
+
stepError = e instanceof Error ? e : new Error(messageOf(e));
|
|
241
|
+
}
|
|
242
|
+
emit({
|
|
243
|
+
event: 'end',
|
|
244
|
+
package: pkgLabel,
|
|
245
|
+
step: step.name,
|
|
246
|
+
status: stepError ? 'failed' : 'success',
|
|
247
|
+
ms: Date.now() - stepStart,
|
|
248
|
+
error: stepError?.message,
|
|
249
|
+
}, origin);
|
|
250
|
+
if (stepError)
|
|
251
|
+
throw stepError;
|
|
231
252
|
}
|
|
232
253
|
/** Another part is coming, so the package is not done - and it is about to sit at the
|
|
233
254
|
* barrier, which the row says out loud rather than leaving the last step's command on
|
|
@@ -272,16 +293,17 @@ export class RunService extends Service {
|
|
|
272
293
|
/**
|
|
273
294
|
* **Where this package starts waiting for its dependencies**, or `-1` for not at all.
|
|
274
295
|
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* wait is before the first step - where it has always been.
|
|
296
|
+
* The first step marked `true`. With none, the first step that says nothing - an unmarked
|
|
297
|
+
* step takes `run.<script>.topo`, which got here only by being on. Only a script whose every
|
|
298
|
+
* step says `false` does not wait.
|
|
279
299
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
300
|
+
* A step marked `false` frees itself and nothing else. It used to free the whole script: any
|
|
301
|
+
* mention of `topo` was read as the complete statement, so one `after: { topo: false }` from
|
|
302
|
+
* a shared preset left every unmarked step beside it - a repository's own `before`, a
|
|
303
|
+
* package's own `build` script - running with no wait at all. Measured on opra, where
|
|
304
|
+
* `@opra/angular`'s `ng build` started while `@opra/client` was still cleaning.
|
|
283
305
|
*/
|
|
284
|
-
const barrier = !plan.topo ? -1 :
|
|
306
|
+
const barrier = !plan.topo ? -1 : this.barrierOf(steps);
|
|
285
307
|
/**
|
|
286
308
|
* **Split only when the wait is in the middle of the package and there is something to wait
|
|
287
309
|
* for.** A barrier at step 0 is one task, as it always was; a package with no in-run
|
|
@@ -320,6 +342,22 @@ export class RunService extends Service {
|
|
|
320
342
|
}
|
|
321
343
|
panel.detail = this.repository.name;
|
|
322
344
|
panel.start(this.repository.app.statusRegion);
|
|
345
|
+
/**
|
|
346
|
+
* **With no panel, the status line is silenced for the run as well** - not handed over, silenced:
|
|
347
|
+
* the children's lines are printed straight to the terminal (see the panel-off path in
|
|
348
|
+
* `runSteps`), and a spinner redrawing in place moves the cursor up over whatever was printed
|
|
349
|
+
* since. This is the case `--no-progress` does not cover, since the CLI never draws the line under
|
|
350
|
+
* that flag: a panel turned off by `.rmanrc run.<script>.progress: false`, or by stdout being
|
|
351
|
+
* redirected while stderr is still a terminal.
|
|
352
|
+
*
|
|
353
|
+
* It used to be left drawing on the grounds that it was "the one thing such a run still shows".
|
|
354
|
+
* That held while the children inherited the terminal and nothing was printed past it; once the
|
|
355
|
+
* run prints their lines itself, the two cannot share the screen.
|
|
356
|
+
*/
|
|
357
|
+
const statusRegion = this.repository.app.statusRegion;
|
|
358
|
+
const muted = !panel.enabled && !!statusRegion?.live;
|
|
359
|
+
if (muted)
|
|
360
|
+
statusRegion.suspend({ passThrough: text => process.stderr.write(text) });
|
|
323
361
|
try {
|
|
324
362
|
/** bail:false here - each package's own resolved bail setting decides whether to call
|
|
325
363
|
* `rootTask.abort()` itself (see `runSteps`), since power-tasks' own `bail` can't vary per package. */
|
|
@@ -338,13 +376,19 @@ export class RunService extends Service {
|
|
|
338
376
|
* packages went on to succeed. */
|
|
339
377
|
await Promise.allSettled(children.map(child => child.toPromise()));
|
|
340
378
|
panel.stop();
|
|
379
|
+
if (muted)
|
|
380
|
+
statusRegion.resume();
|
|
341
381
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
382
|
+
/** Counted once, then reported - a screen reporter prints the recap from the same tally, so the
|
|
383
|
+
* prose and the `summary` event cannot disagree. */
|
|
384
|
+
const summary = panel.tally();
|
|
385
|
+
emit({
|
|
386
|
+
event: 'summary',
|
|
387
|
+
succeeded: summary.successCount,
|
|
388
|
+
failed: summary.failedCount,
|
|
389
|
+
skipped: summary.skippedCount,
|
|
390
|
+
ms: Date.now() - runStarted,
|
|
391
|
+
});
|
|
348
392
|
/** The tallies, never `rootTask.toPromise()`'s own outcome: with a sibling still in flight at
|
|
349
393
|
* the moment one package failed, that promise *resolves*, and this command used to exit 0 on a
|
|
350
394
|
* run it had just reported as failed - non-deterministically, since it came down to which
|
|
@@ -380,7 +424,7 @@ export class RunService extends Service {
|
|
|
380
424
|
const label = options.label ?? 'each';
|
|
381
425
|
const logLevel = options.logLevel ?? resolveRootLogLevel(repository);
|
|
382
426
|
const concurrency = resolveConcurrency(options.parallel, os.cpus().length);
|
|
383
|
-
const panel = new ProgressPanel(label.toUpperCase(), !!process.stdout.isTTY && options.progress !== false);
|
|
427
|
+
const panel = new ProgressPanel(label.toUpperCase(), !!process.stdout.isTTY && options.progress !== false && !repository.app.logSink?.json);
|
|
384
428
|
/** Nothing to do is not a failure, and the caller is the one that can tell the two endings
|
|
385
429
|
* apart - it knows why its list is empty. `check` says so itself and returns. */
|
|
386
430
|
if (!packages.length)
|
|
@@ -455,6 +499,43 @@ export class RunService extends Service {
|
|
|
455
499
|
throw failure;
|
|
456
500
|
return results;
|
|
457
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* The index of the step a package waits for its dependencies before, or `-1` when every step is
|
|
504
|
+
* marked `topo: false`: the first step marked `true`, else the first unmarked one.
|
|
505
|
+
*/
|
|
506
|
+
barrierOf(steps) {
|
|
507
|
+
/* No steps still waits, as it always did: dependents wait on this package's task, so one that
|
|
508
|
+
* skipped the wait would let them past its own dependencies. */
|
|
509
|
+
if (!steps.length)
|
|
510
|
+
return 0;
|
|
511
|
+
const marked = steps.findIndex(s => s.topo === true);
|
|
512
|
+
if (marked >= 0)
|
|
513
|
+
return marked;
|
|
514
|
+
return steps.findIndex(s => s.topo === undefined);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* A line of rman's own about the run - "nothing to run", "no package defines it" - on the console
|
|
518
|
+
* as prose, or as a `message` event under `--json`, where stdout belongs to the events. Also in the
|
|
519
|
+
* log file when there is one.
|
|
520
|
+
*/
|
|
521
|
+
say(level, message) {
|
|
522
|
+
for (const reporter of this.reportersFor('run'))
|
|
523
|
+
reporter.report({ event: 'message', level, message });
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Who renders a run: the panel when it draws, plain lines when it does not, neither under `--json`
|
|
527
|
+
* (stdout belongs to the events) - plus the run's log, when the CLI asked for one.
|
|
528
|
+
*/
|
|
529
|
+
reportersFor(commandName, panel) {
|
|
530
|
+
const sink = this.repository.app.logSink;
|
|
531
|
+
const reporters = [];
|
|
532
|
+
if (!sink?.json) {
|
|
533
|
+
reporters.push(panel?.enabled ? new PanelReporter(commandName, panel) : new PlainReporter(commandName, panel));
|
|
534
|
+
}
|
|
535
|
+
if (sink?.active)
|
|
536
|
+
reporters.push(sink);
|
|
537
|
+
return reporters;
|
|
538
|
+
}
|
|
458
539
|
}
|
|
459
540
|
(function (RunService) {
|
|
460
541
|
/**
|
|
@@ -601,15 +682,28 @@ export class RunService extends Service {
|
|
|
601
682
|
return pristine[method](...args);
|
|
602
683
|
/** Split, because one `console.log` may carry several lines and the panel's log is a list
|
|
603
684
|
* of them - a multi-line entry would render as one unreadable row. */
|
|
685
|
+
const stream = method === 'error' || method === 'warn' ? 'stderr' : 'stdout';
|
|
604
686
|
for (const line of format(args).split('\n'))
|
|
605
|
-
sink(line);
|
|
687
|
+
sink(line, stream);
|
|
606
688
|
};
|
|
607
689
|
}
|
|
608
690
|
pristineConsole = pristine;
|
|
609
691
|
}
|
|
610
692
|
/** `Promise.resolve`, because a step may be a plain function - `RunStepFn` returns
|
|
611
|
-
* `void | Promise<void>` and only the promise form can be awaited.
|
|
612
|
-
|
|
693
|
+
* `void | Promise<void>` and only the promise form can be awaited.
|
|
694
|
+
*
|
|
695
|
+
* **A synchronous throw is caught into a rejection**, or it leaves before `finally` is attached
|
|
696
|
+
* and the depth never comes back down - every later step then finds the console "already
|
|
697
|
+
* patched" and captures nothing. Hidden while only the panel path captured; it surfaced the
|
|
698
|
+
* day a plain run did, as two specs in another describe block failing only after this one. */
|
|
699
|
+
let result;
|
|
700
|
+
try {
|
|
701
|
+
result = consoleSink.run(onLine, fn);
|
|
702
|
+
}
|
|
703
|
+
catch (e) {
|
|
704
|
+
result = Promise.reject(e);
|
|
705
|
+
}
|
|
706
|
+
return Promise.resolve(result).finally(() => {
|
|
613
707
|
if (--consoleDepth > 0 || !pristineConsole)
|
|
614
708
|
return;
|
|
615
709
|
for (const method of CAPTURED_CONSOLE)
|
|
@@ -724,36 +818,6 @@ export class RunService extends Service {
|
|
|
724
818
|
}
|
|
725
819
|
RunService.evaluateIf = evaluateIf;
|
|
726
820
|
})(RunService || (RunService = {}));
|
|
727
|
-
/**
|
|
728
|
-
* Classic one-line-per-step log ("info build pkg ┆ step success ┆ command (123 ms)"),
|
|
729
|
-
* matching rman's original npmlog-based output. Used when the live panel is off.
|
|
730
|
-
*
|
|
731
|
-
* Respects `logLevel`: 'verbose' also prints an "executing" line before the step runs
|
|
732
|
-
* (rman's old behavior, hidden by default); 'error' suppresses success lines; 'silent'
|
|
733
|
-
* suppresses everything but a real failure.
|
|
734
|
-
*/
|
|
735
|
-
function printLegacyExecutingLine(commandName, pkgLabel, step, level) {
|
|
736
|
-
if (LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf('verbose'))
|
|
737
|
-
return;
|
|
738
|
-
const sep = colors.gray('┆');
|
|
739
|
-
console.log(colors.magenta('verbose'), commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), colors.cyanBright.bold('executing'), ...describing(step));
|
|
740
|
-
}
|
|
741
|
-
function printLegacyStepLine(commandName, pkgLabel, step, durationMs, level, error) {
|
|
742
|
-
if (!error && LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf('info'))
|
|
743
|
-
return;
|
|
744
|
-
if (error && level === 'silent')
|
|
745
|
-
return;
|
|
746
|
-
const sep = colors.gray('┆');
|
|
747
|
-
const levelLabel = error ? colors.red('error') : colors.green('info');
|
|
748
|
-
const status = error ? colors.red.bold('failed') : colors.green.bold('success');
|
|
749
|
-
console.log(levelLabel, commandName, colors.cyan(pkgLabel), sep, colors.cyanBright.bold(step.name), status, ...describing(step), colors.yellow(` (${durationMs} ms)`));
|
|
750
|
-
}
|
|
751
|
-
/** The `┆ <what it runs>` tail of a legacy log line, **or nothing at all** when the step has no
|
|
752
|
-
* label of its own - an anonymous function handed to `forEachPackage`, where the slot name already
|
|
753
|
-
* said everything there is to say and a separator with nothing after it is just noise. */
|
|
754
|
-
function describing(step) {
|
|
755
|
-
return step.label ? [colors.gray('┆'), step.label] : [];
|
|
756
|
-
}
|
|
757
821
|
function describeValue(value) {
|
|
758
822
|
if (Array.isArray(value))
|
|
759
823
|
return 'a nested array';
|
|
@@ -814,50 +878,30 @@ function assertStepObject(item, where, allowTopo) {
|
|
|
814
878
|
/** What a step object may hold - the step itself, and the marker beside it. */
|
|
815
879
|
const STEP_OBJECT_KEYS = ['command', 'topo'];
|
|
816
880
|
/**
|
|
817
|
-
* Runs a function step
|
|
818
|
-
*
|
|
819
|
-
* **`console` is redirected while it runs, but only when the panel is on** - and that is the same
|
|
820
|
-
* split a shell step already makes. `exec` hands the panel its output through `stdio: 'pipe'` and
|
|
821
|
-
* `onLine`, so a function writing straight to the real stdout would print *over* the panel it is
|
|
822
|
-
* being rendered inside. With the panel off, `exec` uses `stdio: 'inherit'` and the step owns the
|
|
823
|
-
* terminal; a function gets the same, untouched.
|
|
881
|
+
* Runs a function step, with its `console` captured into `onLine` - the step's own lines, routed to
|
|
882
|
+
* whichever reporters the run has, beside those of the children it spawns.
|
|
824
883
|
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
884
|
+
* **Always captured, panel or not.** With no panel a function's `console.log` used to go straight to
|
|
885
|
+
* the terminal, so it was the one kind of step output a plain run could not lead with its package -
|
|
886
|
+
* and it reached `--json` stdout and the log file not at all.
|
|
827
887
|
*
|
|
828
888
|
* **Its failure message is reported here, because nothing else does it.** A shell step's reason
|
|
829
|
-
* reaches the user on its own -
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
* carefully worded message about a missing `tsconfig.json`: the run printed
|
|
889
|
+
* reaches the user on its own - its output streams and `exec` names the command and its exit code.
|
|
890
|
+
* A function step fails by throwing, and the throw goes to the catch that marks the package failed
|
|
891
|
+
* and rethrows an error the CLI treats as already-logged. Measured on a real config whose build step
|
|
892
|
+
* threw a carefully worded message about a missing `tsconfig.json`: the run printed
|
|
834
893
|
* `error build pkg-forgot ┆ exec failed ┆ buildWithTsc` and exited 1, and the message appeared
|
|
835
|
-
* nowhere at all
|
|
836
|
-
*
|
|
837
|
-
* Written where a shell step's output goes, so it needs no second channel: through `onLine` with
|
|
838
|
-
* the panel on (the step's own log, which is what the panel shows for a failed item), and to
|
|
839
|
-
* stderr with it off, ahead of the `failed` line - the order a shell step already produces.
|
|
894
|
+
* nowhere at all. Written to `onLine` on stderr, ahead of the step's `end` - the order a shell step
|
|
895
|
+
* already produces.
|
|
840
896
|
*/
|
|
841
897
|
async function runFunctionStep(run, pkg, cwd, onLine, onCommand) {
|
|
842
898
|
const context = RunService.createStepContext(pkg, cwd, onCommand, onLine);
|
|
843
|
-
if (!onLine) {
|
|
844
|
-
try {
|
|
845
|
-
await run(context);
|
|
846
|
-
}
|
|
847
|
-
catch (e) {
|
|
848
|
-
console.error(colors.red(messageOf(e)));
|
|
849
|
-
throw e;
|
|
850
|
-
}
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
899
|
try {
|
|
854
900
|
await RunService.withCapturedConsole(onLine, () => run(context));
|
|
855
901
|
}
|
|
856
902
|
catch (e) {
|
|
857
|
-
/** Straight to `onLine`, so the message lands in this step's log rather than over the panel it
|
|
858
|
-
* is drawn inside. */
|
|
859
903
|
for (const line of messageOf(e).split('\n'))
|
|
860
|
-
onLine(line);
|
|
904
|
+
onLine(colors.red(line), 'stderr', 'error');
|
|
861
905
|
throw e;
|
|
862
906
|
}
|
|
863
907
|
}
|
|
@@ -959,9 +1003,30 @@ function slotValues(slot, own, configured, override) {
|
|
|
959
1003
|
if (override)
|
|
960
1004
|
return configured.length ? configured : own;
|
|
961
1005
|
if (slot === 'exec')
|
|
962
|
-
return own.length ? own : configured;
|
|
1006
|
+
return own.length ? withTopoOf(configured, own) : configured;
|
|
963
1007
|
return slot === 'before' ? [...configured, ...own] : [...own, ...configured];
|
|
964
1008
|
}
|
|
1009
|
+
/**
|
|
1010
|
+
* The package's own `exec` steps, carrying the `topo` mark of the configured `exec` they replace.
|
|
1011
|
+
*/
|
|
1012
|
+
/* **The command is the package's; the ordering is the config's.** A `package.json` script cannot
|
|
1013
|
+
* say `topo` at all, so replacing the configured `exec: { topo: true, command }` with it dropped the
|
|
1014
|
+
* mark - and a shared preset marks `exec` precisely so the build waits for its dependencies.
|
|
1015
|
+
* Measured on opra: `@opra/angular`'s own `"build": "ng build"` started while `@opra/client`, a
|
|
1016
|
+
* peer it builds against, was still cleaning. Every package with its own build script had the same
|
|
1017
|
+
* hole; angular is where a missing dependency output fails loudly. A `true` anywhere in the
|
|
1018
|
+
* replaced slot wins, since a missed wait is the failure that does not look like one. */
|
|
1019
|
+
function withTopoOf(configured, own) {
|
|
1020
|
+
const marks = configured.map(v => (typeof v === 'object' ? v.topo : undefined)).filter(t => t !== undefined);
|
|
1021
|
+
if (!marks.length)
|
|
1022
|
+
return own;
|
|
1023
|
+
const topo = marks.includes(true);
|
|
1024
|
+
return own.map(v => {
|
|
1025
|
+
if (typeof v === 'object')
|
|
1026
|
+
return v.topo === undefined ? { ...v, topo } : v;
|
|
1027
|
+
return { command: v, topo };
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
965
1030
|
/**
|
|
966
1031
|
* What this package's own technology says it declares for `script`.
|
|
967
1032
|
*
|
|
@@ -4,7 +4,7 @@ import { assertOneScheme, semverScheme, VersionScheme } from '../core/classes/ve
|
|
|
4
4
|
import { GitHelper } from '../utils/git.js';
|
|
5
5
|
import { filterPackages } from '../utils/package-filter.js';
|
|
6
6
|
import { findLastReleaseVersion, formatCalendarVersion, usesCalendarVersion } from '../utils/release-version.js';
|
|
7
|
-
import { groupKeyOf } from '../utils/version-group.js';
|
|
7
|
+
import { groupKeyOf, ROOT_GROUP_KEY } from '../utils/version-group.js';
|
|
8
8
|
import { ConventionalCommitsService } from './conventional-commits.service.js';
|
|
9
9
|
/**
|
|
10
10
|
* Question A, computed and nothing else: **which packages have changed since their last release,
|
|
@@ -479,7 +479,7 @@ export class VersionPlanService {
|
|
|
479
479
|
const root = repository.rootPackage;
|
|
480
480
|
const anyBumped = memberEntries.some(e => e.status === 'bump');
|
|
481
481
|
if (!anyBumped) {
|
|
482
|
-
return { package: root, groupKey:
|
|
482
|
+
return { package: root, groupKey: ROOT_GROUP_KEY, group: 'root', status: 'no-change', from: root.version };
|
|
483
483
|
}
|
|
484
484
|
const calendar = usesCalendarVersion({
|
|
485
485
|
groupCount: context.groupCount,
|
|
@@ -492,7 +492,7 @@ export class VersionPlanService {
|
|
|
492
492
|
: (root.versionScheme.highestVersion(finalVersions) ?? root.version);
|
|
493
493
|
return {
|
|
494
494
|
package: root,
|
|
495
|
-
groupKey:
|
|
495
|
+
groupKey: ROOT_GROUP_KEY,
|
|
496
496
|
group: 'root',
|
|
497
497
|
status: 'bump',
|
|
498
498
|
from: root.version,
|
|
@@ -90,5 +90,11 @@ export declare class ProgressPanel implements TerminalRegion {
|
|
|
90
90
|
* per-item recap lines are only printed when the live panel was actually on - with it off, the
|
|
91
91
|
* caller's own plain logging already showed each item's outcome as it happened.
|
|
92
92
|
*/
|
|
93
|
+
/**
|
|
94
|
+
* The tally `printSummary` reports, without printing anything - for a run whose output is not
|
|
95
|
+
* prose (`--json`). Settles every still-pending item as skipped, exactly as `printSummary` does, so
|
|
96
|
+
* the two can never count differently.
|
|
97
|
+
*/
|
|
98
|
+
tally(): ProgressSummary;
|
|
93
99
|
printSummary(): ProgressSummary;
|
|
94
100
|
}
|
package/utils/progress-panel.js
CHANGED
|
@@ -226,21 +226,38 @@ export class ProgressPanel {
|
|
|
226
226
|
* per-item recap lines are only printed when the live panel was actually on - with it off, the
|
|
227
227
|
* caller's own plain logging already showed each item's outcome as it happened.
|
|
228
228
|
*/
|
|
229
|
-
|
|
229
|
+
/**
|
|
230
|
+
* The tally `printSummary` reports, without printing anything - for a run whose output is not
|
|
231
|
+
* prose (`--json`). Settles every still-pending item as skipped, exactly as `printSummary` does, so
|
|
232
|
+
* the two can never count differently.
|
|
233
|
+
*/
|
|
234
|
+
tally() {
|
|
230
235
|
let successCount = 0;
|
|
231
236
|
let failedCount = 0;
|
|
232
237
|
let skippedCount = 0;
|
|
233
238
|
for (const item of this.items.values()) {
|
|
234
239
|
if (item.status === 'pending')
|
|
235
240
|
item.status = 'skipped';
|
|
241
|
+
if (item.status === 'success')
|
|
242
|
+
successCount++;
|
|
243
|
+
else if (item.status === 'failed')
|
|
244
|
+
failedCount++;
|
|
245
|
+
else
|
|
246
|
+
skippedCount++;
|
|
247
|
+
}
|
|
248
|
+
return { successCount, failedCount, skippedCount };
|
|
249
|
+
}
|
|
250
|
+
printSummary() {
|
|
251
|
+
/** Counted by `tally`, which also settles the pending items - so a `--json` run, which asks
|
|
252
|
+
* `tally` alone, can never count differently from one that prints. */
|
|
253
|
+
const { successCount, failedCount, skippedCount } = this.tally();
|
|
254
|
+
for (const item of this.items.values()) {
|
|
236
255
|
const duration = item.startedAt && item.finishedAt ? colors.gray(formatDuration(item.finishedAt - item.startedAt)) : '';
|
|
237
256
|
if (item.status === 'success') {
|
|
238
|
-
successCount++;
|
|
239
257
|
if (this.live.enabled)
|
|
240
258
|
console.log(colors.green('✓'), item.name, duration);
|
|
241
259
|
}
|
|
242
260
|
else if (item.status === 'failed') {
|
|
243
|
-
failedCount++;
|
|
244
261
|
if (this.live.enabled) {
|
|
245
262
|
console.log(colors.red.bold('X'), item.name, duration);
|
|
246
263
|
/* **The replayed log is printed as it was captured, never painted.** It holds the output
|
|
@@ -261,10 +278,8 @@ export class ProgressPanel {
|
|
|
261
278
|
console.log(item.log.join('\n'));
|
|
262
279
|
}
|
|
263
280
|
}
|
|
264
|
-
else {
|
|
265
|
-
|
|
266
|
-
if (this.live.enabled)
|
|
267
|
-
console.log(colors.gray('○'), item.name, colors.gray('skipped'));
|
|
281
|
+
else if (this.live.enabled) {
|
|
282
|
+
console.log(colors.gray('○'), item.name, colors.gray('skipped'));
|
|
268
283
|
}
|
|
269
284
|
}
|
|
270
285
|
const totalElapsed = formatDuration(Date.now() - this.startedAt);
|
package/utils/run-bin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RmanApplication } from '../core/application.js';
|
|
2
|
-
import { type LogLevel } from '
|
|
2
|
+
import { type LogLevel } from '../core/classes/logger.js';
|
|
3
3
|
export interface RunBinOptions {
|
|
4
4
|
/**
|
|
5
5
|
* The application whose technologies put a repository's locally installed binaries on PATH -
|
|
@@ -30,7 +30,8 @@ export interface RunBinOptions {
|
|
|
30
30
|
/**
|
|
31
31
|
* Called once per line of the child's output **instead of writing it to the terminal**.
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
/** The stream each line came from, so a caller printing them can keep stdout and stderr apart. */
|
|
34
|
+
onLine?: (line: string, stream: 'stdout' | 'stderr') => void;
|
|
34
35
|
env?: Record<string, string | undefined>;
|
|
35
36
|
}
|
|
36
37
|
export interface RunBinResult {
|
|
@@ -58,3 +59,10 @@ export interface RunBinResult {
|
|
|
58
59
|
* nothing is the outcome worth ruling out, and that is what silently discarding a code produces.
|
|
59
60
|
*/
|
|
60
61
|
export declare function runBin(bin: string, argv: string[], options?: RunBinOptions): Promise<RunBinResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Whether a child whose lines rman prints itself should be told to colour them: our own stdout is a
|
|
64
|
+
* terminal, and `NO_COLOR` is not set. The one rule for both kinds of step - `runBin`'s, above, and a
|
|
65
|
+
* shell step `exec` runs with its lines printed by `RunService` - so the two cannot disagree about
|
|
66
|
+
* when a piped child gets its colour back.
|
|
67
|
+
*/
|
|
68
|
+
export declare function colorsPrintedOutput(): boolean;
|