#!/usr/bin/env node /** * Usage: npm run endpoint:generate -- [slug] * Example: npm run endpoint:generate -- home-page * Expects src/.../slug.gql.ts with export const name = gql`...` * Runs graphql-codegen, then writes slug.gql.generated.ts beside it. */ import { execSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, ".."); const slug = process.argv[2]; if (!slug || slug.startsWith("-")) { console.error("Usage: npm run endpoint:generate -- "); console.error('Example: npm run endpoint:generate -- home-page'); process.exit(1); } function findGqlFile() { const exact = path.join(root, "src", `${slug}.gql.ts`); if (fs.existsSync(exact)) return path.relative(root, exact); const stack = [path.join(root, "src")]; while (stack.length) { const dir = stack.pop(); if (!fs.existsSync(dir)) continue; for (const name of fs.readdirSync(dir, { withFileTypes: true })) { const p = path.join(dir, name.name); if (name.isDirectory()) stack.push(p); else if (name.name === `${slug}.gql.ts`) return path.relative(root, p); } } return null; } const gqlPath = findGqlFile(); if (!gqlPath) { console.error(`No file named "${slug}.gql.ts" found under src/`); process.exit(1); } const absGql = path.join(root, gqlPath); const source = fs.readFileSync(absGql, "utf8"); const exportMatch = source.match(/export\s+const\s+(\w+)\s*=\s*gql\s*`/); if (!exportMatch) { console.error(`${gqlPath}: expected: export const SomeName = gql\`...\``); process.exit(1); } const documentExport = exportMatch[1]; const opMatch = source.match(/\b(query|mutation|subscription)\s+(\w+)\s*[\({]/); if (!opMatch) { console.error(`${gqlPath}: could not find operation name (query|mutation|subscription Name)`); process.exit(1); } const operationKind = opMatch[1]; const operationName = opMatch[2]; const resultType = operationKind === "mutation" ? `${operationName}Mutation` : operationKind === "subscription" ? `${operationName}Subscription` : `${operationName}Query`; const variablesType = `${resultType}Variables`; const endpointName = operationKind === "mutation" ? operationName[0].toLowerCase() + operationName.slice(1) + "Mutation" : operationKind === "subscription" ? operationName[0].toLowerCase() + operationName.slice(1) + "Subscription" : operationName[0].toLowerCase() + operationName.slice(1) + "Query"; const outPath = absGql.replace(/\.gql\.ts$/, ".gql.generated.ts"); const outRel = path.relative(root, outPath); const gqlDir = path.dirname(absGql); const executeRel = path .relative(gqlDir, path.join(root, "src", "graphql", "execute.ts")) .replace(/\\/g, "/") .replace(/\.ts$/, ""); const typesRel = path .relative(gqlDir, path.join(root, "src", "graphql", "graphql.ts")) .replace(/\\/g, "/") .replace(/\.ts$/, ""); const gqlBase = path.basename(gqlPath); const gqlImportStem = gqlBase.replace(/\.ts$/, ""); console.log("Running graphql-codegen…"); execSync("npx graphql-codegen", { cwd: root, stdio: "inherit" }); function quoteImport(rel) { if (rel.startsWith(".")) return rel; return `./${rel}`; } const body = `/* eslint-disable */ /* Generated by scripts/endpoint-generate.mjs — do not edit by hand */ import type { ExecutionResult } from "graphql"; import { execute } from "${quoteImport(executeRel)}"; import type { ${resultType}, ${variablesType} } from "${quoteImport(typesRel)}"; import { ${documentExport} } from "./${gqlImportStem}"; export const ${endpointName} = { async execute(variables?: ${variablesType}): Promise> { return execute<${resultType}, ${variablesType}>(${documentExport}, variables); }, }; `; fs.writeFileSync(outPath, body, "utf8"); console.log(`Wrote ${outRel}`);