1import { parseManifest, type SimManifest } from './manifest.ts';
2import type { MFile } from './mrender.ts';
4/**
5 * Discovery: every ../simulated_instruments/<repo>/instrument.json, with
6 * that repo's matlab/*.m sources, bundled at build time. A repo without a
7 * manifest is simply not an instrument; a manifest that fails validation
8 * is reported and skipped rather than taking the app down.
9 */
11const manifestModules = import.meta.glob('../../../../../simulated_instruments/*/instrument.json', {
12 eager: true,
13 query: '?raw',
14 import: 'default',
15}) as Record<string, string>;
17const mModules = import.meta.glob('../../../../../simulated_instruments/*/matlab/*.m', {
18 eager: true,
19 query: '?raw',
20 import: 'default',
21}) as Record<string, string>;
23export interface SimInstrumentDef {
24 manifest: SimManifest;
25 mfiles: MFile[];
26}
28export function loadCatalog(): SimInstrumentDef[] {
29 const out: SimInstrumentDef[] = [];
30 for (const [path, text] of Object.entries(manifestModules)) {
31 const dir = path.slice(0, path.lastIndexOf('/'));
32 let manifest: SimManifest;
33 try {
34 manifest = parseManifest(text);
35 } catch (err) {
36 console.warn(`skipping ${path}: ${err instanceof Error ? err.message : String(err)}`);
37 continue;
38 }
39 const mfiles: MFile[] = Object.entries(mModules)
40 .filter(([p]) => p.startsWith(dir + '/matlab/'))
41 .map(([p, source]) => ({ name: p.split('/').pop()!, source }))
42 .sort((a, b) => a.name.localeCompare(b.name));
43 if (mfiles.length === 0) {
44 console.warn(`skipping ${path}: no matlab/*.m sources found`);
45 continue;
46 }
47 out.push({ manifest, mfiles });
48 }
49 out.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name));
50 return out;
51}