storytellurx/scripts/image-generation/regenerate-world.ts
2026-09-27 19:39:04 +02:00

83 lines
3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { readFile } from "node:fs/promises";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { z } from "zod";
import { defaultPreset } from "./presets.ts";
const assetSchema = z.object({
name: z.string().regex(/^[a-z0-9][a-z0-9_-]*$/),
publicPath: z.string().min(1),
outputPath: z.string().min(1),
prompt: z.string().min(1),
usage: z.array(z.string()).min(1),
});
const root = fileURLToPath(new URL("../../", import.meta.url));
const assets = z
.record(z.string(), assetSchema.extend({ placements: z.record(z.string(), assetSchema) }))
.parse(
JSON.parse(
await readFile(
new URL("../../src/apps/product/content/persona-assets.json", import.meta.url),
"utf8",
),
),
);
const { values } = parseArgs({
options: {
persona: { type: "string" },
placement: { type: "string" },
all: { type: "boolean" },
"dry-run": { type: "boolean" },
help: { type: "boolean" },
},
});
if (values.help || (!values.persona && !values.all)) {
console.log(
"vp run generate:world --persona polaire [--placement frame] [--dry-run]\nvp run generate:world --all\nEdit src/apps/product/content/persona-assets.json to change a world’s prompt/name.\nPersonas: " +
Object.keys(assets).join(", "),
);
} else {
if (values.persona && values.all) throw new Error("Choose --persona or --all, not both.");
const ids = values.all ? Object.keys(assets) : [values.persona!];
for (const id of ids) {
if (!Object.hasOwn(assets, id)) throw new Error(`Unknown persona: ${id}`);
const world = assets[id]!;
const asset = values.placement ? world.placements[values.placement] : world;
if (!asset) throw new Error(`Unknown placement: ${values.placement}`);
const expected = `/assets/generations/${asset.name}.${defaultPreset}.webp`;
if (asset.publicPath !== expected || asset.outputPath !== `public${expected}`)
throw new Error(
`Path mismatch for ${id}: update publicPath and outputPath to match --name and the default preset.`,
);
const args = [
fileURLToPath(new URL("./generate.ts", import.meta.url)),
"--prompt",
asset.prompt,
"--name",
asset.name,
];
console.log(`${id} → ${asset.outputPath}\nUsed by: ${asset.usage.join(", ")}`);
if (values["dry-run"]) {
console.log(JSON.stringify({ executable: process.execPath, args }, null, 2));
continue;
}
// Argument array, never a shell: weighted prompts remain literal data.
const child = spawn(process.execPath, args, { cwd: root, stdio: "inherit" });
const interrupt = () => child.kill("SIGINT");
process.once("SIGINT", interrupt);
process.once("SIGTERM", interrupt);
const code = await new Promise<number>((resolve, reject) => {
child.once("error", reject);
child.once("exit", (code) => resolve(code ?? 1));
}).finally(() => {
process.off("SIGINT", interrupt);
process.off("SIGTERM", interrupt);
});
if (code) {
process.exitCode = code;
break;
}
}
}