// Build the views an agent host draws (src/views/) into one self-contained
// page: the view's script (the Studio's components, bundled), the Studio's
// styles, and the engine inside the view (wai/wai-quantum-views) twice, as
// WebAssembly (base64) and as the same build translated to JavaScript. The page
// loads nothing and connects nowhere. The servers serve it for each of the six
// views, naming the view in its .
//
// node build-views.mjs --wasm VIEWS.wasm --js VIEWS.mjs --out DIR
//
// writes DIR/wai-quantum-views.html and DIR/manifest.json (each part's size and
// SHA-256, and every source the page is built from with its SHA-256). Run it
// through wai/wai-quantum-views/build.sh, which builds the two engines first.
//
// node build-views.mjs --check
//
// says whether the committed page (wai/wai-quantum/views/) is built from the
// sources as they are: every source it names unchanged, and no source added.
import { build, transform } from 'esbuild';
import { createHash } from 'node:crypto';
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
import { dirname, join, relative } from 'node:path';
import { fileURLToPath } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const repo = join(here, '..', '..');
const arg = (k) => { const i = process.argv.indexOf(k); return i > 0 ? process.argv[i + 1] : null; };
const [wasmPath, jsPath, out] = [arg('--wasm'), arg('--js'), arg('--out')];
const checking = process.argv.includes('--check');
if (!checking && (!wasmPath || !jsPath || !out)) {
console.error('usage: node build-views.mjs --wasm VIEWS.wasm --js VIEWS.mjs --out DIR');
process.exit(2);
}
// The most a view may be: the servers' tests hold every view to it.
export const LIMIT = 1 << 20;
// The name the servers replace with the view's.
export const PLACEHOLDER = '__WAI_QUANTUM_VIEW__';
const sha = (b) => createHash('sha256').update(b).digest('hex');
const bytes = (s) => Buffer.byteLength(s, 'utf8');
// The views are in English: the Studio's components they share read their
// words through src/i18n.js, and here its locales are English alone, so the
// page carries no other language's words.
const englishOnly = {
name: 'english-only',
setup(b) {
b.onResolve({ filter: /\/locales\/index\.js$/ }, () => ({ path: 'english-only', namespace: 'wai-views-locales' }));
b.onLoad({ filter: /.*/, namespace: 'wai-views-locales' }, () => {
const dir = join(here, 'locales', 'en');
const areas = readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
const names = areas.map((f, i) => `en${i}`);
return {
resolveDir: dir,
loader: 'js',
contents: `${areas.map((f, i) => `import ${names[i]} from './${f}';`).join('\n')}\nexport const AREAS = ${JSON.stringify(areas.map((f) => f.replace(/\.json$/, '')))};\nexport default { en: Object.assign({}, ${names.join(', ')}) };\n`,
};
});
},
};
// The view's script, and every module it bundles.
const app = await build({ absWorkingDir: here, entryPoints: [join(here, 'src/views/main.js')], bundle: true, format: 'esm', target: ['es2022'], minify: true, legalComments: 'none', write: false, metafile: true, logLevel: 'warning', plugins: [englishOnly] });
const script = app.outputFiles[0].text;
const styleFiles = ['src/tokens.css', 'src/studio.css', 'src/views/views.css'];
// Every source, relative to the repository.
const sourcesOf = (inputs) => [
...Object.keys(inputs).filter((p) => !p.startsWith('wai-views-locales:')).map((p) => join(here, p)),
...styleFiles.map((f) => join(here, f)),
join(here, 'build-views.mjs'),
...['src/lib.rs', 'Cargo.toml', 'Cargo.lock', 'build.sh'].map((f) => join(repo, 'wai/wai-quantum-views', f)),
].map((p) => relative(repo, p).split('\\').join('/')).sort();
if (checking) {
const committed = JSON.parse(readFileSync(join(repo, 'wai/wai-quantum/views/manifest.json'), 'utf8'));
const now = sourcesOf(app.metafile.inputs);
const stale = now.filter((p) => committed.sources[p] !== sha(readFileSync(join(repo, p))));
const gone = Object.keys(committed.sources).filter((p) => !now.includes(p));
if (stale.length || gone.length) {
console.error(`the committed views page is not built from these sources: ${[...stale.map((p) => `${p} changed`), ...gone.map((p) => `${p} no longer used`)].join('; ')}. Rebuild it: bash wai/wai-quantum-views/build.sh`);
process.exit(1);
}
console.log(`the committed views page is built from these ${now.length} sources`);
process.exit(0);
}
// The engine as JavaScript, made a factory: it runs only when a host's policy refuses the WebAssembly.
const engineJs = (await transform(readFileSync(jsPath, 'utf8'), { format: 'iife', globalName: '__wqEngine', minify: true, legalComments: 'none', target: 'es2022' })).code;
const factory = `globalThis.wqEngineJs=function(){${engineJs};return __wqEngine};`;
const wasm = readFileSync(wasmPath);
const css = (await transform(styleFiles.map((f) => readFileSync(join(here, f), 'utf8')).join('\n'), { loader: 'css', minify: true, legalComments: 'none' })).code;
// Text inside a