#!/usr/bin/env bun // vim:ft=typescript:ts=4:et /* * Generates bin/rapydscript.mjs — the bun compilation entry point — by scanning * the filesystem for embeddable assets and prepending their import declarations * and the __rapydscript_embedded__ global setup to the contents of bin/rapydscript. * * Usage: * bun bin/build.ts # generate bin/rapydscript.mjs only * bun bin/build.ts --compile --outfile foo # generate + run: bun build bin/rapydscript.mjs --compile --outfile foo */ import path from 'path'; import fs from 'fs'; const repo_root = path.dirname(path.dirname(Bun.main)); const dev_dir = path.join(repo_root, 'dev'); const src_lib_dir = path.join(repo_root, 'src', 'lib'); const bin_dir = path.join(repo_root, 'bin'); const out_path = path.join(bin_dir, 'rapydscript.mjs'); async function exists(p: string): Promise { try { await fs.promises.access(p); return true; } catch { return false; } } // --- Scan assets --- const DEV_TEXT_ASSETS = ['compiler.js', 'baselib-plain-pretty.js', 'baselib-plain-ugly.js']; const DEV_JSON_ASSETS = ['stdlib_modules.json']; const found_dev_text: string[] = []; for (const a of DEV_TEXT_ASSETS) { if (await exists(path.join(dev_dir, a))) found_dev_text.push(a); } const found_dev_json: string[] = []; for (const a of DEV_JSON_ASSETS) { if (await exists(path.join(dev_dir, a))) found_dev_json.push(a); } const stdlib_files: string[] = (await fs.promises.readdir(src_lib_dir)) .filter(f => f.endsWith('.pyj')) .sort(); // --- Pre-bundle the lint worker --- // lint-worker.mjs is loaded via Worker() at runtime. In a compiled standalone // binary Bun can't JIT-bundle it from the /$bunfs/ virtual FS unless all its // transitive imports are already inlined. We pre-bundle it here into a single // self-contained file and embed it via `with { type: 'file' }` so the compiled // binary has a ready-to-use worker chunk at a known virtual-FS path. const lint_worker_src = path.join(repo_root, 'tools', 'lint-worker.mjs'); const lint_worker_bundle_path = path.join(bin_dir, 'lint-worker-bundle.mjs'); const worker_build = await Bun.build({ entrypoints: [lint_worker_src], target: 'bun', format: 'esm', minify: false, }); if (!worker_build.success) { console.error('lint-worker bundle failed:'); worker_build.logs.forEach(l => console.error(l)); process.exit(1); } await Bun.write(lint_worker_bundle_path, worker_build.outputs[0]); console.log(`Written ${path.relative(repo_root, lint_worker_bundle_path)}`); // --- Build the generated asset section --- function var_name(prefix: string, filename: string): string { return prefix + filename.replace(/[^a-zA-Z0-9]/g, '_'); } const lines: string[] = []; lines.push('// Generated by bin/build.ts — do not edit manually'); lines.push('// vim:ft=javascript:ts=4:et'); lines.push(''); lines.push('// === Embedded assets (generated by bin/build.ts) ==='); // Pre-bundled lint worker: type:'file' embeds the bundle in the binary and // returns a virtual-FS path that can be passed directly to new Worker(). lines.push(`import _lint_worker_url from './lint-worker-bundle.mjs' with { type: 'file' };`); for (const a of found_dev_text) { lines.push(`import ${var_name('_dev_', a)} from '../dev/${a}' with { type: 'text' };`); } for (const a of found_dev_json) { lines.push(`import ${var_name('_dev_', a)} from '../dev/${a}' with { type: 'json' };`); } for (const f of stdlib_files) { lines.push(`import ${var_name('_stdlib_', f)} from '../src/lib/${f}' with { type: 'text' };`); } lines.push(''); lines.push('globalThis.__rapydscript_embedded__ = {'); for (const a of found_dev_text) { lines.push(` '${a}': ${var_name('_dev_', a)},`); } for (const a of found_dev_json) { // strip extension for the key so consumers use e.g. embedded.stdlib_modules const key = a.replace(/\.json$/, '').replace(/-/g, '_'); lines.push(` ${key}: ${var_name('_dev_', a)},`); } lines.push(' stdlib: {'); for (const f of stdlib_files) { lines.push(` '${f}': ${var_name('_stdlib_', f)},`); } lines.push(' },'); lines.push(' // Virtual-FS path to the pre-bundled lint worker (set via type:\'file\' import above).'); lines.push(' \'lint-worker-url\': _lint_worker_url,'); lines.push('};'); lines.push(''); lines.push('// === Entry point (from bin/rapydscript) ==='); lines.push(''); // --- Append bin/rapydscript content (strip the shebang line) --- const entry_src = await fs.promises.readFile(path.join(bin_dir, 'rapydscript'), 'utf-8'); const entry_lines = entry_src.split('\n'); // Drop the first line if it's the shebang, and the vim modeline comment right after let start = 0; if (entry_lines[0].startsWith('#!')) start = 1; if (start < entry_lines.length && entry_lines[start].trim().startsWith('// vim:')) start++; lines.push(...entry_lines.slice(start)); // Ensure the file ends with a newline const content = lines.join('\n'); const final_content = content.endsWith('\n') ? content : content + '\n'; await fs.promises.writeFile(out_path, final_content, 'utf-8'); console.log(`Written ${path.relative(repo_root, out_path)}`); console.log(` ${found_dev_text.length} dev text assets, ${found_dev_json.length} dev json assets, ${stdlib_files.length} stdlib files`); // --- Optionally run bun build with passed-through args --- const extra_args = process.argv.slice(2); if (extra_args.length > 0) { const cmd_args = ['build', out_path, ...extra_args]; console.log(`Running: bun ${cmd_args.join(' ')}`); const proc = Bun.spawn(['bun', ...cmd_args], { stdout: 'inherit', stderr: 'inherit', stdin: 'inherit' }); const code = await proc.exited; process.exit(code); }