uploadthing-codegen
Version:
Generates types for uploadthing routes
100 lines (99 loc) • 3.39 kB
JavaScript
import { exec } from "child_process";
import fs from "fs";
import { writeFile } from "fs/promises";
import { createJiti } from "jiti";
import path from "path";
import process from "process";
import { Project } from "ts-morph";
import { promisify } from "util";
import { z } from "zod";
const codegenConfigZ = z
.object({
routers: z.array(z.object({
schema: z.object({
endpoint: z.string(),
headers: z.object({
Authorization: z.string(),
}),
}),
generates: z.string(),
})),
hooks: z.object({
afterAllFileWrite: z.array(z.string()),
}),
})
.refine((data) => {
const endpoints = new Set(data.routers.map((d) => d.schema.endpoint));
const generates = new Set(data.routers.map((d) => d.generates));
return (endpoints.size === data.routers.length &&
generates.size === data.routers.length);
});
const endpointSchemaZ = z.array(z.object({
router: z.string(),
endpoints: z.custom(),
}));
const __dirname = process.cwd();
const configFile = "codegen.ut.ts";
const configPath = path.join(__dirname, configFile);
const jiti = createJiti(__dirname);
const untypedConfig = await jiti.import(configPath, { default: true });
const { success, data: config, error, } = codegenConfigZ.safeParse(untypedConfig);
if (!success) {
console.error(error);
process.exit(1);
}
console.log("✔ Parse Configuration");
for (const router of config.routers) {
const res = await fetch(router.schema.endpoint, {
headers: {
Authorization: router.schema.headers.Authorization,
},
});
if (!res.ok) {
console.error(`Failed to fetch schema from ${router.schema.endpoint}\n${JSON.stringify(await res.json())}`);
process.exit(1);
}
const untypedSchema = await res.json();
const { success, data: routers, error, } = endpointSchemaZ.safeParse(untypedSchema);
if (!success) {
console.error(error);
process.exit(1);
}
const conjustedRouters = routers.reduce((acc, { endpoints, router }) => ({
...acc,
[router]: endpoints.reduce((acc, { config, slug }) => ({ ...acc, [slug]: config }), {}),
}), {});
const generatedPath = path.join(__dirname, router.generates);
const project = new Project();
const sourceFile = project.createSourceFile(generatedPath, "", {
overwrite: true,
});
sourceFile.addTypeAlias({
isExported: true,
name: "UploadthingRouter",
type: JSON.stringify(conjustedRouters, null, 2),
});
sourceFile.addTypeAlias({
isExported: true,
name: "Endpoint",
type: Object.keys(conjustedRouters)
.map((router) => Object.keys(conjustedRouters[router])
.map((endpoint) => `"${router}/${endpoint}"`)
.join(" | "))
.join(" | "),
});
const dir = path.dirname(generatedPath);
if (!fs.existsSync(dir))
fs.mkdirSync(dir, { recursive: true });
await writeFile(generatedPath, sourceFile.getFullText());
for (const command of config.hooks.afterAllFileWrite) {
const { stderr } = await promisify(exec)(`${command} ${router.generates}`);
if (stderr) {
console.error(error);
process.exit(1);
}
}
}
console.log("✔ Generate Outputs");
process.exit(0);