UNPKG

@graphql-hive/cli

Version:

A CLI util to manage and control your GraphQL Hive

217 lines 10.1 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 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, _f, _g, _h, _j, _k; 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), }); (_d = (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug) === null || _d === void 0 ? void 0 : _d.call(_c, 'Attempt "_Service" type lookup via "Query.__type".'); // We can check if the schema is a subgraph by looking for the `_Service` type. const isSubgraph = await client.request({ operation: (0, graphql_1.parse)(/* GraphQL */ ` query ${'LookupService'} { __type(name: "_Service") ${' '}{ name } } `), }); if (isSubgraph.__type === null) { (_f = (_e = this.logger) === null || _e === void 0 ? void 0 : _e.debug) === null || _f === void 0 ? void 0 : _f.call(_e, 'Type not found, this is not a Federation subgraph.'); return []; } (_h = (_g = this.logger) === null || _g === void 0 ? void 0 : _g.debug) === null || _h === void 0 ? void 0 : _h.call(_g, 'Resolved "_Service" type. Federation subgraph detected.' + 'Attempt Federation introspection via "Query._service" field.'); const response = await client.request({ operation: (0, graphql_1.parse)(/* GraphQL */ ` query ${'GetFederationSchema'} { _service { sdl } } `), }); (_k = (_j = this.logger) === null || _j === void 0 ? void 0 : _j.debug) === null || _k === void 0 ? void 0 : _k.call(_j, 'Resolved subgraph SDL successfully.'); const sdl = minifySchema(response._service.sdl); return [ { document: (0, graphql_1.parse)(sdl), rawSDL: sdl, }, ]; } } 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, _c, _d; (_b = (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug) === null || _b === void 0 ? void 0 : _b.call(_a, 'Attempt federation introspection'); let result = await this.federationLoader.load(pointer, options); if (!result.length) { (_d = (_c = this.logger) === null || _c === void 0 ? void 0 : _c.debug) === null || _d === void 0 ? void 0 : _d.call(_c, 'Attempt GraphQL introspection'); result = await this.urlLoader.load(pointer, options); } return result; } } //# sourceMappingURL=schema.js.map