All files / watch.ts

100.00% Branches 0/0
9.80% Lines 5/51
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 
x2
x2
x2
x2
x2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

































































import type { Meta } from './index.ts';
import { Fn, msec, entrypoint as entry } from './format.ts';
import { bold, blue } from './format/ansi.ts';
import { getCwd, resolvePath, realpathSync, stdout, watchFs } from './deps.ts';
export * from './watch/denoCheck.ts';
export * from './watch/runTest.ts';
/** Entrypoint that reruns on file change. */
export const entrypoint = function watchEntrypoint (
  meta: Meta, mode: Fn<[string, string[]]>, ...options: unknown[]
) {
  return entry(meta, Fn(watch, mode, options||[]))
};
const toRealPath = (x: string) => { try { return realpathSync(x) } catch (e) { if (e.code!=='ENOENT') throw e } };
const toRelativePath = (cwd: string) => (x: string) => resolvePath(x).replace(cwd, '.');
/** Run a watcher function. */
export async function watch (mode: Fn<[string, string[]]>, options: unknown[]) {
  // todo: make configurable
  const cwd = getCwd();
  // debounce timer
  let timer = null;
  // debounce interval
  const interval = 100;
  // initial run
  await update({ force: true });
  // update on every event
  for await (const event of watchFs(".")) await update(event);
  // main update function
  async function update ({
    force = false, kind = null, paths = [],
    filter = (x: string) => !(
      x.endsWith('~')||
      x.includes('/.git/')||
      x.includes('/toolbox/')||
      x.includes('/coverage/')||
      x.includes('/.deno.lock')||
      x.includes('/node_modules/.deno/')
    ),
  } = {}) {
    // non-forced updates go through the debounce
    if (!force) {
      // ignore access events; todo: configurable
      if (kind === 'access') return;
      // ignore paths we don't care about
      paths = paths.filter(filter);
      // skip if only ignored paths were updated
      if (paths.length === 0) return;
      // convert paths to relative and filter again
      paths = paths.map(toRealPath).filter(Boolean).map(toRelativePath(cwd));
      // log update at bottom left corner
      stdout.write(`\x1b[${stdout.rows||1};1H` + `\x1b[0K`
        + blue(bold(kind) + ' ' + paths.join(', ').slice(0, stdout.columns)));
    }
    if (timer) clearTimeout(timer);
    timer = setTimeout(async () => {
      const t0 = performance.now();
      try {
        await mode(kind, paths, ...options)
      } finally {
        stdout.write(
          `\x1b[${stdout.rows||1};${1}H` + blue('waiting for changes') +
          `\x1b[${stdout.rows||1};1H` +
          `\x1b[${Math.max(0, stdout.columns - 10)}G` +
          blue(msec(performance.now() - t0)));
      }
    }, force ? 0 : interval);
  }
}