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
JavaScript
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);
});
;