@pnpm/cli.default-reporter 1100.3.17 → 1100.3.19

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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @pnpm/default-reporter
2
2
 
3
+ ## 1100.3.19
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @pnpm/error@1100.1.4
9
+
10
+ ## 1100.3.18
11
+
12
+ ### Patch Changes
13
+
14
+ - The progress output no longer overwrites the lines above it once it grows taller than the terminal window [#14270](https://github.com/pnpm/pnpm/issues/14270).
15
+
16
+ - The update notification now suggests `pnpm self-update` when `PNPM_HOME` manages the pnpm in use, and the [standalone install script](https://pnpm.io/installation) otherwise — under Corepack, or when another package manager installed pnpm. `pnpm self-update` under Corepack names the standalone install script too.
17
+
18
+ - Updated dependencies:
19
+ - @pnpm/cli.meta@1100.1.0
20
+ - @pnpm/core-loggers@1100.3.4
21
+ - @pnpm/deps.inspection.peers-issues-renderer@1100.0.14
22
+ - @pnpm/types@1102.1.0
23
+
3
24
  ## 1100.3.17
4
25
 
5
26
  ### Patch Changes
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import createDiffer from 'ansi-diff';
2
2
  import * as Rx from 'rxjs';
3
3
  import { filter, map, mergeAll } from 'rxjs/operators';
4
+ import stringLength from 'string-length';
4
5
  import { EOL } from './constants.js';
5
6
  import { mergeOutputs } from './mergeOutputs.js';
6
7
  import { reporterForClient } from './reporterForClient/index.js';
@@ -12,7 +13,10 @@ export { formatWarn };
12
13
  const ERASE_TO_END_OF_DISPLAY = '\x1b[0J';
13
14
  export function initDefaultReporter(opts) {
14
15
  const proc = opts.context.process ?? process;
15
- const outputMaxWidth = opts.reportingOptions?.outputMaxWidth ?? (proc.stdout.columns && proc.stdout.columns - 2) ?? 80;
16
+ // At least one column: `columns - 2` is zero on a two-column terminal, and a
17
+ // caller may pass zero outright. A zero width would make every wrap
18
+ // calculation — the differ's and `renderedRows`' — meaningless.
19
+ const outputMaxWidth = Math.max(1, opts.reportingOptions?.outputMaxWidth ?? (proc.stdout.columns && proc.stdout.columns - 2) ?? 80);
16
20
  const output$ = toOutput$({
17
21
  ...opts,
18
22
  reportingOptions: {
@@ -38,11 +42,20 @@ export function initDefaultReporter(opts) {
38
42
  }
39
43
  const stream = opts.useStderr ? proc.stderr : proc.stdout;
40
44
  const write = stream.write.bind(stream);
41
- const newDiffer = () => createDiffer({
42
- height: stream.rows,
43
- width: stream.columns ?? outputMaxWidth,
44
- });
45
+ // The width the live differ wraps its frame at, so a resize can be noticed.
46
+ let differWidth = 0;
47
+ const newDiffer = () => {
48
+ differWidth = Math.max(1, stream.columns ?? outputMaxWidth);
49
+ return createDiffer({ height: stream.rows, width: differWidth });
50
+ };
45
51
  let diff = newDiffer();
52
+ // How many leading lines of the view have scrolled out of the differ's frame
53
+ // and been committed to the scrollback, how many rows the frame the differ is
54
+ // holding takes up, and whether it already outgrew the terminal it was drawn
55
+ // on. See `commitOverflow`.
56
+ let committedLines = 0;
57
+ let renderedFrameRows = 0;
58
+ let renderedFrameOutgrewTerminal = false;
46
59
  // Hold redraws while an interactive prompt owns the terminal (see PromptMessage).
47
60
  let promptActive = false;
48
61
  const onLog = (log) => {
@@ -74,6 +87,11 @@ export function initDefaultReporter(opts) {
74
87
  // An example of such prompt may be seen by running: pnpm update --interactive
75
88
  if (!view.endsWith(EOL))
76
89
  view += EOL;
90
+ const lines = view.slice(0, -EOL.length).split(EOL);
91
+ const committed = commitOverflow(lines);
92
+ // The lines from `committedLines` on are already laid out contiguously in
93
+ // the view, so the visible frame is a slice of it rather than a second copy.
94
+ const frame = view.slice(viewOffsetOfLine(view, lines, committedLines));
77
95
  // `\r` resets the column to 0 in case an external process (e.g. an SSH
78
96
  // passphrase prompt) left the cursor mid-line. `ansi-diff` then writes
79
97
  // only the differential — the characters that actually changed between
@@ -82,13 +100,104 @@ export function initDefaultReporter(opts) {
82
100
  // tick. `\x1b[K` erases trailing characters on the current line;
83
101
  // `\x1b[0J` erases anything an external process wrote below the
84
102
  // rendered frame.
85
- write(`\r${diff.update(view)}\x1b[K${ERASE_TO_END_OF_DISPLAY}`);
103
+ write(`\r${committed}${diff.update(frame)}\x1b[K${ERASE_TO_END_OF_DISPLAY}`);
104
+ }
105
+ /**
106
+ * Hands the lines that no longer fit on screen over to the scrollback and
107
+ * restarts the differ below them, returning the differential that performs
108
+ * the handover.
109
+ *
110
+ * `ansi-diff` redraws by moving the cursor up from the end of its frame, so
111
+ * it can only reach lines that are still on screen. A frame taller than the
112
+ * terminal has scrolled its top away, and every later redraw then lands that
113
+ * many rows too low — overwriting output above the frame instead of updating
114
+ * it (pnpm/pnpm#14270). Committing the overflow keeps the frame within the
115
+ * terminal, at the cost of no longer being able to revise what was committed.
116
+ */
117
+ function commitOverflow(lines) {
118
+ if (Math.max(1, stream.columns ?? outputMaxWidth) !== differWidth) {
119
+ // The terminal was resized. The frame on screen has reflowed at the new
120
+ // width, so every position the differ tracked against the old one is
121
+ // wrong: start over below what is already there.
122
+ diff = newDiffer();
123
+ }
124
+ if (lines.length <= committedLines) {
125
+ // The view no longer reaches past what was committed — an error frame
126
+ // replaces it rather than extending it. Render it whole, below.
127
+ committedLines = 0;
128
+ diff = newDiffer();
129
+ return '';
130
+ }
131
+ const rows = stream.rows;
132
+ if (!rows)
133
+ return '';
134
+ const width = differWidth;
135
+ // One row is left over for the cursor line that the trailing EOL puts
136
+ // below the frame.
137
+ const maxRows = Math.max(rows - 1, 1);
138
+ let uncommittedRows = 0;
139
+ for (let i = committedLines; i < lines.length; i++) {
140
+ uncommittedRows += renderedRows(lines[i], width);
141
+ }
142
+ // The last line always stays in the frame — there would be nothing left to
143
+ // redraw otherwise — so the walk upwards starts one line above it.
144
+ let firstVisible = lines.length - 1;
145
+ let frameRows = renderedRows(lines[firstVisible], width);
146
+ for (let i = firstVisible - 1; i >= committedLines; i--) {
147
+ const lineRows = renderedRows(lines[i], width);
148
+ if (frameRows + lineRows > maxRows)
149
+ break;
150
+ frameRows += lineRows;
151
+ firstVisible = i;
152
+ }
153
+ // A frame taller than the terminal has scrolled its own top away — whether
154
+ // because a line outgrew the screen or because the window shrank under it —
155
+ // so no cursor move reaches back into it, and growing the window again does
156
+ // not bring it back. Start afresh below instead, reprinting rather than
157
+ // revising, and leave the commit for the next frame, whose layout is one
158
+ // this differ laid out itself.
159
+ const cannotRevise = renderedFrameOutgrewTerminal || renderedFrameRows > maxRows;
160
+ if (cannotRevise || firstVisible === committedLines) {
161
+ renderedFrameRows = uncommittedRows;
162
+ renderedFrameOutgrewTerminal = uncommittedRows > maxRows;
163
+ if (cannotRevise || renderedFrameOutgrewTerminal)
164
+ diff = newDiffer();
165
+ return '';
166
+ }
167
+ renderedFrameRows = frameRows;
168
+ renderedFrameOutgrewTerminal = false;
169
+ // Shrinking the frame to just the overflow leaves those lines untouched
170
+ // where they already are, erases the rest of the frame below them, and
171
+ // parks the cursor on the next line — where the fresh differ starts.
172
+ const handover = diff.update(`${lines.slice(committedLines, firstVisible).join(EOL)}${EOL}`).toString();
173
+ diff = newDiffer();
174
+ committedLines = firstVisible;
175
+ return handover;
86
176
  }
87
177
  return () => {
88
178
  subscription.unsubscribe();
89
179
  opts.streamParser.removeListener('data', onLog);
90
180
  };
91
181
  }
182
+ /**
183
+ * Where the `index`-th of `lines` starts in the `view` they were split from.
184
+ * Measured from the end, so a long committed prefix costs nothing.
185
+ */
186
+ function viewOffsetOfLine(view, lines, index) {
187
+ let trailing = 0;
188
+ for (let i = lines.length - 1; i >= index; i--) {
189
+ trailing += lines[i].length + EOL.length;
190
+ }
191
+ return view.length - trailing;
192
+ }
193
+ /**
194
+ * How many terminal rows `line` occupies once wrapped at `width`, counting the
195
+ * escape sequences in it as zero-width. Never zero: an empty line still takes a
196
+ * row. `width` is the terminal's own column count, clamped to at least one.
197
+ */
198
+ function renderedRows(line, width) {
199
+ return Math.max(1, Math.ceil(stringLength(line) / width));
200
+ }
92
201
  export function toOutput$(opts) {
93
202
  opts = opts || {};
94
203
  const contextPushStream = new Rx.Subject();
@@ -1,4 +1,4 @@
1
- import { detectIfCurrentPkgIsExecutable, isExecutedByCorepack } from '@pnpm/cli.meta';
1
+ import { isExecutedByCorepack, standaloneInstallCommand } from '@pnpm/cli.meta';
2
2
  import boxen from 'boxen';
3
3
  import chalk from 'chalk';
4
4
  import * as Rx from 'rxjs';
@@ -7,9 +7,8 @@ import semver from 'semver';
7
7
  export function reportUpdateCheck(log$, opts) {
8
8
  return log$.pipe(take(1), filter((log) => semver.gt(log.latestVersion, log.currentVersion)), map((log) => {
9
9
  const updateMessage = renderUpdateMessage({
10
- currentPkgIsExecutable: detectIfCurrentPkgIsExecutable(opts.process),
11
- latestVersion: log.latestVersion,
12
10
  env: opts.env,
11
+ platform: opts.process.platform,
13
12
  });
14
13
  return Rx.of({
15
14
  msg: boxen(`\
@@ -30,13 +29,14 @@ function renderUpdateMessage(opts) {
30
29
  return `To update, run: ${chalk.magenta(updateCommand)}`;
31
30
  }
32
31
  function renderUpdateCommand(opts) {
33
- if (isExecutedByCorepack(opts.env)) {
34
- return `corepack use pnpm@${opts.latestVersion}`;
32
+ // `pnpm self-update` replaces the pnpm that PNPM_HOME manages. Corepack
33
+ // refuses it outright, and an install another package manager owns is
34
+ // resolved from that manager's bin directory rather than pnpm's home, so a
35
+ // self-update would land beside the executable in use instead of replacing
36
+ // it. The installer is the command that updates either one.
37
+ if (isExecutedByCorepack(opts.env) || !opts.env.PNPM_HOME) {
38
+ return standaloneInstallCommand(opts.platform);
35
39
  }
36
- if (opts.env.PNPM_HOME) {
37
- return 'pnpm self-update';
38
- }
39
- const pkgName = opts.currentPkgIsExecutable ? '@pnpm/exe' : 'pnpm';
40
- return `pnpm add -g ${pkgName}`;
40
+ return 'pnpm self-update';
41
41
  }
42
42
  //# sourceMappingURL=reportUpdateCheck.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/cli.default-reporter",
3
- "version": "1100.3.17",
3
+ "version": "1100.3.19",
4
4
  "description": "The default reporter of pnpm",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -32,21 +32,21 @@
32
32
  "pnpm-render": "bin/pnpm-render.mjs"
33
33
  },
34
34
  "dependencies": {
35
- "@pnpm/cli.meta": "1100.0.15",
36
- "@pnpm/core-loggers": "1100.3.3",
37
- "@pnpm/deps.inspection.peers-issues-renderer": "1100.0.13",
38
- "@pnpm/error": "1100.1.3",
35
+ "@pnpm/cli.meta": "1100.1.0",
36
+ "@pnpm/core-loggers": "1100.3.4",
37
+ "@pnpm/deps.inspection.peers-issues-renderer": "1100.0.14",
38
+ "@pnpm/error": "1100.1.4",
39
39
  "@pnpm/installing.dedupe.issues-renderer": "1100.0.3",
40
40
  "@pnpm/installing.dedupe.types": "1100.0.2",
41
41
  "@pnpm/text.ordinal-comparator": "1100.0.0",
42
- "@pnpm/types": "1102.0.0",
42
+ "@pnpm/types": "1102.1.0",
43
43
  "ansi-diff": "^1.2.0",
44
44
  "boxen": "npm:@zkochan/boxen@5.1.2",
45
45
  "chalk": "^6.0.0",
46
46
  "cli-truncate": "^6.1.1",
47
47
  "normalize-path": "^3.0.0",
48
- "pretty-bytes": "^7.1.1",
49
- "pretty-ms": "^9.3.0",
48
+ "pretty-bytes": "^7.1.3",
49
+ "pretty-ms": "^9.3.1",
50
50
  "ramda": "npm:@pnpm/ramda@0.28.1",
51
51
  "rxjs": "^7.8.2",
52
52
  "semver": "^7.8.5",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@jest/globals": "30.4.1",
61
- "@pnpm/cli.default-reporter": "1100.3.17",
61
+ "@pnpm/cli.default-reporter": "1100.3.19",
62
62
  "@pnpm/logger": "1100.0.0",
63
63
  "@types/normalize-path": "^3.0.2",
64
64
  "@types/ramda": "0.32.0",