@graphql-inspector/diff-command
Version: 
Compare GraphQL Schemas
195 lines (194 loc) • 6.82 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.handler = handler;
const fs_1 = require("fs");
const commands_1 = require("@graphql-inspector/commands");
const core_1 = require("@graphql-inspector/core");
const logger_1 = require("@graphql-inspector/logger");
async function handler(input) {
    const onComplete = input.onComplete
        ? resolveCompletionHandler(input.onComplete)
        : failOnBreakingChanges;
    const rules = input.rules
        ? input.rules
            .filter(isString)
            .map((name) => {
            const rule = resolveRule(name);
            if (!rule) {
                throw new Error(`Rule '${name}' does not exist!\n`);
            }
            return rule;
        })
            .filter(f => f)
        : [];
    const changes = await (0, core_1.diff)(input.oldSchema, input.newSchema, rules, {
        checkUsage: input.onUsage ? resolveUsageHandler(input.onUsage) : undefined,
    });
    if (changes.length === 0) {
        logger_1.Logger.success('No changes detected');
        return;
    }
    logger_1.Logger.log(`\nDetected the following changes (${changes.length}) between schemas:\n`);
    const breakingChanges = changes.filter(change => change.criticality.level === core_1.CriticalityLevel.Breaking);
    const dangerousChanges = changes.filter(change => change.criticality.level === core_1.CriticalityLevel.Dangerous);
    const nonBreakingChanges = changes.filter(change => change.criticality.level === core_1.CriticalityLevel.NonBreaking);
    if (breakingChanges.length) {
        reportBreakingChanges(breakingChanges);
    }
    if (dangerousChanges.length) {
        reportDangerousChanges(dangerousChanges);
    }
    if (nonBreakingChanges.length) {
        reportNonBreakingChanges(nonBreakingChanges);
    }
    onComplete({ breakingChanges, dangerousChanges, nonBreakingChanges });
}
exports.default = (0, commands_1.createCommand)(api => {
    const { loaders } = api;
    return {
        command: 'diff <oldSchema> <newSchema>',
        describe: 'Compare two GraphQL Schemas',
        builder(yargs) {
            return yargs
                .positional('oldSchema', {
                describe: 'Point to an old schema',
                type: 'string',
                demandOption: true,
            })
                .positional('newSchema', {
                describe: 'Point to a new schema',
                type: 'string',
                demandOption: true,
            })
                .options({
                rule: {
                    describe: 'Add rules',
                    array: true,
                },
                onComplete: {
                    describe: 'Handle Completion',
                    type: 'string',
                },
                onUsage: {
                    describe: 'Checks usage of schema',
                    type: 'string',
                },
            });
        },
        async handler(args) {
            try {
                const oldSchemaPointer = args.oldSchema;
                const newSchemaPointer = args.newSchema;
                const apolloFederation = args.federation || false;
                const aws = args.aws || false;
                const method = args.method?.toUpperCase() || 'POST';
                const { headers, leftHeaders, rightHeaders, token } = (0, commands_1.parseGlobalArgs)(args);
                const oldSchemaHeaders = {
                    ...headers,
                    ...leftHeaders,
                };
                const newSchemaHeaders = {
                    ...headers,
                    ...rightHeaders,
                };
                const oldSchema = await loaders.loadSchema(oldSchemaPointer, {
                    headers: oldSchemaHeaders,
                    token,
                    method,
                }, apolloFederation, aws);
                const newSchema = await loaders.loadSchema(newSchemaPointer, {
                    headers: newSchemaHeaders,
                    token,
                    method,
                }, apolloFederation, aws);
                await handler({
                    oldSchema,
                    newSchema,
                    rules: args.rule,
                    onComplete: args.onComplete,
                    onUsage: args.onUsage,
                });
            }
            catch (error) {
                logger_1.Logger.error(error);
                throw error;
            }
        },
    };
});
function sortChanges(changes) {
    return changes.slice().sort((a, b) => {
        const aPath = a.path || '';
        const bPath = b.path || '';
        if (aPath > bPath) {
            return 1;
        }
        if (bPath > aPath) {
            return -1;
        }
        return 0;
    });
}
function reportBreakingChanges(changes) {
    const label = logger_1.symbols.error;
    const sorted = sortChanges(changes);
    for (const change of sorted) {
        logger_1.Logger.log(`${label}  ${(0, logger_1.bolderize)(change.message)}`);
    }
}
function reportDangerousChanges(changes) {
    const label = logger_1.symbols.warning;
    const sorted = sortChanges(changes);
    for (const change of sorted) {
        logger_1.Logger.log(`${label}  ${(0, logger_1.bolderize)(change.message)}`);
    }
}
function reportNonBreakingChanges(changes) {
    const label = logger_1.symbols.success;
    const sorted = sortChanges(changes);
    for (const change of sorted) {
        logger_1.Logger.log(`${label}  ${(0, logger_1.bolderize)(change.message)}`);
    }
}
function resolveRule(name) {
    const filepath = (0, commands_1.ensureAbsolute)(name);
    if ((0, fs_1.existsSync)(filepath)) {
        return require(filepath);
    }
    return core_1.DiffRule[name];
}
function resolveCompletionHandler(name) {
    const filepath = (0, commands_1.ensureAbsolute)(name);
    try {
        require.resolve(filepath);
    }
    catch (error) {
        throw new Error(`CompletionHandler '${name}' does not exist!`);
    }
    const mod = require(filepath);
    return mod?.default || mod;
}
function resolveUsageHandler(name) {
    const filepath = (0, commands_1.ensureAbsolute)(name);
    try {
        require.resolve(filepath);
    }
    catch (error) {
        throw new Error(`UsageHandler '${name}' does not exist!`);
    }
    const mod = require(filepath);
    return mod?.default || mod;
}
function failOnBreakingChanges({ breakingChanges }) {
    const breakingCount = breakingChanges.length;
    if (breakingCount) {
        logger_1.Logger.error(`Detected ${breakingCount} breaking change${breakingCount > 1 ? 's' : ''}`);
        process.exit(1);
    }
    else {
        logger_1.Logger.success('No breaking changes detected');
    }
}
function isString(val) {
    return typeof val === 'string';
}