UNPKG

@graphql-inspector/validate-command

Version:
273 lines (272 loc) • 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.handler = handler; const fs_1 = require("fs"); const path_1 = require("path"); const graphql_1 = require("graphql"); const commands_1 = require("@graphql-inspector/commands"); const core_1 = require("@graphql-inspector/core"); const logger_1 = require("@graphql-inspector/logger"); function handler({ schema, documents, strictFragments, maxDepth, maxDirectiveCount, maxAliasCount, maxTokenCount, apollo, keepClientFields, failOnDeprecated, filter, onlyErrors, relativePaths, output, silent, validateComplexityConfig, }) { let invalidDocuments = (0, core_1.validate)(schema, documents.map(doc => new graphql_1.Source((0, graphql_1.print)(doc.document), doc.location)), { strictFragments, maxDepth, maxAliasCount, maxDirectiveCount, maxTokenCount, apollo, keepClientFields, validateComplexityConfig, }); if (!invalidDocuments.length) { logger_1.Logger.success('All documents are valid'); return; } if (failOnDeprecated) { invalidDocuments = moveDeprecatedToErrors(invalidDocuments); } if (relativePaths) { invalidDocuments = useRelativePaths(invalidDocuments); } const errorsCount = countErrors(invalidDocuments); const deprecated = countDeprecated(invalidDocuments); const shouldFailProcess = errorsCount > 0; if (errorsCount) { if (!silent) { logger_1.Logger.log(`\nDetected ${errorsCount} invalid document${errorsCount > 1 ? 's' : ''}:\n`); } printInvalidDocuments(useFilter(invalidDocuments, filter), 'errors', true, silent); } else { logger_1.Logger.success('All documents are valid'); } if (deprecated && !onlyErrors) { if (!silent) { logger_1.Logger.info(`\nDetected ${deprecated} document${deprecated > 1 ? 's' : ''} with deprecated fields:\n`); } printInvalidDocuments(useFilter(invalidDocuments, filter), 'deprecated', false, silent); } if (output) { (0, fs_1.writeFileSync)(output, JSON.stringify({ status: !shouldFailProcess, documents: useFilter(invalidDocuments, filter), }, null, 2), 'utf8'); } if (shouldFailProcess) { process.exit(1); } } function moveDeprecatedToErrors(docs) { return docs.map(doc => ({ source: doc.source, errors: [...(doc.errors ?? []), ...(doc.deprecated ?? [])], deprecated: [], })); } function useRelativePaths(docs) { return docs.map(doc => { doc.source.name = (0, path_1.relative)(process.cwd(), doc.source.name); return doc; }); } function useFilter(docs, patterns) { if (!patterns?.length) { return docs; } return docs.filter(doc => patterns.some(filepath => doc.source.name.includes(filepath))); } exports.default = (0, commands_1.createCommand)(api => { const { loaders } = api; return { command: 'validate <documents> <schema>', describe: 'Validate Fragments and Operations', builder(yargs) { return yargs .positional('schema', { describe: 'Point to a schema', type: 'string', demandOption: true, }) .positional('documents', { describe: 'Point to documents', type: 'string', demandOption: true, }) .options({ deprecated: { alias: 'd', describe: 'Fail on deprecated usage', type: 'boolean', default: false, }, noStrictFragments: { describe: 'Do not fail on duplicated fragment names', type: 'boolean', default: false, }, maxDepth: { describe: 'Fail on deep operations', type: 'number', }, maxAliasCount: { describe: 'Fail on operations with too many aliases', type: 'number', }, maxDirectiveCount: { describe: 'Fail on operations with too many directives', type: 'number', }, maxTokenCount: { describe: 'Fail on operations with too many tokens', type: 'number', }, apollo: { describe: 'Support Apollo directives', type: 'boolean', default: false, }, keepClientFields: { describe: 'Keeps the fields with @client, but removes @client directive from them', type: 'boolean', default: false, }, filter: { describe: 'Show results only from a list of files (or file)', array: true, type: 'string', }, ignore: { describe: 'Ignore and do not load these files (supports glob)', array: true, type: 'string', }, onlyErrors: { describe: 'Show only errors', type: 'boolean', default: false, }, relativePaths: { describe: 'Show relative paths', type: 'boolean', default: false, }, silent: { describe: 'Do not print results', type: 'boolean', default: false, }, output: { describe: 'Output JSON file', type: 'string', }, maxComplexityScore: { describe: 'Fail on complexity score operations', type: 'number', }, complexityScalarCost: { describe: 'Scalar cost config to use with maxComplexityScore', type: 'number', default: 1, }, complexityObjectCost: { describe: 'Object cost config to use with maxComplexityScore', type: 'number', default: 2, }, complexityDepthCostFactor: { describe: 'Depth cost factor config to use with maxComplexityScore', type: 'number', default: 1.5, }, }); }, async handler(args) { const { headers, token } = (0, commands_1.parseGlobalArgs)(args); const apollo = args.apollo || false; const aws = args.aws || false; const apolloFederation = args.federation || false; const method = args.method?.toUpperCase() || 'POST'; const maxDepth = args.maxDepth == null ? undefined : args.maxDepth; const maxAliasCount = args.maxAliasCount == null ? undefined : args.maxAliasCount; const maxDirectiveCount = args.maxDirectiveCount == null ? undefined : args.maxDirectiveCount; const maxTokenCount = args.maxTokenCount == null ? undefined : args.maxTokenCount; const strictFragments = !args.noStrictFragments; const keepClientFields = args.keepClientFields || false; const failOnDeprecated = args.deprecated; const output = args.output; const silent = args.silent || false; const relativePaths = args.relativePaths || false; const onlyErrors = args.onlyErrors || false; const ignore = args.ignore || []; const validateComplexityConfig = (() => { if (args.maxComplexityScore == null) return; return { maxComplexityScore: args.maxComplexityScore, complexityScalarCost: args.complexityScalarCost, complexityObjectCost: args.complexityObjectCost, complexityDepthCostFactor: args.complexityDepthCostFactor, }; })(); const schema = await loaders.loadSchema(args.schema, { headers, token, method, }, apolloFederation, aws); const documents = await loaders.loadDocuments(args.documents, { ignore, }); return handler({ schema, documents, apollo, maxDepth, maxAliasCount, maxDirectiveCount, maxTokenCount, strictFragments, keepClientFields, failOnDeprecated, filter: args.filter, silent, output, relativePaths, onlyErrors, validateComplexityConfig, }); }, }; }); function countErrors(invalidDocuments) { if (invalidDocuments.length) { return invalidDocuments.filter(doc => doc.errors?.length).length; } return 0; } function countDeprecated(invalidDocuments) { if (invalidDocuments.length) { return invalidDocuments.filter(doc => doc.deprecated?.length).length; } return 0; } function printInvalidDocuments(invalidDocuments, listKey, isError = false, silent = false) { if (silent) { return; } for (const doc of invalidDocuments) { if (doc.errors.length) { for (const line of renderErrors(doc.source.name, doc[listKey], isError)) { logger_1.Logger.log(line); } } } } function renderErrors(sourceName, errors, isError = false) { const errorsAsString = errors.map(e => ` - ${(0, logger_1.bolderize)(e.message)}`).join('\n'); return [ isError ? logger_1.chalk.redBright('error') : logger_1.chalk.yellowBright('warn'), `in ${sourceName}:\n\n`, errorsAsString, '\n\n', ]; }