@graphql-hive/cli
Version:
A CLI util to manage and control your GraphQL Hive
220 lines • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderWarnings = exports.renderChanges = exports.renderErrors = exports.RenderErrors_SchemaErrorConnectionFragment = void 0;
exports.loadSchema = loadSchema;
exports.minifySchema = minifySchema;
const graphql_1 = require("graphql");
const code_file_loader_1 = require("@graphql-tools/code-file-loader");
const graphql_file_loader_1 = require("@graphql-tools/graphql-file-loader");
const json_file_loader_1 = require("@graphql-tools/json-file-loader");
const load_1 = require("@graphql-tools/load");
const url_loader_1 = require("@graphql-tools/url-loader");
const gql_1 = require("../gql");
const graphql_2 = require("../gql/graphql");
const errors_1 = require("./errors");
const graphql_request_1 = require("./graphql-request");
const texture_1 = require("./texture/texture");
const severityLevelMap = {
[graphql_2.SeverityLevelType.Breaking]: texture_1.Texture.colors.red('-'),
[graphql_2.SeverityLevelType.Safe]: texture_1.Texture.colors.green('-'),
[graphql_2.SeverityLevelType.Dangerous]: texture_1.Texture.colors.green('-'),
};
exports.RenderErrors_SchemaErrorConnectionFragment = (0, gql_1.graphql)(`
fragment RenderErrors_SchemaErrorConnectionFragment on SchemaErrorConnection {
edges {
node {
message
}
}
}
`);
const renderErrors = (errors) => {
const e = (0, gql_1.useFragment)(exports.RenderErrors_SchemaErrorConnectionFragment, errors);
const t = texture_1.Texture.createBuilder();
t.failure(`Detected ${e.edges.length} error${e.edges.length > 1 ? 's' : ''}`);
t.line();
e.edges.forEach(edge => {
t.indent(texture_1.Texture.colors.red('-') + ' ' + texture_1.Texture.boldQuotedWords(edge.node.message));
});
return t.state.value;
};
exports.renderErrors = renderErrors;
const RenderChanges_SchemaChanges = (0, gql_1.graphql)(`
fragment RenderChanges_schemaChanges on SchemaChangeConnection {
edges {
node {
severityLevel
isSafeBasedOnUsage
message(withSafeBasedOnUsageNote: false)
approval {
approvedBy {
displayName
}
}
affectedAppDeployments(first: 0) {
totalCount
}
}
}
}
`);
const renderChanges = (maskedChanges) => {
const t = texture_1.Texture.createBuilder();
const changes = (0, gql_1.useFragment)(RenderChanges_SchemaChanges, maskedChanges);
const writeChanges = (changes) => {
changes.forEach(change => {
var _a, _b, _c;
const messageParts = [
severityLevelMap[change.isSafeBasedOnUsage ? graphql_2.SeverityLevelType.Safe : change.severityLevel],
texture_1.Texture.boldQuotedWords(change.message),
];
if (change.isSafeBasedOnUsage) {
messageParts.push(texture_1.Texture.colors.green('(Safe based on usage ✓)'));
}
if (change.approval) {
messageParts.push(texture_1.Texture.colors.green(`(Approved by ${(_b = (_a = change.approval.approvedBy) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : '<unknown>'} ✓)`));
}
if ((_c = change.affectedAppDeployments) === null || _c === void 0 ? void 0 : _c.totalCount) {
const count = change.affectedAppDeployments.totalCount;
messageParts.push(texture_1.Texture.colors.yellow(`[${count} app deployment${count !== 1 ? 's' : ''} affected]`));
}
t.indent(messageParts.join(' '));
});
};
t.info(`Detected ${changes.edges.length} change${changes.edges.length > 1 ? 's' : ''}`);
t.line();
const breakingChanges = changes.edges.filter(edge => edge.node.severityLevel === graphql_2.SeverityLevelType.Breaking);
const dangerousChanges = changes.edges.filter(edge => edge.node.severityLevel === graphql_2.SeverityLevelType.Dangerous);
const safeChanges = changes.edges.filter(edge => edge.node.severityLevel === graphql_2.SeverityLevelType.Safe);
const otherChanges = changes.edges.filter(edge => !Object.values(graphql_2.SeverityLevelType).includes(edge.node.severityLevel));
if (breakingChanges.length) {
t.indent(`Breaking changes:`);
writeChanges(breakingChanges.map(edge => edge.node));
}
if (dangerousChanges.length) {
t.indent(`Dangerous changes:`);
writeChanges(dangerousChanges.map(edge => edge.node));
}
if (safeChanges.length) {
t.indent(`Safe changes:`);
writeChanges(safeChanges.map(edge => edge.node));
}
// For backwards compatibility in case more severity levels are added.
// This is unlikely to happen.
if (otherChanges.length) {
t.indent(`Other changes: (Current CLI version does not support these SeverityLevels)`);
writeChanges(otherChanges.map(edge => edge.node));
}
return t.state.value;
};
exports.renderChanges = renderChanges;
const renderWarnings = (warnings) => {
const t = texture_1.Texture.createBuilder();
t.line();
t.warning(`Detected ${warnings.total} warning${warnings.total > 1 ? 's' : ''}`);
t.line();
warnings.nodes.forEach(warning => {
const details = [
warning.source ? `source: ${texture_1.Texture.boldQuotedWords(warning.source)}` : undefined,
]
.filter(Boolean)
.join(', ');
t.indent(`- ${texture_1.Texture.boldQuotedWords(warning.message)}${details ? ` (${details})` : ''}`);
});
return t.state.value;
};
exports.renderWarnings = renderWarnings;
async function loadSchema(
/**
* Behaviour for loading the schema from a HTTP endpoint.
*/
httpLoadingIntent, file, options) {
const logger = options === null || options === void 0 ? void 0 : options.logger;
const loaders = [];
if (httpLoadingIntent === 'first-federation-then-graphql-introspection') {
loaders.unshift(new FederationSubgraphIntrospectionThenGraphQLIntrospectionUrlLoader(logger));
}
else if (httpLoadingIntent === 'only-federation-introspection') {
loaders.unshift(new FederationSubgraphUrlLoader(logger));
}
else if (httpLoadingIntent === 'only-graphql-introspection') {
loaders.unshift(new url_loader_1.UrlLoader());
}
loaders.push(new code_file_loader_1.CodeFileLoader(), new graphql_file_loader_1.GraphQLFileLoader(), new json_file_loader_1.JsonFileLoader());
const sources = await (0, load_1.loadTypedefs)(file, Object.assign(Object.assign({}, options), { cwd: process.cwd(), loaders }));
return (0, graphql_1.print)((0, graphql_1.concatAST)(sources.map(s => s.document)));
}
function minifySchema(schema) {
return (0, graphql_1.stripIgnoredCharacters)(schema);
}
class FederationSubgraphUrlLoader {
constructor(logger) {
this.logger = logger;
}
async load(pointer, options) {
var _a, _b, _c, _d, _e;
if (!pointer.startsWith('http://') && !pointer.startsWith('https://')) {
(_b = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug) === null || _b === void 0 ? void 0 : _b.call(_a, 'Provided endpoint is not HTTP, skip introspection.');
return [];
}
const client = (0, graphql_request_1.graphqlRequest)({
logger: this.logger,
endpoint: pointer,
additionalHeaders: Object.assign({}, options === null || options === void 0 ? void 0 : options.headers),
});
try {
const response = await client.request({
operation: (0, graphql_1.parse)(/* GraphQL */ `
query ${'GetFederationSchema'} {
_service {
sdl
}
}
`),
});
(_d = (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug) === null || _d === void 0 ? void 0 : _d.call(_c, 'Resolved subgraph SDL successfully.');
const sdl = minifySchema(response._service.sdl);
return [
{
document: (0, graphql_1.parse)(sdl),
rawSDL: sdl,
},
];
}
catch (err) {
if (err instanceof errors_1.APIError &&
((_e = err.graphQLErrors) === null || _e === void 0 ? void 0 : _e.some(err => err.message.includes('Cannot query field "_service" on type "Query"') ||
err.message.includes('Cannot query field "sdl" on type "_Service"')))) {
throw new errors_1.InvalidFederationSubgraphError('The GraphQL server responded with the following errors:\n' +
err.graphQLErrors.map(error => `- ${error.message}`).join('\n'));
}
throw err;
}
}
}
class FederationSubgraphIntrospectionThenGraphQLIntrospectionUrlLoader {
constructor(logger) {
this.logger = logger;
this.urlLoader = new url_loader_1.UrlLoader();
this.federationLoader = new FederationSubgraphUrlLoader(logger);
}
async load(pointer, options) {
var _a, _b;
try {
return await this.federationLoader.load(pointer, options);
}
catch (e) {
// if this error is because because federated introspection isnt supported, then ignore and try
// normal introspection.
if (!(e instanceof errors_1.IntrospectionError || e instanceof errors_1.InvalidFederationSubgraphError)) {
// otherwise, raise an introspection error because some unknown error happened during introspection.
// this may be unintuitive, but we don't want to raise an API Error since users may believe our API is the one at fault.
// We'd rather nudge them to look into their service's behavior.
throw new errors_1.IntrospectionError();
}
}
(_b = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug) === null || _b === void 0 ? void 0 : _b.call(_a, 'Query._service not found. This is a not a Federation subgraph.');
return await this.urlLoader.load(pointer, options);
}
}
//# sourceMappingURL=schema.js.map