reslop 0.2.7 → 0.3.0

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 (158) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +504 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +16 -0
  7. package/lib/agents/models.js +460 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +123 -96
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +475 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +111 -48
  45. package/lib/{editor.js → input/editor.js} +7 -8
  46. package/lib/{keys.js → input/keys.js} +15 -43
  47. package/lib/input/line-edit.js +77 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +640 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +383 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +319 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +46 -14
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +9 -0
  81. package/lib/review/format.js +426 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +958 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -39
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +181 -145
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +85 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +82 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +75 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +249 -0
  111. package/lib/session/npm/npm.js +815 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +156 -63
  120. package/lib/session/tasks.js +320 -65
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +2 -2
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
package/lib/cli.js CHANGED
@@ -2,52 +2,27 @@
2
2
 
3
3
  const path = require('node:path');
4
4
 
5
- const metautil = require('metautil');
6
- const { exists } = metautil;
7
-
8
- const git = require('./git.js');
9
- const { createGitRepo } = git;
10
- const github = require('./github.js');
11
- const { githubToken, loadPullRequest } = github;
12
- const gitlab = require('./gitlab.js');
13
- const { gitlabToken, loadMergeRequest } = gitlab;
14
- const source = require('./source.js');
15
- const { selectChangeSource, createLoadedSource } = source;
16
- const session = require('./session.js');
17
- const { Session } = session;
18
- const capabilities = require('./capabilities.js');
19
- const { capabilitiesFor, attachCapabilities } = capabilities;
20
- const ansi = require('./ansi.js');
21
- const { setTheme } = ansi;
22
- const reportRun = require('./report-run.js');
23
- const { runCapture } = reportRun;
24
- const workspace = require('./workspace.js');
25
- const { workspaceFromArgs } = workspace;
5
+ const { exists } = require('metautil');
6
+
7
+ const { errorMessage } = require('./common/utilities.js');
8
+ const { createGitRepo } = require('./git/git.js');
9
+ const source = require('./source/source.js');
10
+ const { remoteKind, selectChangeSource, createLoadedSource } = source;
11
+ const { Session } = require('./session/session.js');
12
+ const { hasNotes } = require('./review/review.js');
13
+ const {
14
+ capabilitiesFor,
15
+ attachCapabilities,
16
+ } = require('./source/capabilities.js');
17
+ const { runCapture } = require('./report/run.js');
18
+ const { workspaceFromArgs } = require('./dashboard/workspace.js');
26
19
 
27
20
  const HEX_REV = /^[0-9a-f]{4,40}$/i;
28
21
  const HEAD_REV = /^HEAD([~^].*)?$/i;
29
22
  const RANGE_REV = /[~^]/;
30
23
 
31
24
  const USAGE =
32
- 'Usage: reslop [-n] [-r] [-light] [path | commit | pr-url | mr-url]';
33
-
34
- const REMOTE_CHANGE = {
35
- pr: {
36
- load: 'loadPullRequest',
37
- fallback: loadPullRequest,
38
- token: githubToken,
39
- target: (selected) => selected.pr,
40
- },
41
- mr: {
42
- load: 'loadMergeRequest',
43
- fallback: loadMergeRequest,
44
- token: gitlabToken,
45
- target: (selected) => selected.mr,
46
- },
47
- };
48
-
49
- const errorMessage = (reason) =>
50
- reason instanceof Error ? reason.message : `${reason ?? ''}`;
25
+ 'Usage: reslop [-r] [path | commit | pr-url | mr-url | issue-url]';
51
26
 
52
27
  const fail = (stderr, reason) => {
53
28
  stderr.write(`reslop: ${errorMessage(reason)}\n`);
@@ -56,27 +31,27 @@ const fail = (stderr, reason) => {
56
31
 
57
32
  const parseArgv = (argv) => {
58
33
  const paths = [];
59
- let newReview = false;
60
34
  let readOnly = false;
61
- let theme = 'dark';
62
35
  for (const arg of argv) {
63
- if (arg === '-n') newReview = true;
64
- else if (arg === '-r') readOnly = true;
65
- else if (arg === '-light') theme = 'light';
36
+ if (arg === '-r') readOnly = true;
66
37
  else if (arg.startsWith('-')) throw new Error(`unknown option ${arg}`);
67
38
  else paths.push(arg);
68
39
  }
69
- return { paths, newReview, readOnly, theme };
40
+ return { paths, readOnly };
70
41
  };
71
42
 
72
- const looksLikeRev = (spec) =>
73
- HEX_REV.test(spec) || HEAD_REV.test(spec) || RANGE_REV.test(spec);
43
+ const looksLikeRev = (spec) => {
44
+ const hex = HEX_REV.test(spec);
45
+ const head = HEAD_REV.test(spec);
46
+ const range = RANGE_REV.test(spec);
47
+ return hex || head || range;
48
+ };
74
49
 
75
50
  const resolveScope = async (cwd, paths, repo) => {
76
51
  if (!paths.length) return { rev: null, paths };
77
52
  const first = paths[0];
78
53
  if (await exists(path.resolve(cwd, first))) return { rev: null, paths };
79
- const resolved = repo.resolveRev(cwd, first);
54
+ const resolved = await repo.resolveRev(cwd, first);
80
55
  if (resolved) {
81
56
  if (paths.length > 1) throw new Error('use a commit or a path, not both');
82
57
  return { rev: resolved, paths: [] };
@@ -116,22 +91,21 @@ const localOptions = async (deps, args, selected, cwd) => {
116
91
 
117
92
  const remoteRepo = (deps, selected, env, cwd) => {
118
93
  if (deps.repo) return deps.repo;
119
- const remote = REMOTE_CHANGE[selected.kind];
120
- const loader = deps[remote.load] ?? remote.fallback;
94
+ const remote = remoteKind(selected);
95
+ const loader = deps[remote.option] ?? remote.load;
121
96
  const token = remote.token(env);
122
97
  const loadRemote = async (_cwd, _paths, options = {}) => {
123
- const loaded = await loader(remote.target(selected), {
124
- cwd,
125
- paths: selected.paths,
126
- token,
127
- fetch: deps.fetch,
128
- signal: options.signal,
129
- });
98
+ const target = remote.target(selected);
99
+ const paths = selected.paths;
100
+ const fetch = deps.fetch;
101
+ const signal = options.signal;
102
+ const request = { cwd, paths, token, fetch, signal };
103
+ const loaded = await loader(target, request);
130
104
  const items = loaded.items ?? [];
131
105
  return { ...loaded, items: [...items] };
132
106
  };
133
107
  return createLoadedSource({ items: [] }, selected.kind, {
134
- loadAsync: loadRemote,
108
+ load: loadRemote,
135
109
  });
136
110
  };
137
111
 
@@ -145,7 +119,8 @@ const loadSession = async (proc, deps, args) => {
145
119
  const env = proc.env ?? {};
146
120
  const cwd = deps.cwd ?? proc.cwd();
147
121
  const selected = selectChangeSource(args.paths);
148
- const extra = REMOTE_CHANGE[selected.kind]
122
+ const kind = remoteKind(selected);
123
+ const extra = kind
149
124
  ? remoteOptions(deps, args, selected, cwd, env)
150
125
  : await localOptions(deps, args, selected, cwd);
151
126
  const session = new Session({
@@ -154,12 +129,11 @@ const loadSession = async (proc, deps, args) => {
154
129
  stdout: proc.stdout,
155
130
  color: proc.stdout.isTTY === true && !env.NO_COLOR,
156
131
  startPane: 'files',
157
- newReview: args.newReview,
158
132
  readOnly: args.readOnly,
159
133
  audit: true,
160
134
  ...extra,
161
135
  });
162
- if (session.pane !== 'repos') session.ensureRepo();
136
+ if (session.pane !== 'repos') await session.ensureRepo();
163
137
  return session;
164
138
  };
165
139
 
@@ -175,21 +149,21 @@ const run = async (proc, deps = {}) => {
175
149
  if (argv[0] === 't') return runCapture(proc, deps);
176
150
  const stdout = proc.stdout;
177
151
  const stderr = proc.stderr;
178
- let args;
152
+ let args = null;
179
153
  try {
180
154
  args = parseArgv(argv);
181
155
  } catch (error) {
182
156
  fail(stderr, error);
183
157
  stderr.write(`${USAGE}\n`);
184
- return 1;
185
158
  }
186
- setTheme(args.theme);
187
- let session;
159
+ if (!args) return 1;
160
+ let session = null;
188
161
  try {
189
162
  session = await loadSession(proc, deps, args);
190
163
  } catch (error) {
191
- return fail(stderr, error);
164
+ fail(stderr, error);
192
165
  }
166
+ if (!session) return 1;
193
167
  const tty = proc.stdin.isTTY === true && stdout.isTTY === true;
194
168
  if (tty) return runUi(session, proc);
195
169
  if (session.pane === 'repos') {
@@ -201,7 +175,8 @@ const run = async (proc, deps = {}) => {
201
175
  } catch (error) {
202
176
  return fail(stderr, error);
203
177
  }
204
- if (!session.items.length) {
178
+ const noted = session.notes ? hasNotes(session.notes) : false;
179
+ if (!session.items.length && !noted) {
205
180
  stdout.write('nothing to review\n');
206
181
  return 0;
207
182
  }
@@ -1,28 +1,20 @@
1
1
  'use strict';
2
2
 
3
- const manifest = require('./manifest.js');
4
- const { listFilePath } = manifest;
3
+ const path = require('node:path');
5
4
 
6
- const REVIEW_DIR = '.review';
5
+ const REVIEW_DIR = '.plan';
6
+ const LOG_DIR = '.log';
7
+ const ISSUE_FILE = 'issue';
7
8
  const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
8
9
  const TASKS_FILE = 'TODOs';
9
- const REPO_TASKS_LABEL = [
10
- 'Project Tasks: Feature requests, Bug reports,',
11
- 'Technical debt, Research, Security',
12
- ].join(' ');
13
- const TASK_KINDS = [
14
- { id: 'features', title: 'Feature requests' },
15
- { id: 'bugs', title: 'Bug reports' },
16
- { id: 'debt', title: 'Technical debt' },
17
- { id: 'research', title: 'Research' },
18
- { id: 'security', title: 'Security' },
19
- ];
20
- const TASK_KIND_IDS = new Set(TASK_KINDS.map((kind) => kind.id));
10
+ const { label: REPO_TASKS_LABEL, kinds: TASK_KINDS } = require('./tasks.json');
11
+
12
+ const TASK_KIND_IDS = TASK_KINDS.map((kind) => kind.id);
21
13
  const TASK_KIND_ALIAS = { backlog: 'debt', issues: 'debt' };
22
14
 
23
15
  const taskKind = (todo) => {
24
16
  const kind = todo && todo.kind;
25
- if (TASK_KIND_IDS.has(kind)) return kind;
17
+ if (TASK_KIND_IDS.includes(kind)) return kind;
26
18
  return TASK_KIND_ALIAS[kind] ?? 'debt';
27
19
  };
28
20
 
@@ -33,17 +25,9 @@ const taskTitle = (id) => {
33
25
  return TASK_KINDS[0].title;
34
26
  };
35
27
  const TOTAL_LABEL = 'total';
36
- const AGE_UNIT = {
37
- second: 's',
38
- minute: 'm',
39
- hour: 'h',
40
- day: 'd',
41
- week: 'w',
42
- month: 'mo',
43
- year: 'y',
44
- };
45
- const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
46
- const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
28
+ const MANIFEST = 'package.json';
29
+ const LOCKFILE = 'package-lock.json';
30
+ const DEP_KIND = { [MANIFEST]: 'manifest', [LOCKFILE]: 'lockfile' };
47
31
 
48
32
  const isPathInScope = (rel, paths) => {
49
33
  if (!paths.length) return true;
@@ -57,6 +41,34 @@ const isPathInScope = (rel, paths) => {
57
41
 
58
42
  const itemPath = (item) => item?.file?.newPath || item?.file?.oldPath || '';
59
43
 
44
+ const posixRel = (rel) => `${rel ?? ''}`.replaceAll('\\', '/');
45
+
46
+ const depFileMeta = (rel) => {
47
+ const normalized = posixRel(rel);
48
+ const kind = DEP_KIND[path.posix.basename(normalized)];
49
+ if (!normalized || !kind) return null;
50
+ return { kind, dir: path.posix.dirname(normalized) };
51
+ };
52
+
53
+ const relOf = (dir, name) => (dir === '.' ? name : `${dir}/${name}`);
54
+
55
+ const listFilePath = (rel) => {
56
+ const meta = depFileMeta(rel);
57
+ if (meta && meta.kind === 'lockfile') return relOf(meta.dir, MANIFEST);
58
+ return posixRel(rel);
59
+ };
60
+
61
+ const groupByPath = (items) => {
62
+ const byPath = new Map();
63
+ for (const item of items) {
64
+ const rel = itemPath(item);
65
+ const list = byPath.get(rel);
66
+ if (list) list.push(item);
67
+ else byPath.set(rel, [item]);
68
+ }
69
+ return byPath;
70
+ };
71
+
60
72
  const listPath = (item) => listFilePath(itemPath(item));
61
73
 
62
74
  const isTaskItem = (item) => item?.origin === 'task';
@@ -102,32 +114,6 @@ const lineDelta = ({ hunk, blockId }) => {
102
114
  return delta;
103
115
  };
104
116
 
105
- const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
106
-
107
- const shortAge = (text) => {
108
- const raw = `${text ?? ''}`.trim();
109
- if (/^yesterday$/i.test(raw)) return '1d ago';
110
- const match = AGE_AGO.exec(raw);
111
- if (match) return shortUnit(match[1], match[2].toLowerCase());
112
- const brief = AGE_SHORT.exec(raw);
113
- if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
114
- return raw;
115
- };
116
-
117
- const relativeAge = (thenMs, nowMs = Date.now()) => {
118
- const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
119
- if (diff < 90) return shortUnit(diff, 'second');
120
- const minutes = Math.round(diff / 60);
121
- if (minutes < 90) return shortUnit(minutes, 'minute');
122
- const hours = Math.round(minutes / 60);
123
- if (hours < 36) return shortUnit(hours, 'hour');
124
- const days = Math.round(hours / 24);
125
- if (days < 14) return shortUnit(days, 'day');
126
- if (days < 70) return shortUnit(Math.round(days / 7), 'week');
127
- if (days < 365) return shortUnit(Math.round(days / 30), 'month');
128
- return shortUnit(Math.round(days / 365), 'year');
129
- };
130
-
131
117
  const emptyCounts = () => ({
132
118
  remaining: 0,
133
119
  added: 0,
@@ -226,12 +212,21 @@ const fileTotals = (files) => {
226
212
 
227
213
  module.exports = {
228
214
  REVIEW_DIR,
215
+ LOG_DIR,
216
+ ISSUE_FILE,
217
+ posixRel,
218
+ groupByPath,
229
219
  TASKS_FILE,
230
220
  REPO_TASKS_LABEL,
231
221
  TASK_KINDS,
232
222
  taskKind,
233
223
  taskTitle,
234
224
  TOTAL_LABEL,
225
+ MANIFEST,
226
+ LOCKFILE,
227
+ depFileMeta,
228
+ relOf,
229
+ listFilePath,
235
230
  isPathInScope,
236
231
  itemPath,
237
232
  listPath,
@@ -244,6 +239,4 @@ module.exports = {
244
239
  lineDelta,
245
240
  fileEntries,
246
241
  fileTotals,
247
- relativeAge,
248
- shortAge,
249
242
  };
@@ -58,4 +58,4 @@ const spansFor = (shown, full, query, hit) => {
58
58
  }));
59
59
  };
60
60
 
61
- module.exports = { matchesIn, fileMatches, activeHit, spansFor };
61
+ module.exports = { fileMatches, activeHit, spansFor };
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ const { bytesToSize } = require('metautil');
4
+ const { trimText, pad2 } = require('./utilities.js');
5
+
6
+ const AGE_UNIT = {
7
+ second: 's',
8
+ minute: 'm',
9
+ hour: 'h',
10
+ day: 'd',
11
+ week: 'w',
12
+ month: 'mo',
13
+ year: 'y',
14
+ };
15
+ const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
16
+ const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
17
+ const SECONDS_AGO = /^\d+s ago$/;
18
+
19
+ const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
20
+
21
+ const AGE_SEC = {
22
+ s: 1,
23
+ m: 60,
24
+ h: 3600,
25
+ d: 86400,
26
+ w: 604800,
27
+ mo: 2592000,
28
+ y: 31536000,
29
+ };
30
+
31
+ const ageParts = (text) => {
32
+ const raw = trimText(text);
33
+ if (/^yesterday$/i.test(raw)) return { count: 1, unit: 'd' };
34
+ const long = AGE_AGO.exec(raw);
35
+ if (long) return { count: long[1], unit: AGE_UNIT[long[2].toLowerCase()] };
36
+ const brief = AGE_SHORT.exec(raw);
37
+ if (brief) return { count: brief[1], unit: brief[2].toLowerCase() };
38
+ return null;
39
+ };
40
+
41
+ const shortAge = (text) => {
42
+ const age = ageParts(text);
43
+ return age ? `${age.count}${age.unit} ago` : trimText(text);
44
+ };
45
+
46
+ const ageSeconds = (text) => {
47
+ const age = ageParts(text);
48
+ if (!age) return Number.POSITIVE_INFINITY;
49
+ return Number(age.count) * AGE_SEC[age.unit];
50
+ };
51
+
52
+ const relativeAge = (thenMs, nowMs = Date.now()) => {
53
+ const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
54
+ if (diff < 90) return shortUnit(diff, 'second');
55
+ const minutes = Math.round(diff / 60);
56
+ if (minutes < 90) return shortUnit(minutes, 'minute');
57
+ const hours = Math.round(minutes / 60);
58
+ if (hours < 36) return shortUnit(hours, 'hour');
59
+ const days = Math.round(hours / 24);
60
+ if (days < 14) return shortUnit(days, 'day');
61
+ if (days < 70) return shortUnit(Math.round(days / 7), 'week');
62
+ if (days < 365) return shortUnit(Math.round(days / 30), 'month');
63
+ return shortUnit(Math.round(days / 365), 'year');
64
+ };
65
+
66
+ const trimmed = (value) =>
67
+ value >= 10 ? `${Math.round(value)}` : `${Math.round(value * 10) / 10}`;
68
+
69
+ const num = (value) => {
70
+ const n = Math.max(0, Math.round(value));
71
+ if (n >= 1e6) return `${trimmed(n / 1e6)}m`;
72
+ if (n >= 1e4) return `${trimmed(n / 1e3)}k`;
73
+ return `${n}`;
74
+ };
75
+
76
+ const size = (bytes) => bytesToSize(bytes > 0 ? bytes : 0);
77
+
78
+ const ago = (at, now) => {
79
+ if (!at) return '';
80
+ const text = relativeAge(at, now);
81
+ return SECONDS_AGO.test(text) ? '<1m ago' : text;
82
+ };
83
+
84
+ const duration = (ms) => {
85
+ const value = Math.max(0, ms);
86
+ if (value < 1000) return `${Math.round(value)}ms`;
87
+ if (value < 60000) return `${(value / 1000).toFixed(1)}s`;
88
+ const minutes = Math.floor(value / 60000);
89
+ const seconds = Math.floor((value % 60000) / 1000);
90
+ return `${minutes}m${pad2(seconds)}s`;
91
+ };
92
+
93
+ module.exports = {
94
+ shortAge,
95
+ ageSeconds,
96
+ relativeAge,
97
+ num,
98
+ size,
99
+ ago,
100
+ duration,
101
+ };
@@ -1,48 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const childProcess = require('node:child_process');
4
- const { spawn, spawnSync } = childProcess;
5
- const metautil = require('metautil');
6
- const { split } = metautil;
3
+ const { spawn } = require('node:child_process');
7
4
 
8
5
  const IS_WIN = process.platform === 'win32';
6
+ const INSTALL_MS = 120000;
9
7
  const DEFAULT_ENCODING = 'utf8';
10
8
  const DEFAULT_MAX_BUFFER = 32 * 1024 * 1024;
11
9
 
12
- const parseVersion = (text) => {
13
- const match = /^(\d+)\.(\d+)\.(\d+)/.exec(`${text ?? ''}`);
14
- if (!match) return null;
15
- const major = Number(match[1]);
16
- const minor = Number(match[2]);
17
- const patch = Number(match[3]);
18
- return { major, minor, patch };
19
- };
20
-
21
- const cmpVersion = (left, right) =>
22
- left.major - right.major ||
23
- left.minor - right.minor ||
24
- left.patch - right.patch;
25
-
26
- const oneLine = (text, fallback = '') =>
27
- split(`${text ?? ''}`.trim(), '\n')[0] || fallback;
28
-
29
- const asText = (value) => (typeof value === 'string' ? value : '');
30
-
31
- const unquote = (value) => {
32
- const text = `${value ?? ''}`;
33
- if (text.length < 2) return text;
34
- const open = text[0];
35
- const quoted = open === text.at(-1) && (open === '"' || open === '\x27');
36
- return quoted ? text.slice(1, -1) : text;
37
- };
38
-
39
- const pad2 = (n) => `${n}`.padStart(2, '0');
40
-
41
- const dateStamp = (date) => {
42
- const month = pad2(date.getMonth() + 1);
43
- return `${date.getFullYear()}-${month}-${pad2(date.getDate())}`;
44
- };
45
-
46
10
  const npmBin = () => (IS_WIN ? 'npm.cmd' : 'npm');
47
11
 
48
12
  const spawnBase = (extra = {}) => ({ windowsHide: true, ...extra });
@@ -50,36 +14,6 @@ const spawnBase = (extra = {}) => ({ windowsHide: true, ...extra });
50
14
  const npmOpts = (extra = {}, platform = process.platform) =>
51
15
  spawnBase({ shell: platform === 'win32', ...extra });
52
16
 
53
- const listen = (emitter, event, handler) => {
54
- if (!emitter || typeof emitter.on !== 'function') return () => {};
55
- emitter.on(event, handler);
56
- return () => {
57
- if (typeof emitter.removeListener === 'function') {
58
- return void emitter.removeListener(event, handler);
59
- }
60
- if (typeof emitter.off === 'function') emitter.off(event, handler);
61
- };
62
- };
63
-
64
- const watchResize = (stdout, proc, onResize, signal) => {
65
- const stopStdout = listen(stdout, 'resize', onResize);
66
- let stopProc = () => {};
67
- try {
68
- stopProc = listen(proc, 'SIGWINCH', onResize);
69
- } catch {
70
- // SIGWINCH is not available on Windows
71
- }
72
- const stop = () => {
73
- stopStdout();
74
- stopProc();
75
- };
76
- if (signal) {
77
- if (signal.aborted) stop();
78
- else signal.addEventListener('abort', stop, { once: true });
79
- }
80
- return stop;
81
- };
82
-
83
17
  const codeError = (message, code) =>
84
18
  Object.assign(new Error(message), { code });
85
19
 
@@ -94,19 +28,6 @@ const procResult = (values = {}) => ({
94
28
  error: values.error ?? null,
95
29
  });
96
30
 
97
- const runProcSync = (cmd, args, options = {}) => {
98
- const opts = spawnBase({
99
- cwd: options.cwd,
100
- env: options.env,
101
- encoding: options.encoding ?? DEFAULT_ENCODING,
102
- input: options.input,
103
- maxBuffer: options.maxBuffer ?? DEFAULT_MAX_BUFFER,
104
- timeout: options.timeout,
105
- });
106
- if (options.shell) opts.shell = true;
107
- return procResult(spawnSync(cmd, args, opts));
108
- };
109
-
110
31
  const listenChild = (child, options, resolve) => {
111
32
  const encoding = options.encoding ?? DEFAULT_ENCODING;
112
33
  const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
@@ -156,6 +77,8 @@ const listenChild = (child, options, resolve) => {
156
77
  finish(procResult({ status, stdout, stderr }));
157
78
  });
158
79
  if (!child.stdin) return;
80
+ // A child that exits before reading its input reports that by its status
81
+ child.stdin.on('error', () => {});
159
82
  try {
160
83
  if (options.input) child.stdin.end(options.input);
161
84
  else child.stdin.end();
@@ -173,34 +96,23 @@ const runProc = (cmd, args, options = {}) =>
173
96
  }
174
97
  const spawnOpts = { cwd: options.cwd, env: options.env };
175
98
  if (options.shell) spawnOpts.shell = true;
176
- let child;
99
+ let child = null;
177
100
  try {
178
101
  child = spawn(cmd, args, spawnBase(spawnOpts));
179
102
  } catch (error) {
180
103
  resolve(procResult({ error }));
181
- return;
182
104
  }
105
+ if (!child) return;
183
106
  listenChild(child, options, resolve);
184
107
  });
185
108
 
186
109
  module.exports = {
187
110
  IS_WIN,
188
- DEFAULT_ENCODING,
189
- DEFAULT_MAX_BUFFER,
190
- parseVersion,
191
- cmpVersion,
192
- oneLine,
193
- asText,
194
- unquote,
195
- pad2,
196
- dateStamp,
111
+ INSTALL_MS,
197
112
  npmBin,
198
113
  spawnBase,
199
114
  npmOpts,
200
- listen,
201
- watchResize,
202
115
  abortError,
203
116
  isAbort,
204
- runProcSync,
205
117
  runProc,
206
118
  };
@@ -0,0 +1,27 @@
1
+ {
2
+ "label": "Project Tasks: Feature requests, Improvements, Bug reports, Refactoring, Research, Security",
3
+ "kinds": [
4
+ {
5
+ "id": "features",
6
+ "title": "Feature requests and Enhancements",
7
+ "short": "Features"
8
+ },
9
+ { "id": "improvements", "title": "Improvements", "short": "Improvements" },
10
+ { "id": "bugs", "title": "Bug Reports and Fixes", "short": "Bug Reports" },
11
+ {
12
+ "id": "debt",
13
+ "title": "Refactoring and Technical debt",
14
+ "short": "Refactoring"
15
+ },
16
+ {
17
+ "id": "research",
18
+ "title": "Research and Experiments",
19
+ "short": "Research"
20
+ },
21
+ {
22
+ "id": "security",
23
+ "title": "Security and Compliance",
24
+ "short": "Security"
25
+ }
26
+ ]
27
+ }