UNPKG

@graphql-hive/cli

Version:

A CLI util to manage and control your GraphQL Hive

362 lines • 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const core_1 = require("@oclif/core"); const base_command_1 = tslib_1.__importDefault(require("../../base-command")); const gql_1 = require("../../gql"); const config_1 = require("../../helpers/config"); const errors_1 = require("../../helpers/errors"); const git_1 = require("../../helpers/git"); const schema_1 = require("../../helpers/schema"); const TargetInput = tslib_1.__importStar(require("../../helpers/target-input")); const schemaPublishMutation = (0, gql_1.graphql)(/* GraphQL */ ` mutation schemaPublish($input: SchemaPublishInput!) { schemaPublish(input: $input) { __typename ... on SchemaPublishSuccess { initial valid successMessage: message linkToWebsite changes { edges { __typename } ...RenderChanges_schemaChanges } } ... on SchemaPublishError { valid linkToWebsite changes { edges { __typename } ...RenderChanges_schemaChanges } errors { ...RenderErrors_SchemaErrorConnectionFragment } } ... on SchemaPublishMissingServiceError { missingServiceError: message } ... on SchemaPublishMissingUrlError { missingUrlError: message } ... on SchemaPublishRetry { reason } ... on GitHubSchemaPublishSuccess { message } ... on GitHubSchemaPublishError { message } } } `); class SchemaPublish extends base_command_1.default { constructor() { super(...arguments); this.resolveMetadata = (metadata) => { if (!metadata) { return; } try { JSON.parse(metadata); // If we are able to parse it, it means it's a valid JSON, let's use it as-is return metadata; } catch (e) { this.logDebug(e); // If we can't parse it, we can try to load it from FS return this.readJSON(metadata); } }; } async run() { var _a, _b; try { const { flags, args } = await this.parse(SchemaPublish); await this.require(flags); let endpoint, accessToken; try { endpoint = this.ensure({ key: 'registry.endpoint', args: flags, legacyFlagName: 'registry', defaultValue: config_1.graphqlEndpoint, env: 'HIVE_REGISTRY', description: SchemaPublish.flags['registry.endpoint'].description, }); } catch (e) { throw e instanceof errors_1.MissingArgumentsError ? new errors_1.MissingEndpointError() : e; } try { accessToken = this.ensure({ key: 'registry.accessToken', args: flags, legacyFlagName: 'token', env: 'HIVE_TOKEN', description: SchemaPublish.flags['registry.accessToken'].description, }); } catch (e) { throw e instanceof errors_1.MissingArgumentsError ? new errors_1.MissingRegistryTokenError() : e; } const service = flags.service; const url = flags.url; const file = args.file; const revision = flags.revision; const force = flags.force; const experimental_acceptBreakingChanges = flags.experimental_acceptBreakingChanges; const metadata = this.resolveMetadata(flags.metadata); const usesGitHubApp = flags.github; let commit = this.maybe({ key: 'commit', args: flags, env: 'HIVE_COMMIT', }); let author = this.maybe({ key: 'author', args: flags, env: 'HIVE_AUTHOR', }); let gitHub = null; if (!commit || !author) { const git = await (0, git_1.gitInfo)(() => { this.warn(`No git information found. Couldn't resolve author and commit.`); }); if (!commit) { commit = git.commit; } if (!author) { author = git.author; } } if (!author) { throw new errors_1.AuthorRequiredError(); } if (!commit) { throw new errors_1.CommitRequiredError(); } if (usesGitHubApp) { // eslint-disable-next-line no-process-env const repository = (_a = process.env['GITHUB_REPOSITORY']) !== null && _a !== void 0 ? _a : null; if (!repository) { throw new errors_1.MissingEnvironmentError([ 'GITHUB_REPOSITORY', 'Github repository full name, e.g. graphql-hive/console', ]); } gitHub = { repository, commit, }; } let target = null; if (flags.target) { const result = TargetInput.parse(flags.target); if (result.type === 'error') { throw new errors_1.InvalidTargetError(); } target = result.data; } if (revision && file) { throw new errors_1.ConflictingOptionsError(['FILE', '--revision'], 'A pushed revision already contains the schema, so the FILE argument must be omitted.'); } let schema = null; if (revision) { schema = { revision: revision }; } else { if (!file) { throw new errors_1.MissingArgumentsError(['file', 'Path to the schema file(s)']); } const rawSdl = await (0, schema_1.loadSchemaSdl)(file, { logger: this.logger }); schema = { sdl: (0, schema_1.minifySchema)(rawSdl) }; } /** A rejected publish did not store anything. `--force` has no effect on the server. */ const handleRejectedPublish = (linkToWebsite) => { if (force) { this.logSuccess('Schema published (forced)'); if (linkToWebsite) { this.logInfo(`Available at ${linkToWebsite}`); } return; } throw new errors_1.SchemaPublishFailedError(linkToWebsite ? `See ${linkToWebsite}` : null); }; const input = { service, url, author, commit, schema, force, experimental_acceptBreakingChanges: experimental_acceptBreakingChanges === true, failOnCompositionError: flags['fail-on-composition-error'], metadata, gitHub, supportsRetry: true, target, }; const api = this.registryApi(endpoint, accessToken); /** Gateway timeout is 60 seconds. */ const timeout = 55000; let result = null; do { result = await api.request({ operation: schemaPublishMutation, variables: { input }, timeout, }); const payload = result.schemaPublish; if (payload.__typename === 'SchemaPublishSuccess') { const changes = payload.changes; if (payload.initial) { this.logSuccess('Published initial schema.'); } else if (payload.successMessage) { this.logSuccess(payload.successMessage); } else if (((_b = changes === null || changes === void 0 ? void 0 : changes.edges) === null || _b === void 0 ? void 0 : _b.length) === 0) { this.logSuccess('No changes. Skipping.'); } else { if (changes) { this.log((0, schema_1.renderChanges)(changes)); } this.logSuccess('Schema published'); } if (payload.linkToWebsite) { this.logInfo(`Available at ${payload.linkToWebsite}`); } } else if (payload.__typename === 'SchemaPublishRetry') { this.log(payload.reason); this.log('Waiting for other schema publishes to complete...'); result = null; } else if (payload.__typename === 'SchemaPublishMissingServiceError') { throw new errors_1.SchemaPublishMissingServiceError(payload.missingServiceError); } else if (payload.__typename === 'SchemaPublishMissingUrlError') { throw new errors_1.SchemaPublishMissingUrlError(payload.missingUrlError); } else if (payload.__typename === 'SchemaPublishError') { const changes = payload.changes; const errors = payload.errors; if (errors) { this.log((0, schema_1.renderErrors)(errors)); } if (changes === null || changes === void 0 ? void 0 : changes.edges.length) { this.log(''); this.log((0, schema_1.renderChanges)(changes)); } this.log(''); handleRejectedPublish(payload.linkToWebsite); } else if (payload.__typename === 'GitHubSchemaPublishSuccess') { this.logSuccess(payload.message); } else { throw new errors_1.APIError(payload.message); } } while (result === null); } catch (error) { if (error instanceof core_1.Errors.CLIError) { throw error; } else { this.logFailure('Failed to publish schema'); throw new errors_1.UnexpectedError(error instanceof Error ? error.message : JSON.stringify(error)); } } } } SchemaPublish.description = 'publishes schema'; SchemaPublish.flags = { service: core_1.Flags.string({ description: 'service name (only for distributed schemas)', }), url: core_1.Flags.string({ description: 'service url (only for distributed schemas)', }), metadata: core_1.Flags.string({ description: 'additional metadata to attach to the GraphQL schema. This can be a string with a valid JSON, or a path to a file containing a valid JSON', }), 'registry.endpoint': core_1.Flags.string({ description: 'registry endpoint', }), /** @deprecated */ registry: core_1.Flags.string({ description: 'registry address', deprecated: { message: 'use --registry.endpoint instead', version: '0.21.0', }, }), 'registry.accessToken': core_1.Flags.string({ description: 'registry access token', }), /** @deprecated */ token: core_1.Flags.string({ description: 'api token', deprecated: { message: 'use --registry.accessToken instead', version: '0.21.0', }, }), author: core_1.Flags.string({ description: 'author of the change', }), commit: core_1.Flags.string({ description: 'The associated commit SHA, or optionally any external identifier that references the schema', }), revision: core_1.Flags.string({ description: 'publish a previously pushed schema revision', }), github: core_1.Flags.boolean({ description: 'Connect with GitHub Application', default: false, }), force: core_1.Flags.boolean({ description: 'force publish even on breaking changes', deprecated: { message: '--force is enabled by default for newly created projects', }, }), experimental_acceptBreakingChanges: core_1.Flags.boolean({ description: '(experimental) accept breaking changes and mark schema as valid (only if composable)', deprecated: { message: '--experimental_acceptBreakingChanges is enabled by default for newly created projects', }, }), 'fail-on-composition-error': core_1.Flags.boolean({ description: 'prevent publishing a federation schema if it would cause a composition error', }), require: core_1.Flags.string({ description: 'Loads specific require.extensions before running the codegen and reading the configuration', default: [], multiple: true, }), target: core_1.Flags.string({ description: 'The target to which to publish to (slug or ID).' + ' This can either be a slug following the format "$organizationSlug/$projectSlug/$targetSlug" (e.g "the-guild/graphql-hive/staging")' + ' or an UUID (e.g. "a0f4c605-6541-4350-8cfe-b31f21a4bf80").', }), }; SchemaPublish.args = { file: core_1.Args.string({ name: 'file', required: false, description: 'Path to the schema file(s), must be omitted when using --revision', hidden: false, }), }; exports.default = SchemaPublish; //# sourceMappingURL=publish.js.map