rman 2.20.0 → 2.21.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.
- package/builtins/platforms/node/services/clean.service.js +42 -7
- package/cli.js +6 -0
- package/constants.js +1 -1
- package/core/classes/progress-panel.d.ts +19 -0
- package/core/classes/progress-panel.js +26 -1
- package/core/classes/run-reporters.d.ts +9 -4
- package/core/classes/run-reporters.js +12 -4
- package/package.json +1 -1
|
@@ -119,7 +119,9 @@ async function remove(file, dryRun) {
|
|
|
119
119
|
function pruneEmptyDirs(dir, includeSelf, dryRun) {
|
|
120
120
|
if (dryRun || !fs.existsSync(dir))
|
|
121
121
|
return;
|
|
122
|
-
|
|
122
|
+
/** Not through a link: a link to a directory is not a directory of this one to prune - removing
|
|
123
|
+
* it as one is `ENOTDIR`, and walking into it reaches somewhere else entirely. */
|
|
124
|
+
const entries = fg.sync('**', { cwd: dir, onlyDirectories: true, dot: true, followSymbolicLinks: false });
|
|
123
125
|
entries.sort((a, b) => b.split(path.sep).length - a.split(path.sep).length);
|
|
124
126
|
for (const rel of entries) {
|
|
125
127
|
const abs = path.join(dir, rel);
|
|
@@ -140,23 +142,48 @@ function pruneEmptyDirs(dir, includeSelf, dryRun) {
|
|
|
140
142
|
* matching files *inside* an otherwise-deleted directory, leaving the rest of it gone and the
|
|
141
143
|
* protected files (and their now non-empty parent) in place.
|
|
142
144
|
*/
|
|
145
|
+
/* **A symbolic link is removed as a link, and never followed.** A build directory routinely holds
|
|
146
|
+
* one - `build/node_modules/@scope/sibling` pointing at another package's own build - and walking
|
|
147
|
+
* through it deleted *that* package's output, then failed `rmdir` on the link with `ENOTDIR`.
|
|
148
|
+
* Measured on `panates/syncbridge`: cleaning `syncbuild` emptied `common/build`, and the run
|
|
149
|
+
* reported only `0 succeeded, 1 failed`. fast-glob follows links by default, so every glob here
|
|
150
|
+
* says `followSymbolicLinks: false`. */
|
|
143
151
|
async function cleanGlobs(dirname, include, exclude, dryRun) {
|
|
144
152
|
if (!include.length)
|
|
145
153
|
return [];
|
|
146
|
-
const matches = await fg(include, {
|
|
154
|
+
const matches = await fg(include, {
|
|
155
|
+
cwd: dirname,
|
|
156
|
+
ignore: exclude,
|
|
157
|
+
onlyFiles: false,
|
|
158
|
+
dot: true,
|
|
159
|
+
absolute: true,
|
|
160
|
+
followSymbolicLinks: false,
|
|
161
|
+
});
|
|
147
162
|
const protectedFiles = exclude.length
|
|
148
|
-
? new Set(await fg(exclude, { cwd: dirname, onlyFiles: true, dot: true, absolute: true }))
|
|
163
|
+
? new Set(await fg(exclude, { cwd: dirname, onlyFiles: true, dot: true, absolute: true, followSymbolicLinks: false }))
|
|
149
164
|
: new Set();
|
|
150
165
|
const removed = [];
|
|
151
166
|
for (const m of matches) {
|
|
152
|
-
|
|
167
|
+
/** `lstat`, not `stat`: a link is removed as the link it is, and never read as the thing it
|
|
168
|
+
* points at. */
|
|
169
|
+
const stat = await fs.promises.lstat(m).catch(() => undefined);
|
|
153
170
|
if (!stat)
|
|
154
171
|
continue;
|
|
155
172
|
if (stat.isDirectory()) {
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
/** Files *and* links inside, and a link never entered - see `cleanGlobs`' own note. */
|
|
174
|
+
const inside = await fg('**', {
|
|
175
|
+
cwd: m,
|
|
176
|
+
onlyFiles: false,
|
|
177
|
+
dot: true,
|
|
178
|
+
absolute: true,
|
|
179
|
+
followSymbolicLinks: false,
|
|
180
|
+
});
|
|
181
|
+
for (const f of inside) {
|
|
158
182
|
if (protectedFiles.has(f))
|
|
159
183
|
continue;
|
|
184
|
+
const entry = await fs.promises.lstat(f).catch(() => undefined);
|
|
185
|
+
if (!entry || entry.isDirectory())
|
|
186
|
+
continue;
|
|
160
187
|
await remove(f, dryRun);
|
|
161
188
|
removed.push(path.relative(dirname, f));
|
|
162
189
|
}
|
|
@@ -208,6 +235,8 @@ async function cleanTsArtifacts(pkg, dryRun) {
|
|
|
208
235
|
onlyFiles: true,
|
|
209
236
|
dot: true,
|
|
210
237
|
absolute: true,
|
|
238
|
+
/** A link to a directory elsewhere is not this package's source to sweep. */
|
|
239
|
+
followSymbolicLinks: false,
|
|
211
240
|
});
|
|
212
241
|
const removed = [];
|
|
213
242
|
for (const f of files) {
|
|
@@ -277,6 +306,7 @@ async function cleanTsBuildInfo(dirname, dryRun) {
|
|
|
277
306
|
onlyFiles: true,
|
|
278
307
|
dot: true,
|
|
279
308
|
absolute: true,
|
|
309
|
+
followSymbolicLinks: false,
|
|
280
310
|
});
|
|
281
311
|
for (const f of files)
|
|
282
312
|
await remove(f, dryRun);
|
|
@@ -310,7 +340,12 @@ async function cleanPackage(pkg, item, panelEnabled, dryRun, logger) {
|
|
|
310
340
|
}
|
|
311
341
|
catch (e) {
|
|
312
342
|
item.status = 'failed';
|
|
313
|
-
|
|
343
|
+
const message = e?.message ?? String(e);
|
|
344
|
+
item.log.push(message);
|
|
345
|
+
/** The panel replays `log` in its recap; with no panel nothing does, and the run said only
|
|
346
|
+
* `0 succeeded, 1 failed` - which is how the link bug above went unexplained. */
|
|
347
|
+
if (!panelEnabled)
|
|
348
|
+
logger.error(colors.red('clean'), colors.cyan(item.name), message);
|
|
314
349
|
throw e;
|
|
315
350
|
}
|
|
316
351
|
finally {
|
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.1';
|
|
@@ -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,11 @@ 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;
|
|
84
103
|
/** Every line of a replayed log moved two columns right - an empty line left empty. */
|
|
85
104
|
protected indentLog(text: string): string;
|
|
86
105
|
private render;
|
|
@@ -79,6 +79,28 @@ 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
|
+
}
|
|
82
104
|
/** Every line of a replayed log moved two columns right - an empty line left empty. */
|
|
83
105
|
indentLog(text) {
|
|
84
106
|
return text
|
|
@@ -284,7 +306,9 @@ export class ProgressPanel {
|
|
|
284
306
|
*
|
|
285
307
|
* **Indented two columns**, so the log reads as belonging to the `X` line above it - level
|
|
286
308
|
* with the recap's own lines it ran together with them, a nested rman's recap most of all. */
|
|
287
|
-
if (item.
|
|
309
|
+
if (item.steps?.length)
|
|
310
|
+
console.log(this.indentLog(this.stepRecap(item.steps)));
|
|
311
|
+
else if (item.log.length)
|
|
288
312
|
console.log(this.indentLog(item.log.join('\n')));
|
|
289
313
|
}
|
|
290
314
|
}
|
|
@@ -349,6 +373,7 @@ class PanelItem {
|
|
|
349
373
|
startedAt;
|
|
350
374
|
finishedAt;
|
|
351
375
|
log = [];
|
|
376
|
+
steps = [];
|
|
352
377
|
_currentStep;
|
|
353
378
|
_currentCommand;
|
|
354
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
|
}
|