Rename Foundry handoffs to tasks (#239)

* Restore foundry onboarding stack

* Consolidate foundry rename

* Create foundry tasks without prompts

* Rename Foundry handoffs to tasks
This commit is contained in:
Nathan Flurry 2026-03-11 13:23:54 -07:00 committed by GitHub
parent d30cc0bcc8
commit d75e8c31d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
281 changed files with 9242 additions and 4356 deletions

View file

@ -0,0 +1,135 @@
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join, resolve } from "node:path";
type Journal = {
entries?: Array<{
idx: number;
when: number;
tag: string;
breakpoints?: boolean;
version?: string;
}>;
};
function padMigrationKey(idx: number): string {
return `m${String(idx).padStart(4, "0")}`;
}
function escapeTemplateLiteral(value: string): string {
return value.replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
}
async function fileExists(path: string): Promise<boolean> {
try {
await readFile(path);
return true;
} catch {
return false;
}
}
async function walkDirectories(root: string, onDir: (dir: string) => Promise<void>): Promise<void> {
const entries = await readdir(root, { withFileTypes: true });
await onDir(root);
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (entry.name === "node_modules" || entry.name === "dist" || entry.name.startsWith(".")) {
continue;
}
await walkDirectories(join(root, entry.name), onDir);
}
}
async function generateOne(drizzleDir: string): Promise<void> {
const metaDir = resolve(drizzleDir, "meta");
const journalPath = resolve(metaDir, "_journal.json");
if (!(await fileExists(journalPath))) {
return;
}
const drizzleEntries = (await readdir(drizzleDir, { withFileTypes: true }))
.filter((entry) => entry.isFile() && entry.name.endsWith(".sql"))
.map((entry) => entry.name)
.sort();
if (drizzleEntries.length === 0) {
return;
}
const journalRaw = await readFile(journalPath, "utf8");
const journal = JSON.parse(journalRaw) as Journal;
const entries = journal.entries ?? [];
const sqlByKey = new Map<string, string>();
for (const entry of entries) {
const file = drizzleEntries[entry.idx];
if (!file) {
throw new Error(`Missing migration SQL file for idx=${entry.idx} in ${drizzleDir}`);
}
const sqlPath = resolve(drizzleDir, file);
const sqlRaw = await readFile(sqlPath, "utf8");
sqlByKey.set(padMigrationKey(entry.idx), sqlRaw);
}
const migrationsObjectLines: string[] = [];
for (const entry of entries) {
const key = padMigrationKey(entry.idx);
const sql = sqlByKey.get(key);
if (!sql) continue;
migrationsObjectLines.push(` ${key}: \`${escapeTemplateLiteral(sql)}\`,`);
}
const banner = `// This file is generated by src/actors/_scripts/generate-actor-migrations.ts.
// Source of truth is drizzle-kit output under ./drizzle (meta/_journal.json + *.sql).
// Do not hand-edit this file.
`;
const journalLiteral = JSON.stringify(
{
entries: entries.map((entry) => ({
idx: entry.idx,
when: entry.when,
tag: entry.tag,
breakpoints: Boolean(entry.breakpoints),
})),
},
null,
2,
);
const outPath = resolve(drizzleDir, "..", "migrations.ts");
const content = `${banner}
const journal = ${journalLiteral} as const;
export default {
journal,
migrations: {
${migrationsObjectLines.join("\n")}
} as const
};
`;
await mkdir(dirname(outPath), { recursive: true });
await writeFile(outPath, content, "utf8");
// drizzle-kit generates a JS helper file by default; delete to keep TS-only sources.
await rm(resolve(drizzleDir, "migrations.js"), { force: true });
}
async function main(): Promise<void> {
const packageRoot = resolve(import.meta.dirname, "..", "..", ".."); // packages/backend
const actorsRoot = resolve(packageRoot, "src", "actors");
await walkDirectories(actorsRoot, async (dir) => {
if (dir.endsWith(`${join("db", "drizzle")}`)) {
await generateOne(dir);
}
});
}
main().catch((error: unknown) => {
const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
// eslint-disable-next-line no-console
console.error(message);
process.exitCode = 1;
});