Files
softclick-landing/scripts/endpoint-generate.mjs
Zotov Ivan Yuryevich 8b100c18b7 feat: add graphql
2026-04-05 23:47:29 +03:00

120 lines
3.9 KiB
JavaScript

#!/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 -- <slug>");
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<ExecutionResult<${resultType}>> {
return execute<${resultType}, ${variablesType}>(${documentExport}, variables);
},
};
`;
fs.writeFileSync(outPath, body, "utf8");
console.log(`Wrote ${outRel}`);