rman 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -72,7 +72,7 @@ export var CiService;
72
72
  const progress = options.progress ?? true;
73
73
  const panel = new ProgressPanel('CI', !!process.stdout.isTTY && progress);
74
74
  const runStartedAt = Date.now();
75
- panel.start();
75
+ panel.start(repository.app.statusRegion);
76
76
  const items = [];
77
77
  let failed = false;
78
78
  try {
@@ -53,7 +53,7 @@ export var CleanService;
53
53
  const progress = options.progress ?? true;
54
54
  const logger = new Logger(options.logLevel ?? resolveRootLogLevel(repository));
55
55
  const panel = new ProgressPanel(dryRun ? 'CLEAN (dry-run)' : 'CLEAN', !!process.stdout.isTTY && progress);
56
- panel.start();
56
+ panel.start(repository.app.statusRegion);
57
57
  let failed = false;
58
58
  try {
59
59
  const results = await Promise.allSettled(targets.map(pkg => {
@@ -225,7 +225,7 @@ const changelogCommand = registerCommand(app => {
225
225
  progress: panelReporter(panel),
226
226
  };
227
227
  const changelog = app.getService('changelog');
228
- panel.start();
228
+ panel.start(app.statusRegion);
229
229
  let entries;
230
230
  try {
231
231
  entries = write ? await changelog.generateToFile(options) : await changelog.getEntries(options);
package/constants.js CHANGED
@@ -1 +1 @@
1
- export const version = '2.6.0';
1
+ export const version = '2.6.1';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rman",
3
3
  "description": "Repository manager",
4
- "version": "2.6.0",
4
+ "version": "2.6.1",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
7
  "dependencies": {
@@ -55,7 +55,7 @@ export class ExecService extends Service {
55
55
  const dependencies = topo ? pkg.dependencies.filter(d => names.has(d.name)).map(d => d.name) : [];
56
56
  return new Task(() => execForPackage(this.app, ctx, panel.enabled, pkg.dirname, command, logger, bail ? () => rootTask?.abort() : () => { }), { name: pkg.name, dependencies });
57
57
  });
58
- panel.start();
58
+ panel.start(repository.app.statusRegion);
59
59
  let failed = false;
60
60
  try {
61
61
  /** bail:false here - each item's own task decides whether to call `rootTask.abort()`
@@ -228,7 +228,7 @@ export class RunService extends Service {
228
228
  err.logged = true;
229
229
  throw err;
230
230
  }
231
- panel.start();
231
+ panel.start(repository.app.statusRegion);
232
232
  try {
233
233
  /** bail:false here - each package's own resolved bail setting decides whether to call
234
234
  * `rootTask.abort()` itself (see `runSteps`), since power-tasks' own `bail` can't vary per package. */
@@ -1,4 +1,5 @@
1
1
  import { LiveRegion } from './live-region.js';
2
+ import type { StatusRegion, TerminalRegion } from './status-region.js';
2
3
  export type ProgressStatus = 'pending' | 'running' | 'success' | 'failed' | 'skipped';
3
4
  export interface ProgressItem {
4
5
  readonly name: string;
@@ -41,11 +42,13 @@ export declare function formatDuration(ms: number): string;
41
42
  * callers are expected to fall back to their own plain logging in that case, the way `run`
42
43
  * falls back to its "classic" per-step log.
43
44
  */
44
- export declare class ProgressPanel {
45
+ export declare class ProgressPanel implements TerminalRegion {
45
46
  private readonly title;
46
47
  readonly live: LiveRegion;
47
48
  private readonly items;
48
49
  private renderLoop?;
50
+ /** The region this panel took the terminal from, handed back in `stop`. */
51
+ private statusRegion?;
49
52
  private spinnerFrame;
50
53
  private startedAt;
51
54
  /**
@@ -59,8 +62,19 @@ export declare class ProgressPanel {
59
62
  /** Registers an item and returns it - callers mutate the returned object directly (`status`,
60
63
  * `currentStep`, `lastLine`, ...) to drive the next render tick. */
61
64
  addItem(name: string, stepsTotal?: number): ProgressItem;
62
- /** Starts the redraw loop. No-op (and no timer) when disabled. */
63
- start(): void;
65
+ /**
66
+ * Starts the redraw loop. No-op (and no timer) when disabled.
67
+ *
68
+ * **Pass `statusRegion` wherever one exists** - `app.statusRegion`, which every command has while
69
+ * it runs. The panel takes the terminal over for as long as it draws and hands it back in `stop`;
70
+ * without that, two regions redraw on top of each other and the bottom lines visibly swap places
71
+ * several times a second. See `StatusRegion.suspend`.
72
+ */
73
+ start(statusRegion?: StatusRegion): void;
74
+ /** Writes `text` above the panel without corrupting it - erase, write, redraw underneath. The
75
+ * `TerminalRegion` half of the takeover, so a `runBin` child's output goes somewhere sane while
76
+ * a panel is up. */
77
+ passThrough(text: string): void;
64
78
  private render;
65
79
  /** Stops the redraw loop and erases the panel, leaving the cursor where it started. */
66
80
  stop(): void;
@@ -19,6 +19,8 @@ export class ProgressPanel {
19
19
  live;
20
20
  items = new Map();
21
21
  renderLoop;
22
+ /** The region this panel took the terminal from, handed back in `stop`. */
23
+ statusRegion;
22
24
  spinnerFrame = 0;
23
25
  startedAt = 0;
24
26
  /**
@@ -41,13 +43,36 @@ export class ProgressPanel {
41
43
  this.items.set(name, item);
42
44
  return item;
43
45
  }
44
- /** Starts the redraw loop. No-op (and no timer) when disabled. */
45
- start() {
46
+ /**
47
+ * Starts the redraw loop. No-op (and no timer) when disabled.
48
+ *
49
+ * **Pass `statusRegion` wherever one exists** - `app.statusRegion`, which every command has while
50
+ * it runs. The panel takes the terminal over for as long as it draws and hands it back in `stop`;
51
+ * without that, two regions redraw on top of each other and the bottom lines visibly swap places
52
+ * several times a second. See `StatusRegion.suspend`.
53
+ */
54
+ start(statusRegion) {
46
55
  this.startedAt = Date.now();
47
56
  if (!this.live.enabled)
48
57
  return;
58
+ /** Only when this panel is actually drawing: a disabled panel owns nothing, and suspending the
59
+ * status line for it would take away the one thing a non-TTY run still shows. */
60
+ this.statusRegion = statusRegion;
61
+ this.statusRegion?.suspend(this);
49
62
  this.renderLoop = setInterval(() => this.render(), 100);
50
63
  }
64
+ /** Writes `text` above the panel without corrupting it - erase, write, redraw underneath. The
65
+ * `TerminalRegion` half of the takeover, so a `runBin` child's output goes somewhere sane while
66
+ * a panel is up. */
67
+ passThrough(text) {
68
+ if (!this.live.enabled) {
69
+ process.stdout.write(text);
70
+ return;
71
+ }
72
+ this.live.clear();
73
+ process.stdout.write(text);
74
+ this.render();
75
+ }
51
76
  render() {
52
77
  this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
53
78
  const spinner = colors.cyan(SPINNER_FRAMES[this.spinnerFrame]);
@@ -129,6 +154,11 @@ export class ProgressPanel {
129
154
  clearInterval(this.renderLoop);
130
155
  this.renderLoop = undefined;
131
156
  this.live.clear();
157
+ /** **Handed back, not left suspended.** A command keeps working after its panel comes down -
158
+ * `ci` prints its summary, `run` its recap - and those writes have to go through a region
159
+ * again, or they scroll the status line's own block. */
160
+ this.statusRegion?.resume();
161
+ this.statusRegion = undefined;
132
162
  }
133
163
  /**
134
164
  * Prints the final per-item recap (✓/X/○) and the "N succeeded, M failed (Xs)" summary line,
@@ -7,19 +7,56 @@
7
7
  * ✔ lint 3.6s <- once it ends
8
8
  * ```
9
9
  */
10
- export declare class StatusRegion {
10
+ /**
11
+ * What a region has to offer to take the terminal over from `StatusRegion` - and `passThrough` is
12
+ * the whole of it.
13
+ *
14
+ * **Deliberately not `live`.** `StatusRegion.live` answers "does something own this terminal", which
15
+ * is what `runBin` reads and which stays true across a takeover; `ProgressPanel` spells the same
16
+ * question `enabled` and uses `live` for the `LiveRegion` itself. Putting it in the contract would
17
+ * force one of the two to be renamed for no gain - the handover needs somewhere to write, nothing
18
+ * more.
19
+ */
20
+ export interface TerminalRegion {
21
+ /** Write `text` without corrupting the block: erase, write, redraw underneath. */
22
+ passThrough(text: string): void;
23
+ }
24
+ export declare class StatusRegion implements TerminalRegion {
11
25
  private readonly label;
12
26
  private readonly detail;
13
27
  private readonly region;
14
28
  private readonly startedAt;
15
29
  private timer?;
16
30
  private frame;
31
+ /**
32
+ * Who owns the terminal while this region is suspended - see `suspend`. Pass-through is forwarded
33
+ * there, so `runBin` keeps asking one object (`app.statusRegion`) whatever is actually drawing.
34
+ */
35
+ private takeover?;
17
36
  constructor(label: string, detail?: string, enabled?: boolean);
18
37
  /** Whether the spinner is actually drawn. `false` leaves `passThrough` a plain write and `stop`
19
38
  * its single line, which is what a non-TTY wants. */
20
39
  get live(): boolean;
21
40
  /** Starts the spinner. Safe to call when not live - it does nothing. */
22
41
  start(): void;
42
+ /**
43
+ * **Hands the terminal to another region**, usually a `ProgressPanel` - the spinner stops, its
44
+ * line is erased, and pass-through is forwarded to `takeover` until `resume`.
45
+ *
46
+ * **Two live regions on one terminal cannot both work, and this is what enforces that.** Each
47
+ * redraws by moving the cursor up by *its own* line count; interleaved, every redraw lands on the
48
+ * other's rows. On screen that reads as the last line and the status line swapping places several
49
+ * times a second - reported on `rman ci`, and `rman build` has it too through `runBin`, whose
50
+ * pass-through went to this region while the panel was drawing. `LiveRegion`'s own doc has said
51
+ * "only one region is ever live" since it grew a `stream` parameter; nothing enforced it.
52
+ *
53
+ * `live` deliberately stays `true` while suspended: it answers "does something own this
54
+ * terminal", which is what `runBin` needs to decide to pipe rather than let a child scroll the
55
+ * screen. The answer to *which* region is the forwarding above.
56
+ */
57
+ suspend(takeover?: TerminalRegion): void;
58
+ /** Takes the terminal back and starts drawing again - a no-op when it was never suspended. */
59
+ resume(): void;
23
60
  /** Writes `text` without corrupting the block: erase, write, redraw underneath. */
24
61
  passThrough(text: string): void;
25
62
  /** Erases the spinner and leaves one line saying how it went. */
@@ -1,32 +1,5 @@
1
1
  import colors from 'ansi-colors';
2
2
  import { LiveRegion } from './live-region.js';
3
- /**
4
- * One line at the bottom of the terminal while a command runs - a spinner, the command's name, the
5
- * repository, and the elapsed time counting up - replaced by a result line when it ends.
6
- *
7
- * ```
8
- * ⠹ lint postgrejs 2.4s <- while running, redrawn in place
9
- * ✔ lint 3.6s <- once it ends
10
- * ```
11
- */
12
- /* **Why a live line rather than one printed at the start**: a command that prints nothing is
13
- * indistinguishable from one that never ran, and a static start line answers "did it start" without
14
- * answering "is it still going". `rman lint` on a clean repository is silent for several seconds
15
- * because eslint says nothing when it has nothing to say.
16
- *
17
- * **On stderr, unlike `ProgressPanel`'s region.** A command's *answer* goes to stdout, and
18
- * `rman changelog > NOTES.md` has to leave the notes alone in the file - cursor-movement codes in
19
- * there would be worse than noise. The two streams share a terminal, so only one region is ever
20
- * live.
21
- *
22
- * **Anything printed while it is live has to go through `passThrough`**, which erases the block,
23
- * writes, and redraws below. A write that bypasses it scrolls the screen, and the next redraw's
24
- * "move up N rows" then lands on the wrong rows and erases what was just printed - the same cursor
25
- * arithmetic `LiveRegion` documents as its own hard edge. That is why `runBin` pipes a child while
26
- * a region is live instead of letting it inherit the terminal.
27
- *
28
- * **No-op when stderr is not a TTY** (CI, a pipe), where the escape codes mean nothing. There the
29
- * caller still gets the result line, which is the part that carries information. */
30
3
  export class StatusRegion {
31
4
  label;
32
5
  detail;
@@ -34,6 +7,11 @@ export class StatusRegion {
34
7
  startedAt = Date.now();
35
8
  timer;
36
9
  frame = 0;
10
+ /**
11
+ * Who owns the terminal while this region is suspended - see `suspend`. Pass-through is forwarded
12
+ * there, so `runBin` keeps asking one object (`app.statusRegion`) whatever is actually drawing.
13
+ */
14
+ takeover;
37
15
  constructor(label, detail = '', enabled) {
38
16
  this.label = label;
39
17
  this.detail = detail;
@@ -57,8 +35,41 @@ export class StatusRegion {
57
35
  }, FRAME_MS);
58
36
  this.timer.unref();
59
37
  }
38
+ /**
39
+ * **Hands the terminal to another region**, usually a `ProgressPanel` - the spinner stops, its
40
+ * line is erased, and pass-through is forwarded to `takeover` until `resume`.
41
+ *
42
+ * **Two live regions on one terminal cannot both work, and this is what enforces that.** Each
43
+ * redraws by moving the cursor up by *its own* line count; interleaved, every redraw lands on the
44
+ * other's rows. On screen that reads as the last line and the status line swapping places several
45
+ * times a second - reported on `rman ci`, and `rman build` has it too through `runBin`, whose
46
+ * pass-through went to this region while the panel was drawing. `LiveRegion`'s own doc has said
47
+ * "only one region is ever live" since it grew a `stream` parameter; nothing enforced it.
48
+ *
49
+ * `live` deliberately stays `true` while suspended: it answers "does something own this
50
+ * terminal", which is what `runBin` needs to decide to pipe rather than let a child scroll the
51
+ * screen. The answer to *which* region is the forwarding above.
52
+ */
53
+ suspend(takeover) {
54
+ this.takeover = takeover;
55
+ if (this.timer)
56
+ clearInterval(this.timer);
57
+ this.timer = undefined;
58
+ this.region.clear();
59
+ }
60
+ /** Takes the terminal back and starts drawing again - a no-op when it was never suspended. */
61
+ resume() {
62
+ if (!this.takeover && this.timer)
63
+ return;
64
+ this.takeover = undefined;
65
+ this.start();
66
+ }
60
67
  /** Writes `text` without corrupting the block: erase, write, redraw underneath. */
61
68
  passThrough(text) {
69
+ if (this.takeover) {
70
+ this.takeover.passThrough(text);
71
+ return;
72
+ }
62
73
  if (!this.region.enabled) {
63
74
  process.stderr.write(text);
64
75
  return;
@@ -69,6 +80,7 @@ export class StatusRegion {
69
80
  }
70
81
  /** Erases the spinner and leaves one line saying how it went. */
71
82
  stop(outcome) {
83
+ this.takeover = undefined;
72
84
  if (this.timer)
73
85
  clearInterval(this.timer);
74
86
  this.timer = undefined;