reslop 0.2.3 → 0.2.5

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 (89) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +8 -4
  3. package/bin/reslop.js +4 -2
  4. package/lib/api.js +2 -1
  5. package/lib/capabilities.js +0 -4
  6. package/lib/cli.js +19 -10
  7. package/lib/clipboard.js +45 -4
  8. package/lib/deps-diff.js +61 -118
  9. package/lib/deps.js +166 -235
  10. package/lib/detect.js +4 -2
  11. package/lib/diff/align.js +2 -1
  12. package/lib/diff/diff.js +6 -3
  13. package/lib/diff/display.js +2 -1
  14. package/lib/diff/inline.js +68 -2
  15. package/lib/diff/patch.js +2 -1
  16. package/lib/diff/unit.js +15 -30
  17. package/lib/editor.js +91 -13
  18. package/lib/files.js +54 -27
  19. package/lib/git-branches.js +107 -154
  20. package/lib/git-deps.js +78 -155
  21. package/lib/git-worktree.js +508 -0
  22. package/lib/git.js +23 -580
  23. package/lib/github.js +9 -8
  24. package/lib/gitlab.js +5 -4
  25. package/lib/highlight/bash.js +2 -1
  26. package/lib/highlight/core.js +11 -0
  27. package/lib/highlight/css.js +2 -1
  28. package/lib/highlight/csv.js +2 -1
  29. package/lib/highlight/dot.js +3 -12
  30. package/lib/highlight/html.js +2 -1
  31. package/lib/highlight/json.js +2 -1
  32. package/lib/highlight/log.js +5 -11
  33. package/lib/highlight/txt.js +3 -12
  34. package/lib/highlight.js +2 -1
  35. package/lib/keys.js +22 -12
  36. package/lib/manifest.js +4 -6
  37. package/lib/npm-commands.js +82 -24
  38. package/lib/remote.js +21 -20
  39. package/lib/render/branches.js +9 -27
  40. package/lib/render/chrome.js +109 -35
  41. package/lib/render/commits.js +216 -216
  42. package/lib/render/diff.js +160 -47
  43. package/lib/render/files.js +9 -26
  44. package/lib/render/frame.js +97 -118
  45. package/lib/render/notes.js +74 -26
  46. package/lib/render/npm.js +104 -30
  47. package/lib/render/present.js +2 -1
  48. package/lib/render/primitives.js +128 -52
  49. package/lib/render/render.js +2 -1
  50. package/lib/render/unit.js +17 -11
  51. package/lib/report-model.js +6 -4
  52. package/lib/report-parse.js +8 -14
  53. package/lib/report-render.js +38 -21
  54. package/lib/report-run.js +12 -11
  55. package/lib/report-test.js +45 -51
  56. package/lib/report-tools.js +44 -25
  57. package/lib/review-merge.js +102 -131
  58. package/lib/review.js +69 -78
  59. package/lib/select.js +52 -0
  60. package/lib/session/actions.js +72 -87
  61. package/lib/session/branches.js +18 -52
  62. package/lib/session/change-actions.js +175 -147
  63. package/lib/session/commit.js +65 -61
  64. package/lib/session/compose.js +152 -83
  65. package/lib/session/dispatch.js +53 -46
  66. package/lib/session/edit-click.js +57 -0
  67. package/lib/session/files-pane.js +30 -51
  68. package/lib/session/items.js +73 -90
  69. package/lib/session/lifecycle.js +37 -98
  70. package/lib/session/load.js +52 -57
  71. package/lib/session/navigation.js +105 -95
  72. package/lib/session/npm.js +205 -71
  73. package/lib/session/ops.js +200 -45
  74. package/lib/session/pointer.js +33 -8
  75. package/lib/session/progress.js +9 -6
  76. package/lib/session/review.js +35 -53
  77. package/lib/session/tasks.js +292 -0
  78. package/lib/session/terminal.js +17 -19
  79. package/lib/session/unit.js +3 -4
  80. package/lib/session/update.js +76 -96
  81. package/lib/session/view.js +45 -16
  82. package/lib/session/watch.js +47 -27
  83. package/lib/session.js +66 -97
  84. package/lib/source.js +6 -3
  85. package/lib/update.js +5 -13
  86. package/lib/utilities.js +25 -3
  87. package/lib/wrap.js +18 -1
  88. package/package.json +1 -1
  89. package/lib/session/todos.js +0 -293
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ const files = require('../files.js');
4
+ const { isTasksEntry, TASKS_FILE } = files;
5
+ const review = require('../review.js');
6
+ const { addTask, setTaskText, setTaskDone, removeTask, checkLabel } = review;
7
+
8
+ const TASK_TYPE_SKIP = [
9
+ 'enter',
10
+ 'escape',
11
+ 'tab',
12
+ 'backspace',
13
+ 'delete',
14
+ 'left',
15
+ 'right',
16
+ 'up',
17
+ 'down',
18
+ 'home',
19
+ 'end',
20
+ 'pageUp',
21
+ 'pageDown',
22
+ 'j',
23
+ 'k',
24
+ 'n',
25
+ 'p',
26
+ 'q',
27
+ ];
28
+
29
+ const isTaskTypeKey = (key) => {
30
+ if (!key) return false;
31
+ if (TASK_TYPE_SKIP.includes(key)) return false;
32
+ if (key.startsWith('ctrl-')) return false;
33
+ if (key.startsWith('alt-')) return false;
34
+ return key.charCodeAt(0) >= 32;
35
+ };
36
+
37
+ const listTasks = (api) => {
38
+ const notes = api.review.store;
39
+ if (!notes) return [];
40
+ return notes.tasks;
41
+ };
42
+
43
+ const isDraftCompose = (api) => {
44
+ const { state } = api;
45
+ return state.composeKind === 'tasks' && state.composeTaskId === null;
46
+ };
47
+
48
+ const taskTexts = (api) => {
49
+ const nav = api.nav;
50
+ if (!nav.tasksOpen) return [];
51
+ const todos = listTasks(api);
52
+ const texts = new Array(todos.length + 1);
53
+ const { state } = api;
54
+ const editingTodo = state.composeKind === 'tasks';
55
+ const editingId = editingTodo ? state.composeTaskId : null;
56
+ for (let i = 0; i < todos.length; i++) {
57
+ const todo = todos[i];
58
+ const live = editingId === todo.id && state.editor;
59
+ const text = live ? state.editor.text : todo.text;
60
+ texts[i] = checkLabel(text, todo.done);
61
+ }
62
+ const draft = isDraftCompose(api) && state.editor ? state.editor.text : '';
63
+ texts[todos.length] = checkLabel(draft, false);
64
+ return texts;
65
+ };
66
+
67
+ const taskRowCount = (api) => {
68
+ if (!api.nav.tasksOpen) return 0;
69
+ return listTasks(api).length + 1;
70
+ };
71
+
72
+ const clampedTaskFocus = (api) => {
73
+ const last = Math.max(0, taskRowCount(api) - 1);
74
+ const focus = api.nav.tasksFocus;
75
+ if (focus < 0) return 0;
76
+ if (focus > last) return last;
77
+ return focus;
78
+ };
79
+
80
+ const moveTaskFocus = (api, delta) => {
81
+ const nav = api.nav;
82
+ if (!nav.tasksOpen) return;
83
+ const n = taskRowCount(api);
84
+ if (!n) return;
85
+ const next = clampedTaskFocus(api) + delta;
86
+ if (next < 0) nav.tasksFocus = 0;
87
+ else if (next >= n) nav.tasksFocus = n - 1;
88
+ else nav.tasksFocus = next;
89
+ };
90
+
91
+ const openTasksPage = (api) => {
92
+ const nav = api.nav;
93
+ nav.tasksOpen = true;
94
+ nav.pane = 'diff';
95
+ nav.scroll = 0;
96
+ nav.clearSelection();
97
+ api.ui.syncReviewPath();
98
+ };
99
+
100
+ const closeTasksPage = (api) => {
101
+ api.nav.tasksOpen = false;
102
+ api.ui.syncReviewPath();
103
+ api.ui.syncFileCursor();
104
+ api.ui.lifecycle.catchUpWatch();
105
+ };
106
+
107
+ const focusTodoPage = (api, todoId) => {
108
+ openTasksPage(api);
109
+ const todos = listTasks(api);
110
+ let focus = 0;
111
+ if (todoId === null) {
112
+ focus = todos.length;
113
+ } else if (todoId !== undefined) {
114
+ const at = todos.findIndex((todo) => todo.id === todoId);
115
+ if (at >= 0) focus = at;
116
+ else if (todos.length) focus = todos.length - 1;
117
+ }
118
+ api.nav.tasksFocus = focus;
119
+ api.nav.scroll = 0;
120
+ return true;
121
+ };
122
+
123
+ const startDraftCompose = (api) => {
124
+ focusTodoPage(api, null);
125
+ api.open('tasks', '', null);
126
+ };
127
+
128
+ const editFocusedTask = (api) => {
129
+ if (!api.nav.tasksOpen) return;
130
+ const todos = listTasks(api);
131
+ const todo = todos[clampedTaskFocus(api)];
132
+ if (!todo) return void startDraftCompose(api);
133
+ api.open('tasks', todo.text, todo.id);
134
+ };
135
+
136
+ const editNextTask = (api) => {
137
+ if (!api.nav.tasksOpen) return;
138
+ const last = taskRowCount(api) - 1;
139
+ const next = clampedTaskFocus(api) + 1;
140
+ if (next <= last) api.nav.tasksFocus = next;
141
+ editFocusedTask(api);
142
+ };
143
+
144
+ const moveTaskCompose = (api, delta, width, extend) => {
145
+ if (!api.nav.tasksOpen) return;
146
+ const editor = api.state.editor;
147
+ if (editor && width) {
148
+ const before = editor.cursor;
149
+ editor.moveLine(delta, width, extend);
150
+ if (editor.cursor !== before) return;
151
+ if (extend) return;
152
+ }
153
+ const last = taskRowCount(api) - 1;
154
+ const at = clampedTaskFocus(api);
155
+ let next = at + delta;
156
+ if (next < 0) next = 0;
157
+ if (next > last) next = last;
158
+ if (next === at) return;
159
+ api.commit();
160
+ api.ui.flushReview();
161
+ api.close();
162
+ const max = Math.max(0, taskRowCount(api) - 1);
163
+ api.nav.tasksFocus = Math.min(next, max);
164
+ editFocusedTask(api);
165
+ };
166
+
167
+ const typeIntoTask = (api, key) => {
168
+ const nav = api.nav;
169
+ if (!nav.tasksOpen) return false;
170
+ if (nav.pane !== 'diff') return false;
171
+ if (api.ui.mode !== 'review') return false;
172
+ if (!isTaskTypeKey(key)) return false;
173
+ editFocusedTask(api);
174
+ api.state.editor.insert(key);
175
+ return true;
176
+ };
177
+
178
+ const removeFocusedTask = (api) => {
179
+ const nav = api.nav;
180
+ if (nav.pane !== 'diff' || !nav.tasksOpen) return;
181
+ const todos = listTasks(api);
182
+ const todo = todos[clampedTaskFocus(api)];
183
+ if (!todo) return;
184
+ const notes = api.review.store;
185
+ removeTask(notes, todo.id);
186
+ const left = listTasks(api);
187
+ if (nav.tasksFocus >= left.length) {
188
+ nav.tasksFocus = Math.max(0, left.length - 1);
189
+ }
190
+ api.ui.flushReview(true);
191
+ };
192
+
193
+ const toggleFocusedTask = (api) => {
194
+ const nav = api.nav;
195
+ if (!nav.tasksOpen || nav.pane !== 'diff') return false;
196
+ if (api.ui.mode !== 'review') return false;
197
+ const todo = listTasks(api)[clampedTaskFocus(api)];
198
+ if (!todo) return false;
199
+ setTaskDone(api.review.store, todo.id, !todo.done);
200
+ api.ui.flushReview(true);
201
+ return true;
202
+ };
203
+
204
+ const onTaskHit = (api, cursor) => {
205
+ if (!api.nav.tasksOpen) return;
206
+ const todos = listTasks(api);
207
+ const draft = cursor === todos.length;
208
+ const target = todos[cursor];
209
+ if (!draft && !target) return;
210
+ const { state } = api;
211
+ if (state.composeKind === 'tasks') {
212
+ if (draft && state.composeTaskId === null) return;
213
+ if (target && target.id === state.composeTaskId) return;
214
+ api.commit();
215
+ api.close();
216
+ }
217
+ api.nav.tasksFocus = cursor;
218
+ editFocusedTask(api);
219
+ };
220
+
221
+ const onTaskCheck = (api, cursor) => {
222
+ if (!api.nav.tasksOpen) return;
223
+ const todo = listTasks(api)[cursor];
224
+ if (!todo) return void onTaskHit(api, cursor);
225
+ const { state } = api;
226
+ if (state.composeKind === 'tasks' && state.composeTaskId !== todo.id) {
227
+ api.commit();
228
+ api.close();
229
+ }
230
+ setTaskDone(api.review.store, todo.id, !todo.done);
231
+ api.nav.tasksFocus = cursor;
232
+ api.ui.flushReview(true);
233
+ };
234
+
235
+ const createTask = (api) => {
236
+ startDraftCompose(api);
237
+ };
238
+
239
+ const onTasks = (api) => {
240
+ const { nav, ui } = api;
241
+ if (nav.pane === 'files') {
242
+ const entry = ui.fileCursorEntry();
243
+ if (entry && !isTasksEntry(entry)) {
244
+ nav.index = entry.openIndex ?? entry.firstIndex;
245
+ }
246
+ }
247
+ createTask(api);
248
+ };
249
+
250
+ const taskEditView = (api) => {
251
+ const { state } = api;
252
+ if (state.composeKind !== 'tasks') return null;
253
+ if (!state.editor) return null;
254
+ return { cursor: state.editor.cursor };
255
+ };
256
+
257
+ const commitTask = (api, text) => {
258
+ const notes = api.review.store;
259
+ const id = api.state.composeTaskId;
260
+ const todos = notes.tasks;
261
+ const prev = id === null ? null : todos.find((entry) => entry.id === id);
262
+ if (prev) return void setTaskText(notes, id, text);
263
+ if (!text.trim()) return;
264
+ const todo = addTask(notes, TASKS_FILE, text);
265
+ api.state.composeTaskId = todo.id;
266
+ };
267
+
268
+ const createTasksCompose = (api) => ({
269
+ taskTexts: () => taskTexts(api),
270
+ clampedTaskFocus: () => clampedTaskFocus(api),
271
+ taskRowCount: () => taskRowCount(api),
272
+ moveTaskFocus: (delta) => moveTaskFocus(api, delta),
273
+ openTasksPage: () => openTasksPage(api),
274
+ closeTasksPage: () => closeTasksPage(api),
275
+ startDraftCompose: () => startDraftCompose(api),
276
+ editFocusedTask: () => editFocusedTask(api),
277
+ editNextTask: () => editNextTask(api),
278
+ moveTaskCompose: (delta, width, extend) =>
279
+ moveTaskCompose(api, delta, width, extend),
280
+ typeIntoTask: (key) => typeIntoTask(api, key),
281
+ removeFocusedTask: () => removeFocusedTask(api),
282
+ onTaskHit: (cursor) => onTaskHit(api, cursor),
283
+ onTaskCheck: (cursor) => onTaskCheck(api, cursor),
284
+ toggleFocusedTask: () => toggleFocusedTask(api),
285
+ onTasks: () => onTasks(api),
286
+ taskEditView: () => taskEditView(api),
287
+ commitTask: (text) => commitTask(api, text),
288
+ });
289
+
290
+ module.exports = {
291
+ createTasksCompose,
292
+ };
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const { presentRows, presentCursor } = require('../render/render.js');
4
- const { listen, watchResize } = require('../utilities.js');
3
+ const render = require('../render/render.js');
4
+ const { presentRows, presentCursor } = render;
5
+ const utilities = require('../utilities.js');
6
+ const { listen, watchResize } = utilities;
5
7
 
6
8
  const ENTER_TERM = '\x1b[?1049h\x1b[?25l\x1b[?7l\x1b[?1002h\x1b[?1006h';
7
9
  const LEAVE_TERM =
@@ -15,28 +17,25 @@ const sameCursor = (a, b) => {
15
17
 
16
18
  const hasTimer = (api, name) => api.timers.has(name);
17
19
 
18
- const stopTimer = (api, name) => {
20
+ const cancel = (api, name) => {
19
21
  const id = api.timers.get(name);
20
22
  if (id === undefined) return;
21
- clearInterval(id);
23
+ clearTimeout(id);
22
24
  api.timers.delete(name);
23
25
  };
24
26
 
25
- const startTimer = (api, name, fn, ms) => {
26
- if (api.state.disposed) return;
27
- if (api.timers.has(name)) return;
28
- const id = setInterval(fn, ms);
29
- if (id && typeof id.unref === 'function') id.unref();
27
+ const later = (api, name, fn, ms) => {
28
+ if (api.state.disposed || api.timers.has(name)) return;
29
+ const id = setTimeout(() => {
30
+ api.timers.delete(name);
31
+ fn();
32
+ }, ms);
33
+ if (typeof id.unref === 'function') id.unref();
30
34
  api.timers.set(name, id);
31
35
  };
32
36
 
33
- const restartTimer = (api, name, fn, ms) => {
34
- stopTimer(api, name);
35
- startTimer(api, name, fn, ms);
36
- };
37
-
38
37
  const clearTimers = (api) => {
39
- for (const name of [...api.timers.keys()]) stopTimer(api, name);
38
+ for (const name of [...api.timers.keys()]) cancel(api, name);
40
39
  };
41
40
 
42
41
  const stopListening = (api) => {
@@ -96,6 +95,7 @@ const paint = (api, frame, size, cursor) => {
96
95
  api.state.lastFrame &&
97
96
  api.state.lastFrame.text === frame.text
98
97
  ) {
98
+ api.state.lastFrame = frame;
99
99
  if (sameCursor(api.state.lastPresentedCursor, cursor)) return;
100
100
  api.state.lastPresentedCursor = cursor;
101
101
  api.stdout.write(presentCursor(cursor));
@@ -146,9 +146,8 @@ const createTerminal = (options) => {
146
146
  paint: (frame, size, cursor) => paint(api, frame, size, cursor),
147
147
  startListening: (handlers) => startListening(api, handlers),
148
148
  stopListening: () => stopListening(api),
149
- startTimer: (name, fn, ms) => startTimer(api, name, fn, ms),
150
- stopTimer: (name) => stopTimer(api, name),
151
- restartTimer: (name, fn, ms) => restartTimer(api, name, fn, ms),
149
+ later: (name, fn, ms) => later(api, name, fn, ms),
150
+ cancel: (name) => cancel(api, name),
152
151
  clearTimers: () => clearTimers(api),
153
152
  hasTimer: (name) => hasTimer(api, name),
154
153
  resetCache: () => resetCache(api),
@@ -158,6 +157,5 @@ const createTerminal = (options) => {
158
157
  module.exports = {
159
158
  ENTER_TERM,
160
159
  LEAVE_TERM,
161
- sameCursor,
162
160
  createTerminal,
163
161
  };
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const files = require('../files.js');
4
- const { listPath, isTodosEntry } = files;
4
+ const { listPath, isTasksEntry } = files;
5
5
  const diff = require('../diff/diff.js');
6
6
  const { unitLines, itemsForPath, blockLineIndex } = diff;
7
7
  const { hunkLineRange, sameHunk, hunkKey } = diff;
@@ -150,10 +150,10 @@ const moveUnitBlock = (ui, step) => {
150
150
  };
151
151
 
152
152
  const openUnitFile = (ui, entry) => {
153
- if (!entry || isTodosEntry(entry)) return;
153
+ if (!entry || isTasksEntry(entry)) return;
154
154
  const nav = ui.nav;
155
155
  nav.reviewPath = entry.path;
156
- nav.todoOpen = false;
156
+ nav.tasksOpen = false;
157
157
  nav.pane = 'unit';
158
158
  nav.scroll = 0;
159
159
  nav.unitLine = 0;
@@ -203,6 +203,5 @@ module.exports = {
203
203
  openUnitFile,
204
204
  setFileScope,
205
205
  editCursor,
206
- ensureVisible,
207
206
  followFileCursor,
208
207
  };
@@ -1,112 +1,92 @@
1
1
  'use strict';
2
2
 
3
- const { stateView } = require('./accessors.js');
4
-
5
3
  const update = require('../update.js');
6
4
  const { checkUpdate, installUpdate, markSkipped } = update;
7
5
 
8
- const optsOf = (api) => {
9
- const value = api.ui.updateOpts;
10
- if (!value || value === true) return {};
11
- return value;
12
- };
13
-
14
- const reset = (state) => {
15
- state.offer = false;
16
- state.from = '';
17
- state.to = '';
18
- state.checkPromise = null;
19
- state.installPromise = null;
20
- };
6
+ class UpdateCoordinator {
7
+ constructor(ui) {
8
+ this.ui = ui;
9
+ this.reset();
10
+ }
21
11
 
22
- const maybePrompt = (api) => {
23
- if (!api.state.offer) return;
24
- if (!api.ui.loader.didLoad) return;
25
- if (api.ui.mode !== 'review') return;
26
- api.ui.mode = 'confirmUpdate';
27
- };
12
+ get opts() {
13
+ const value = this.ui.updateOpts;
14
+ if (!value || value === true) return {};
15
+ return value;
16
+ }
28
17
 
29
- const runInstall = async (api, version) => {
30
- api.ui.status = 'updating reslop';
31
- api.ui.progressFrame = 0;
32
- api.ui.progress.start('install');
33
- api.ui.paint();
34
- let ok = false;
35
- try {
36
- await installUpdate(version, optsOf(api));
37
- ok = true;
38
- } catch {
39
- // ignore install errors
18
+ reset() {
19
+ this.offer = false;
20
+ this.from = '';
21
+ this.to = '';
22
+ this.checkPromise = null;
23
+ this.installPromise = null;
40
24
  }
41
- api.ui.progress.stop('install');
42
- if (api.ui.done || !api.ui.uiOpen) return;
43
- api.ui.status = ok ? 'updated' : 'update failed';
44
- api.ui.paint();
45
- };
46
25
 
47
- const acceptUpdate = (api) => {
48
- api.ui.mode = 'review';
49
- api.state.offer = false;
50
- const version = api.state.to;
51
- api.state.installPromise = runInstall(api, version);
52
- };
26
+ maybePrompt() {
27
+ const { ui } = this;
28
+ if (!this.offer || !ui.loader.didLoad) return;
29
+ if (ui.mode === 'review') ui.mode = 'confirmUpdate';
30
+ }
53
31
 
54
- const declineUpdate = (api) => {
55
- api.ui.mode = 'review';
56
- api.state.offer = false;
57
- api.ui.status = '';
58
- markSkipped(api.state.to, optsOf(api));
59
- };
32
+ async install(version) {
33
+ const { ui } = this;
34
+ ui.status = 'updating reslop';
35
+ ui.progressFrame = 0;
36
+ ui.progress.start('install');
37
+ ui.paint();
38
+ let ok = false;
39
+ try {
40
+ await installUpdate(version, this.opts);
41
+ ok = true;
42
+ } catch {
43
+ // ignore install errors
44
+ }
45
+ ui.progress.stop('install');
46
+ if (ui.done || !ui.uiOpen) return;
47
+ ui.status = ok ? 'updated' : 'update failed';
48
+ ui.paint();
49
+ }
60
50
 
61
- const runOpenUpdate = async (api) => {
62
- let plan;
63
- try {
64
- plan = await checkUpdate(optsOf(api));
65
- } catch {
66
- return;
51
+ accept() {
52
+ this.ui.mode = 'review';
53
+ this.offer = false;
54
+ this.installPromise = this.install(this.to);
67
55
  }
68
- if (api.ui.done) return;
69
- if (plan.action === 'install') {
70
- api.state.installPromise = runInstall(api, plan.latest);
71
- await api.state.installPromise;
72
- return;
56
+
57
+ decline() {
58
+ this.ui.mode = 'review';
59
+ this.offer = false;
60
+ this.ui.status = '';
61
+ markSkipped(this.to, this.opts);
73
62
  }
74
- if (plan.action !== 'confirm') return;
75
- api.state.from = plan.current;
76
- api.state.to = plan.latest;
77
- api.state.offer = true;
78
- maybePrompt(api);
79
- api.ui.paint();
80
- };
81
63
 
82
- const openUpdate = (api) => {
83
- if (api.ui.updateOpts === false) return Promise.resolve();
84
- if (api.state.checkPromise) return api.state.checkPromise;
85
- api.state.checkPromise = runOpenUpdate(api);
86
- return api.state.checkPromise;
87
- };
64
+ async check() {
65
+ let plan;
66
+ try {
67
+ plan = await checkUpdate(this.opts);
68
+ } catch {
69
+ return;
70
+ }
71
+ if (this.ui.done) return;
72
+ if (plan.action === 'install') {
73
+ this.installPromise = this.install(plan.latest);
74
+ await this.installPromise;
75
+ return;
76
+ }
77
+ if (plan.action !== 'confirm') return;
78
+ this.from = plan.current;
79
+ this.to = plan.latest;
80
+ this.offer = true;
81
+ this.maybePrompt();
82
+ this.ui.paint();
83
+ }
88
84
 
89
- const createUpdateCoordinator = (ui) => {
90
- const state = {
91
- offer: false,
92
- from: '',
93
- to: '',
94
- checkPromise: null,
95
- installPromise: null,
96
- };
97
- const api = { ui, state };
98
- return stateView(
99
- state,
100
- {
101
- reset: () => reset(state),
102
- maybePrompt: () => maybePrompt(api),
103
- accept: () => acceptUpdate(api),
104
- decline: () => declineUpdate(api),
105
- open: () => openUpdate(api),
106
- },
107
- ['offer', 'installPromise'],
108
- ['from', 'to', 'checkPromise'],
109
- );
110
- };
85
+ open() {
86
+ if (this.ui.updateOpts === false) return Promise.resolve();
87
+ if (!this.checkPromise) this.checkPromise = this.check();
88
+ return this.checkPromise;
89
+ }
90
+ }
111
91
 
112
- module.exports = { createUpdateCoordinator };
92
+ module.exports = { UpdateCoordinator };