jevstrudel.git / tools / worktree-samples / worktree-samples.mjs

A worktree's own samples, in a page on a dev server that serves another checkout: for the headless tools (tools/measure, tools/check-songs), which play songs on the main dev server while a review may have re-rendered or added samples in its worktree.

The dev server's sample map (pages/jev-samples/strudel.json.js) lists each file as @fs/<its checkout>/samples/<sound>/<file>, so the map says which checkout it serves. When that is not this one, route(page) answers the page's request for the map with one built from this checkout's samples/ (sampleMap.mjs, the site's own builder), and its requests for sample files under either checkout's samples/ from this checkout's files, so a sample only this worktree has plays too, and one it deleted does not.

13import { existsSync, readdirSync, realpathSync, statSync } from 'node:fs';
14import { extname, join } from 'node:path';
15import { SAMPLE_EXTENSIONS, sampleMap } from '../../website/src/jev/sampleMap.mjs';
17const FS_ENTRY = /^@fs(\/.*?)\/samples\/[^/]+\/[^/]+$/;

The checkout whose samples/ the dev server at url serves.

20async function servedRoot(url) {
21  const res = await fetch(`${url}/jev-samples/strudel.json`);
22  if (!res.ok) throw new Error(`${url}/jev-samples/strudel.json answered ${res.status}`);
23  const map = await res.json();
24  for (const [sound, files] of Object.entries(map)) {
25    if (sound === '_base') continue;
26    const root = [files].flat()[0]?.match(FS_ENTRY)?.[1];
27    if (root) return root;
28  }
29  throw new Error(`${url}'s sample map names no /@fs/…/samples/ file, so which checkout it serves is unknown`);
30}

[samples/<sound>/<file>, absolute path] for every sample file in root.

33function samplesIn(root) {
34  const dir = join(root, 'samples');
35  const isDir = (p) => statSync(p).isDirectory();
36  return readdirSync(dir)
37    .filter((sound) => isDir(join(dir, sound)))
38    .flatMap((sound) =>
39      readdirSync(join(dir, sound))
40        .filter((file) => SAMPLE_EXTENSIONS.includes(extname(file).slice(1)))
41        .map((file) => [`samples/${sound}/${file}`, join(dir, sound, file)]),
42    );
43}

null when the dev server at url serves root itself; otherwise { served, root, sounds, route(page) }, served being the checkout the server has, and route making a page hear root's samples instead.

48export async function worktreeSamples({ url, root }) {
49  const served = realpathSync(await servedRoot(url));
50  root = realpathSync(root);
51  if (served === root) return null;
52  const files = samplesIn(root);
53  const map = sampleMap(files.map(([rel]) => [rel, `/@fs${root}/${rel}`]));
54  const prefixes = [served, root].map((r) => `/@fs${r}/`);
55  const local = (requestUrl) => {
56    const path = decodeURIComponent(new URL(requestUrl).pathname);
57    const prefix = prefixes.find((p) => path.startsWith(`${p}samples/`));
58    return prefix && join(root, path.slice(prefix.length));
59  };
60  return {
61    served,
62    root,
63    sounds: Object.keys(map).length - 1,
64    async route(page) {
65      await page.route('**/jev-samples/strudel.json', (route) => route.fulfill({ json: map }));
66      await page.route(
67        (u) => Boolean(local(u.href)),
68        (route) => {
69          const file = local(route.request().url());
70          if (!existsSync(file)) return route.fulfill({ status: 404, body: `not in ${root}/samples` });
71          return route.fulfill({ path: file });
72        },
73      );
74    },
75  };
76}

The line a tool prints when it routes, so its output says whose samples it heard.

80export const routingNote = (tool, w) =>
81  `${tool}: this is a worktree; songs hear its samples (${w.root}/samples, ${w.sounds} sounds), ` +
82  `not the dev server's (${w.served}/samples)`;