#!/usr/bin/env bun /** * graphql-schema-drift — shared CI guard that validates an MFE's inline * GraphQL query/mutation/subscription documents against its own configured * schema snapshot(s), catching the class of bug where a backend rename or * pagination change silently breaks a hand-rolled template-literal query that * `graphql-codegen` never sees (BOFF-3922; incidents BOFF-3915, BOFF-4703, * BOFF-3529, and the movethewheels useOrders.ts rewrite of 2026-07-25). * * Usage (from a consuming MFE repo root): * bun run node_modules/@burdenoff/fe-libs/scripts/graphql-schema-drift/cli.ts [configPath] * or, since fe-libs declares this as a package `bin`: * graphql-schema-drift [configPath] * * `configPath` defaults to `./schema-drift.config.json`. See README.md in * this directory for the config shape and worked examples. * * Exit code: 0 if every extracted document validates cleanly, 1 otherwise. */ import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { runValidation } from './validate'; import type { SchemaDriftConfig } from './types'; function main(): void { const configPathArg = process.argv[2] ?? 'schema-drift.config.json'; const repoRoot = process.cwd(); const configPath = resolve(repoRoot, configPathArg); if (!existsSync(configPath)) { console.error(`[graphql-schema-drift] Config not found: ${configPath}`); console.error( 'Create a schema-drift.config.json at your repo root (or pass a path as the first argument). ' + 'See @burdenoff/fe-libs/scripts/graphql-schema-drift/README.md for the config shape.' ); process.exit(1); } let config: SchemaDriftConfig; try { config = JSON.parse(readFileSync(configPath, 'utf8')) as SchemaDriftConfig; } catch (e) { console.error(`[graphql-schema-drift] Failed to parse ${configPath}: ${(e as Error).message}`); process.exit(1); return; } let report: ReturnType; try { report = runValidation(config, repoRoot); } catch (e) { console.error(`[graphql-schema-drift] ${(e as Error).message}`); process.exit(1); return; } if (report.errors.length > 0) { const fileCount = new Set(report.errors.map((e) => e.file)).size; console.error( `\n[graphql-schema-drift] FAILED — ${report.errors.length} operation(s) with schema drift across ${fileCount} file(s):\n` ); for (const err of report.errors) { const label = err.operationName ?? `block #${err.index}`; console.error(` ${err.file} :: ${label}`); for (const msg of err.messages) { console.error(` - ${msg}`); } } console.error( '\nEach of these operations must validate against at least one schema group configured in ' + 'schema-drift.config.json. Either fix the query/mutation to match the current schema, or refresh ' + 'the schema snapshot file(s) referenced there if the schema itself changed.\n' ); process.exit(1); } console.log( `[graphql-schema-drift] OK — validated ${report.documentCount} operation(s) across ${report.fileCount} source file(s). 0 errors.` ); } main();