UNPKG

gqlcheck

Version:

Performs additional checks on your GraphQL documents and operations to ensure they conform to your rules, whilst allow-listing existing operations and their constituent parts (and allowing overrides on a per-field basis). Rules include max selection set d

187 lines (182 loc) 6.31 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const promises_1 = require("node:fs/promises"); const node_util_1 = require("node:util"); const json5_1 = tslib_1.__importDefault(require("json5")); const kjsonl_1 = require("kjsonl"); const baseline_js_1 = require("./baseline.js"); const main_js_1 = require("./main.js"); const print_js_1 = require("./print.js"); const version_js_1 = require("./version.js"); const parseArgsConfig = { options: { help: { type: "boolean", placeholder: undefined, short: "h", description: "Output available CLI flags", }, version: { type: "boolean", placeholder: undefined, short: "v", description: "Output the version", }, config: { type: "string", placeholder: "configPath", short: "C", description: "The path to the config file", }, schema: { type: "string", placeholder: "sdlPath", short: "s", description: "Path to the GraphQL schema SDL file", }, baseline: { type: "string", placeholder: "jsonPath", short: "b", description: "Path to the baseline file (.json or .json5)", }, "update-baseline": { type: "boolean", placeholder: undefined, short: "u", description: "Update the baseline.json file to allow all passed documents even if they break the rules.", }, list: { short: "l", type: "boolean", placeholder: undefined, description: "Don't output any details, just list the affected source names.", }, "only-errors": { short: "e", type: "boolean", placeholder: undefined, description: "Only output details about errors (not infractions); combine with `-l` to list only the files with errors.", }, }, allowPositionals: true, strict: true, }; const { values, positionals } = (0, node_util_1.parseArgs)(parseArgsConfig); async function* getOperationsFromKJSONL(path) { const handle = await (0, promises_1.open)(path, "r"); try { for await (const line of (0, kjsonl_1.kjsonlLines)(handle)) { const hash = line.keyBuffer.toString("utf8"); const value = JSON.parse(line.valueBuffer.toString("utf8")); const sourceString = typeof value === "string" ? value : value.document; yield { body: sourceString, name: hash }; } } finally { await handle.close(); } } async function* getOperationsFromPath(path) { const stats = await (0, promises_1.stat)(path); if (stats.isDirectory()) { const files = await (0, promises_1.readdir)(path); for (const file of files) { if (!file.startsWith(".")) { yield* getOperationsFromPath(path + "/" + file); } } } else { if (path.endsWith(".graphql")) { const body = await (0, promises_1.readFile)(path, "utf8"); yield { body, name: path }; } else if (path.endsWith(".kjsonl")) { yield* getOperationsFromKJSONL(path); } else { // TODO: move this to warnings throw new Error(`Path '${path}' not understood`); } } } async function* getOperations() { for (const positional of positionals) { yield* getOperationsFromPath(positional); } } function printArg([name, value,]) { const { type, short, description, placeholder } = value; return ` --${name}${type === "boolean" ? "" : ` <${placeholder}>`}\ ${short ? `\n-${short}${type === "boolean" ? "" : ` <${placeholder}>`}` : ""}\ ${description ? ` ${description?.replace(/\n/g, "\n ")}` : ""} `.trim(); } async function main() { if (values.help) { console.log(` Usage: gqlcheck [-s schema.graphqls] [-b baseline.json5] [-u] [-l] [-e] doc1.graphql doc2.graphql Flags: ${Object.entries(parseArgsConfig.options).map(printArg).join("\n\n")} `.trim()); return; } if (values.version) { console.log("v" + version_js_1.version); return; } const conf = { ...(values.schema ? { schemaSdlPath: values.schema } : null), ...(values.baseline ? { baselinePath: values.baseline } : null), }; let result = await (0, main_js_1.checkOperations)(getOperations, values.config, conf); if (values["only-errors"]) { if (values["update-baseline"]) { throw new Error(`--update-baseline cannot be combined with --only-errors`); } result = (0, baseline_js_1.filterOnlyErrors)(result); } else if (values["update-baseline"]) { const baselinePath = result.resolvedPreset.gqlcheck?.baselinePath; if (!baselinePath) { throw new Error(`--update-baseline was specified without --baseline, and no preset.gqlcheck.baselinePath was found in your configuration; aborting.`); } const newBaseline = (0, baseline_js_1.generateBaseline)(result.rawResultsBySourceName); const data = baselinePath.endsWith(".json5") ? json5_1.default.stringify(newBaseline, null, 2) : JSON.stringify(newBaseline, null, 2); await (0, promises_1.writeFile)(baselinePath, data + "\n"); result = (0, baseline_js_1.filterBaseline)(newBaseline, result); console.log(`New baseline written to ${baselinePath}`); console.log(); } const { output, errors, infractions } = (0, print_js_1.generateOutputAndCounts)(result); if (values["list"]) { for (const [sourceName, { output }] of Object.entries(result.resultsBySourceName)) { if (output.errors.length > 0) { console.log(sourceName); } } } else { console.log(output); } if (errors > 0) { process.exitCode = 1; } if (infractions > 0) { process.exitCode = 2; } } main().catch((e) => { console.error(e); process.exit(1); });