@graphql-hive/cli
Version: 
A CLI util to manage and control your GraphQL Hive
360 lines • 14.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const graphql_1 = require("graphql");
const utils_1 = require("@graphql-tools/utils");
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 validation_1 = require("../../helpers/validation");
const schemaPublishMutation = (0, gql_1.graphql)(/* GraphQL */ `
  mutation schemaPublish($input: SchemaPublishInput!, $usesGitHubApp: Boolean!) {
    schemaPublish(input: $input) {
      __typename
      ... on SchemaPublishSuccess @skip(if: $usesGitHubApp) {
        initial
        valid
        successMessage: message
        linkToWebsite
        changes {
          nodes {
            message(withSafeBasedOnUsageNote: false)
            criticality
            isSafeBasedOnUsage
          }
          total
          ...RenderChanges_schemaChanges
        }
      }
      ... on SchemaPublishError @skip(if: $usesGitHubApp) {
        valid
        linkToWebsite
        changes {
          nodes {
            message(withSafeBasedOnUsageNote: false)
            criticality
            isSafeBasedOnUsage
          }
          total
          ...RenderChanges_schemaChanges
        }
        errors {
          nodes {
            message
          }
          total
        }
      }
      ... on SchemaPublishMissingServiceError @skip(if: $usesGitHubApp) {
        missingServiceError: message
      }
      ... on SchemaPublishMissingUrlError @skip(if: $usesGitHubApp) {
        missingUrlError: message
      }
      ... on GitHubSchemaPublishSuccess @include(if: $usesGitHubApp) {
        message
      }
      ... on GitHubSchemaPublishError @include(if: $usesGitHubApp) {
        message
      }
      ... on SchemaPublishRetry {
        reason
      }
    }
  }
`);
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) {
                // 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 new errors_1.MissingEndpointError();
            }
            try {
                accessToken = this.ensure({
                    key: 'registry.accessToken',
                    args: flags,
                    legacyFlagName: 'token',
                    env: 'HIVE_TOKEN',
                    description: SchemaPublish.flags['registry.accessToken'].description,
                });
            }
            catch (e) {
                throw new errors_1.MissingRegistryTokenError();
            }
            const service = flags.service;
            const url = flags.url;
            const file = args.file;
            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.GithubAuthorRequiredError();
            }
            if (!commit) {
                throw new errors_1.GithubCommitRequiredError();
            }
            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;
            }
            let sdl;
            try {
                const rawSdl = await (0, schema_1.loadSchema)(file);
                (0, validation_1.invariant)(typeof rawSdl === 'string' && rawSdl.length > 0, 'Schema seems empty');
                const transformedSDL = (0, graphql_1.print)((0, utils_1.transformCommentsToDescriptions)(rawSdl));
                sdl = (0, schema_1.minifySchema)(transformedSDL);
            }
            catch (err) {
                if (err instanceof graphql_1.GraphQLError) {
                    throw new errors_1.InvalidSDLError(err);
                }
                throw err;
            }
            let result = null;
            do {
                result = await this.registryApi(endpoint, accessToken).request({
                    operation: schemaPublishMutation,
                    variables: {
                        input: {
                            service,
                            url,
                            author,
                            commit,
                            sdl,
                            force,
                            experimental_acceptBreakingChanges: experimental_acceptBreakingChanges === true,
                            metadata,
                            gitHub,
                            supportsRetry: true,
                            target,
                        },
                        usesGitHubApp: !!gitHub,
                    },
                    /** Gateway timeout is 60 seconds. */
                    timeout: 55000,
                });
                if (result.schemaPublish.__typename === 'SchemaPublishSuccess') {
                    const changes = result.schemaPublish.changes;
                    if (result.schemaPublish.initial) {
                        this.logSuccess('Published initial schema.');
                    }
                    else if (result.schemaPublish.successMessage) {
                        this.logSuccess(result.schemaPublish.successMessage);
                    }
                    else if (changes && changes.total === 0) {
                        this.logSuccess('No changes. Skipping.');
                    }
                    else {
                        if (changes) {
                            this.log((0, schema_1.renderChanges)(changes));
                        }
                        this.logSuccess('Schema published');
                    }
                    if (result.schemaPublish.linkToWebsite) {
                        this.logInfo(`Available at ${result.schemaPublish.linkToWebsite}`);
                    }
                }
                else if (result.schemaPublish.__typename === 'SchemaPublishRetry') {
                    this.log(result.schemaPublish.reason);
                    this.log('Waiting for other schema publishes to complete...');
                    result = null;
                }
                else if (result.schemaPublish.__typename === 'SchemaPublishMissingServiceError') {
                    throw new errors_1.SchemaPublishMissingServiceError(result.schemaPublish.missingServiceError);
                }
                else if (result.schemaPublish.__typename === 'SchemaPublishMissingUrlError') {
                    throw new errors_1.SchemaPublishMissingUrlError(result.schemaPublish.missingUrlError);
                }
                else if (result.schemaPublish.__typename === 'SchemaPublishError') {
                    const changes = result.schemaPublish.changes;
                    const errors = result.schemaPublish.errors;
                    this.log((0, schema_1.renderErrors)(errors));
                    if (changes && changes.total) {
                        this.log('');
                        this.log((0, schema_1.renderChanges)(changes));
                    }
                    this.log('');
                    if (!force) {
                        throw new errors_1.SchemaPublishFailedError();
                    }
                    else {
                        this.logSuccess('Schema published (forced)');
                    }
                    if (result.schemaPublish.linkToWebsite) {
                        this.logInfo(`Available at ${result.schemaPublish.linkToWebsite}`);
                    }
                }
                else if (result.schemaPublish.__typename === 'GitHubSchemaPublishSuccess') {
                    this.logSuccess(result.schemaPublish.message);
                }
                else {
                    throw new errors_1.APIError('message' in result.schemaPublish
                        ? result.schemaPublish.message
                        : `Received unhandled type "${(_b = result.schemaPublish) === null || _b === void 0 ? void 0 : _b.__typename}" in response.`);
                }
            } 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: 'associated commit sha',
    }),
    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',
        },
    }),
    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: true,
        description: 'Path to the schema file(s)',
        hidden: false,
    }),
};
exports.default = SchemaPublish;
//# sourceMappingURL=publish.js.map