reslop 0.2.5 → 0.2.7

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +72 -19
  6. package/lib/api.js +1 -1
  7. package/lib/capabilities.js +8 -8
  8. package/lib/cli.js +61 -75
  9. package/lib/clipboard.js +39 -47
  10. package/lib/dashboard/git.js +128 -0
  11. package/lib/dashboard/model.js +357 -0
  12. package/lib/dashboard/npm.js +290 -0
  13. package/lib/dashboard/tree.js +278 -0
  14. package/lib/deps-diff.js +197 -402
  15. package/lib/deps.js +113 -240
  16. package/lib/detect.js +8 -18
  17. package/lib/diff/align.js +41 -113
  18. package/lib/diff/diff.js +12 -43
  19. package/lib/diff/display.js +25 -36
  20. package/lib/diff/edit.js +6 -20
  21. package/lib/diff/inline.js +76 -144
  22. package/lib/diff/items.js +33 -47
  23. package/lib/diff/parse.js +28 -56
  24. package/lib/diff/patch.js +12 -33
  25. package/lib/diff/unit.js +79 -178
  26. package/lib/editor.js +52 -69
  27. package/lib/files.js +79 -91
  28. package/lib/find.js +61 -0
  29. package/lib/git-branches.js +254 -416
  30. package/lib/git-deps.js +148 -272
  31. package/lib/git-reword-todo.js +5 -13
  32. package/lib/git-worktree.js +161 -214
  33. package/lib/git.js +21 -49
  34. package/lib/github.js +67 -181
  35. package/lib/gitlab.js +40 -90
  36. package/lib/highlight/bash.js +55 -177
  37. package/lib/highlight/core.js +155 -141
  38. package/lib/highlight/css.js +25 -58
  39. package/lib/highlight/csv.js +19 -37
  40. package/lib/highlight/dart.js +72 -213
  41. package/lib/highlight/dot.js +14 -14
  42. package/lib/highlight/html.js +42 -63
  43. package/lib/highlight/js.js +177 -506
  44. package/lib/highlight/json.js +17 -37
  45. package/lib/highlight/log.js +19 -25
  46. package/lib/highlight/txt.js +7 -10
  47. package/lib/highlight.js +17 -46
  48. package/lib/keys.js +75 -83
  49. package/lib/manifest.js +67 -126
  50. package/lib/npm-commands.js +120 -169
  51. package/lib/remote.js +140 -190
  52. package/lib/render/branches.js +63 -61
  53. package/lib/render/chrome.js +412 -347
  54. package/lib/render/commits.js +160 -361
  55. package/lib/render/dash-activity.js +212 -0
  56. package/lib/render/dash-blocks.js +496 -0
  57. package/lib/render/dash-table.js +295 -0
  58. package/lib/render/dashboard.js +62 -0
  59. package/lib/render/diff.js +123 -258
  60. package/lib/render/files.js +60 -86
  61. package/lib/render/frame.js +82 -117
  62. package/lib/render/notes.js +150 -210
  63. package/lib/render/npm.js +213 -177
  64. package/lib/render/packages.js +93 -0
  65. package/lib/render/present.js +4 -11
  66. package/lib/render/primitives.js +77 -172
  67. package/lib/render/render.js +5 -14
  68. package/lib/render/report.js +43 -0
  69. package/lib/render/repos.js +226 -0
  70. package/lib/render/search.js +25 -0
  71. package/lib/render/tiles.js +355 -0
  72. package/lib/render/unit.js +15 -31
  73. package/lib/report-model.js +110 -33
  74. package/lib/report-parse.js +56 -37
  75. package/lib/report-render.js +102 -59
  76. package/lib/report-run.js +87 -77
  77. package/lib/report-test.js +221 -99
  78. package/lib/report-tools.js +96 -154
  79. package/lib/review-merge.js +33 -41
  80. package/lib/review.js +69 -129
  81. package/lib/runs.js +225 -0
  82. package/lib/select.js +53 -91
  83. package/lib/session/accessors.js +14 -21
  84. package/lib/session/actions.js +189 -65
  85. package/lib/session/branches.js +164 -222
  86. package/lib/session/change-actions.js +49 -119
  87. package/lib/session/commit.js +298 -352
  88. package/lib/session/compose.js +572 -715
  89. package/lib/session/dashboard.js +530 -0
  90. package/lib/session/dispatch.js +197 -171
  91. package/lib/session/files-pane.js +92 -45
  92. package/lib/session/find.js +94 -0
  93. package/lib/session/items.js +6 -17
  94. package/lib/session/lifecycle.js +420 -466
  95. package/lib/session/load.js +1 -14
  96. package/lib/session/navigation.js +4 -2
  97. package/lib/session/npm.js +403 -420
  98. package/lib/session/ops.js +5 -13
  99. package/lib/session/packages.js +143 -0
  100. package/lib/session/pointer.js +30 -14
  101. package/lib/session/progress.js +39 -30
  102. package/lib/session/review.js +1 -3
  103. package/lib/session/tasks.js +277 -236
  104. package/lib/session/templates.js +51 -59
  105. package/lib/session/terminal.js +85 -132
  106. package/lib/session/view.js +59 -80
  107. package/lib/session/watch.js +201 -212
  108. package/lib/session/workspace.js +455 -0
  109. package/lib/session.js +95 -161
  110. package/lib/update.js +44 -111
  111. package/lib/utilities.js +46 -88
  112. package/lib/workspace.js +166 -0
  113. package/lib/wrap.js +29 -55
  114. package/package.json +1 -1
@@ -3,7 +3,12 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
+ const runs = require('../runs.js');
7
+ const { isRunsRel } = runs;
8
+
6
9
  const DEBOUNCE_MS = 200;
10
+ const UNKNOWN_PATH = '.';
11
+ const MODULES_STAMP = 'node_modules/.package-lock.json';
7
12
 
8
13
  const SKIP_DIRS = ['node_modules', '.review', 'coverage', 'dist', '.cache'];
9
14
 
@@ -14,41 +19,30 @@ const GIT_WATCH_NAMES = [
14
19
  'FETCH_HEAD',
15
20
  'ORIG_HEAD',
16
21
  'refs',
22
+ 'rebase-merge',
23
+ 'rebase-apply',
17
24
  ];
18
25
 
19
- const linuxRecursiveNode = (version) => {
20
- const parts = `${version}`.split('.');
21
- const major = Number(parts[0]);
22
- const minor = Number(parts[1]);
23
- if (major > 19) return true;
24
- return major === 19 && minor >= 1;
25
- };
26
-
27
26
  const canWatchRecursive = (
28
27
  platform = process.platform,
29
28
  version = process.versions.node,
30
29
  ) => {
31
30
  if (platform === 'win32' || platform === 'darwin') return true;
32
- if (platform === 'linux') return linuxRecursiveNode(version);
33
- return false;
31
+ if (platform !== 'linux') return false;
32
+ const [major, minor] = `${version}`.split('.').map(Number);
33
+ return major > 19 || (major === 19 && minor >= 1);
34
34
  };
35
35
 
36
- const ignoredName = (name) => {
37
- if (!name) return false;
38
- if (name.endsWith('.lock')) return true;
39
- if (name.endsWith('~')) return true;
40
- if (name.endsWith('.swp') || name.endsWith('.swo')) return true;
41
- if (name.startsWith('.#')) return true;
42
- if (name === '.DS_Store') return true;
43
- return false;
44
- };
36
+ const IGNORED_SUFFIXES = ['.lock', '~', '.swp', '.swo'];
45
37
 
46
- const ignoredGitRel = (parts) => {
47
- if (parts[1] === 'logs' && parts[2] === 'HEAD') return false;
48
- const name = parts[1] ?? '';
49
- if (!name) return false;
50
- if (GIT_WATCH_NAMES.includes(name)) return false;
51
- return true;
38
+ const ignoredName = (name) =>
39
+ IGNORED_SUFFIXES.some((suffix) => name.endsWith(suffix)) ||
40
+ name.startsWith('.#') ||
41
+ name === '.DS_Store';
42
+
43
+ const ignoredGitRel = ([, name = '', sub]) => {
44
+ if (name === 'logs' && sub === 'HEAD') return false;
45
+ return name !== '' && !GIT_WATCH_NAMES.includes(name);
52
46
  };
53
47
 
54
48
  const slashRel = (rel) => `${rel}`.replaceAll('\\', '/');
@@ -56,34 +50,24 @@ const slashRel = (rel) => `${rel}`.replaceAll('\\', '/');
56
50
  const ignoredRel = (rel) => {
57
51
  const norm = slashRel(rel);
58
52
  if (!norm || norm === '.') return false;
53
+ if (norm === MODULES_STAMP) return false;
59
54
  const parts = norm.split('/');
60
- for (const part of parts) {
61
- if (SKIP_DIRS.includes(part)) return true;
62
- }
55
+ if (parts.some((part) => SKIP_DIRS.includes(part))) return true;
63
56
  if (parts[0] === '.git') return ignoredGitRel(parts);
64
57
  return ignoredName(parts[parts.length - 1]);
65
58
  };
66
59
 
67
60
  const skipDirName = (name) => name === '.git' || SKIP_DIRS.includes(name);
68
61
 
69
- const closeWatcher = (watcher) => {
70
- try {
71
- watcher.close();
72
- } catch {
73
- // already closed
74
- }
75
- };
76
-
77
62
  const reviewMarkdown = (rel) => {
78
- const norm = `${rel}`.replaceAll('\\', '/');
79
- if (!norm.startsWith('.review/')) return false;
80
- const rest = norm.slice('.review/'.length);
63
+ if (!rel.startsWith('.review/')) return false;
64
+ const rest = rel.slice('.review/'.length);
81
65
  if (!rest || rest.includes('/')) return false;
82
66
  return rest.endsWith('.md');
83
67
  };
84
68
 
85
69
  const isReviewDir = (root, dir) => {
86
- const rel = path.relative(root, dir).replaceAll('\\', '/');
70
+ const rel = slashRel(path.relative(root, dir));
87
71
  return rel === '.review' || rel.startsWith('.review/');
88
72
  };
89
73
 
@@ -106,16 +90,15 @@ const isBareReviewName = (root, dir, rel) => {
106
90
  const isReviewEvent = (root, dir, name) => {
107
91
  if (isReviewDir(root, dir)) return true;
108
92
  if (!name) return false;
109
- const rel = path.relative(root, path.join(dir, name));
110
- const norm = rel.replaceAll('\\', '/');
111
- if (reviewMarkdown(norm)) return true;
112
- return isBareReviewName(root, dir, norm);
93
+ const rel = slashRel(path.relative(root, path.join(dir, name)));
94
+ if (reviewMarkdown(rel)) return true;
95
+ return isBareReviewName(root, dir, rel);
113
96
  };
114
97
 
115
98
  const isOwnDirEvent = (dir, name) => {
116
99
  if (!name) return false;
117
100
  const base = path.basename(dir);
118
- const slash = `${name}`.replaceAll('\\', '/');
101
+ const slash = slashRel(name);
119
102
  const bare = slash.startsWith('./') ? slash.slice(2) : slash;
120
103
  const trimmed = bare.endsWith('/') ? bare.slice(0, -1) : bare;
121
104
  if (trimmed === base) {
@@ -144,200 +127,206 @@ const unchangedSince = (full, since) => {
144
127
  }
145
128
  };
146
129
 
147
- const schedule = (state, timerKey, fire) => {
148
- if (state.closed || !fire) return;
149
- if (state[timerKey] !== null) clearTimeout(state[timerKey]);
150
- state[timerKey] = setTimeout(() => {
151
- state[timerKey] = null;
152
- if (state.closed) return;
153
- fire();
154
- }, state.debounceMs);
155
- const timer = state[timerKey];
156
- if (timer && typeof timer.unref === 'function') timer.unref();
157
- };
158
-
159
130
  const REVIEW_HOLD_MS = 200;
160
131
 
161
- const reviewBurst = (state) => {
162
- const seen = state.reviewSeenAt;
163
- if (!seen) return false;
164
- const hold = Math.max(state.debounceMs * 2, REVIEW_HOLD_MS);
165
- return Date.now() - seen < hold;
166
- };
132
+ class DiskWatcher {
133
+ constructor(options) {
134
+ this.root = path.resolve(options.root);
135
+ this.debounceMs = options.debounceMs ?? DEBOUNCE_MS;
136
+ this.onChange = options.onChange;
137
+ this.onReview = options.onReview;
138
+ this.onPaths = options.onPaths;
139
+ this.matchReview = options.matchReview ?? null;
140
+ this.recursive = options.recursive ?? canWatchRecursive();
141
+ this.watchers = [];
142
+ this.watched = new Set();
143
+ this.files = new Set();
144
+ this.pending = new Set();
145
+ this.timers = { change: null, review: null, loose: null, paths: null };
146
+ this.looseChange = false;
147
+ this.reviewSeenAt = 0;
148
+ this.startedAt = Date.now();
149
+ this.closed = false;
150
+ const scopes = options.paths?.length ? options.paths : ['.'];
151
+ for (const spec of scopes) this.watchScope(spec);
152
+ const gitDir = path.resolve(this.root, '.git');
153
+ this.watchOne(gitDir);
154
+ this.watchOne(path.join(gitDir, 'refs'));
155
+ this.watchOne(path.join(gitDir, 'logs'));
156
+ this.watchOne(path.resolve(this.root, runs.runsDir('.')));
157
+ this.watchOne(path.resolve(this.root, '.review'));
158
+ }
167
159
 
168
- const scheduleChange = (state, loose = false) => {
169
- state.looseChange = loose && (state.timer === null || state.looseChange);
170
- schedule(state, 'timer', () => {
171
- if (state.looseChange && reviewBurst(state)) return;
172
- if (state.onChange) state.onChange();
173
- });
174
- };
160
+ schedule(key, fire) {
161
+ if (this.closed || !fire) return;
162
+ this.clearTimer(key);
163
+ this.timers[key] = setTimeout(() => {
164
+ this.timers[key] = null;
165
+ if (!this.closed) fire();
166
+ }, this.debounceMs);
167
+ this.timers[key].unref();
168
+ }
175
169
 
176
- const scheduleUnnamed = (state) => {
177
- if (reviewBurst(state)) return;
178
- schedule(state, 'looseTimer', () => {
179
- if (!reviewBurst(state)) scheduleChange(state, true);
180
- });
181
- };
170
+ clearTimer(key) {
171
+ if (this.timers[key] !== null) clearTimeout(this.timers[key]);
172
+ this.timers[key] = null;
173
+ }
182
174
 
183
- const clearTimer = (state, timerKey) => {
184
- if (state[timerKey] !== null) clearTimeout(state[timerKey]);
185
- state[timerKey] = null;
186
- };
175
+ reviewBurst() {
176
+ if (!this.reviewSeenAt) return false;
177
+ const hold = Math.max(this.debounceMs * 2, REVIEW_HOLD_MS);
178
+ return Date.now() - this.reviewSeenAt < hold;
179
+ }
187
180
 
188
- const dropLooseChange = (state) => {
189
- clearTimer(state, 'looseTimer');
190
- if (state.looseChange) clearTimer(state, 'timer');
191
- };
181
+ flushPaths() {
182
+ if (!this.pending.size) return;
183
+ const list = [...this.pending];
184
+ this.pending.clear();
185
+ if (this.onPaths) this.onPaths(list);
186
+ }
192
187
 
193
- const handleWatchEvent = (state, dir, eventType, filename) => {
194
- if (state.closed) return;
195
- const name = filename ? `${filename}` : '';
196
- const rel = name
197
- ? slashRel(path.relative(state.root, path.join(dir, name)))
198
- : '';
199
- const wanted = state.files.has(rel);
200
- if (isReviewEvent(state.root, dir, name)) {
201
- state.reviewSeenAt = Date.now();
202
- dropLooseChange(state);
203
- schedule(state, 'reviewTimer', state.onReview);
204
- if (!wanted) return;
188
+ scheduleChange(loose = false) {
189
+ const idle = this.timers.change === null;
190
+ this.looseChange = loose && (idle || this.looseChange);
191
+ this.schedule('change', () => {
192
+ if (this.looseChange && this.reviewBurst()) return;
193
+ this.flushPaths();
194
+ if (this.onChange) this.onChange();
195
+ });
205
196
  }
206
- if (!name) return void scheduleUnnamed(state);
207
- if (!wanted && ignoredRel(rel)) return;
208
- if (isOwnDirEvent(dir, name)) return;
209
- const full = path.join(dir, name);
210
- if (unchangedSince(full, state.startedAt)) return;
211
- if (eventType === 'rename') state.watchNewDir(full);
212
- scheduleChange(state);
213
- };
214
197
 
215
- const watchOne = (state, target, extra = {}) => {
216
- if (state.closed) return;
217
- if (state.watched.has(target)) return;
218
- try {
219
- const onEvent = (eventType, filename) => {
220
- handleWatchEvent(state, target, eventType, filename);
221
- };
222
- const watcher = fs.watch(
223
- target,
224
- { persistent: false, encoding: 'utf8', ...extra },
225
- onEvent,
226
- );
227
- if (typeof watcher.on === 'function') {
228
- watcher.on('error', () => {});
229
- }
230
- state.watched.add(target);
231
- state.watchers.push(watcher);
232
- } catch {
233
- // missing path or too many watchers
198
+ scheduleUnnamed() {
199
+ if (this.reviewBurst()) return;
200
+ this.pending.add(UNKNOWN_PATH);
201
+ this.schedule('loose', () => {
202
+ if (!this.reviewBurst()) this.scheduleChange(true);
203
+ });
234
204
  }
235
- };
236
205
 
237
- const watchTree = (state, dir) => {
238
- watchOne(state, dir, state.dirWatchOpts);
239
- if (state.recursive) return;
240
- let entries;
241
- try {
242
- entries = fs.readdirSync(dir, { withFileTypes: true });
243
- } catch {
244
- return;
206
+ dropLooseChange() {
207
+ this.clearTimer('loose');
208
+ if (this.looseChange) this.clearTimer('change');
245
209
  }
246
- for (const entry of entries) {
247
- if (!entry.isDirectory()) continue;
248
- if (skipDirName(entry.name)) continue;
249
- watchTree(state, path.join(dir, entry.name));
210
+
211
+ onEvent(dir, eventType, filename) {
212
+ if (this.closed) return;
213
+ const name = filename ? `${filename}` : '';
214
+ const rel = name
215
+ ? slashRel(path.relative(this.root, path.join(dir, name)))
216
+ : '';
217
+ const wanted = this.files.has(rel);
218
+ if (isRunsRel(rel)) {
219
+ this.pending.add(rel);
220
+ if (name && eventType === 'rename') {
221
+ this.watchNewDir(path.join(dir, name));
222
+ }
223
+ return void this.schedule('paths', () => this.flushPaths());
224
+ }
225
+ if (isReviewEvent(this.root, dir, name)) {
226
+ this.reviewSeenAt = Date.now();
227
+ this.dropLooseChange();
228
+ this.schedule('review', this.onReview);
229
+ if (!wanted) return;
230
+ }
231
+ if (rel && this.matchReview && this.matchReview(rel)) {
232
+ this.pending.add(rel);
233
+ return void this.schedule('paths', () => this.flushPaths());
234
+ }
235
+ if (!name) return void this.scheduleUnnamed();
236
+ if (!wanted && ignoredRel(rel)) return;
237
+ if (isOwnDirEvent(dir, name)) return;
238
+ const full = path.join(dir, name);
239
+ if (unchangedSince(full, this.startedAt)) return;
240
+ if (eventType === 'rename') this.watchNewDir(full);
241
+ this.pending.add(rel);
242
+ this.scheduleChange();
250
243
  }
251
- };
252
244
 
253
- const maybeWatchNewDir = (state, full) => {
254
- if (state.recursive) return;
255
- let stat;
256
- try {
257
- stat = fs.statSync(full);
258
- } catch {
259
- return;
245
+ watchOne(target, recursive = false) {
246
+ if (this.closed || this.watched.has(target)) return;
247
+ try {
248
+ const options = { persistent: false, encoding: 'utf8', recursive };
249
+ const watcher = fs.watch(target, options, (eventType, filename) => {
250
+ this.onEvent(target, eventType, filename);
251
+ });
252
+ watcher.on('error', () => {});
253
+ this.watched.add(target);
254
+ this.watchers.push(watcher);
255
+ } catch {
256
+ // missing path or too many watchers
257
+ }
260
258
  }
261
- if (!stat.isDirectory()) return;
262
- if (path.basename(full) === '.review') return void watchOne(state, full);
263
- if (skipDirName(path.basename(full))) return;
264
- watchTree(state, full);
265
- };
266
259
 
267
- const watchScope = (state, spec) => {
268
- const target = path.resolve(state.root, spec);
269
- let stat;
270
- try {
271
- stat = fs.statSync(target);
272
- } catch {
273
- return void watchOne(state, path.dirname(target));
260
+ watchTree(dir) {
261
+ this.watchOne(dir, this.recursive);
262
+ if (this.recursive) return;
263
+ let entries;
264
+ try {
265
+ entries = fs.readdirSync(dir, { withFileTypes: true });
266
+ } catch {
267
+ return;
268
+ }
269
+ for (const entry of entries) {
270
+ if (!entry.isDirectory() || skipDirName(entry.name)) continue;
271
+ this.watchTree(path.join(dir, entry.name));
272
+ }
274
273
  }
275
- if (stat.isDirectory()) return void watchTree(state, target);
276
- watchOne(state, path.dirname(target));
277
- };
278
274
 
279
- const watchGit = (state) => {
280
- const gitDir = path.resolve(state.root, '.git');
281
- watchOne(state, gitDir);
282
- watchOne(state, path.resolve(gitDir, 'refs'));
283
- watchOne(state, path.resolve(gitDir, 'logs'));
284
- };
275
+ watchNewDir(full) {
276
+ if (this.recursive) return;
277
+ try {
278
+ if (!fs.statSync(full).isDirectory()) return;
279
+ } catch {
280
+ return;
281
+ }
282
+ const name = path.basename(full);
283
+ if (name === '.review') this.watchOne(full);
284
+ else if (!skipDirName(name)) this.watchTree(full);
285
+ }
285
286
 
286
- const stopState = (state) => {
287
- state.closed = true;
288
- clearTimer(state, 'timer');
289
- clearTimer(state, 'reviewTimer');
290
- clearTimer(state, 'looseTimer');
291
- while (state.watchers.length) closeWatcher(state.watchers.pop());
292
- state.watched.clear();
293
- };
287
+ watchScope(spec) {
288
+ const target = path.resolve(this.root, spec);
289
+ try {
290
+ if (fs.statSync(target).isDirectory()) return void this.watchTree(target);
291
+ } catch {
292
+ // watch the parent until the path appears
293
+ }
294
+ this.watchOne(path.dirname(target));
295
+ }
294
296
 
295
- const watchFiles = (state, rels) => {
296
- state.files = new Set();
297
- for (const rel of rels) {
298
- if (!rel) continue;
299
- const full = path.resolve(state.root, rel);
300
- state.files.add(slashRel(path.relative(state.root, full)));
301
- watchOne(state, path.dirname(full));
297
+ watchFiles(rels) {
298
+ this.files = new Set();
299
+ for (const rel of rels) {
300
+ if (!rel) continue;
301
+ const full = path.resolve(this.root, rel);
302
+ this.files.add(slashRel(path.relative(this.root, full)));
303
+ this.watchOne(path.dirname(full));
304
+ }
302
305
  }
303
- };
304
306
 
305
- const createDiskWatcher = (options) => {
306
- const recursive = options.recursive ?? canWatchRecursive();
307
- const state = {
308
- root: path.resolve(options.root),
309
- debounceMs: options.debounceMs ?? DEBOUNCE_MS,
310
- onChange: options.onChange,
311
- recursive,
312
- dirWatchOpts: recursive ? { recursive: true } : {},
313
- watchers: [],
314
- watched: new Set(),
315
- files: new Set(),
316
- onReview: options.onReview,
317
- timer: null,
318
- reviewTimer: null,
319
- looseTimer: null,
320
- looseChange: false,
321
- reviewSeenAt: 0,
322
- startedAt: Date.now(),
323
- closed: false,
324
- watchNewDir: (full) => maybeWatchNewDir(state, full),
325
- };
326
- const scopes = options.paths && options.paths.length ? options.paths : ['.'];
327
- for (const spec of scopes) watchScope(state, spec);
328
- watchGit(state);
329
- watchOne(state, path.resolve(state.root, '.review'));
330
- return {
331
- close: () => stopState(state),
332
- watchFiles: (rels) => watchFiles(state, rels),
333
- };
334
- };
307
+ close() {
308
+ this.closed = true;
309
+ for (const key of Object.keys(this.timers)) this.clearTimer(key);
310
+ this.pending.clear();
311
+ for (const watcher of this.watchers) {
312
+ try {
313
+ watcher.close();
314
+ } catch {
315
+ // already closed
316
+ }
317
+ }
318
+ this.watchers = [];
319
+ this.watched.clear();
320
+ }
321
+ }
335
322
 
336
323
  module.exports = {
337
324
  DEBOUNCE_MS,
325
+ UNKNOWN_PATH,
326
+ MODULES_STAMP,
338
327
  canWatchRecursive,
339
328
  ignoredRel,
340
329
  isOwnDirEvent,
341
330
  unchangedSince,
342
- createDiskWatcher,
331
+ DiskWatcher,
343
332
  };