@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
JavaScript
;
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';
}