reslop 0.2.5 → 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.
- package/CHANGELOG.md +12 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +39 -47
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +197 -402
- package/lib/deps.js +113 -240
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +76 -144
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +79 -178
- package/lib/editor.js +52 -69
- package/lib/files.js +71 -89
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +147 -272
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +155 -141
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +14 -14
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +19 -25
- package/lib/highlight/txt.js +7 -10
- package/lib/highlight.js +17 -46
- package/lib/keys.js +49 -82
- package/lib/manifest.js +67 -126
- package/lib/npm-commands.js +120 -169
- package/lib/remote.js +140 -190
- package/lib/render/branches.js +63 -61
- package/lib/render/chrome.js +220 -333
- package/lib/render/commits.js +160 -361
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -258
- package/lib/render/files.js +40 -85
- package/lib/render/frame.js +78 -117
- package/lib/render/notes.js +139 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +72 -174
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -31
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -37
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -77
- package/lib/report-test.js +221 -99
- package/lib/report-tools.js +96 -154
- package/lib/review-merge.js +33 -41
- package/lib/review.js +68 -129
- package/lib/runs.js +225 -0
- package/lib/select.js +53 -91
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +61 -43
- package/lib/session/branches.js +166 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +274 -355
- package/lib/session/compose.js +556 -714
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +162 -170
- package/lib/session/files-pane.js +76 -44
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +393 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +2 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/pointer.js +16 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +276 -235
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +47 -81
- package/lib/session/watch.js +196 -212
- package/lib/session.js +81 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/lib/session.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const render = require('./render/render.js');
|
|
4
|
+
const { renderFrame } = render;
|
|
4
5
|
const keys = require('./keys.js');
|
|
6
|
+
const { decodeChunk } = keys;
|
|
5
7
|
const review = require('./review.js');
|
|
8
|
+
const { AUTOSAVE_MS, hasNotes } = review;
|
|
6
9
|
const files = require('./files.js');
|
|
7
10
|
const { makeTaskItem } = files;
|
|
8
|
-
const { renderFrame } = render;
|
|
9
|
-
const { decodeChunk } = keys;
|
|
10
|
-
const { AUTOSAVE_MS, hasNotes } = review;
|
|
11
11
|
const capabilities = require('./capabilities.js');
|
|
12
|
+
const { sessionCapabilities } = capabilities;
|
|
12
13
|
const actions = require('./session/actions.js');
|
|
13
14
|
const { LIST_PANES } = actions;
|
|
14
|
-
const { sessionCapabilities } = capabilities;
|
|
15
15
|
const terminal = require('./session/terminal.js');
|
|
16
|
-
const {
|
|
16
|
+
const { Terminal, LEAVE_TERM } = terminal;
|
|
17
17
|
const load = require('./session/load.js');
|
|
18
18
|
const { LoadCoordinator } = load;
|
|
19
19
|
const items = require('./session/items.js');
|
|
@@ -25,31 +25,27 @@ const { ReviewController } = sessionReview;
|
|
|
25
25
|
const update = require('./session/update.js');
|
|
26
26
|
const { UpdateCoordinator } = update;
|
|
27
27
|
const progress = require('./session/progress.js');
|
|
28
|
-
const {
|
|
28
|
+
const { Progress } = progress;
|
|
29
29
|
const compose = require('./session/compose.js');
|
|
30
|
-
const {
|
|
30
|
+
const { Composer } = compose;
|
|
31
31
|
const ops = require('./session/ops.js');
|
|
32
32
|
const { OpsRunner } = ops;
|
|
33
|
-
const changeActions = require('./session/change-actions.js');
|
|
34
|
-
const { createChangeActions } = changeActions;
|
|
35
33
|
const commit = require('./session/commit.js');
|
|
36
|
-
const {
|
|
34
|
+
const { CommitController } = commit;
|
|
37
35
|
const branches = require('./session/branches.js');
|
|
38
|
-
const {
|
|
36
|
+
const { BranchController } = branches;
|
|
39
37
|
const npm = require('./session/npm.js');
|
|
40
|
-
const {
|
|
38
|
+
const { NpmController } = npm;
|
|
41
39
|
const accessors = require('./session/accessors.js');
|
|
42
|
-
const { bindAccessors, bindGetters
|
|
40
|
+
const { bindAccessors, bindGetters } = accessors;
|
|
43
41
|
const view = require('./session/view.js');
|
|
44
|
-
const { createView } = view;
|
|
45
42
|
const filesPane = require('./session/files-pane.js');
|
|
46
|
-
const { createFilesPane } = filesPane;
|
|
47
43
|
const lifecycle = require('./session/lifecycle.js');
|
|
48
|
-
const {
|
|
49
|
-
const pointer = require('./session/pointer.js');
|
|
50
|
-
const { createPointer } = pointer;
|
|
44
|
+
const { Lifecycle } = lifecycle;
|
|
51
45
|
const dispatch = require('./session/dispatch.js');
|
|
52
|
-
const {
|
|
46
|
+
const { dispatchAction, confirmChoice, handleEvent } = dispatch;
|
|
47
|
+
const dashboard = require('./session/dashboard.js');
|
|
48
|
+
const { Dashboard } = dashboard;
|
|
53
49
|
|
|
54
50
|
const PROGRESS_MS = 160;
|
|
55
51
|
|
|
@@ -77,7 +73,6 @@ const COMPOSER_FIELDS = [
|
|
|
77
73
|
'composeKind',
|
|
78
74
|
'commitKind',
|
|
79
75
|
'composeTaskId',
|
|
80
|
-
'composeBaseline',
|
|
81
76
|
'templateIndex',
|
|
82
77
|
'templateFocus',
|
|
83
78
|
];
|
|
@@ -90,11 +85,6 @@ const assignOptions = (session, options) => {
|
|
|
90
85
|
session.stdout = options.stdout;
|
|
91
86
|
session.proc = options.proc ?? process;
|
|
92
87
|
session.color = options.color ?? true;
|
|
93
|
-
session.getSize = () => {
|
|
94
|
-
const width = session.stdout.columns ?? 80;
|
|
95
|
-
const height = session.stdout.rows ?? 24;
|
|
96
|
-
return { width, height };
|
|
97
|
-
};
|
|
98
88
|
session.rev = options.rev ?? null;
|
|
99
89
|
session.revShort = '';
|
|
100
90
|
session.sourceLabel = options.sourceLabel ?? '';
|
|
@@ -107,41 +97,31 @@ const assignOptions = (session, options) => {
|
|
|
107
97
|
session.outdatedMap = options.outdatedMap;
|
|
108
98
|
session.auditMap = options.auditMap;
|
|
109
99
|
session.updateOpts = options.update;
|
|
110
|
-
session.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
100
|
+
session.dashboardHome = options.startPane === 'dashboard';
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const assignParts = (session, options) => {
|
|
104
|
+
const { stdin, stdout, proc } = session;
|
|
105
|
+
session.term = new Terminal({ stdin, stdout, proc });
|
|
115
106
|
session.loader = new LoadCoordinator();
|
|
116
107
|
session.collection = new ItemCollection();
|
|
117
108
|
session.nav = new Navigation({ startPane: options.startPane ?? 'files' });
|
|
118
109
|
session.review = new ReviewController();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const assignComposer = (session) => {
|
|
127
|
-
session.composer = createComposer({
|
|
128
|
-
nav: session.nav,
|
|
129
|
-
review: session.review,
|
|
130
|
-
ui: session,
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const assignControllers = (session) => {
|
|
110
|
+
const tick = () => session.tickProgress();
|
|
111
|
+
session.progress = new Progress(session.term, tick, PROGRESS_MS);
|
|
112
|
+
session.updater = new UpdateCoordinator(session);
|
|
113
|
+
session.composer = new Composer(session);
|
|
135
114
|
session.ops = new OpsRunner(session);
|
|
136
|
-
session.
|
|
137
|
-
session.
|
|
138
|
-
session.
|
|
139
|
-
session.
|
|
115
|
+
session.commits = new CommitController(session);
|
|
116
|
+
session.gitBranches = new BranchController(session);
|
|
117
|
+
session.npm = new NpmController(session);
|
|
118
|
+
session.dashboard = new Dashboard(session);
|
|
119
|
+
session.lifecycle = new Lifecycle(session);
|
|
140
120
|
};
|
|
141
121
|
|
|
142
|
-
const
|
|
122
|
+
const bindFields = (session) => {
|
|
143
123
|
bindAccessors(session, session.nav, NAV_FIELDS);
|
|
144
|
-
bindAccessors(session, session.composer
|
|
124
|
+
bindAccessors(session, session.composer, COMPOSER_FIELDS);
|
|
145
125
|
bindAccessors(session, session.loader, ['pendingExtras']);
|
|
146
126
|
bindGetters(session, session.loader, ['didLoad']);
|
|
147
127
|
bindAccessors(session, session.collection, ['items']);
|
|
@@ -150,76 +130,36 @@ const bindSessionAccessors = (session) => {
|
|
|
150
130
|
bindAccessors(session, session.ops, ['gitBusy', 'busy']);
|
|
151
131
|
bindAccessors(session, session.gitBranches, ['branches', 'dropName']);
|
|
152
132
|
bindGetters(session, session.term, ['lastFrame', 'lastSize']);
|
|
153
|
-
bindAlias(session, 'notes', () => session.review.store);
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const bindViewer = (session) => {
|
|
157
|
-
session.viewer = createView({
|
|
158
|
-
nav: session.nav,
|
|
159
|
-
composer: session.composer,
|
|
160
|
-
collection: session.collection,
|
|
161
|
-
review: session.review,
|
|
162
|
-
gitBranches: session.gitBranches,
|
|
163
|
-
commits: session.commits,
|
|
164
|
-
npm: session.npm,
|
|
165
|
-
updater: session.updater,
|
|
166
|
-
ui: session,
|
|
167
|
-
});
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const bindLifecycle = (session) => {
|
|
171
|
-
session.lifecycle = createLifecycle({
|
|
172
|
-
loader: session.loader,
|
|
173
|
-
collection: session.collection,
|
|
174
|
-
review: session.review,
|
|
175
|
-
nav: session.nav,
|
|
176
|
-
ops: session.ops,
|
|
177
|
-
updater: session.updater,
|
|
178
|
-
loadConfig: {
|
|
179
|
-
repo: session.repo,
|
|
180
|
-
cwd: session.cwd,
|
|
181
|
-
paths: session.paths,
|
|
182
|
-
audit: session.audit,
|
|
183
|
-
outdatedMap: session.outdatedMap,
|
|
184
|
-
auditMap: session.auditMap,
|
|
185
|
-
},
|
|
186
|
-
ui: session,
|
|
187
|
-
});
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const bindDispatch = (session) => {
|
|
191
|
-
session.dispatcher = createDispatcher(session);
|
|
192
|
-
session.pointer = createPointer(session);
|
|
193
|
-
session.input = createInput(session);
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const composeSession = (session, options) => {
|
|
197
|
-
assignOptions(session, options);
|
|
198
|
-
session.updater = new UpdateCoordinator(session);
|
|
199
|
-
assignComposer(session);
|
|
200
|
-
assignControllers(session);
|
|
201
|
-
bindSessionAccessors(session);
|
|
202
|
-
bindViewer(session);
|
|
203
|
-
session.filesPane = createFilesPane(session);
|
|
204
|
-
bindLifecycle(session);
|
|
205
|
-
bindDispatch(session);
|
|
206
133
|
};
|
|
207
134
|
|
|
208
135
|
class Session {
|
|
209
136
|
constructor(options) {
|
|
210
|
-
|
|
137
|
+
assignOptions(this, options);
|
|
138
|
+
assignParts(this, options);
|
|
139
|
+
bindFields(this);
|
|
211
140
|
this.resetState();
|
|
212
141
|
}
|
|
213
142
|
|
|
143
|
+
get notes() {
|
|
144
|
+
return this.review.store;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getSize() {
|
|
148
|
+
const width = this.stdout.columns ?? 80;
|
|
149
|
+
const height = this.stdout.rows ?? 24;
|
|
150
|
+
return { width, height };
|
|
151
|
+
}
|
|
152
|
+
|
|
214
153
|
resetState() {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
154
|
+
this.collection.reset();
|
|
155
|
+
this.nav.reset();
|
|
156
|
+
this.review.reset();
|
|
157
|
+
this.composer.reset();
|
|
158
|
+
this.ops.reset();
|
|
159
|
+
this.gitBranches.reset();
|
|
160
|
+
this.commits.reset();
|
|
161
|
+
this.npm.reset();
|
|
162
|
+
this.dashboard.reset();
|
|
223
163
|
this.top = this.cwd;
|
|
224
164
|
this.branch = '';
|
|
225
165
|
this.status = '';
|
|
@@ -233,12 +173,12 @@ class Session {
|
|
|
233
173
|
this.loadError = null;
|
|
234
174
|
this.uiOpen = false;
|
|
235
175
|
this.finishReadLoop = null;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
176
|
+
this.lifecycle.stopWatch();
|
|
177
|
+
this.lifecycle.clearNpmExtras();
|
|
178
|
+
this.term.resetCache();
|
|
179
|
+
this.loader.reset();
|
|
180
|
+
this.updater.reset();
|
|
181
|
+
this.progress.clear();
|
|
242
182
|
}
|
|
243
183
|
|
|
244
184
|
ensureRepo() {
|
|
@@ -271,8 +211,7 @@ class Session {
|
|
|
271
211
|
}
|
|
272
212
|
|
|
273
213
|
paint() {
|
|
274
|
-
if (
|
|
275
|
-
this.draw();
|
|
214
|
+
if (this.uiOpen) this.draw();
|
|
276
215
|
}
|
|
277
216
|
|
|
278
217
|
draw() {
|
|
@@ -294,6 +233,7 @@ class Session {
|
|
|
294
233
|
const composing = this.mode === 'compose';
|
|
295
234
|
this.term.paint(frame, size, composing ? frame.cursor : null);
|
|
296
235
|
this.scheduleAutosave();
|
|
236
|
+
this.dashboard.afterDraw();
|
|
297
237
|
}
|
|
298
238
|
|
|
299
239
|
scheduleAutosave() {
|
|
@@ -304,19 +244,11 @@ class Session {
|
|
|
304
244
|
}
|
|
305
245
|
|
|
306
246
|
view() {
|
|
307
|
-
return
|
|
247
|
+
return view.view(this);
|
|
308
248
|
}
|
|
309
249
|
|
|
310
250
|
fileList() {
|
|
311
|
-
return
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
counts() {
|
|
315
|
-
return this.viewer.counts();
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
viewStatus() {
|
|
319
|
-
return this.viewer.viewStatus();
|
|
251
|
+
return view.fileList(this);
|
|
320
252
|
}
|
|
321
253
|
|
|
322
254
|
current() {
|
|
@@ -324,31 +256,35 @@ class Session {
|
|
|
324
256
|
}
|
|
325
257
|
|
|
326
258
|
fileCursorEntry() {
|
|
327
|
-
return
|
|
259
|
+
return filesPane.fileCursorEntry(this);
|
|
328
260
|
}
|
|
329
261
|
|
|
330
262
|
clampIndex() {
|
|
331
|
-
|
|
263
|
+
filesPane.clampIndex(this);
|
|
332
264
|
}
|
|
333
265
|
|
|
334
266
|
syncReviewPath() {
|
|
335
|
-
|
|
267
|
+
filesPane.syncReviewPath(this);
|
|
336
268
|
}
|
|
337
269
|
|
|
338
270
|
syncFileCursor() {
|
|
339
|
-
|
|
271
|
+
filesPane.syncFileCursor(this);
|
|
340
272
|
}
|
|
341
273
|
|
|
342
274
|
showFiles() {
|
|
343
|
-
|
|
275
|
+
filesPane.showFiles(this);
|
|
344
276
|
}
|
|
345
277
|
|
|
346
278
|
dispatch(action) {
|
|
347
|
-
this
|
|
279
|
+
dispatchAction(this, action);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
confirmChoice(key) {
|
|
283
|
+
confirmChoice(this, key);
|
|
348
284
|
}
|
|
349
285
|
|
|
350
286
|
handleEvent(event) {
|
|
351
|
-
|
|
287
|
+
handleEvent(this, event);
|
|
352
288
|
}
|
|
353
289
|
|
|
354
290
|
pushInput(text) {
|
|
@@ -376,7 +312,7 @@ class Session {
|
|
|
376
312
|
this.status = result.error.message;
|
|
377
313
|
return false;
|
|
378
314
|
}
|
|
379
|
-
|
|
315
|
+
this.lifecycle.noteReviewStamp();
|
|
380
316
|
return result.wrote;
|
|
381
317
|
}
|
|
382
318
|
|
|
@@ -388,19 +324,7 @@ class Session {
|
|
|
388
324
|
}
|
|
389
325
|
|
|
390
326
|
selectTemplate(index) {
|
|
391
|
-
this.runComposeCommand(this.composer.selectTemplate(index));
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
idleNoteText() {
|
|
395
|
-
return this.composer.idleNoteText();
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
shownTemplates() {
|
|
399
|
-
return this.composer.shownTemplates();
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
startDraftCompose() {
|
|
403
|
-
this.composer.startDraftCompose();
|
|
327
|
+
this.runComposeCommand(this.composer.templates.selectTemplate(index));
|
|
404
328
|
}
|
|
405
329
|
|
|
406
330
|
autosave() {
|
|
@@ -487,6 +411,7 @@ class Session {
|
|
|
487
411
|
if (settled) return;
|
|
488
412
|
settled = true;
|
|
489
413
|
this.lifecycle.stopWatch();
|
|
414
|
+
this.dashboard.stop();
|
|
490
415
|
this.term.stopListening();
|
|
491
416
|
this.progress.clear();
|
|
492
417
|
this.term.clearTimers();
|
|
@@ -503,16 +428,11 @@ class Session {
|
|
|
503
428
|
if (this.done) return void finish();
|
|
504
429
|
this.draw();
|
|
505
430
|
};
|
|
506
|
-
this.term.startListening({
|
|
507
|
-
onData,
|
|
508
|
-
onResize: () => this.draw(),
|
|
509
|
-
});
|
|
431
|
+
this.term.startListening({ onData, onResize: () => this.draw() });
|
|
510
432
|
this.lifecycle.startWatch();
|
|
433
|
+
if (this.dashboardHome) this.dashboard.start();
|
|
511
434
|
});
|
|
512
435
|
}
|
|
513
436
|
}
|
|
514
437
|
|
|
515
|
-
module.exports = {
|
|
516
|
-
LEAVE_TERM,
|
|
517
|
-
Session,
|
|
518
|
-
};
|
|
438
|
+
module.exports = { LEAVE_TERM, Session };
|
package/lib/update.js
CHANGED
|
@@ -21,19 +21,10 @@ const INSTALL_MS = 120000;
|
|
|
21
21
|
const REGISTRY = 'https://registry.npmjs.org';
|
|
22
22
|
|
|
23
23
|
const RELEASE = /^\d+\.\d+\.\d+$/;
|
|
24
|
-
const
|
|
25
|
-
checkedAt: 0,
|
|
26
|
-
compatible: '',
|
|
27
|
-
major: '',
|
|
28
|
-
skipped: '',
|
|
29
|
-
});
|
|
24
|
+
const NO_TARGETS = { compatible: '', major: '' };
|
|
30
25
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const isNpmInstall = (pkgDir) => {
|
|
34
|
-
const parent = path.basename(path.dirname(pkgDir));
|
|
35
|
-
return parent === 'node_modules';
|
|
36
|
-
};
|
|
26
|
+
const isNpmInstall = (pkgDir) =>
|
|
27
|
+
path.basename(path.dirname(pkgDir)) === 'node_modules';
|
|
37
28
|
|
|
38
29
|
const cacheFile = (env = process.env) => {
|
|
39
30
|
if (env.XDG_CACHE_HOME) {
|
|
@@ -49,29 +40,26 @@ const cacheFile = (env = process.env) => {
|
|
|
49
40
|
|
|
50
41
|
const readCache = (file) => {
|
|
51
42
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
const data = jsonParse(fs.readFileSync(file, 'utf8'));
|
|
44
|
+
if (isHashObject(data)) {
|
|
45
|
+
return {
|
|
46
|
+
checkedAt: parseInt(data.checkedAt, 10) || 0,
|
|
47
|
+
compatible: asText(data.compatible),
|
|
48
|
+
major: asText(data.major) || asText(data.latest),
|
|
49
|
+
skipped: asText(data.skipped) || asText(data.handled),
|
|
50
|
+
};
|
|
56
51
|
}
|
|
57
|
-
const checkedAt = toInt(data.checkedAt) || 0;
|
|
58
|
-
const compatible = asText(data.compatible);
|
|
59
|
-
const major = asText(data.major) || asText(data.latest);
|
|
60
|
-
const skipped = asText(data.skipped) || asText(data.handled);
|
|
61
|
-
return { checkedAt, compatible, major, skipped };
|
|
62
52
|
} catch {
|
|
63
|
-
|
|
53
|
+
// missing or unreadable cache
|
|
64
54
|
}
|
|
55
|
+
return { checkedAt: 0, compatible: '', major: '', skipped: '' };
|
|
65
56
|
};
|
|
66
57
|
|
|
67
58
|
const writeCache = (file, data) => {
|
|
59
|
+
const { checkedAt, compatible, major, skipped } = data;
|
|
60
|
+
const body = JSON.stringify({ checkedAt, compatible, major, skipped });
|
|
68
61
|
try {
|
|
69
62
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
70
|
-
const checkedAt = data.checkedAt ?? 0;
|
|
71
|
-
const compatible = data.compatible ?? '';
|
|
72
|
-
const major = data.major ?? '';
|
|
73
|
-
const skipped = data.skipped ?? '';
|
|
74
|
-
const body = JSON.stringify({ checkedAt, compatible, major, skipped });
|
|
75
63
|
fs.writeFileSync(file, `${body}\n`);
|
|
76
64
|
} catch {
|
|
77
65
|
// ignore cache write errors
|
|
@@ -79,76 +67,43 @@ const writeCache = (file, data) => {
|
|
|
79
67
|
};
|
|
80
68
|
|
|
81
69
|
const isFresh = (cache, now, interval = CHECK_INTERVAL_MS) => {
|
|
82
|
-
if (!cache ||
|
|
83
|
-
if (now < cache.checkedAt) return false;
|
|
70
|
+
if (!cache.checkedAt || now < cache.checkedAt) return false;
|
|
84
71
|
return now - cache.checkedAt < interval;
|
|
85
72
|
};
|
|
86
73
|
|
|
87
74
|
const listVersions = (data) => {
|
|
88
75
|
if (!data || typeof data !== 'object') return [];
|
|
89
76
|
const names = [];
|
|
90
|
-
|
|
91
|
-
if (isHashObject(map)) {
|
|
92
|
-
for (const name of Object.keys(map)) names.push(name);
|
|
93
|
-
}
|
|
77
|
+
if (isHashObject(data.versions)) names.push(...Object.keys(data.versions));
|
|
94
78
|
const tags = data['dist-tags'];
|
|
95
79
|
if (tags && typeof tags.latest === 'string') names.push(tags.latest);
|
|
96
80
|
if (typeof data.version === 'string') names.push(data.version);
|
|
97
|
-
|
|
98
|
-
for (const name of names) {
|
|
99
|
-
if (!RELEASE.test(name)) continue;
|
|
100
|
-
if (listed.includes(name)) continue;
|
|
101
|
-
listed.push(name);
|
|
102
|
-
}
|
|
103
|
-
return listed;
|
|
81
|
+
return [...new Set(names)].filter((name) => RELEASE.test(name));
|
|
104
82
|
};
|
|
105
83
|
|
|
106
|
-
const
|
|
84
|
+
const newer = (best, name) => {
|
|
107
85
|
if (!best) return name;
|
|
108
|
-
|
|
109
|
-
if (!prev || cmpVersion(ver, prev) > 0) return name;
|
|
110
|
-
return best;
|
|
86
|
+
return cmpVersion(parseVersion(name), parseVersion(best)) > 0 ? name : best;
|
|
111
87
|
};
|
|
112
88
|
|
|
113
89
|
const pickTargets = (current, names) => {
|
|
114
90
|
const from = parseVersion(current);
|
|
115
|
-
if (!from) return
|
|
91
|
+
if (!from) return NO_TARGETS;
|
|
116
92
|
let compatible = '';
|
|
117
93
|
let major = '';
|
|
118
94
|
for (const name of names) {
|
|
119
95
|
const ver = parseVersion(name);
|
|
120
|
-
if (!ver) continue;
|
|
121
96
|
if (cmpVersion(ver, from) <= 0) continue;
|
|
122
|
-
if (ver.major === from.major)
|
|
123
|
-
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (ver.major > from.major) major = pickNewerName(major, name, ver);
|
|
97
|
+
if (ver.major === from.major) compatible = newer(compatible, name);
|
|
98
|
+
else if (ver.major > from.major) major = newer(major, name);
|
|
127
99
|
}
|
|
128
100
|
return { compatible, major };
|
|
129
101
|
};
|
|
130
102
|
|
|
131
|
-
const
|
|
132
|
-
const from = parseVersion(current);
|
|
133
|
-
const to = parseVersion(latest);
|
|
134
|
-
if (!from || !to || cmpVersion(to, from) <= 0) {
|
|
135
|
-
return { compatible: '', major: '' };
|
|
136
|
-
}
|
|
137
|
-
if (to.major > from.major) return { compatible: '', major: latest };
|
|
138
|
-
return { compatible: latest, major: '' };
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const planUpdate = (current, extra = {}) => {
|
|
103
|
+
const planUpdate = (current, targets) => {
|
|
142
104
|
const from = parseVersion(current);
|
|
143
105
|
if (!from) return { action: 'none' };
|
|
144
|
-
|
|
145
|
-
let major = extra.major ?? '';
|
|
146
|
-
const skipped = extra.skipped ?? extra.handled ?? '';
|
|
147
|
-
if (!compatible && !major && extra.latest) {
|
|
148
|
-
const classified = classifyLatest(current, extra.latest);
|
|
149
|
-
compatible = classified.compatible;
|
|
150
|
-
major = classified.major;
|
|
151
|
-
}
|
|
106
|
+
const { compatible, major, skipped } = targets;
|
|
152
107
|
const compatVer = parseVersion(compatible);
|
|
153
108
|
if (compatVer && compatVer.major === from.major) {
|
|
154
109
|
if (cmpVersion(compatVer, from) > 0) {
|
|
@@ -162,53 +117,40 @@ const planUpdate = (current, extra = {}) => {
|
|
|
162
117
|
return { action: 'confirm', current, latest: major };
|
|
163
118
|
};
|
|
164
119
|
|
|
165
|
-
const isEnabled = (options
|
|
166
|
-
if (options.enabled ===
|
|
167
|
-
|
|
168
|
-
const pkgDir = options.pkgDir ?? PKG_DIR;
|
|
169
|
-
return isNpmInstall(pkgDir);
|
|
120
|
+
const isEnabled = (options) => {
|
|
121
|
+
if (typeof options.enabled === 'boolean') return options.enabled;
|
|
122
|
+
return isNpmInstall(options.pkgDir ?? PKG_DIR);
|
|
170
123
|
};
|
|
171
124
|
|
|
172
|
-
const fetchTargets = async (current, options
|
|
173
|
-
const empty = { compatible: '', major: '' };
|
|
125
|
+
const fetchTargets = async (current, options) => {
|
|
174
126
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
175
|
-
if (typeof fetchImpl !== 'function') return
|
|
176
|
-
const
|
|
177
|
-
const url = `${REGISTRY}/${encoded}`;
|
|
127
|
+
if (typeof fetchImpl !== 'function') return NO_TARGETS;
|
|
128
|
+
const url = `${REGISTRY}/${encodeURIComponent(PKG_NAME)}`;
|
|
178
129
|
const headers = { accept: 'application/json' };
|
|
179
130
|
const signal = options.signal ?? AbortSignal.timeout(FETCH_MS);
|
|
180
131
|
const response = await fetchImpl(url, { headers, signal });
|
|
181
|
-
if (!response || !response.ok) return
|
|
132
|
+
if (!response || !response.ok) return NO_TARGETS;
|
|
182
133
|
const data = await response.json();
|
|
183
134
|
return pickTargets(current, listVersions(data));
|
|
184
135
|
};
|
|
185
136
|
|
|
186
|
-
const npmInstall = async (spec, extra = {}) => {
|
|
187
|
-
const timeout = extra.timeout ?? INSTALL_MS;
|
|
188
|
-
const result = await runProc(
|
|
189
|
-
npmBin(),
|
|
190
|
-
['install', '-g', spec],
|
|
191
|
-
npmOpts({ timeout }),
|
|
192
|
-
);
|
|
193
|
-
if (result.error) throw result.error;
|
|
194
|
-
if (result.status !== 0) {
|
|
195
|
-
throw new Error(oneLine(result.stderr, 'npm install failed'));
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
137
|
const installUpdate = async (version, options = {}) => {
|
|
200
138
|
if (typeof options.install === 'function') {
|
|
201
139
|
await options.install(version);
|
|
202
140
|
return;
|
|
203
141
|
}
|
|
204
|
-
const
|
|
205
|
-
|
|
142
|
+
const timeout = options.timeout ?? INSTALL_MS;
|
|
143
|
+
const args = ['install', '-g', `${PKG_NAME}@${version}`];
|
|
144
|
+
const result = await runProc(npmBin(), args, npmOpts({ timeout }));
|
|
145
|
+
if (result.error) throw result.error;
|
|
146
|
+
if (result.status !== 0) {
|
|
147
|
+
throw new Error(oneLine(result.stderr, 'npm install failed'));
|
|
148
|
+
}
|
|
206
149
|
};
|
|
207
150
|
|
|
208
151
|
const markSkipped = (version, options = {}) => {
|
|
209
152
|
const file = options.cacheFile ?? cacheFile(options.env);
|
|
210
|
-
|
|
211
|
-
writeCache(file, { ...cache, skipped: version });
|
|
153
|
+
writeCache(file, { ...readCache(file), skipped: version });
|
|
212
154
|
};
|
|
213
155
|
|
|
214
156
|
const checkUpdate = async (options = {}) => {
|
|
@@ -216,37 +158,28 @@ const checkUpdate = async (options = {}) => {
|
|
|
216
158
|
const now = Date.now();
|
|
217
159
|
const current = options.current ?? PKG_VERSION;
|
|
218
160
|
const file = options.cacheFile ?? cacheFile(options.env);
|
|
219
|
-
const interval = options.interval ?? CHECK_INTERVAL_MS;
|
|
220
161
|
const cache = readCache(file);
|
|
221
|
-
if (isFresh(cache, now, interval)) {
|
|
162
|
+
if (isFresh(cache, now, options.interval)) {
|
|
222
163
|
const plan = planUpdate(current, cache);
|
|
223
|
-
|
|
224
|
-
return plan;
|
|
164
|
+
return plan.action === 'install' ? { action: 'none' } : plan;
|
|
225
165
|
}
|
|
226
|
-
let targets =
|
|
166
|
+
let targets = NO_TARGETS;
|
|
227
167
|
try {
|
|
228
168
|
targets = await fetchTargets(current, options);
|
|
229
169
|
} catch {
|
|
230
170
|
// ignore registry errors
|
|
231
171
|
}
|
|
232
|
-
const next = {
|
|
233
|
-
...cache,
|
|
234
|
-
checkedAt: now,
|
|
235
|
-
compatible: targets.compatible,
|
|
236
|
-
major: targets.major,
|
|
237
|
-
};
|
|
238
172
|
if (!targets.compatible && !targets.major) {
|
|
239
173
|
writeCache(file, { ...cache, checkedAt: now });
|
|
240
174
|
return { action: 'none' };
|
|
241
175
|
}
|
|
176
|
+
const next = { ...cache, checkedAt: now, ...targets };
|
|
242
177
|
writeCache(file, next);
|
|
243
178
|
return planUpdate(current, next);
|
|
244
179
|
};
|
|
245
180
|
|
|
246
181
|
module.exports = {
|
|
247
182
|
CHECK_INTERVAL_MS,
|
|
248
|
-
parseVersion,
|
|
249
|
-
cmpVersion,
|
|
250
183
|
isNpmInstall,
|
|
251
184
|
cacheFile,
|
|
252
185
|
readCache,
|