HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/shop.komma.nl/node_modules/apollo/lib/commands/service/check.js
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const command_1 = require("@oclif/command");
const table_1 = require("table");
const graphql_1 = require("graphql");
const chalk_1 = __importDefault(require("chalk"));
const env_ci_1 = __importDefault(require("env-ci"));
const git_1 = require("../../git");
const Command_1 = require("../../Command");
const utils_1 = require("../../utils");
const graphqlTypes_1 = require("apollo-language-server/lib/graphqlTypes");
const moment_1 = __importDefault(require("moment"));
const lodash_sortby_1 = __importDefault(require("lodash.sortby"));
const apollo_env_1 = require("apollo-env");
const sharedMessages_1 = require("../../utils/sharedMessages");
const formatChange = (change) => {
    let color = (x) => x;
    if (change.severity === graphqlTypes_1.ChangeSeverity.FAILURE) {
        color = chalk_1.default.red;
    }
    const changeDictionary = {
        [graphqlTypes_1.ChangeSeverity.FAILURE]: "FAIL",
        [graphqlTypes_1.ChangeSeverity.NOTICE]: "PASS"
    };
    return {
        severity: color(changeDictionary[change.severity]),
        code: color(change.code),
        description: color(change.description)
    };
};
function formatTimePeriod(hours) {
    if (hours <= 24) {
        return utils_1.pluralize(hours, "hour");
    }
    return utils_1.pluralize(Math.floor(hours / 24), "day");
}
exports.formatTimePeriod = formatTimePeriod;
function formatMarkdown({ checkSchemaResult, graphName, serviceName, tag, graphCompositionID }) {
    const { diffToPrevious } = checkSchemaResult;
    if (!diffToPrevious) {
        throw new Error("checkSchemaResult.diffToPrevious missing");
    }
    const { validationConfig } = diffToPrevious;
    let validationText = "";
    if (validationConfig) {
        const hours = Math.abs(moment_1.default()
            .add(validationConfig.from, "second")
            .diff(moment_1.default().add(validationConfig.to, "second"), "hours"));
        validationText = `🔢 Compared **${utils_1.pluralize(diffToPrevious.changes.length, "schema change")}** against **${utils_1.pluralize(diffToPrevious.numberOfCheckedOperations, "operation")}** seen over the **last ${formatTimePeriod(hours)}**.`;
    }
    const breakingChanges = diffToPrevious.changes.filter(change => change.severity === "FAILURE");
    const affectedQueryCount = diffToPrevious.affectedQueries
        ? diffToPrevious.affectedQueries.length
        : 0;
    return `
### Apollo Service Check
🔄 Validated your local schema against metrics from variant \`${tag}\` ${serviceName ? `for graph \`${serviceName}\` ` : ""}on graph \`${graphName}@${tag}\`.
${validationText}
${breakingChanges.length > 0
        ? `❌ Found **${utils_1.pluralize(diffToPrevious.changes.filter(change => change.severity === "FAILURE")
            .length, "breaking change")}** that would affect **${utils_1.pluralize(affectedQueryCount, "operation")}** across **${utils_1.pluralize(diffToPrevious.affectedClients && diffToPrevious.affectedClients.length, "client")}**`
        : diffToPrevious.changes.length === 0
            ? `✅ Found **no changes**.`
            : `✅ Found **no breaking changes**.`}

🔗 [View your service check details](${checkSchemaResult.targetUrl +
        (graphCompositionID ? `?graphCompositionId=${graphCompositionID})` : `)`)}.
`;
}
exports.formatMarkdown = formatMarkdown;
function formatCompositionErrorsMarkdown({ compositionErrors, graphName, serviceName, tag }) {
    return `
### Apollo Service Check
🔄 Validated graph composition for service \`${serviceName}\` on graph \`${graphName}@${tag}\`.
❌ Found **${compositionErrors.length} composition errors**

| Service   | Field     | Message   |
| --------- | --------- | --------- |
${compositionErrors
        .map(({ service, field, message }) => `| ${service} | ${field} | ${message} |`)
        .join("\n")}
`;
}
exports.formatCompositionErrorsMarkdown = formatCompositionErrorsMarkdown;
function formatHumanReadable({ checkSchemaResult, graphCompositionID }) {
    const { targetUrl, diffToPrevious: { changes } } = checkSchemaResult;
    let result = "";
    if (changes.length === 0) {
        result = "\nNo changes present between schemas";
    }
    else {
        const sortedChanges = lodash_sortby_1.default(changes, [
            change => change.code,
            change => change.description
        ]);
        const breakingChanges = sortedChanges.filter(change => change.severity === graphqlTypes_1.ChangeSeverity.FAILURE);
        lodash_sortby_1.default(breakingChanges, change => change.severity);
        const nonBreakingChanges = sortedChanges.filter(change => change.severity !== graphqlTypes_1.ChangeSeverity.FAILURE);
        result += table_1.table([
            ["Change", "Code", "Description"],
            ...[
                ...breakingChanges.map(formatChange).map(Object.values),
                ...nonBreakingChanges.map(formatChange).map(Object.values)
            ].filter(Boolean)
        ]);
    }
    if (targetUrl) {
        result += `\n\nView full details at: ${targetUrl}${graphCompositionID ? `?graphCompositionId=${graphCompositionID}` : ``}`;
    }
    return result;
}
exports.formatHumanReadable = formatHumanReadable;
class ServiceCheck extends Command_1.ProjectCommand {
    async run() {
        const taskOutput = {};
        const breakingChangesErrorMessage = "breaking changes found";
        const federatedServiceCompositionUnsuccessfulErrorMessage = "Federated service composition was unsuccessful. Please see the reasons below.";
        const { isCi } = env_ci_1.default();
        let schema;
        let graphID;
        let graphVariant;
        try {
            await this.runTasks(({ config, flags, project }) => {
                graphID = config.graph;
                graphVariant = config.variant;
                const serviceName = flags.serviceName;
                if (!graphID) {
                    throw sharedMessages_1.graphUndefinedError;
                }
                const graphSpecifier = `${graphID}@${graphVariant}`;
                taskOutput.shouldOutputJson = !!flags.json;
                taskOutput.shouldOutputMarkdown = !!flags.markdown;
                taskOutput.shouldAlwaysExit0 = !!flags.ignoreFailures;
                taskOutput.serviceName = flags.serviceName;
                taskOutput.config = config;
                return [
                    {
                        enabled: () => !!serviceName,
                        title: `Validate graph composition for service ${chalk_1.default.cyan(serviceName || "")} on graph ${chalk_1.default.cyan(graphSpecifier)}`,
                        task: async (ctx, task) => {
                            if (!serviceName) {
                                throw new Error("This task should not be run without a `serviceName`. Check the `enabled` function.");
                            }
                            task.output = "Fetching local service's partial schema";
                            const sdl = await project.resolveFederatedServiceSDL();
                            if (!sdl) {
                                throw new Error("No SDL found for federated service");
                            }
                            task.output = `Attempting to compose graph with ${chalk_1.default.cyan(serviceName)} service's partial schema`;
                            const historicParameters = utils_1.validateHistoricParams({
                                validationPeriod: flags.validationPeriod,
                                queryCountThreshold: flags.queryCountThreshold,
                                queryCountThresholdPercentage: flags.queryCountThresholdPercentage
                            });
                            const gitInfoFromEnv = await git_1.gitInfo(this.log);
                            const { compositionValidationResult, checkSchemaResult } = await project.engine.checkPartialSchema(Object.assign(Object.assign({ id: graphID, graphVariant: graphVariant, implementingServiceName: serviceName, partialSchema: {
                                    sdl
                                } }, (historicParameters && { historicParameters })), { gitContext: Object.assign(Object.assign(Object.assign({}, gitInfoFromEnv), (flags.author ? { committer: flags.author } : undefined)), (flags.branch ? { branch: flags.branch } : undefined)) }));
                            task.title = `Found ${utils_1.pluralize(compositionValidationResult.errors.length, "graph composition error")} for service ${chalk_1.default.cyan(serviceName)} on graph ${chalk_1.default.cyan(graphSpecifier)}`;
                            if (compositionValidationResult.errors.length > 0) {
                                taskOutput.compositionErrors = compositionValidationResult.errors
                                    .filter(apollo_env_1.isNotNullOrUndefined)
                                    .map(error => {
                                    const match = error.message.match(/^\[([^\[]+)\]\s+(\S+)\ ->\ (.+)/);
                                    if (!match) {
                                        return { message: error.message };
                                    }
                                    const [, service, field, message] = match;
                                    return { service, field, message };
                                });
                                taskOutput.graphCompositionID =
                                    compositionValidationResult.graphCompositionID;
                                this.error(federatedServiceCompositionUnsuccessfulErrorMessage);
                            }
                            else {
                                if (!checkSchemaResult) {
                                    throw new Error("Violated invariant. Schema should have been validated against operations if" +
                                        "there were no composition errors");
                                }
                                taskOutput.checkSchemaResult = checkSchemaResult;
                                ctx.checkSchemaResult = checkSchemaResult;
                            }
                        }
                    },
                    {
                        title: `Validating ${serviceName ? "composed " : ""}schema against metrics from variant ${chalk_1.default.cyan(graphVariant)} on graph ${chalk_1.default.cyan(graphSpecifier)}`,
                        enabled: () => !serviceName,
                        task: async (ctx, task) => {
                            let schemaCheckSchemaVariables;
                            task.output = "Resolving schema";
                            schema = await project.resolveSchema({ tag: config.variant });
                            if (!schema) {
                                throw new Error("Failed to resolve schema");
                            }
                            schemaCheckSchemaVariables = {
                                schema: graphql_1.introspectionFromSchema(schema)
                                    .__schema
                            };
                            const historicParameters = utils_1.validateHistoricParams({
                                validationPeriod: flags.validationPeriod,
                                queryCountThreshold: flags.queryCountThreshold,
                                queryCountThresholdPercentage: flags.queryCountThresholdPercentage
                            });
                            task.output = "Validating schema";
                            const gitInfoFromEnv = await git_1.gitInfo(this.log);
                            const variables = Object.assign(Object.assign({ id: graphID, tag: config.variant, gitContext: Object.assign(Object.assign(Object.assign({}, gitInfoFromEnv), (flags.committer
                                    ? { committer: flags.committer }
                                    : undefined)), (flags.branch ? { branch: flags.branch } : undefined)) }, (historicParameters && { historicParameters })), schemaCheckSchemaVariables);
                            const { schema: _ } = variables, restVariables = __rest(variables, ["schema"]);
                            this.debug("Variables sent to Apollo:");
                            this.debug(restVariables);
                            if (schema) {
                                this.debug("SDL of introspection sent to Apollo:");
                                this.debug(graphql_1.printSchema(schema));
                            }
                            else {
                                this.debug("Schema hash generated:");
                                this.debug(schemaCheckSchemaVariables);
                            }
                            const checkSchemaResult = await project.engine.checkSchema(variables);
                            ctx.checkSchemaResult = checkSchemaResult;
                            taskOutput.checkSchemaResult = checkSchemaResult;
                            task.title = task.title.replace("Validating", "Validated");
                        }
                    },
                    {
                        title: "Comparing schema changes",
                        task: async (ctx, task) => {
                            const schemaChanges = ctx.checkSchemaResult.diffToPrevious.changes;
                            const numberOfCheckedOperations = ctx.checkSchemaResult.diffToPrevious
                                .numberOfCheckedOperations || 0;
                            const validationConfig = ctx.checkSchemaResult.diffToPrevious.validationConfig;
                            const hours = validationConfig
                                ? Math.abs(moment_1.default()
                                    .add(validationConfig.from, "second")
                                    .diff(moment_1.default().add(validationConfig.to, "second"), "hours"))
                                : null;
                            task.title = `Compared ${utils_1.pluralize(chalk_1.default.cyan(schemaChanges.length.toString()), "schema change")} against ${utils_1.pluralize(chalk_1.default.cyan(numberOfCheckedOperations.toString()), "operation")}${hours
                                ? ` over the last ${chalk_1.default.cyan(formatTimePeriod(hours))}`
                                : ""}`;
                        }
                    },
                    {
                        title: "Reporting result",
                        task: async (ctx, task) => {
                            const breakingSchemaChangeCount = ctx.checkSchemaResult.diffToPrevious.changes.filter(change => change.severity === graphqlTypes_1.ChangeSeverity.FAILURE).length;
                            const nonBreakingSchemaChangeCount = ctx.checkSchemaResult.diffToPrevious.changes.length -
                                breakingSchemaChangeCount;
                            task.title = `Found ${utils_1.pluralize(chalk_1.default.cyan(breakingSchemaChangeCount.toString()), "breaking change")} and ${utils_1.pluralize(chalk_1.default.cyan(nonBreakingSchemaChangeCount.toString()), "compatible change")}`;
                            if (breakingSchemaChangeCount) {
                                throw new Error(breakingChangesErrorMessage);
                            }
                        }
                    }
                ];
            }, context => ({
                renderer: isCi
                    ? utils_1.CompactRenderer
                    : context.flags.markdown || context.flags.json
                        ? "silent"
                        : "default"
            }));
        }
        catch (error) {
            if (error.message.includes("/upgrade")) {
                this.exit(1);
                return;
            }
            if (error.message !== breakingChangesErrorMessage &&
                error.message !== federatedServiceCompositionUnsuccessfulErrorMessage) {
                throw error;
            }
        }
        const { checkSchemaResult, config, shouldOutputJson, shouldOutputMarkdown, serviceName, compositionErrors, graphCompositionID, shouldAlwaysExit0 } = taskOutput;
        if (shouldOutputJson) {
            if (compositionErrors) {
                return this.log(JSON.stringify({ errors: compositionErrors }, null, 2));
            }
            return this.log(JSON.stringify({
                targetUrl: checkSchemaResult.targetUrl +
                    (graphCompositionID
                        ? `?graphCompositionId=${graphCompositionID}`
                        : ``),
                changes: checkSchemaResult.diffToPrevious.changes,
                validationConfig: checkSchemaResult.diffToPrevious.validationConfig
            }, null, 2));
        }
        else if (shouldOutputMarkdown) {
            if (!graphID) {
                throw new Error("The graph name should have been defined in the Apollo config and validated when the config was loaded. Please file an issue if you're seeing this error.");
            }
            if (compositionErrors) {
                if (!serviceName) {
                    throw new Error("Composition errors should only occur when `serviceName` is present. Please file an issue if you're seeing this error.");
                }
                return this.log(formatCompositionErrorsMarkdown({
                    compositionErrors,
                    graphName: graphID,
                    serviceName,
                    tag: config.variant
                }));
            }
            return this.log(formatMarkdown({
                checkSchemaResult,
                graphName: graphID,
                serviceName,
                tag: config.variant,
                graphCompositionID
            }));
        }
        if (compositionErrors) {
            console.log("");
            const unformattedErrors = compositionErrors.filter(e => !e.field && !e.service);
            const formattedErrors = compositionErrors.filter(e => e.field || e.service);
            if (formattedErrors.length)
                this.log(table_1.table([
                    ["Service", "Field", "Message"],
                    ...formattedErrors.map(Object.values)
                ], {
                    columns: {
                        2: {
                            width: 50,
                            wrapWord: true
                        }
                    }
                }));
            if (unformattedErrors.length)
                this.log(table_1.table([["Message"], ...unformattedErrors.map(e => [e.message])]));
            if (shouldAlwaysExit0) {
                return;
            }
            this.exit(1);
        }
        else {
            this.log(formatHumanReadable({ checkSchemaResult, graphCompositionID }));
            if (checkSchemaResult.diffToPrevious.changes.find(({ severity }) => severity === graphqlTypes_1.ChangeSeverity.FAILURE)) {
                if (shouldAlwaysExit0) {
                    return;
                }
                this.exit(1);
            }
        }
    }
}
exports.default = ServiceCheck;
ServiceCheck.aliases = ["schema:check"];
ServiceCheck.description = "Check a service against known operation workloads to find breaking changes";
ServiceCheck.flags = Object.assign(Object.assign({}, Command_1.ProjectCommand.flags), { tag: command_1.flags.string({
        char: "t",
        description: "[Deprecated: please use --variant instead] The tag (AKA variant) to check the proposed schema against",
        hidden: true,
        exclusive: ["variant"]
    }), variant: command_1.flags.string({
        char: "v",
        description: "The variant to check the proposed schema against",
        exclusive: ["tag"]
    }), graph: command_1.flags.string({
        char: "g",
        description: "The ID of the graph in Apollo to check your proposed schema changes against. Overrides config file if set."
    }), branch: command_1.flags.string({
        description: "The branch name to associate with this check"
    }), author: command_1.flags.string({
        description: "The author to associate with this proposed schema"
    }), validationPeriod: command_1.flags.string({
        description: "The size of the time window with which to validate the schema against. You may provide a number (in seconds), or an ISO8601 format duration for more granularity (see: https://en.wikipedia.org/wiki/ISO_8601#Durations)"
    }), queryCountThreshold: command_1.flags.integer({
        description: "Minimum number of requests within the requested time window for a query to be considered."
    }), queryCountThresholdPercentage: command_1.flags.integer({
        description: "Number of requests within the requested time window for a query to be considered, relative to total request count. Expected values are between 0 and 0.05 (minimum 5% of total request volume)"
    }), json: command_1.flags.boolean({
        description: "Output result in json, which can then be parsed by CLI tools such as jq.",
        exclusive: ["markdown"]
    }), localSchemaFile: command_1.flags.string({
        description: "Path to one or more local GraphQL schema file(s), as introspection result or SDL. Supports comma-separated list of paths (ex. `--localSchemaFile=schema.graphql,extensions.graphql`)"
    }), markdown: command_1.flags.boolean({
        description: "Output result in markdown.",
        exclusive: ["json"]
    }), serviceName: command_1.flags.string({
        description: "Provides the name of the implementing service for a federated graph. This flag will indicate that the schema is a partial schema from a federated service"
    }), ignoreFailures: command_1.flags.boolean({
        description: "Exit with status 0 when the check completes, even if errors are found"
    }) });
//# sourceMappingURL=check.js.map