UNPKG

@graphql-hive/cli

Version:

A CLI util to manage and control your GraphQL Hive

220 lines 10.2 kB
"use strict"; 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