reslop 0.2.4 → 0.2.6

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.
Files changed (109) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/LICENSE +1 -1
  3. package/README.md +12 -13
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -12
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +48 -18
  9. package/lib/dashboard/git.js +128 -0
  10. package/lib/dashboard/model.js +332 -0
  11. package/lib/dashboard/npm.js +193 -0
  12. package/lib/dashboard/tree.js +278 -0
  13. package/lib/deps-diff.js +196 -404
  14. package/lib/deps.js +240 -399
  15. package/lib/detect.js +8 -18
  16. package/lib/diff/align.js +41 -113
  17. package/lib/diff/diff.js +12 -43
  18. package/lib/diff/display.js +25 -36
  19. package/lib/diff/edit.js +6 -20
  20. package/lib/diff/inline.js +138 -141
  21. package/lib/diff/items.js +33 -47
  22. package/lib/diff/parse.js +28 -56
  23. package/lib/diff/patch.js +12 -33
  24. package/lib/diff/unit.js +90 -206
  25. package/lib/editor.js +98 -55
  26. package/lib/files.js +86 -103
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +182 -344
  29. package/lib/git-reword-todo.js +5 -13
  30. package/lib/git-worktree.js +141 -212
  31. package/lib/git.js +21 -49
  32. package/lib/github.js +67 -181
  33. package/lib/gitlab.js +40 -90
  34. package/lib/highlight/bash.js +55 -177
  35. package/lib/highlight/core.js +157 -132
  36. package/lib/highlight/css.js +25 -58
  37. package/lib/highlight/csv.js +19 -37
  38. package/lib/highlight/dart.js +72 -213
  39. package/lib/highlight/dot.js +16 -26
  40. package/lib/highlight/html.js +42 -63
  41. package/lib/highlight/js.js +177 -506
  42. package/lib/highlight/json.js +17 -37
  43. package/lib/highlight/log.js +22 -35
  44. package/lib/highlight/txt.js +9 -22
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +62 -85
  47. package/lib/manifest.js +67 -130
  48. package/lib/npm-commands.js +119 -189
  49. package/lib/remote.js +141 -197
  50. package/lib/render/branches.js +66 -73
  51. package/lib/render/chrome.js +264 -323
  52. package/lib/render/commits.js +187 -500
  53. package/lib/render/dash-activity.js +212 -0
  54. package/lib/render/dash-blocks.js +245 -0
  55. package/lib/render/dash-table.js +290 -0
  56. package/lib/render/dashboard.js +62 -0
  57. package/lib/render/diff.js +123 -228
  58. package/lib/render/files.js +45 -99
  59. package/lib/render/frame.js +129 -183
  60. package/lib/render/notes.js +144 -160
  61. package/lib/render/npm.js +234 -112
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +131 -170
  64. package/lib/render/render.js +5 -14
  65. package/lib/render/report.js +43 -0
  66. package/lib/render/tiles.js +260 -0
  67. package/lib/render/unit.js +15 -28
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -38
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -78
  72. package/lib/report-test.js +256 -141
  73. package/lib/report-tools.js +96 -161
  74. package/lib/review-merge.js +114 -152
  75. package/lib/review.js +115 -189
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +89 -75
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +82 -63
  80. package/lib/session/branches.js +193 -247
  81. package/lib/session/change-actions.js +171 -214
  82. package/lib/session/commit.js +280 -342
  83. package/lib/session/compose.js +559 -674
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +164 -157
  86. package/lib/session/edit-click.js +57 -0
  87. package/lib/session/files-pane.js +85 -53
  88. package/lib/session/items.js +73 -104
  89. package/lib/session/lifecycle.js +403 -541
  90. package/lib/session/load.js +48 -68
  91. package/lib/session/navigation.js +96 -87
  92. package/lib/session/npm.js +409 -345
  93. package/lib/session/ops.js +196 -63
  94. package/lib/session/pointer.js +23 -15
  95. package/lib/session/progress.js +33 -21
  96. package/lib/session/review.js +33 -54
  97. package/lib/session/tasks.js +333 -0
  98. package/lib/session/templates.js +51 -59
  99. package/lib/session/terminal.js +85 -136
  100. package/lib/session/unit.js +3 -4
  101. package/lib/session/update.js +76 -97
  102. package/lib/session/view.js +52 -83
  103. package/lib/session/watch.js +208 -204
  104. package/lib/session.js +112 -237
  105. package/lib/update.js +45 -116
  106. package/lib/utilities.js +67 -89
  107. package/lib/wrap.js +33 -44
  108. package/package.json +1 -1
  109. package/lib/session/todos.js +0 -293
@@ -9,7 +9,7 @@ const { jsonParse } = metautil;
9
9
  const ansi = require('./ansi.js');
10
10
  const { stripAnsi, visibleWidth } = ansi;
11
11
  const utilities = require('./utilities.js');
12
- const { npmBin, npmOpts, spawnBase } = utilities;
12
+ const { npmBin, npmOpts, spawnBase, unquote, dateStamp } = utilities;
13
13
 
14
14
  const MANIFEST = 'package.json';
15
15
  const SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies'];
@@ -36,7 +36,7 @@ const writeManifest = (root, pkg) => {
36
36
  };
37
37
 
38
38
  const scriptEntries = (pkg) => {
39
- const scripts = pkg && pkg.scripts;
39
+ const scripts = pkg.scripts;
40
40
  if (!scripts || typeof scripts !== 'object') return [];
41
41
  const entries = [];
42
42
  for (const name of Object.keys(scripts)) {
@@ -56,11 +56,8 @@ const binNames = (pkg) => {
56
56
  return base ? [base] : [];
57
57
  }
58
58
  if (!bin || typeof bin !== 'object' || Array.isArray(bin)) return [];
59
- const names = [];
60
- for (const name of Object.keys(bin)) {
61
- if (typeof bin[name] === 'string' && bin[name]) names.push(name);
62
- }
63
- return names;
59
+ const isPath = (name) => typeof bin[name] === 'string' && bin[name] !== '';
60
+ return Object.keys(bin).filter(isPath);
64
61
  };
65
62
 
66
63
  const declaredNames = (pkg) => {
@@ -68,7 +65,7 @@ const declaredNames = (pkg) => {
68
65
  for (const section of SECTIONS) {
69
66
  const map = pkg[section];
70
67
  if (!map || typeof map !== 'object') continue;
71
- for (const name of Object.keys(map)) names.push(name);
68
+ names.push(...Object.keys(map));
72
69
  }
73
70
  return names;
74
71
  };
@@ -107,36 +104,50 @@ const isPassingTest = (line) => {
107
104
  if (!text) return false;
108
105
  if (/^\s*[✔✓√] /.test(plain)) return true;
109
106
  if (/^ok \d+ /.test(text)) return true;
110
- if (/^\s*PASS\b/.test(plain)) return true;
111
- return false;
107
+ return /^\s*PASS\b/.test(plain);
112
108
  };
113
109
 
114
- const isNodeFrame = (line) => stripAnsi(line).includes('(node:');
115
-
116
110
  const indentStack = (line) => {
117
- const plain = stripAnsi(line);
118
- if (!/^\s*at\s/.test(plain)) return line;
119
- const lead = line.match(/^\s*/)?.[0] ?? '';
120
- const body = lead ? line.slice(lead.length) : line;
121
- return ` ${body}`;
111
+ if (!/^\s*at\s/.test(stripAnsi(line))) return line;
112
+ return ` ${line.trimStart()}`;
122
113
  };
123
114
 
124
115
  const withoutNodeFrame = (line) => {
125
- if (!isNodeFrame(line)) return line;
126
116
  const plain = stripAnsi(line);
127
- if (/\{\s*$/.test(plain)) return '{';
128
- return null;
117
+ if (!plain.includes('(node:')) return line;
118
+ return /\{\s*$/.test(plain) ? '{' : null;
129
119
  };
130
120
 
131
- const unquote = (value) => {
132
- const text = `${value ?? ''}`;
133
- if (text.length < 2) return text;
134
- const open = text[0];
135
- const close = text[text.length - 1];
136
- const code = open.charCodeAt(0);
137
- const quoted = open === close && (code === 34 || code === 39);
138
- if (!quoted) return text;
139
- return text.slice(1, -1);
121
+ const withSep = (dir) => {
122
+ const sep = dir.includes('\\') ? '\\' : '/';
123
+ if (dir.endsWith(sep)) return dir;
124
+ return `${dir}${sep}`;
125
+ };
126
+
127
+ const stripOne = (text, dir) => {
128
+ if (!dir) return text;
129
+ let next = text.split(withSep(dir)).join('');
130
+ if (next.includes(dir)) next = next.split(dir).join('.');
131
+ return next;
132
+ };
133
+
134
+ const relativize = (text, root) => {
135
+ if (!root) return text;
136
+ const slash = root.replaceAll('\\', '/');
137
+ const back = slash.replaceAll('/', '\\');
138
+ const resolved = path.resolve(root).replaceAll('\\', '/');
139
+ const resolvedBack = resolved.replaceAll('/', '\\');
140
+ let next = text;
141
+ for (const dir of [slash, back, resolved, resolvedBack]) {
142
+ next = stripOne(next, dir);
143
+ }
144
+ return next;
145
+ };
146
+
147
+ const traceLine = (line, root) => {
148
+ const next = withoutNodeFrame(line);
149
+ if (next === null) return null;
150
+ return relativize(indentStack(next), root);
140
151
  };
141
152
 
142
153
  const objectEnd = (lines, start) => {
@@ -173,19 +184,18 @@ const showValue = (value) => {
173
184
  };
174
185
 
175
186
  const parseObject = (lines) => {
176
- const body = lines.slice(1, -1);
177
187
  try {
178
188
  const value = JSON.parse(lines.join('\n'));
179
189
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
180
190
  return null;
181
191
  }
182
- const entries = [];
183
- for (const key of Object.keys(value)) {
184
- entries.push([key, showValue(value[key])]);
185
- }
192
+ const entries = Object.keys(value).map((key) => [
193
+ key,
194
+ showValue(value[key]),
195
+ ]);
186
196
  return entries.length ? entries : null;
187
197
  } catch {
188
- return parseInspect(body);
198
+ return parseInspect(lines.slice(1, -1));
189
199
  }
190
200
  };
191
201
 
@@ -193,18 +203,22 @@ const TABLE_KEY = '\x1b[900m';
193
203
  const TABLE_VAL = '\x1b[901m';
194
204
 
195
205
  const formatTable = (entries) => {
196
- let keyW = 0;
206
+ const prepared = [];
207
+ let keyWidth = 0;
197
208
  for (const entry of entries) {
198
- keyW = Math.max(keyW, visibleWidth(entry[0]));
209
+ const lines = `${entry[1]}`.split('\n').filter((line) => line.trim());
210
+ if (!lines.length) continue;
211
+ prepared.push([entry[0], lines]);
212
+ keyWidth = Math.max(keyWidth, visibleWidth(entry[0]));
199
213
  }
200
214
  const rows = [];
201
- for (const entry of entries) {
202
- const parts = `${entry[1]}`.split('\n');
203
- for (let i = 0; i < parts.length; i++) {
215
+ for (const entry of prepared) {
216
+ const lines = entry[1];
217
+ for (let i = 0; i < lines.length; i++) {
204
218
  const name = i === 0 ? entry[0] : '';
205
- const pad = ' '.repeat(Math.max(0, keyW - visibleWidth(name)));
219
+ const pad = ' '.repeat(Math.max(0, keyWidth - visibleWidth(name)));
206
220
  const key = ` ${pad}${name} `;
207
- const value = ` ${parts[i]} `;
221
+ const value = ` ${lines[i]} `;
208
222
  rows.push(`${TABLE_KEY}${key}${TABLE_VAL} ${value}`);
209
223
  }
210
224
  }
@@ -215,67 +229,30 @@ const foldObjects = (lines) => {
215
229
  const out = [];
216
230
  for (let i = 0; i < lines.length; i++) {
217
231
  const end = objectEnd(lines, i);
218
- if (end < 0) {
219
- out.push(lines[i]);
220
- continue;
221
- }
222
- const entries = parseObject(lines.slice(i, end + 1));
232
+ const entries = end < 0 ? null : parseObject(lines.slice(i, end + 1));
223
233
  if (!entries) {
224
234
  out.push(lines[i]);
225
235
  continue;
226
236
  }
227
237
  if (out.length && out[out.length - 1] !== '') out.push('');
228
- for (const row of formatTable(entries)) out.push(row);
238
+ out.push(...formatTable(entries));
229
239
  i = end;
230
240
  }
231
241
  return out;
232
242
  };
233
243
 
234
- const withSep = (dir) => {
235
- const sep = dir.includes('\\') ? '\\' : '/';
236
- if (dir.endsWith(sep)) return dir;
237
- return `${dir}${sep}`;
238
- };
239
-
240
- const stripOne = (text, dir) => {
241
- if (!dir) return text;
242
- let next = text.split(withSep(dir)).join('');
243
- if (next.includes(dir)) next = next.split(dir).join('.');
244
- return next;
245
- };
246
-
247
- const relativize = (text, root) => {
248
- if (!root) return text;
249
- const slash = root.replaceAll('\\', '/');
250
- const back = slash.replaceAll('/', '\\');
251
- const resolved = path.resolve(root).replaceAll('\\', '/');
252
- const resolvedBack = resolved.replaceAll('/', '\\');
253
- let next = text;
254
- for (const dir of [slash, back, resolved, resolvedBack]) {
255
- next = stripOne(next, dir);
256
- }
257
- return next;
258
- };
259
-
260
244
  const reduceOutput = (text, root, status) => {
261
- const lines = `${text ?? ''}`.split(/\r?\n/);
262
245
  const kept = [];
263
- for (const line of lines) {
246
+ for (const line of `${text ?? ''}`.split(/\r?\n/)) {
264
247
  if (isPassingTest(line)) continue;
265
- const next = withoutNodeFrame(line);
266
- if (next === null) continue;
267
- kept.push(relativize(indentStack(next), root));
248
+ const next = traceLine(line, root);
249
+ if (next !== null) kept.push(next);
268
250
  }
269
251
  const folded = foldObjects(kept);
270
252
  while (folded.length && folded[folded.length - 1] === '') folded.pop();
271
- if (status === '') {
272
- if (!folded.length) return '';
273
- return `${folded.join('\n')}\n`;
274
- }
275
- const code = status === undefined || status === null ? 'null' : status;
276
- const exit = `exit ${code}`;
277
- if (!folded.length) return `${exit}\n`;
278
- return `${folded.join('\n')}\n${exit}\n`;
253
+ if (status !== '') folded.push(`exit ${status ?? 'null'}`);
254
+ if (!folded.length) return '';
255
+ return `${folded.join('\n')}\n`;
279
256
  };
280
257
 
281
258
  const logFileName = (command, date, index) => {
@@ -284,13 +261,6 @@ const logFileName = (command, date, index) => {
284
261
  return `${date}-${safe}-${n}.log`;
285
262
  };
286
263
 
287
- const dateStamp = (now) => {
288
- const y = now.getFullYear();
289
- const m = `${now.getMonth() + 1}`.padStart(2, '0');
290
- const d = `${now.getDate()}`.padStart(2, '0');
291
- return `${y}-${m}-${d}`;
292
- };
293
-
294
264
  const LOG_DIR = '.log';
295
265
  const LOG_STALE_DAYS = 5;
296
266
  const DAY_MS = 24 * 60 * 60 * 1000;
@@ -326,10 +296,9 @@ const readLogNames = (dir) => {
326
296
 
327
297
  const staleLogFiles = (root, now = new Date()) => {
328
298
  const dir = path.join(root, LOG_DIR);
329
- const names = readLogNames(dir);
330
299
  const files = [];
331
300
  let bytes = 0;
332
- for (const name of names) {
301
+ for (const name of readLogNames(dir)) {
333
302
  if (!name.endsWith('.log') || !isStaleLog(name, now)) continue;
334
303
  const full = path.join(dir, name);
335
304
  let stat;
@@ -352,31 +321,22 @@ const removeStaleLogs = (root, now = new Date()) => {
352
321
  return found.bytes;
353
322
  };
354
323
 
355
- const scaled = (value) => {
356
- if (value >= 10) return `${Math.round(value)}`;
357
- return `${Math.round(value * 10) / 10}`;
358
- };
359
-
360
- const formatSize = (bytes) => {
361
- const size = bytes > 0 ? bytes : 0;
362
- if (size < 1024) return `${size}b`;
363
- const kb = size / 1024;
364
- if (kb < 1024) return `${scaled(kb)}k`;
365
- return `${scaled(kb / 1024)}m`;
324
+ const logSlot = (dir, name) => {
325
+ const rawName = name.replace(/\.log$/, '.raw.log');
326
+ const full = path.join(dir, name);
327
+ const rawFull = path.join(dir, rawName);
328
+ return { name, full, dir, rawName, rawFull };
366
329
  };
367
330
 
368
331
  const nextLogFile = (root, command, now = new Date()) => {
369
332
  const date = dateStamp(now);
370
333
  const dir = path.join(root, LOG_DIR);
371
- let index = 1;
372
- let name = logFileName(command, date, index);
373
- let full = path.join(dir, name);
374
- while (index < 100 && fs.existsSync(full)) {
375
- index += 1;
376
- name = logFileName(command, date, index);
377
- full = path.join(dir, name);
334
+ let slot = null;
335
+ for (let index = 1; index <= 100; index++) {
336
+ slot = logSlot(dir, logFileName(command, date, index));
337
+ if (!fs.existsSync(slot.full) && !fs.existsSync(slot.rawFull)) break;
378
338
  }
379
- return { name, full, dir };
339
+ return slot;
380
340
  };
381
341
 
382
342
  const logText = (text) =>
@@ -385,26 +345,14 @@ const logText = (text) =>
385
345
  .map((line) => line.trimEnd())
386
346
  .join('\n');
387
347
 
388
- const writeLog = (root, command, text, now) => {
389
- const file = nextLogFile(root, command, now);
390
- fs.mkdirSync(file.dir, { recursive: true });
391
- fs.writeFileSync(file.full, logText(text));
392
- return file.name;
393
- };
394
-
395
- const parseScriptLine = (text) => {
396
- const raw = `${text ?? ''}`.trim();
397
- const at = raw.indexOf(': ');
398
- if (at <= 0) return null;
399
- const name = raw.slice(0, at).trim();
400
- const command = raw.slice(at + 2).trim();
401
- if (!name || !command) return null;
402
- if (!/^[\w:.-]+$/.test(name)) return null;
403
- return { name, command };
348
+ const saveLogs = async (slot, reduced, raw) => {
349
+ await fs.promises.mkdir(slot.dir, { recursive: true });
350
+ await Promise.all([
351
+ fs.promises.writeFile(slot.full, logText(reduced)),
352
+ fs.promises.writeFile(slot.rawFull, logText(raw)),
353
+ ]);
404
354
  };
405
355
 
406
- const scriptLine = (entry) => `${entry.name}: ${entry.command}`;
407
-
408
356
  const orderedScripts = (pkg) => {
409
357
  const scripts = pkg.scripts;
410
358
  if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts)) {
@@ -413,47 +361,25 @@ const orderedScripts = (pkg) => {
413
361
  return scripts;
414
362
  };
415
363
 
416
- const upsertScript = (pkg, prev, next) => {
417
- const scripts = orderedScripts(pkg);
418
- const names = Object.keys(scripts);
364
+ const pickScripts = (scripts, names) => {
419
365
  const ordered = {};
420
- if (prev && names.includes(prev)) {
421
- for (const key of names) {
422
- if (key === prev) ordered[next.name] = next.command;
423
- else if (key !== next.name) ordered[key] = scripts[key];
424
- }
425
- } else {
426
- for (const key of names) ordered[key] = scripts[key];
427
- ordered[next.name] = next.command;
428
- }
429
- pkg.scripts = ordered;
430
- };
431
-
432
- const dropScript = (pkg, name) => {
433
- const scripts = orderedScripts(pkg);
434
- if (!Object.hasOwn(scripts, name)) return false;
435
- const ordered = {};
436
- for (const key of Object.keys(scripts)) {
437
- if (key !== name) ordered[key] = scripts[key];
438
- }
439
- pkg.scripts = ordered;
440
- return true;
366
+ for (const key of names) ordered[key] = scripts[key];
367
+ return ordered;
441
368
  };
442
369
 
443
- const moveScript = (pkg, name, delta) => {
370
+ const upsertScript = (pkg, prev, next) => {
444
371
  const scripts = orderedScripts(pkg);
445
372
  const names = Object.keys(scripts);
446
- const at = names.indexOf(name);
447
- if (at < 0) return false;
448
- const next = at + delta;
449
- if (next < 0 || next >= names.length) return false;
450
- const swap = names[at];
451
- names[at] = names[next];
452
- names[next] = swap;
373
+ if (!prev || !names.includes(prev)) {
374
+ pkg.scripts = { ...scripts, [next.name]: next.command };
375
+ return;
376
+ }
453
377
  const ordered = {};
454
- for (const key of names) ordered[key] = scripts[key];
378
+ for (const key of names) {
379
+ if (key === prev) ordered[next.name] = next.command;
380
+ else if (key !== next.name) ordered[key] = scripts[key];
381
+ }
455
382
  pkg.scripts = ordered;
456
- return true;
457
383
  };
458
384
 
459
385
  const saveScript = (root, prev, next) => {
@@ -464,23 +390,30 @@ const saveScript = (root, prev, next) => {
464
390
 
465
391
  const removeScript = (root, name) => {
466
392
  const pkg = readManifest(root);
467
- if (!pkg || !dropScript(pkg, name)) return false;
393
+ if (!pkg) return false;
394
+ const scripts = orderedScripts(pkg);
395
+ if (!Object.hasOwn(scripts, name)) return false;
396
+ const names = Object.keys(scripts).filter((key) => key !== name);
397
+ pkg.scripts = pickScripts(scripts, names);
468
398
  writeManifest(root, pkg);
469
399
  return true;
470
400
  };
471
401
 
472
402
  const reorderScript = (root, name, delta) => {
473
403
  const pkg = readManifest(root);
474
- if (!pkg || !moveScript(pkg, name, delta)) return false;
404
+ if (!pkg) return false;
405
+ const scripts = orderedScripts(pkg);
406
+ const names = Object.keys(scripts);
407
+ const at = names.indexOf(name);
408
+ const next = at + delta;
409
+ if (at < 0 || next < 0 || next >= names.length) return false;
410
+ names[at] = names[next];
411
+ names[next] = name;
412
+ pkg.scripts = pickScripts(scripts, names);
475
413
  writeManifest(root, pkg);
476
414
  return true;
477
415
  };
478
416
 
479
- const npmArgs = (entry) => {
480
- if (entry.kind === 'bin') return ['exec', '--', entry.name];
481
- return ['run', entry.name];
482
- };
483
-
484
417
  const signalPid = (pid, signal) => {
485
418
  try {
486
419
  process.kill(pid, signal);
@@ -491,7 +424,7 @@ const signalPid = (pid, signal) => {
491
424
  };
492
425
 
493
426
  const killTree = (proc) => {
494
- const pid = proc && proc.pid;
427
+ const pid = proc.pid;
495
428
  if (!pid) return;
496
429
  if (process.platform === 'win32') {
497
430
  const killer = spawn(
@@ -528,6 +461,8 @@ const startNpm = (cwd, entry, onData, onClose) => {
528
461
  settled = true;
529
462
  onClose({ status, text: raw });
530
463
  };
464
+ const isBin = entry.kind === 'bin';
465
+ const args = isBin ? ['exec', '--', entry.name] : ['run', entry.name];
531
466
  let child;
532
467
  try {
533
468
  const opts = npmOpts({
@@ -535,7 +470,7 @@ const startNpm = (cwd, entry, onData, onClose) => {
535
470
  env: commandEnv(),
536
471
  detached: process.platform !== 'win32',
537
472
  });
538
- child = spawn(npmBin(), npmArgs(entry), opts);
473
+ child = spawn(npmBin(), args, opts);
539
474
  } catch (error) {
540
475
  raw = `${error.message}\n`;
541
476
  finish(1);
@@ -546,13 +481,10 @@ const startNpm = (cwd, entry, onData, onClose) => {
546
481
  raw += chunk;
547
482
  onData(raw);
548
483
  };
549
- if (child.stdout) {
550
- child.stdout.setEncoding('utf8');
551
- child.stdout.on('data', push);
552
- }
553
- if (child.stderr) {
554
- child.stderr.setEncoding('utf8');
555
- child.stderr.on('data', push);
484
+ for (const stream of [child.stdout, child.stderr]) {
485
+ if (!stream) continue;
486
+ stream.setEncoding('utf8');
487
+ stream.on('data', push);
556
488
  }
557
489
  child.on('error', (error) => {
558
490
  if (!stopped) raw += `${error.message}\n`;
@@ -572,21 +504,19 @@ module.exports = {
572
504
  listCommands,
573
505
  reduceOutput,
574
506
  logFileName,
575
- writeLog,
507
+ nextLogFile,
508
+ saveLogs,
576
509
  staleLogFiles,
577
510
  removeStaleLogs,
578
- formatSize,
579
- parseScriptLine,
580
- scriptLine,
581
511
  saveScript,
582
512
  removeScript,
583
513
  reorderScript,
584
514
  startNpm,
585
515
  commandEnv,
586
516
  isPassingTest,
587
- withoutNodeFrame,
588
- indentStack,
517
+ traceLine,
589
518
  relativize,
590
519
  TABLE_KEY,
591
520
  TABLE_VAL,
521
+ formatTable,
592
522
  };