UNPKG

@graphql-inspector/cli

Version:

Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.

222 lines (221 loc) • 8.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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; let verboseChanges = false; const rules = (await Promise.all([...(input.rules ?? [])].filter(isString).map(async (name) => { if (name === 'verboseChanges') { verboseChanges = true; return; } const rule = await resolveRule(name); if (!rule) { throw new Error(`Rule '${name}' does not exist!\n`); } return rule; }))).filter((f) => !!f); if (!verboseChanges) { rules.push(core_1.DiffRule.simplifyChanges); } 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 apolloFederationV2 = args.federationV2 || 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, apolloFederationV2, aws); const newSchema = await loaders.loadSchema(newSchemaPointer, { headers: newSchemaHeaders, token, method, }, apolloFederation, apolloFederationV2, 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)}`); } } async function resolveRule(name) { const filepath = (0, commands_1.ensureAbsolute)(name); if ((0, fs_1.existsSync)(filepath)) { return (await Promise.resolve(`${filepath}`).then(s => __importStar(require(s)))).default; } 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'; }