#!/usr/bin/env node
// embed.mjs — seal what fiapup.js borrows from oskiewar's pipeline into it.
//
// fiapup.js is one global script, like oskiewar.js, because that is all the
// console host loads. It carries three things it doesn't own, each read from
// the file oskiewar uses so the two games share one pipeline:
//
// xbox/live/object-lisp.mjs the object compiler (KidLisp → baked sketches)
// xbox/live/frame-vm.mjs the frame-program interpreter, run in-game on
// hosts that have no `frame` (the console until
// R6, and this game's own web shell)
// xbox/fiapup/objects/*.lisp the pup, the yard and the props
//
// It writes them between `// ` and `// ` and nothing else.
//
// node xbox/fiapup/embed.mjs rewrite the block
// node xbox/fiapup/embed.mjs --check exit 1 if the block is stale
import { readFileSync, writeFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
const here = new URL("./", import.meta.url);
const live = new URL("../live/", import.meta.url);
const read = (url) => readFileSync(url, "utf8");
export const gamePath = fileURLToPath(new URL("fiapup.js", here));
export const start = "// ";
export const end = "// ";
// What the game draws, by the name it asks for, and the file each compiles from.
export const objects = { puppy: "puppy-flat", camp: "camp-flat", ball: "ball-flat",
rope: "rope-flat", hand: "hand-flat", treat: "treat-flat",
// the meadow's things, stamped into chunk sketches (flower … birch) or
// drawn where they move (butterfly, sheep)
flower: "flower-flat", tuft: "tuft-flat", rock: "rock-flat", pine: "pine-flat", birch: "birch-flat",
butterfly: "butterfly-flat", sheep: "sheep-flat" };
// A module's source with its `export` keywords taken off, and the names it
// exported. `export default x;` lines go; the named export already covers x.
function unexport(source, file) {
const names = [];
const body = source.replace(/^export default .*$/gm, "")
.replace(/^export (async function\*?|function\*?|const|let|class) (\w+)/gm, (_, kind, name) => {
names.push(name);
return `${kind} ${name}`;
});
if (/^import /m.test(body)) throw new Error(`${file}: the game can't carry imports`);
return { body, names };
}
const indent = (text) => text.trimEnd().split("\n").map((line) => (line ? " " + line : line)).join("\n");
export function generate() {
const lisp = unexport(read(new URL("object-lisp.mjs", live)), "object-lisp.mjs");
const vm = unexport(read(new URL("frame-vm.mjs", live)), "frame-vm.mjs");
const sources = Object.entries(objects).map(([name, file]) =>
` ${name}: ${JSON.stringify(read(new URL(`objects/${file}.lisp`, here)))},`);
return [
start,
"// Generated by xbox/fiapup/embed.mjs from xbox/live/object-lisp.mjs,",
"// xbox/live/frame-vm.mjs and xbox/fiapup/objects/*.lisp. Don't edit here:",
"// edit those and rerun the tool.",
"const fiapupObjects = (() => {",
" const objectLisp = (() => {",
indent(lisp.body),
` return { ${lisp.names.join(", ")} };`,
" })();",
" const sources = {",
...sources,
" };",
" const compiled = {};",
" for (const name in sources) compiled[name] = objectLisp.compile(sources[name], name);",
" return compiled;",
"})();",
"const fiapupFrameVm = (() => {",
indent(vm.body),
` return { ${vm.names.join(", ")} };`,
"})();",
end,
].join("\n");
}
export function embedded(source = read(gamePath)) {
const a = source.indexOf(start), b = source.indexOf(end);
return a < 0 || b < 0 ? null : source.slice(a, b + end.length);
}
function write() {
const source = read(gamePath), block = generate(), current = embedded(source);
if (!current) throw new Error("fiapup.js: no // block to fill");
const next = source.replace(current, () => block);
if (next !== source) writeFileSync(gamePath, next);
return next !== source;
}
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
if (process.argv.includes("--check")) {
const fresh = embedded() === generate();
console.log(fresh ? "fiapup.js sealed block is current" : "fiapup.js sealed block is stale: run node xbox/fiapup/embed.mjs");
process.exit(fresh ? 0 : 1);
}
console.log(write() ? "sealed the compiler, the frame vm and the objects into fiapup.js" : "sealed block already current");
}