reslop 0.2.3 → 0.2.4

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 (80) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +8 -3
  3. package/bin/reslop.js +4 -2
  4. package/lib/api.js +2 -1
  5. package/lib/cli.js +19 -10
  6. package/lib/clipboard.js +6 -3
  7. package/lib/deps-diff.js +61 -115
  8. package/lib/deps.js +24 -61
  9. package/lib/detect.js +4 -2
  10. package/lib/diff/align.js +2 -1
  11. package/lib/diff/diff.js +6 -3
  12. package/lib/diff/display.js +2 -1
  13. package/lib/diff/inline.js +2 -1
  14. package/lib/diff/patch.js +2 -1
  15. package/lib/diff/unit.js +4 -2
  16. package/lib/editor.js +22 -4
  17. package/lib/files.js +38 -12
  18. package/lib/git-branches.js +107 -154
  19. package/lib/git-deps.js +20 -60
  20. package/lib/git-worktree.js +508 -0
  21. package/lib/git.js +23 -580
  22. package/lib/github.js +2 -1
  23. package/lib/gitlab.js +2 -1
  24. package/lib/highlight/bash.js +2 -1
  25. package/lib/highlight/css.js +2 -1
  26. package/lib/highlight/csv.js +2 -1
  27. package/lib/highlight/dot.js +2 -1
  28. package/lib/highlight/html.js +2 -1
  29. package/lib/highlight/json.js +2 -1
  30. package/lib/highlight/log.js +2 -1
  31. package/lib/highlight/txt.js +2 -1
  32. package/lib/highlight.js +2 -1
  33. package/lib/keys.js +2 -2
  34. package/lib/manifest.js +4 -2
  35. package/lib/npm-commands.js +83 -4
  36. package/lib/remote.js +20 -13
  37. package/lib/render/branches.js +6 -15
  38. package/lib/render/chrome.js +27 -7
  39. package/lib/render/commits.js +158 -46
  40. package/lib/render/diff.js +130 -47
  41. package/lib/render/files.js +4 -12
  42. package/lib/render/frame.js +3 -9
  43. package/lib/render/notes.js +4 -11
  44. package/lib/render/npm.js +1 -13
  45. package/lib/render/present.js +2 -1
  46. package/lib/render/primitives.js +28 -15
  47. package/lib/render/render.js +2 -1
  48. package/lib/render/unit.js +14 -11
  49. package/lib/report-model.js +6 -4
  50. package/lib/report-parse.js +8 -13
  51. package/lib/report-render.js +38 -21
  52. package/lib/report-run.js +12 -10
  53. package/lib/report-test.js +5 -4
  54. package/lib/report-tools.js +44 -18
  55. package/lib/review-merge.js +2 -1
  56. package/lib/review.js +6 -2
  57. package/lib/session/actions.js +49 -65
  58. package/lib/session/branches.js +13 -49
  59. package/lib/session/change-actions.js +2 -1
  60. package/lib/session/commit.js +34 -49
  61. package/lib/session/compose.js +37 -11
  62. package/lib/session/dispatch.js +17 -25
  63. package/lib/session/files-pane.js +6 -27
  64. package/lib/session/items.js +6 -3
  65. package/lib/session/lifecycle.js +8 -4
  66. package/lib/session/load.js +4 -2
  67. package/lib/session/navigation.js +1 -0
  68. package/lib/session/npm.js +54 -1
  69. package/lib/session/ops.js +15 -1
  70. package/lib/session/pointer.js +22 -3
  71. package/lib/session/review.js +2 -1
  72. package/lib/session/terminal.js +4 -2
  73. package/lib/session/update.js +2 -1
  74. package/lib/session/view.js +29 -3
  75. package/lib/session.js +37 -23
  76. package/lib/source.js +6 -3
  77. package/lib/update.js +5 -9
  78. package/lib/utilities.js +4 -2
  79. package/lib/wrap.js +3 -1
  80. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { SQUOTE, emit, skipQuoted } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { SQUOTE, emit, skipQuoted } = core;
4
5
 
5
6
  const colorHtmlTag = (tag) => {
6
7
  const out = [];
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit, readString } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit, readString } = core;
4
5
 
5
6
  const JSON_LITERAL = {
6
7
  true: 'literal',
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit } = core;
4
5
 
5
6
  const LOG_DATETIME_RE = new RegExp(
6
7
  '^(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}' +
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { emit } = require('./core.js');
3
+ const core = require('./core.js');
4
+ const { emit } = core;
4
5
 
5
6
  const highlightTxtLine = (line) => {
6
7
  if (!line) return [];
package/lib/highlight.js CHANGED
@@ -3,7 +3,8 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const { tokensText } = require('./highlight/core.js');
6
+ const core = require('./highlight/core.js');
7
+ const { tokensText } = core;
7
8
 
8
9
  const PLUGIN_DIR = path.join(__dirname, 'highlight');
9
10
  const SKIP = ['core.js'];
package/lib/keys.js CHANGED
@@ -4,8 +4,8 @@ const actions = require('./session/actions.js');
4
4
 
5
5
  const { ACTIONS, ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS } = actions;
6
6
  const { DIFF_DISABLED, FILES_DISABLED, FILES_GIT_DISABLED } = actions;
7
- const { FILES_HIDDEN, FILES_TODO_DISABLED, TODO_DISABLED, BRANCHES_DISABLED } =
8
- actions;
7
+ const { FILES_HIDDEN, FILES_TODO_DISABLED } = actions;
8
+ const { TODO_DISABLED, BRANCHES_DISABLED } = actions;
9
9
  const { COMMITS_DISABLED, UNIT_DISABLED, NPM_DISABLED } = actions;
10
10
  const { disabledActions, diffGitDisabled, branchGitDisabled } = actions;
11
11
  const { commitGitDisabled } = actions;
package/lib/manifest.js CHANGED
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('node:path');
4
- const { isHashObject, jsonParse } = require('metautil');
5
- const { parseVersion, cmpVersion } = require('./utilities.js');
4
+ const metautil = require('metautil');
5
+ const { isHashObject, jsonParse } = metautil;
6
+ const utilities = require('./utilities.js');
7
+ const { parseVersion, cmpVersion } = utilities;
6
8
 
7
9
  const MANIFEST = 'package.json';
8
10
  const LOCKFILE = 'package-lock.json';
@@ -2,10 +2,14 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const { spawn } = require('node:child_process');
6
- const { jsonParse } = require('metautil');
7
- const { stripAnsi, visibleWidth } = require('./ansi.js');
8
- const { npmBin, npmOpts, spawnBase } = require('./utilities.js');
5
+ const childProcess = require('node:child_process');
6
+ const { spawn } = childProcess;
7
+ const metautil = require('metautil');
8
+ const { jsonParse } = metautil;
9
+ const ansi = require('./ansi.js');
10
+ const { stripAnsi, visibleWidth } = ansi;
11
+ const utilities = require('./utilities.js');
12
+ const { npmBin, npmOpts, spawnBase } = utilities;
9
13
 
10
14
  const MANIFEST = 'package.json';
11
15
  const SECTIONS = ['dependencies', 'devDependencies', 'optionalDependencies'];
@@ -288,6 +292,78 @@ const dateStamp = (now) => {
288
292
  };
289
293
 
290
294
  const LOG_DIR = '.log';
295
+ const LOG_STALE_DAYS = 5;
296
+ const DAY_MS = 24 * 60 * 60 * 1000;
297
+ const LOG_DATE = /^(\d{4})-(\d{2})-(\d{2})-/;
298
+
299
+ const logDay = (name) => {
300
+ const match = LOG_DATE.exec(name);
301
+ if (!match) return null;
302
+ const year = Number(match[1]);
303
+ const month = Number(match[2]);
304
+ const day = Number(match[3]);
305
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
306
+ return Date.UTC(year, month - 1, day) / DAY_MS;
307
+ };
308
+
309
+ const todayIndex = (now) =>
310
+ Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()) / DAY_MS;
311
+
312
+ const isStaleLog = (name, now) => {
313
+ const day = logDay(name);
314
+ if (day === null) return false;
315
+ return todayIndex(now) - day > LOG_STALE_DAYS;
316
+ };
317
+
318
+ const readLogNames = (dir) => {
319
+ try {
320
+ return fs.readdirSync(dir);
321
+ } catch (error) {
322
+ if (error.code === 'ENOENT') return [];
323
+ throw error;
324
+ }
325
+ };
326
+
327
+ const staleLogFiles = (root, now = new Date()) => {
328
+ const dir = path.join(root, LOG_DIR);
329
+ const names = readLogNames(dir);
330
+ const files = [];
331
+ let bytes = 0;
332
+ for (const name of names) {
333
+ if (!name.endsWith('.log') || !isStaleLog(name, now)) continue;
334
+ const full = path.join(dir, name);
335
+ let stat;
336
+ try {
337
+ stat = fs.statSync(full);
338
+ } catch (error) {
339
+ if (error.code === 'ENOENT') continue;
340
+ throw error;
341
+ }
342
+ if (!stat.isFile()) continue;
343
+ bytes += stat.size;
344
+ files.push(full);
345
+ }
346
+ return { bytes, files };
347
+ };
348
+
349
+ const removeStaleLogs = (root, now = new Date()) => {
350
+ const found = staleLogFiles(root, now);
351
+ for (const full of found.files) fs.rmSync(full, { force: true });
352
+ return found.bytes;
353
+ };
354
+
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`;
366
+ };
291
367
 
292
368
  const nextLogFile = (root, command, now = new Date()) => {
293
369
  const date = dateStamp(now);
@@ -497,6 +573,9 @@ module.exports = {
497
573
  reduceOutput,
498
574
  logFileName,
499
575
  writeLog,
576
+ staleLogFiles,
577
+ removeStaleLogs,
578
+ formatSize,
500
579
  parseScriptLine,
501
580
  scriptLine,
502
581
  saveScript,
package/lib/remote.js CHANGED
@@ -1,10 +1,15 @@
1
1
  'use strict';
2
2
 
3
- const { delay, jsonParse, isHashObject } = require('metautil');
4
- const { oneLine } = require('./utilities.js');
5
- const { itemPath, isPathInScope } = require('./files.js');
6
- const { parseDiff, itemsFromFiles, isCtxType } = require('./diff/diff.js');
7
- const { foldDepItems } = require('./deps.js');
3
+ const metautil = require('metautil');
4
+ const { delay, jsonParse, isHashObject } = metautil;
5
+ const utilities = require('./utilities.js');
6
+ const { oneLine } = utilities;
7
+ const files = require('./files.js');
8
+ const { itemPath, isPathInScope } = files;
9
+ const diff = require('./diff/diff.js');
10
+ const { parseDiff, itemsFromFiles, isCtxType } = diff;
11
+ const deps = require('./deps.js');
12
+ const { foldDepItems } = deps;
8
13
 
9
14
  const RETRY_ATTEMPTS = 3;
10
15
  const RETRY_DELAY_MS = 200;
@@ -323,13 +328,15 @@ const changeFiles = (items) => {
323
328
 
324
329
  const loadChange = async (ref, options, source) => {
325
330
  const { client, url, diffUrl = url, diffAccept } = source;
326
- const paths = options.paths ?? [];
327
- const request = {
328
- fetch: options.fetch ?? globalThis.fetch,
329
- token: options.token ?? '',
330
- retry: options.retry ?? {},
331
- signal: options.signal,
332
- };
331
+ const {
332
+ cwd,
333
+ paths = [],
334
+ fetch = globalThis.fetch,
335
+ token = '',
336
+ retry = {},
337
+ signal,
338
+ } = options;
339
+ const request = { fetch, token, retry, signal };
333
340
  const jsonPage = await client.request(url, request);
334
341
  const data = client.parseJson(jsonPage.body, `${client.subject} payload`);
335
342
  const diffPage = await client.request(diffUrl, {
@@ -348,7 +355,7 @@ const loadChange = async (ref, options, source) => {
348
355
  if (isAbortError(error, request.signal)) throw error;
349
356
  // Discussion import is optional.
350
357
  }
351
- return { top: options.cwd, items, sourceLabel, change, imported };
358
+ return { top: cwd, items, sourceLabel, change, imported };
352
359
  };
353
360
 
354
361
  module.exports = {
@@ -1,11 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const ansi = require('../ansi.js');
4
+ const files = require('../files.js');
4
5
  const primitives = require('./primitives.js');
6
+ const { shortAge } = files;
5
7
  const { measureColumns, padColumns, joinColumns, paintColumns } = primitives;
6
8
 
7
9
  const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
8
- const { FILE_MARK, paneResult } = primitives;
10
+ const { FILE_MARK } = primitives;
9
11
  const { paintCursorList, paintListEdit } = primitives;
10
12
  const { branchLabel } = primitives;
11
13
 
@@ -61,7 +63,7 @@ const branchRowFg = (entry, selected) => {
61
63
 
62
64
  const branchRowFields = (entry) => {
63
65
  const sha = entry.sha ?? '';
64
- const date = entry.date ?? '';
66
+ const date = shortAge(entry.date ?? '');
65
67
  const gone = entry.gone ? 'gone' : '';
66
68
  const ahead = entry.ahead ? `⇡${entry.ahead}` : '';
67
69
  const behind = entry.behind ? `⇣${entry.behind}` : '';
@@ -110,9 +112,6 @@ const paintBranchRow = (entry, width, color, selected, cols) => {
110
112
  return out;
111
113
  };
112
114
 
113
- const paintBranchEdit = (compose, width, color) =>
114
- paintListEdit(compose, width, color);
115
-
116
115
  const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
117
116
  const branches = view.branches ?? [];
118
117
  const cols = measureColumns(branches, branchRowFields, BRANCH_COL_ALIGN);
@@ -124,10 +123,10 @@ const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
124
123
  const cursor = view.branchCursor ?? 0;
125
124
  const compose = view.compose;
126
125
  const editing = compose && compose.kind === 'branch';
127
- const tail = editing ? paintBranchEdit(compose, width, color) : null;
126
+ const tail = editing ? paintListEdit(compose, width, color) : null;
128
127
  const paintRow = (entry, rowWidth, rowColor, selected) =>
129
128
  paintBranchRow(entry, rowWidth, rowColor, selected, cols);
130
- const painted = paintCursorList(
129
+ return paintCursorList(
131
130
  branches,
132
131
  cursor,
133
132
  width,
@@ -138,14 +137,6 @@ const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
138
137
  tail,
139
138
  view.listScroll,
140
139
  );
141
- return paneResult({
142
- body: painted.body,
143
- fileHits: painted.fileHits,
144
- todoOwners: [],
145
- cursor: painted.cursor,
146
- splitBody: false,
147
- listScroll: painted.offset,
148
- });
149
140
  };
150
141
 
151
142
  module.exports = { paintBodyBranches };
@@ -5,7 +5,7 @@ const keys = require('../keys.js');
5
5
  const files = require('../files.js');
6
6
  const primitives = require('./primitives.js');
7
7
 
8
- const { itemPath, TODO_FILE, isTodosEntry, fileTotals } = files;
8
+ const { itemPath, REPO_TODOS_LABEL, isTodosEntry, fileTotals } = files;
9
9
  const { ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS, actionLetter } = keys;
10
10
  const { buttonWord } = keys;
11
11
  const { FILES_HIDDEN, FILES_GIT_DISABLED, BRANCHES_DISABLED } = keys;
@@ -152,10 +152,10 @@ const headerScreen = (view) => {
152
152
  if (pane === 'files') {
153
153
  const files = view.files ?? [];
154
154
  const entry = files[view.fileCursor ?? 0];
155
- if (isTodosEntry(entry)) return TODO_FILE;
155
+ if (isTodosEntry(entry)) return REPO_TODOS_LABEL;
156
156
  return '';
157
157
  }
158
- if (view.item && view.item.origin === 'todo') return TODO_FILE;
158
+ if (view.item && view.item.origin === 'todo') return REPO_TODOS_LABEL;
159
159
  return '';
160
160
  };
161
161
 
@@ -397,7 +397,9 @@ const statusStats = (view) => {
397
397
  const fallbackN = counts.staged ?? 0;
398
398
  const n = hasGit ? total.staged : fallbackN;
399
399
  const m = hasGit ? total.remaining : counted;
400
- return joinStatus([statusDelta(total), `${n}/${m}`]);
400
+ const git = joinStatus([statusDelta(total), `${n}/${m}`]);
401
+ if (!view.npmLogs) return git;
402
+ return joinStatus([view.npmLogs, git]);
401
403
  };
402
404
 
403
405
  const statusLayout = (view, status, width) => {
@@ -454,6 +456,7 @@ const paintChoicePrompt = (choices, width, color, prefix = '') => {
454
456
  };
455
457
 
456
458
  const STATUS_MARK = /(\+[0-9]+(?:\/[0-9]+)?|-[0-9]+(?:\/[0-9]+)?)/g;
459
+ const OLD_LOGS = /old logs \S+/;
457
460
 
458
461
  const paintStatusMark = (token, color) => {
459
462
  const bgRgb = THEME.chromeBg;
@@ -467,6 +470,18 @@ const paintStatusLeft = (text, color) => {
467
470
  const bgRgb = THEME.chromeBg;
468
471
  const muted = THEME.mutedFg;
469
472
  if (!color) return text;
473
+ const found = text.match(OLD_LOGS);
474
+ if (found) {
475
+ const at = found.index;
476
+ const token = found[0];
477
+ const before = text.slice(0, at);
478
+ const after = text.slice(at + token.length);
479
+ return (
480
+ paintStatusLeft(before, color) +
481
+ paint(token, THEME.shaFg, bgRgb, color) +
482
+ paintStatusLeft(after, color)
483
+ );
484
+ }
470
485
  let out = '';
471
486
  let last = 0;
472
487
  for (const match of text.matchAll(STATUS_MARK)) {
@@ -621,13 +636,18 @@ const dimFooterActions = (view) => {
621
636
  if (pane === 'commits') {
622
637
  const entry = selectedCommit(view);
623
638
  const staged = (view.counts?.staged ?? 0) > 0;
624
- if (!entry) return commitGitDisabled({ canCommit: staged });
625
- return commitGitDisabled({ ...entry, canCommit: staged });
639
+ const unstagedCurrent = !view.rev && !view.revShort;
640
+ return commitGitDisabled({
641
+ ...entry,
642
+ canCommit: staged,
643
+ unstagedCurrent,
644
+ });
626
645
  }
627
646
  if (pane === 'npm' && view.npmView) {
628
647
  if (view.npmRunning) return ['npmRerun'];
629
648
  return [];
630
649
  }
650
+ if (pane === 'npm' && !view.npmLogs) return ['npmLogs'];
631
651
  return [];
632
652
  };
633
653
 
@@ -641,7 +661,7 @@ const extraFooterActions = (view) => {
641
661
  if (pane === 'commits') return Object.keys(COMMIT_ACTIONS);
642
662
  if (pane === 'npm') {
643
663
  if (view.npmView) return ['npmStop', 'npmRerun'];
644
- return ['npmEdit', 'npmNew'];
664
+ return ['npmEdit', 'npmNew', 'npmDrop', 'npmLogs'];
645
665
  }
646
666
  return [];
647
667
  };