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