rman 2.7.0 → 2.9.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/node-manifest.provider.d.ts +10 -0
- package/builtins/platforms/node/node-manifest.provider.js +32 -5
- package/builtins/platforms/node/services/clean.service.js +104 -23
- package/cli.js +31 -13
- package/constants.js +1 -1
- package/core/classes/repository.d.ts +48 -0
- package/core/classes/repository.js +139 -8
- package/core/command-builder.d.ts +9 -1
- package/core/command-builder.js +24 -11
- package/core/config/config-paths.js +24 -0
- package/core/interfaces/custom-command.d.ts +39 -0
- package/core/interfaces/run-step.d.ts +50 -3
- package/index.d.ts +7 -1
- package/index.js +11 -0
- package/interfaces/rman-config.interface.d.ts +21 -3
- package/package.json +1 -1
- package/services/run.service.d.ts +131 -2
- package/services/run.service.js +462 -148
- package/services/version.service.d.ts +2 -0
- package/services/version.service.js +7 -2
- package/utils/progress-panel.js +98 -16
- package/utils/run-bin.d.ts +4 -0
- package/utils/run-bin.js +14 -2
- package/utils/run-options.d.ts +44 -0
- package/utils/run-options.js +55 -0
|
@@ -101,6 +101,8 @@ export declare namespace VersionService {
|
|
|
101
101
|
* never run. (The doc comment also still named `.script`/`.preScript`/`.postScript`, three keys
|
|
102
102
|
* that have been `before`/`exec`/`after` for a long time.)
|
|
103
103
|
*/
|
|
104
|
+
/** `topo` is refused by default here, which is the one way this differs from `RunService`'s: a
|
|
105
|
+
* version hook has no package ordering to join. */
|
|
104
106
|
function normalizeScriptValue(value: unknown, at: string): RunStepValue[];
|
|
105
107
|
/**
|
|
106
108
|
* Keeps a package's Dockerfile `org.opencontainers.image.version` label in step with the version
|
|
@@ -86,7 +86,10 @@ export class VersionService extends Service {
|
|
|
86
86
|
const hook = (slot) => RunService.runLifecycleSlot(pkg, VERSION_LIFECYCLE, slot, RunService.normalizeScriptValue(
|
|
87
87
|
/** `at`: the path is what tells a step function from a value one, and this is a fragment -
|
|
88
88
|
* without it a function here was called while the hook was being prepared. */
|
|
89
|
-
interpolator.interpolate({ config: pkg.config?.version?.[slot], scope, at: ['version', slot] }), `version.${slot}
|
|
89
|
+
interpolator.interpolate({ config: pkg.config?.version?.[slot], scope, at: ['version', slot] }), `version.${slot}`,
|
|
90
|
+
/** A version hook runs for one package around its own version write - there is no
|
|
91
|
+
* package graph here to wait on, so `topo` is refused rather than silently ignored. */
|
|
92
|
+
{ topo: false }));
|
|
90
93
|
await hook('before');
|
|
91
94
|
/** Through the manifest, not through a `package.json` field: where a version is written is
|
|
92
95
|
* the provider's business (see `Plugin`'s manifest members), and this is the one place rman changes
|
|
@@ -286,8 +289,10 @@ export class VersionService extends Service {
|
|
|
286
289
|
* never run. (The doc comment also still named `.script`/`.preScript`/`.postScript`, three keys
|
|
287
290
|
* that have been `before`/`exec`/`after` for a long time.)
|
|
288
291
|
*/
|
|
292
|
+
/** `topo` is refused by default here, which is the one way this differs from `RunService`'s: a
|
|
293
|
+
* version hook has no package ordering to join. */
|
|
289
294
|
function normalizeScriptValue(value, at) {
|
|
290
|
-
return RunService.normalizeScriptValue(value, at);
|
|
295
|
+
return RunService.normalizeScriptValue(value, at, { topo: false });
|
|
291
296
|
}
|
|
292
297
|
VersionService.normalizeScriptValue = normalizeScriptValue;
|
|
293
298
|
/**
|
package/utils/progress-panel.js
CHANGED
|
@@ -45,7 +45,7 @@ export class ProgressPanel {
|
|
|
45
45
|
/** Registers an item and returns it - callers mutate the returned object directly (`status`,
|
|
46
46
|
* `currentStep`, `lastLine`, ...) to drive the next render tick. */
|
|
47
47
|
addItem(name, stepsTotal) {
|
|
48
|
-
const item =
|
|
48
|
+
const item = new PanelItem(name, stepsTotal);
|
|
49
49
|
this.items.set(name, item);
|
|
50
50
|
return item;
|
|
51
51
|
}
|
|
@@ -134,28 +134,46 @@ export class ProgressPanel {
|
|
|
134
134
|
`${header} ${bar} ${colors.bold(`${done}/${total}`)} ${colors.cyan(`${running} running`)} ${failedText} ${colors.yellow(totalElapsed)}${detail}`,
|
|
135
135
|
];
|
|
136
136
|
const runningList = [...this.items.values()].filter(i => i.status === 'running');
|
|
137
|
-
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
137
|
+
/**
|
|
138
|
+
* **A package that failed stays on the list, under the ones still working.**
|
|
139
|
+
* It used to vanish the moment it failed, so on a long run the only sign that anything had gone
|
|
140
|
+
* wrong was a count in the header - and whatever it printed was not read until the recap, by
|
|
141
|
+
* which time the run had been going for another half-minute.
|
|
142
|
+
*
|
|
143
|
+
* **Below the running rows, and only with the space they leave.** Work in progress is what the
|
|
144
|
+
* panel is for; a repository that fails early would otherwise fill the block with corpses and
|
|
145
|
+
* push the live rows off the screen. One line each rather than two, so more of them fit - a
|
|
146
|
+
* failed row's value is that it is *named*, and its output is replayed in full at the end.
|
|
147
|
+
*/
|
|
148
|
+
const failedList = [...this.items.values()].filter(i => i.status === 'failed');
|
|
149
|
+
/** Leave room for the header, a safety margin, and a possible "N more" line - a block taller
|
|
150
|
+
* than the terminal breaks cursor-up math (the terminal scrolls instead of the cursor moving,
|
|
151
|
+
* so redraws land in the wrong place and pile up). */
|
|
140
152
|
const budget = Math.max(0, (process.stdout.rows || 24) - 3);
|
|
153
|
+
const width = process.stdout.columns || 80;
|
|
141
154
|
let used = 0;
|
|
142
|
-
let
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
let shownRunning = 0;
|
|
156
|
+
let shownFailed = 0;
|
|
157
|
+
/** The fixed-width half of a row, plus the command truncated into whatever is left. */
|
|
158
|
+
const describe = (item, elapsed) => {
|
|
145
159
|
const step = item.stepsTotal && item.stepsTotal > 1 && item.stepIndex != null
|
|
146
160
|
? `${item.currentStep} (${item.stepIndex + 1}/${item.stepsTotal})`
|
|
147
161
|
: item.currentStep || '';
|
|
148
|
-
/** Measured against the *plain* text: every piece
|
|
162
|
+
/** Measured against the *plain* text: every piece is wrapped in escape sequences, and
|
|
149
163
|
* `String.length` counts those, so budgeting on the rendered string wraps a row that fits. */
|
|
150
164
|
const fixed = ` ${item.name} ${step} ${elapsed} | `.length;
|
|
151
|
-
|
|
165
|
+
return { step, command: truncate(item.currentCommand ?? '', width - fixed - 2) };
|
|
166
|
+
};
|
|
167
|
+
for (const item of runningList) {
|
|
168
|
+
const elapsed = item.startedAt ? formatDuration(Date.now() - item.startedAt) : '';
|
|
169
|
+
const { step, command } = describe(item, elapsed);
|
|
152
170
|
/**
|
|
153
171
|
* **The command last, after a `|`, and that is not only layout.** It is the one field with no
|
|
154
172
|
* bound on its length - a `tsc -b` line carries a path, a `run` step carries whatever the
|
|
155
173
|
* author wrote - so between the step and the clock it pushed the elapsed time to a different
|
|
156
|
-
* column on every row, and off the end entirely once a command was long. Everything
|
|
157
|
-
* now reads down a straight edge and the variable part runs off to the right,
|
|
158
|
-
* cuts it.
|
|
174
|
+
* column on every row, and off the end entirely once a command was long. Everything
|
|
175
|
+
* fixed-width now reads down a straight edge and the variable part runs off to the right,
|
|
176
|
+
* where `truncate` cuts it.
|
|
159
177
|
*/
|
|
160
178
|
const group = [
|
|
161
179
|
`${spinner} ${colors.bold(item.name)} ${colors.gray(step)} ${colors.yellow(elapsed)}` +
|
|
@@ -167,11 +185,27 @@ export class ProgressPanel {
|
|
|
167
185
|
break;
|
|
168
186
|
lines.push(...group);
|
|
169
187
|
used += group.length;
|
|
170
|
-
|
|
188
|
+
shownRunning++;
|
|
189
|
+
}
|
|
190
|
+
for (const item of failedList) {
|
|
191
|
+
if (used + 1 > budget)
|
|
192
|
+
break;
|
|
193
|
+
const elapsed = item.startedAt && item.finishedAt ? formatDuration(item.finishedAt - item.startedAt) : '';
|
|
194
|
+
const { step, command } = describe(item, elapsed);
|
|
195
|
+
lines.push(`${colors.red.bold('✖')} ${colors.bold(item.name)} ${colors.gray(step)} ${colors.yellow(elapsed)}` +
|
|
196
|
+
(command ? ` ${colors.gray('|')} ${colors.red(command)}` : ''));
|
|
197
|
+
used++;
|
|
198
|
+
shownFailed++;
|
|
171
199
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
200
|
+
/** One line for both, or a run with rows cut from each would need two and the block is already
|
|
201
|
+
* budgeted to the row. */
|
|
202
|
+
const hidden = [];
|
|
203
|
+
if (runningList.length > shownRunning)
|
|
204
|
+
hidden.push(`${runningList.length - shownRunning} more running`);
|
|
205
|
+
if (failedList.length > shownFailed)
|
|
206
|
+
hidden.push(`${failedList.length - shownFailed} more failed`);
|
|
207
|
+
if (hidden.length)
|
|
208
|
+
lines.push(colors.gray(`… and ${hidden.join(', ')}`));
|
|
175
209
|
this.live.render(lines);
|
|
176
210
|
}
|
|
177
211
|
/** Stops the redraw loop and erases the panel, leaving the cursor where it started. */
|
|
@@ -267,3 +301,51 @@ function renderProgressBar(done, total, width = 24) {
|
|
|
267
301
|
const filled = Math.max(0, Math.min(width, cells));
|
|
268
302
|
return colors.green('█'.repeat(filled)) + colors.gray('░'.repeat(width - filled));
|
|
269
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* What `addItem` hands back - a `ProgressItem` whose `currentStep` and `currentCommand` clear
|
|
306
|
+
* `lastLine` when they change.
|
|
307
|
+
*/
|
|
308
|
+
/* **Accessors rather than a line at each call site**, because the call sites are four services and
|
|
309
|
+
* a command and the one that forgets is invisible: stale output under a new command does not look
|
|
310
|
+
* like a bug, it looks like output. Reported from `ci`, where the row read
|
|
311
|
+
* `install (2/2) | npm install` over `removed node_modules, package-lock.json` - the wipe's line,
|
|
312
|
+
* sitting under the install's command as though it belonged to it.
|
|
313
|
+
*
|
|
314
|
+
* **Cleared where it changes, not at render time.** The panel redraws every 100ms, so comparing
|
|
315
|
+
* there would race a line that arrived between the change and the next frame and throw away real
|
|
316
|
+
* output. This is synchronous with the assignment.
|
|
317
|
+
*/
|
|
318
|
+
class PanelItem {
|
|
319
|
+
name;
|
|
320
|
+
status = 'pending';
|
|
321
|
+
stepIndex;
|
|
322
|
+
stepsTotal;
|
|
323
|
+
lastLine;
|
|
324
|
+
startedAt;
|
|
325
|
+
finishedAt;
|
|
326
|
+
log = [];
|
|
327
|
+
_currentStep;
|
|
328
|
+
_currentCommand;
|
|
329
|
+
constructor(name, stepsTotal) {
|
|
330
|
+
this.name = name;
|
|
331
|
+
this.stepsTotal = stepsTotal;
|
|
332
|
+
}
|
|
333
|
+
get currentStep() {
|
|
334
|
+
return this._currentStep;
|
|
335
|
+
}
|
|
336
|
+
set currentStep(value) {
|
|
337
|
+
if (value === this._currentStep)
|
|
338
|
+
return;
|
|
339
|
+
this._currentStep = value;
|
|
340
|
+
this.lastLine = undefined;
|
|
341
|
+
}
|
|
342
|
+
get currentCommand() {
|
|
343
|
+
return this._currentCommand;
|
|
344
|
+
}
|
|
345
|
+
set currentCommand(value) {
|
|
346
|
+
if (value === this._currentCommand)
|
|
347
|
+
return;
|
|
348
|
+
this._currentCommand = value;
|
|
349
|
+
this.lastLine = undefined;
|
|
350
|
+
}
|
|
351
|
+
}
|
package/utils/run-bin.d.ts
CHANGED
|
@@ -27,6 +27,10 @@ export interface RunBinOptions {
|
|
|
27
27
|
* even then. An explicit `stdio` still wins over all of this.
|
|
28
28
|
*/
|
|
29
29
|
logLevel?: LogLevel;
|
|
30
|
+
/**
|
|
31
|
+
* Called once per line of the child's output **instead of writing it to the terminal**.
|
|
32
|
+
*/
|
|
33
|
+
onLine?: (line: string) => void;
|
|
30
34
|
env?: Record<string, string | undefined>;
|
|
31
35
|
}
|
|
32
36
|
export interface RunBinResult {
|
package/utils/run-bin.js
CHANGED
|
@@ -39,7 +39,11 @@ export async function runBin(bin, argv, options = {}) {
|
|
|
39
39
|
const region = options.app?.statusRegion?.live ? options.app.statusRegion : undefined;
|
|
40
40
|
/** Below 'info' the output is captured rather than streamed, so a quiet run stays quiet and a
|
|
41
41
|
* failing one can still say what went wrong. */
|
|
42
|
-
|
|
42
|
+
/** **`onLine` forces `pipe` the same way a live region does.** Left to the default, a caller at
|
|
43
|
+
* `info` would get `inherit` - the child writes straight to the terminal, `child.stdout` is
|
|
44
|
+
* null, and the callback is never called at all. An option that silently does nothing is worse
|
|
45
|
+
* than one that does not exist. */
|
|
46
|
+
const stdio = options.stdio ?? (options.onLine || region ? 'pipe' : atLeast('info') ? 'inherit' : 'pipe');
|
|
43
47
|
if (atLeast('verbose'))
|
|
44
48
|
console.log(colors.magenta('verbose'), colors.gray('$'), bin, argv.join(' '));
|
|
45
49
|
const child = spawn(process.platform === 'win32' ? `${bin}.cmd` : bin, argv, {
|
|
@@ -74,6 +78,14 @@ export async function runBin(bin, argv, options = {}) {
|
|
|
74
78
|
const collect = (d) => {
|
|
75
79
|
const text = d.toString();
|
|
76
80
|
output += text;
|
|
81
|
+
/** A caller taking the lines owns them - it is showing them somewhere of its own, and writing
|
|
82
|
+
* them here as well would both double them and scroll whatever it is drawing. */
|
|
83
|
+
if (options.onLine) {
|
|
84
|
+
for (const line of text.split(/\r?\n/))
|
|
85
|
+
if (line)
|
|
86
|
+
options.onLine(line);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
77
89
|
if (region && atLeast('info'))
|
|
78
90
|
region.passThrough(text);
|
|
79
91
|
};
|
|
@@ -89,7 +101,7 @@ export async function runBin(bin, argv, options = {}) {
|
|
|
89
101
|
/** Captured output has to be surfaced here or it is lost with the process - the one thing
|
|
90
102
|
* worse than a noisy failure is a silent one. 'silent' is the caller saying otherwise. */
|
|
91
103
|
/** Already streamed through the region, so printing it again would double it. */
|
|
92
|
-
if (stdio === 'pipe' && output && logLevel !== 'silent' && !(region && atLeast('info'))) {
|
|
104
|
+
if (stdio === 'pipe' && output && logLevel !== 'silent' && !options.onLine && !(region && atLeast('info'))) {
|
|
93
105
|
process.stderr.write(output);
|
|
94
106
|
}
|
|
95
107
|
const err = new Error(`"${bin} ${argv.join(' ')}" exited with code ${code}`);
|
package/utils/run-options.d.ts
CHANGED
|
@@ -1,4 +1,48 @@
|
|
|
1
1
|
import type { RunService } from '../services/run.service.js';
|
|
2
|
+
/**
|
|
3
|
+
* The three scheduling flags any command that sweeps packages can take - `--parallel`, `--bail`,
|
|
4
|
+
* `--progress`.
|
|
5
|
+
*
|
|
6
|
+
* **Exported as a group for the reason `packageFilterOptions` is**: a command spreading it gets the
|
|
7
|
+
* flags spelled and behaving exactly as `run`'s, where restating them means `--parallel` quietly
|
|
8
|
+
* accepting a number on one command and not on another. `--parallel`'s `coerce` is the part nobody
|
|
9
|
+
* would copy correctly - it takes a boolean *or* a number, and the type of the parsed value is read
|
|
10
|
+
* off that function.
|
|
11
|
+
*
|
|
12
|
+
* **`--topo` is deliberately not in it.** `run`'s default is ordering on; a command reaching for
|
|
13
|
+
* this is sweeping packages with an independent tool, where a wait nobody asked for costs the whole
|
|
14
|
+
* point. A command that does want it declares it, and `forEachPackage` takes `topo` as an option.
|
|
15
|
+
*
|
|
16
|
+
* The `describe` texts here name no `.rmanrc run.<script>` key, because a command using them owns
|
|
17
|
+
* its own (`check.concurrency`) - which is also why `runOptions` keeps its own wording rather than
|
|
18
|
+
* spreading this.
|
|
19
|
+
*/
|
|
20
|
+
export declare const parallelOptions: {
|
|
21
|
+
parallel: {
|
|
22
|
+
target: "cli";
|
|
23
|
+
describe: string;
|
|
24
|
+
/** No `type`: the flag takes a boolean *or* a number, and `coerce` is what says so - which is
|
|
25
|
+
* also where `OptionValue` reads this option's type from. */
|
|
26
|
+
coerce: (v: unknown) => boolean | number | undefined;
|
|
27
|
+
};
|
|
28
|
+
bail: {
|
|
29
|
+
target: "cli";
|
|
30
|
+
describe: string;
|
|
31
|
+
type: "boolean";
|
|
32
|
+
};
|
|
33
|
+
progress: {
|
|
34
|
+
target: "cli";
|
|
35
|
+
describe: string;
|
|
36
|
+
type: "boolean";
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
/** The scheduling half of `forEachPackage`'s options, read off argv - the mirror of
|
|
40
|
+
* `readRunOptions` for a command that declared `parallelOptions`. */
|
|
41
|
+
export declare function readParallelOptions(args: any): {
|
|
42
|
+
parallel?: boolean | number;
|
|
43
|
+
bail?: boolean;
|
|
44
|
+
progress?: boolean;
|
|
45
|
+
};
|
|
2
46
|
/**
|
|
3
47
|
* Everything `run`, `build` and `test` accept - the package filter, the branch guard, `--from-root`, and
|
|
4
48
|
* the scheduling flags on top.
|
package/utils/run-options.js
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
import { branchGuardOptions } from './branch-guard.js';
|
|
2
2
|
import { fromRootOption, packageFilterOptions, readPackageFilterOptions } from './package-filter.js';
|
|
3
|
+
/**
|
|
4
|
+
* The three scheduling flags any command that sweeps packages can take - `--parallel`, `--bail`,
|
|
5
|
+
* `--progress`.
|
|
6
|
+
*
|
|
7
|
+
* **Exported as a group for the reason `packageFilterOptions` is**: a command spreading it gets the
|
|
8
|
+
* flags spelled and behaving exactly as `run`'s, where restating them means `--parallel` quietly
|
|
9
|
+
* accepting a number on one command and not on another. `--parallel`'s `coerce` is the part nobody
|
|
10
|
+
* would copy correctly - it takes a boolean *or* a number, and the type of the parsed value is read
|
|
11
|
+
* off that function.
|
|
12
|
+
*
|
|
13
|
+
* **`--topo` is deliberately not in it.** `run`'s default is ordering on; a command reaching for
|
|
14
|
+
* this is sweeping packages with an independent tool, where a wait nobody asked for costs the whole
|
|
15
|
+
* point. A command that does want it declares it, and `forEachPackage` takes `topo` as an option.
|
|
16
|
+
*
|
|
17
|
+
* The `describe` texts here name no `.rmanrc run.<script>` key, because a command using them owns
|
|
18
|
+
* its own (`check.concurrency`) - which is also why `runOptions` keeps its own wording rather than
|
|
19
|
+
* spreading this.
|
|
20
|
+
*/
|
|
21
|
+
export const parallelOptions = {
|
|
22
|
+
parallel: {
|
|
23
|
+
target: 'cli',
|
|
24
|
+
describe: 'Max packages at once: omit/true for CPU count, a number for that many, false to run serially',
|
|
25
|
+
/** No `type`: the flag takes a boolean *or* a number, and `coerce` is what says so - which is
|
|
26
|
+
* also where `OptionValue` reads this option's type from. */
|
|
27
|
+
coerce: (v) => {
|
|
28
|
+
if (v === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (v === 'false' || v === false)
|
|
31
|
+
return false;
|
|
32
|
+
if (v === 'true' || v === true)
|
|
33
|
+
return true;
|
|
34
|
+
const n = Number(v);
|
|
35
|
+
return Number.isFinite(n) ? n : true;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
bail: {
|
|
39
|
+
target: 'cli',
|
|
40
|
+
describe: 'Stop at the first failure. Default true.',
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
},
|
|
43
|
+
progress: {
|
|
44
|
+
target: 'cli',
|
|
45
|
+
describe: 'Show a live progress panel (default: true; auto-disabled when not a TTY)',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
/** The scheduling half of `forEachPackage`'s options, read off argv - the mirror of
|
|
50
|
+
* `readRunOptions` for a command that declared `parallelOptions`. */
|
|
51
|
+
export function readParallelOptions(args) {
|
|
52
|
+
return {
|
|
53
|
+
parallel: args.parallel,
|
|
54
|
+
bail: args.bail,
|
|
55
|
+
progress: args.progress,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
3
58
|
/**
|
|
4
59
|
* Everything `run`, `build` and `test` accept - the package filter, the branch guard, `--from-root`, and
|
|
5
60
|
* the scheduling flags on top.
|