reslop 0.1.9 → 0.2.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 (94) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +2 -1
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +4 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +7 -14
  7. package/lib/cli.js +28 -47
  8. package/lib/clipboard.js +1 -1
  9. package/lib/{deps/changes.js → deps-diff.js} +309 -19
  10. package/lib/deps.js +498 -29
  11. package/lib/detect.js +1 -7
  12. package/lib/diff/align.js +7 -10
  13. package/lib/diff/diff.js +53 -0
  14. package/lib/diff/display.js +5 -4
  15. package/lib/diff/inline.js +26 -21
  16. package/lib/diff/items.js +6 -4
  17. package/lib/diff/patch.js +1 -3
  18. package/lib/diff/unit.js +392 -0
  19. package/lib/files.js +5 -8
  20. package/lib/{git/branches.js → git-branches.js} +6 -5
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +589 -68
  23. package/lib/github.js +28 -197
  24. package/lib/gitlab.js +24 -195
  25. package/lib/highlight/bash.js +1 -3
  26. package/lib/highlight/css.js +1 -3
  27. package/lib/highlight/js.js +11 -14
  28. package/lib/highlight.js +6 -9
  29. package/lib/keys.js +14 -8
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +14 -68
  33. package/lib/render/chrome.js +24 -9
  34. package/lib/render/commits.js +6 -59
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +6 -34
  37. package/lib/render/frame.js +23 -12
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +45 -0
  40. package/lib/{render.js → render/render.js} +5 -5
  41. package/lib/render/unit.js +41 -0
  42. package/lib/review.js +750 -14
  43. package/lib/select.js +5 -10
  44. package/lib/session/accessors.js +9 -1
  45. package/lib/session/actions.js +32 -16
  46. package/lib/session/branches.js +116 -128
  47. package/lib/session/change-actions.js +202 -123
  48. package/lib/session/commit.js +105 -112
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +104 -60
  51. package/lib/session/files-pane.js +142 -148
  52. package/lib/session/items.js +22 -30
  53. package/lib/session/lifecycle.js +44 -44
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +12 -1
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +54 -57
  58. package/lib/session/review.js +23 -37
  59. package/lib/session/terminal.js +6 -12
  60. package/lib/session/todos.js +8 -12
  61. package/lib/session/unit.js +208 -0
  62. package/lib/session/update.js +33 -46
  63. package/lib/session/view.js +78 -9
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +21 -246
  66. package/lib/source.js +3 -3
  67. package/lib/update.js +40 -75
  68. package/lib/{git/proc.js → utilities.js} +75 -11
  69. package/lib/wrap.js +1 -3
  70. package/package.json +1 -1
  71. package/lib/deps/items.js +0 -134
  72. package/lib/deps/manifest.js +0 -152
  73. package/lib/deps/merge.js +0 -120
  74. package/lib/deps/proposals.js +0 -164
  75. package/lib/deps/reports.js +0 -180
  76. package/lib/deps/rows.js +0 -176
  77. package/lib/deps/scan.js +0 -138
  78. package/lib/deps/usage.js +0 -68
  79. package/lib/deps/versions.js +0 -119
  80. package/lib/diff.js +0 -33
  81. package/lib/git/changes.js +0 -164
  82. package/lib/git/read.js +0 -253
  83. package/lib/git/run.js +0 -100
  84. package/lib/npm/commands.js +0 -100
  85. package/lib/remote/changes.js +0 -37
  86. package/lib/remote/headers.js +0 -36
  87. package/lib/remote/http.js +0 -122
  88. package/lib/remote/notes.js +0 -94
  89. package/lib/review/format.js +0 -434
  90. package/lib/review/storage.js +0 -150
  91. package/lib/review/store.js +0 -178
  92. package/lib/review/templates.js +0 -49
  93. package/lib/session/origins.js +0 -7
  94. package/lib/sys.js +0 -84
@@ -1,150 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const { REVIEW_DIR } = require('../files.js');
7
- const format = require('./format.js');
8
- const { serializeReview, parseReview, parseFrontmatterStatus } = format;
9
- const { hasNotes } = require('./store.js');
10
-
11
- const TEMPLATES_FILE = 'templates.json';
12
- const AUTOSAVE_MS = 3000;
13
- const REVIEW_NAME = /^(\d{4}-\d{2}-\d{2})-(\d+)\.md$/;
14
-
15
- const toInt = (text) => parseInt(text, 10);
16
-
17
- const pad2 = (n) => `${n}`.padStart(2, '0');
18
-
19
- const padIndex = (n) => (n < 100 ? pad2(n) : `${n}`);
20
-
21
- const dateStamp = (date) => {
22
- const y = date.getFullYear();
23
- const month = pad2(date.getMonth() + 1);
24
- const day = pad2(date.getDate());
25
- return `${y}-${month}-${day}`;
26
- };
27
-
28
- const allocateReviewPath = (dir, date, existingNames) => {
29
- const stamp = dateStamp(date);
30
- let max = -1;
31
- for (const name of existingNames) {
32
- const match = REVIEW_NAME.exec(name);
33
- if (!match) continue;
34
- if (match[1] !== stamp) continue;
35
- const n = toInt(match[2]);
36
- if (n > max) max = n;
37
- }
38
- const file = `${stamp}-${padIndex(max + 1)}.md`;
39
- return path.join(dir, REVIEW_DIR, file);
40
- };
41
-
42
- const listReviewNames = (dir, readdirSync = fs.readdirSync) => {
43
- const folder = path.join(dir, REVIEW_DIR);
44
- try {
45
- return readdirSync(folder);
46
- } catch {
47
- return [];
48
- }
49
- };
50
-
51
- const compareReviewNames = (left, right) => {
52
- const a = REVIEW_NAME.exec(left);
53
- const b = REVIEW_NAME.exec(right);
54
- if (!a && !b) return 0;
55
- if (!a) return -1;
56
- if (!b) return 1;
57
- if (a[1] < b[1]) return -1;
58
- if (a[1] > b[1]) return 1;
59
- return toInt(a[2]) - toInt(b[2]);
60
- };
61
-
62
- const latestReviewName = (names) => {
63
- let best = '';
64
- for (const name of names) {
65
- if (!REVIEW_NAME.test(name)) continue;
66
- if (!best || compareReviewNames(name, best) > 0) best = name;
67
- }
68
- return best;
69
- };
70
-
71
- const readReviewStatus = (reviewPath, readFileSync = fs.readFileSync) => {
72
- try {
73
- const text = readFileSync(reviewPath, 'utf8');
74
- return parseFrontmatterStatus(text);
75
- } catch {
76
- return '';
77
- }
78
- };
79
-
80
- const resolveReviewPath = (dir, date, existingNames, options = {}) => {
81
- const forceNew = options.forceNew ?? false;
82
- const readFileSync = options.readFileSync ?? fs.readFileSync;
83
- if (!forceNew) {
84
- const latest = latestReviewName(existingNames);
85
- if (latest) {
86
- const reviewPath = path.join(dir, REVIEW_DIR, latest);
87
- const status = readReviewStatus(reviewPath, readFileSync);
88
- if (status === 'editing') return { reviewPath, resume: true };
89
- }
90
- }
91
- const reviewPath = allocateReviewPath(dir, date, existingNames);
92
- const resume = false;
93
- return { reviewPath, resume };
94
- };
95
-
96
- const loadReview = (reviewPath, templates, readFileSync = fs.readFileSync) => {
97
- const raw = readFileSync(reviewPath, 'utf8');
98
- return parseReview(raw, reviewPath, templates);
99
- };
100
-
101
- const loadTemplates = (dir, readFileSync = fs.readFileSync) => {
102
- const file = path.join(dir, REVIEW_DIR, TEMPLATES_FILE);
103
- try {
104
- const raw = readFileSync(file, 'utf8');
105
- const data = JSON.parse(raw);
106
- if (!Array.isArray(data)) return [];
107
- const templates = [];
108
- for (const entry of data) {
109
- if (!entry || typeof entry.text !== 'string') continue;
110
- const count = toInt(entry.count);
111
- templates.push({
112
- text: entry.text,
113
- count: Number.isFinite(count) ? count : 1,
114
- });
115
- }
116
- return templates;
117
- } catch {
118
- return [];
119
- }
120
- };
121
-
122
- const flushReview = (notes, io = {}) => {
123
- const writeFileSync = io.writeFileSync ?? fs.writeFileSync;
124
- const mkdirSync = io.mkdirSync ?? fs.mkdirSync;
125
- if (!notes || !notes.dirty) return false;
126
- if (!hasNotes(notes) && !io.force) {
127
- notes.dirty = false;
128
- return false;
129
- }
130
- const folder = path.dirname(notes.reviewPath);
131
- mkdirSync(folder, { recursive: true });
132
- writeFileSync(notes.reviewPath, serializeReview(notes), 'utf8');
133
- const top = path.dirname(folder);
134
- const templatesFile = path.join(top, REVIEW_DIR, TEMPLATES_FILE);
135
- const body = `${JSON.stringify(notes.templates, null, 2)}\n`;
136
- writeFileSync(templatesFile, body, 'utf8');
137
- notes.dirty = false;
138
- return true;
139
- };
140
-
141
- module.exports = {
142
- AUTOSAVE_MS,
143
- allocateReviewPath,
144
- listReviewNames,
145
- latestReviewName,
146
- resolveReviewPath,
147
- loadReview,
148
- loadTemplates,
149
- flushReview,
150
- };
@@ -1,178 +0,0 @@
1
- 'use strict';
2
-
3
- const createStore = (reviewPath, templates = []) => ({
4
- reviewPath,
5
- dirty: false,
6
- nextTodoId: 1,
7
- feedback: new Map(),
8
- code: new Map(),
9
- todos: [],
10
- templates: [...templates],
11
- status: 'editing',
12
- });
13
-
14
- const noteCounts = (store) => {
15
- const counts = { feedback: 0, todo: 0, todoDone: 0, code: 0 };
16
- if (!store) return counts;
17
- for (const todo of store.todos) {
18
- if (!todo.text.trim()) continue;
19
- counts.todo += 1;
20
- if (todo.done) counts.todoDone += 1;
21
- }
22
- for (const note of store.feedback.values()) {
23
- if (note.text.trim()) counts.feedback += 1;
24
- }
25
- counts.code = store.code.size;
26
- return counts;
27
- };
28
-
29
- const hasNotes = (store) => {
30
- const counts = noteCounts(store);
31
- return counts.feedback > 0 || counts.todo > 0 || counts.code > 0;
32
- };
33
-
34
- const setFeedback = (store, key, note) => {
35
- const text = note.text ?? '';
36
- const trimmed = text.trim();
37
- const prev = store.feedback.get(key);
38
- if (!trimmed) {
39
- if (!prev) return;
40
- store.feedback.delete(key);
41
- store.dirty = true;
42
- return;
43
- }
44
- if (prev && prev.text === text) return;
45
- store.feedback.set(key, {
46
- file: note.file,
47
- oldStart: note.oldStart ?? 0,
48
- newStart: note.newStart ?? 0,
49
- blockId: note.blockId ?? 0,
50
- origin: note.origin ?? '',
51
- header: note.header ?? '',
52
- text,
53
- done: note.done ?? prev?.done ?? false,
54
- });
55
- store.dirty = true;
56
- };
57
-
58
- const setCode = (store, key, note) => {
59
- const prev = store.code.get(key);
60
- if (note.clear) {
61
- if (!prev) return;
62
- store.code.delete(key);
63
- store.dirty = true;
64
- return;
65
- }
66
- const text = note.text ?? '';
67
- if (prev && prev.text === text) return;
68
- store.code.set(key, {
69
- file: note.file,
70
- oldStart: note.oldStart ?? 0,
71
- newStart: note.newStart ?? 0,
72
- blockId: note.blockId ?? 0,
73
- origin: note.origin ?? '',
74
- header: note.header ?? '',
75
- text,
76
- done: note.done ?? prev?.done ?? false,
77
- });
78
- store.dirty = true;
79
- };
80
-
81
- const addTodo = (store, file, text = '') => {
82
- const id = store.nextTodoId;
83
- store.nextTodoId += 1;
84
- const todo = { id, file, text, done: false };
85
- store.todos.push(todo);
86
- store.dirty = true;
87
- return todo;
88
- };
89
-
90
- const removeTodo = (store, id) => {
91
- const next = [];
92
- let found = false;
93
- for (const todo of store.todos) {
94
- if (todo.id === id) {
95
- found = true;
96
- continue;
97
- }
98
- next.push(todo);
99
- }
100
- if (!found) return false;
101
- store.todos = next;
102
- store.dirty = true;
103
- return true;
104
- };
105
-
106
- const setTodoText = (store, id, text) => {
107
- const i = store.todos.findIndex((todo) => todo.id === id);
108
- if (i < 0) return;
109
- const prev = store.todos[i];
110
- const trimmed = text.trim();
111
- if (!trimmed) {
112
- removeTodo(store, id);
113
- return;
114
- }
115
- if (prev.text === text) return;
116
- store.todos[i] = { id: prev.id, file: prev.file, text, done: prev.done };
117
- store.dirty = true;
118
- };
119
-
120
- const checkLabel = (text, done) => {
121
- const mark = done ? 'x' : ' ';
122
- return `[${mark}] ${text}`;
123
- };
124
-
125
- const feedbackKey = (note) => {
126
- const file = note.file ?? '';
127
- const oldStart = note.oldStart ?? 0;
128
- const newStart = note.newStart ?? 0;
129
- const block = note.blockId ?? 0;
130
- return `${file}:${oldStart}:${newStart}:${block}`;
131
- };
132
-
133
- const applyImportedNotes = (store, imported) => {
134
- if (!store || !imported) return;
135
- const feedbacks = imported.feedback ?? [];
136
- for (const note of feedbacks) {
137
- const key = feedbackKey(note);
138
- const prev = store.feedback.get(key);
139
- const text = prev ? `${prev.text}\n\n${note.text}` : note.text;
140
- const done = prev ? prev.done && !!note.done : !!note.done;
141
- setFeedback(store, key, {
142
- file: note.file,
143
- oldStart: note.oldStart ?? 0,
144
- newStart: note.newStart ?? 0,
145
- blockId: note.blockId ?? 0,
146
- origin: note.origin ?? '',
147
- header: note.header ?? '',
148
- text,
149
- done,
150
- });
151
- }
152
- const todos = imported.todos ?? [];
153
- for (const todo of todos) {
154
- const added = addTodo(store, todo.file || '', todo.text ?? '');
155
- if (todo.done) added.done = true;
156
- }
157
- };
158
-
159
- const setStatus = (store, status) => {
160
- if (!store) return;
161
- store.status = status;
162
- store.dirty = true;
163
- };
164
-
165
- module.exports = {
166
- createStore,
167
- noteCounts,
168
- hasNotes,
169
- setFeedback,
170
- setCode,
171
- addTodo,
172
- removeTodo,
173
- setTodoText,
174
- checkLabel,
175
- feedbackKey,
176
- applyImportedNotes,
177
- setStatus,
178
- };
@@ -1,49 +0,0 @@
1
- 'use strict';
2
-
3
- const TEMPLATE_SHOW = 5;
4
-
5
- const rankedTemplates = (templates) => {
6
- const ranked = [...templates];
7
- ranked.sort((a, b) => {
8
- const byCount = b.count - a.count;
9
- if (byCount) return byCount;
10
- return a.text.localeCompare(b.text);
11
- });
12
- return ranked;
13
- };
14
-
15
- const prefixTemplates = (templates, prefix) => {
16
- if (!prefix) return templates;
17
- return templates.filter((entry) => entry.text.startsWith(prefix));
18
- };
19
-
20
- const upsertTemplate = (templates, text) => {
21
- const trimmed = text.trim();
22
- if (!trimmed) return templates;
23
- const next = [...templates];
24
- const i = next.findIndex((entry) => entry.text === trimmed);
25
- if (i < 0) {
26
- next.push({ text: trimmed, count: 1 });
27
- return next;
28
- }
29
- const prev = next[i];
30
- next[i] = { text: prev.text, count: prev.count + 1 };
31
- return next;
32
- };
33
-
34
- const rememberTemplate = (store, prevText, nextText) => {
35
- const trimmed = nextText.trim();
36
- if (!trimmed) return;
37
- const prev = (prevText ?? '').trim();
38
- if (prev === trimmed) return;
39
- store.templates = upsertTemplate(store.templates, trimmed);
40
- store.dirty = true;
41
- };
42
-
43
- module.exports = {
44
- TEMPLATE_SHOW,
45
- rankedTemplates,
46
- prefixTemplates,
47
- upsertTemplate,
48
- rememberTemplate,
49
- };
@@ -1,7 +0,0 @@
1
- 'use strict';
2
-
3
- const READ_ONLY_ORIGINS = ['commit', 'pr'];
4
-
5
- const isReadOnlyOrigin = (origin) => READ_ONLY_ORIGINS.includes(origin);
6
-
7
- module.exports = { READ_ONLY_ORIGINS, isReadOnlyOrigin };
package/lib/sys.js DELETED
@@ -1,84 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
-
6
- const IS_WIN = process.platform === 'win32';
7
-
8
- const UNIX_CLIP = [
9
- { cmd: 'wl-copy', args: [] },
10
- { cmd: 'xclip', args: ['-selection', 'clipboard'] },
11
- { cmd: 'xsel', args: ['--clipboard', '--input'] },
12
- ];
13
-
14
- const npmBin = (platform = process.platform) =>
15
- platform === 'win32' ? 'npm.cmd' : 'npm';
16
-
17
- const spawnBase = (extra = {}) => ({ windowsHide: true, ...extra });
18
-
19
- const npmOpts = (extra = {}, platform = process.platform) =>
20
- spawnBase({ shell: platform === 'win32', ...extra });
21
-
22
- const clipTools = (platform = process.platform) => {
23
- if (platform === 'win32') return [{ cmd: 'clip', args: [] }];
24
- if (platform === 'darwin') return [{ cmd: 'pbcopy', args: [] }];
25
- return UNIX_CLIP;
26
- };
27
-
28
- const listen = (emitter, event, handler) => {
29
- if (!emitter || typeof emitter.on !== 'function') return () => {};
30
- emitter.on(event, handler);
31
- return () => {
32
- if (typeof emitter.removeListener === 'function') {
33
- emitter.removeListener(event, handler);
34
- return;
35
- }
36
- if (typeof emitter.off === 'function') emitter.off(event, handler);
37
- };
38
- };
39
-
40
- const watchResize = (stdout, proc, onResize, signal) => {
41
- const stopStdout = listen(stdout, 'resize', onResize);
42
- let stopProc = () => {};
43
- try {
44
- stopProc = listen(proc, 'SIGWINCH', onResize);
45
- } catch {
46
- // SIGWINCH is not available on Windows
47
- }
48
- const stop = () => {
49
- stopStdout();
50
- stopProc();
51
- };
52
- if (signal) {
53
- if (signal.aborted) stop();
54
- else signal.addEventListener('abort', stop, { once: true });
55
- }
56
- return stop;
57
- };
58
-
59
- const canonPath = (value) => {
60
- try {
61
- return fs.realpathSync.native(value);
62
- } catch {
63
- return path.resolve(value);
64
- }
65
- };
66
-
67
- const samePath = (left, right) => {
68
- if (left === right) return true;
69
- const a = canonPath(left);
70
- const b = canonPath(right);
71
- if (a === b) return true;
72
- return IS_WIN && a.toLowerCase() === b.toLowerCase();
73
- };
74
-
75
- module.exports = {
76
- IS_WIN,
77
- npmBin,
78
- spawnBase,
79
- npmOpts,
80
- clipTools,
81
- listen,
82
- watchResize,
83
- samePath,
84
- };