@graphql-inspector/audit-command
Version: 
Audit Documents in GraphQL Inspector
110 lines (109 loc) • 4.9 kB
JavaScript
import Table from 'cli-table3';
import { print } from 'graphql';
import { createCommand } from '@graphql-inspector/commands';
import { calculateOperationComplexity, calculateTokenCount, countAliases, countDepth, countDirectives, } from '@graphql-inspector/core';
import { chalk, Logger } from '@graphql-inspector/logger';
export default createCommand(api => {
    return {
        command: 'audit <documents>',
        describe: 'Audit Fragments and Operations for a better understanding of the depth, alias count, and directive count.',
        builder(yargs) {
            return yargs
                .positional('documents', {
                describe: 'Point to some documents',
                type: 'string',
                demandOption: true,
            })
                .options({
                detail: {
                    alias: 'd',
                    describe: 'Print an overview of all operations and their audit breakdown.',
                    type: 'boolean',
                    default: false,
                },
                complexityScalarCost: {
                    describe: 'The cost per scalar for calculating the complexity score.',
                    type: 'number',
                    default: 1,
                },
                complexityObjectCost: {
                    describe: 'The cost per object for calculating the complexity score.',
                    type: 'number',
                    default: 2,
                },
                complexityDepthCostFactor: {
                    describe: 'The cost factor per introduced depth level for calculating the complexity score.',
                    type: 'number',
                    default: 1.5,
                },
            });
        },
        async handler(args) {
            const { loaders } = api;
            const ignore = args.ignore || [];
            const documents = await loaders.loadDocuments(args.documents, {
                ignore,
            });
            const complexityConfig = {
                scalarCost: args.complexityScalarCost,
                objectCost: args.complexityObjectCost,
                depthCostFactor: args.complexityDepthCostFactor,
            };
            return handler({ documents, detail: args.detail, complexityConfig });
        },
    };
});
export function handler(args) {
    const fragments = new Map();
    const fragmentStrings = new Map();
    const operations = new Map();
    const getFragmentReference = (fragmentName) => fragments.get(fragmentName);
    const getFragmentSource = (fragmentName) => fragmentStrings.get(fragmentName);
    for (const record of args.documents) {
        if (record.document) {
            for (const definition of record.document.definitions) {
                if (definition.kind === 'FragmentDefinition') {
                    fragments.set(definition.name.value, definition);
                    fragmentStrings.set(definition.name.value, print(definition));
                }
                else if (definition.kind === 'OperationDefinition' && definition.name) {
                    operations.set(definition.name.value, definition);
                }
            }
        }
    }
    let maxDepth = 0;
    let maxAliases = 0;
    let maxDirectives = 0;
    let maxTokenCount = 0;
    let maxComplexity = 0;
    const results = [];
    for (const [name, operation] of operations.entries()) {
        const depth = countDepth(operation, 0, getFragmentReference);
        const aliases = countAliases(operation, getFragmentReference);
        const directives = countDirectives(operation, getFragmentReference);
        const tokenCount = calculateTokenCount({
            source: print(operation),
            getReferencedFragmentSource: getFragmentSource,
        });
        const complexity = calculateOperationComplexity(operation, args.complexityConfig, getFragmentReference);
        results.push([name, depth, aliases, directives, tokenCount, complexity.toFixed(2)]);
        maxDepth = Math.max(maxDepth, depth);
        maxAliases = Math.max(maxAliases, aliases);
        maxDirectives = Math.max(maxDirectives, directives);
        maxTokenCount = Math.max(maxTokenCount, tokenCount);
        maxComplexity = Math.max(maxComplexity, complexity);
    }
    if (args.detail) {
        const table = new Table({
            head: ['Operation Name', 'Depth', 'Aliases', 'Directives', 'Token Count', 'Complexity Score'],
        });
        table.push(...results);
        Logger.log(table.toString());
    }
    Logger.log(`Maximum depth is ${chalk.bold(maxDepth)}`);
    Logger.log(`Maximum alias amount is ${chalk.bold(maxAliases)}`);
    Logger.log(`Maximum directive amount is ${chalk.bold(maxDirectives)}`);
    Logger.log(`Maximum token count is ${chalk.bold(maxTokenCount)}`);
    Logger.log(`Maximum complexity score is ${chalk.bold(maxComplexity.toFixed(2))}`);
}