"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runPromise = void 0;
const fs = __importStar(require("fs"));
const path_1 = __importDefault(require("path"));
const perf_hooks_1 = require("perf_hooks");
const core = __importStar(require("@actions/core"));
const safe_which_1 = require("@chrisgavin/safe-which");
const actionsUtil = __importStar(require("./actions-util"));
const analyze_1 = require("./analyze");
const api_client_1 = require("./api-client");
const autobuild_1 = require("./autobuild");
const codeql_1 = require("./codeql");
const config_utils_1 = require("./config-utils");
const database_upload_1 = require("./database-upload");
const diagnostics_1 = require("./diagnostics");
const environment_1 = require("./environment");
const feature_flags_1 = require("./feature-flags");
const languages_1 = require("./languages");
const logging_1 = require("./logging");
const repository_1 = require("./repository");
const statusReport = __importStar(require("./status-report"));
const status_report_1 = require("./status-report");
const trap_caching_1 = require("./trap-caching");
const uploadLib = __importStar(require("./upload-lib"));
const util = __importStar(require("./util"));
const util_1 = require("./util");
async function sendStatusReport(startedAt, config, stats, error, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger) {
    const status = (0, status_report_1.getActionsStatus)(error, stats?.analyze_failure_language);
    const statusReportBase = await (0, status_report_1.createStatusReportBase)("finish", status, startedAt, await util.checkDiskUsage(), error?.message, error?.stack);
    const report = {
        ...statusReportBase,
        ...(stats || {}),
        ...(dbCreationTimings || {}),
    };
    if (config && didUploadTrapCaches) {
        const trapCacheUploadStatusReport = {
            ...report,
            trap_cache_upload_duration_ms: Math.round(trapCacheUploadTime || 0),
            trap_cache_upload_size_bytes: Math.round(await (0, trap_caching_1.getTotalCacheSize)(config.trapCaches, logger)),
        };
        await statusReport.sendStatusReport(trapCacheUploadStatusReport);
    }
    else {
        await statusReport.sendStatusReport(report);
    }
}
// `expect-error` should only be set to a non-false value by the CodeQL Action PR checks.
function hasBadExpectErrorInput() {
    return (actionsUtil.getOptionalInput("expect-error") !== "false" &&
        !util.isInTestMode());
}
/**
 * Returns whether any TRAP files exist under the `db-go` folder,
 * indicating whether Go extraction has extracted at least one file.
 */
function doesGoExtractionOutputExist(config) {
    const golangDbDirectory = util.getCodeQLDatabasePath(config, languages_1.Language.go);
    const trapDirectory = path_1.default.join(golangDbDirectory, "trap", languages_1.Language.go);
    return (fs.existsSync(trapDirectory) &&
        fs
            .readdirSync(trapDirectory)
            .some((fileName) => [
            ".trap",
            ".trap.gz",
            ".trap.br",
            ".trap.tar.gz",
            ".trap.tar.br",
            ".trap.tar",
        ].some((ext) => fileName.endsWith(ext))));
}
/**
 * We attempt to autobuild Go to preserve compatibility for users who have
 * set up Go using a legacy scanning style CodeQL workflow, i.e. one without
 * an autobuild step or manual build steps.
 *
 * - We detect whether an autobuild step is present by checking the
 * `CODEQL_ACTION_DID_AUTOBUILD_GOLANG` environment variable, which is set
 * when the autobuilder is invoked.
 * - We detect whether the Go database has already been finalized in case it
 * has been manually set in a prior Action step.
 * - We approximate whether manual build steps are present by looking at
 * whether any extraction output already exists for Go.
 */
async function runAutobuildIfLegacyGoWorkflow(config, logger) {
    if (!config.languages.includes(languages_1.Language.go)) {
        return;
    }
    if (process.env[environment_1.EnvVar.DID_AUTOBUILD_GOLANG] === "true") {
        logger.debug("Won't run Go autobuild since it has already been run.");
        return;
    }
    if ((0, analyze_1.dbIsFinalized)(config, languages_1.Language.go, logger)) {
        logger.debug("Won't run Go autobuild since there is already a finalized database for Go.");
        return;
    }
    // This captures whether a user has added manual build steps for Go
    if (doesGoExtractionOutputExist(config)) {
        logger.debug("Won't run Go autobuild since at least one file of Go code has already been extracted.");
        // If the user has run the manual build step, and has set the `CODEQL_EXTRACTOR_GO_BUILD_TRACING`
        // variable, we suggest they remove it from their workflow.
        if ("CODEQL_EXTRACTOR_GO_BUILD_TRACING" in process.env) {
            logger.warning(`The CODEQL_EXTRACTOR_GO_BUILD_TRACING environment variable has no effect on workflows with manual build steps, so we recommend that you remove it from your workflow.`);
        }
        return;
    }
    logger.debug("Running Go autobuild because extraction output (TRAP files) for Go code has not been found.");
    await (0, autobuild_1.runAutobuild)(languages_1.Language.go, config, logger);
}
async function run() {
    const startedAt = new Date();
    let uploadResult = undefined;
    let runStats = undefined;
    let config = undefined;
    let trapCacheUploadTime = undefined;
    let dbCreationTimings = undefined;
    let didUploadTrapCaches = false;
    util.initializeEnvironment(actionsUtil.getActionVersion());
    const logger = (0, logging_1.getActionsLogger)();
    try {
        if (!(await statusReport.sendStatusReport(await (0, status_report_1.createStatusReportBase)("finish", "starting", startedAt, await util.checkDiskUsage(logger))))) {
            return;
        }
        config = await (0, config_utils_1.getConfig)(actionsUtil.getTemporaryDirectory(), logger);
        if (config === undefined) {
            throw new Error("Config file could not be found at expected location. Has the 'init' action been called?");
        }
        if (hasBadExpectErrorInput()) {
            throw new util.UserError("`expect-error` input parameter is for internal use only. It should only be set by codeql-action or a fork.");
        }
        const apiDetails = (0, api_client_1.getApiDetails)();
        const outputDir = actionsUtil.getRequiredInput("output");
        const threads = util.getThreadsFlag(actionsUtil.getOptionalInput("threads") || process.env["CODEQL_THREADS"], logger);
        const repositoryNwo = (0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY"));
        const gitHubVersion = await (0, api_client_1.getGitHubVersion)();
        const features = new feature_flags_1.Features(gitHubVersion, repositoryNwo, actionsUtil.getTemporaryDirectory(), logger);
        const memory = util.getMemoryFlag(actionsUtil.getOptionalInput("ram") || process.env["CODEQL_RAM"], logger);
        // Check that `which go` still points at the same path it did when the `init` Action ran to ensure that no steps
        // in-between performed any setup. We encourage users to perform all setup tasks before initializing CodeQL so that
        // the setup tasks do not interfere with our analysis.
        // Furthermore, if we installed a wrapper script in the `init` Action, we need to ensure that there isn't a step
        // in the workflow after the `init` step which installs a different version of Go and takes precedence in the PATH,
        // thus potentially circumventing our workaround that allows tracing to work.
        const goInitPath = process.env[environment_1.EnvVar.GO_BINARY_LOCATION];
        if (process.env[environment_1.EnvVar.DID_AUTOBUILD_GOLANG] !== "true" &&
            goInitPath !== undefined) {
            const goBinaryPath = await (0, safe_which_1.safeWhich)("go");
            if (goInitPath !== goBinaryPath) {
                core.warning(`Expected \`which go\` to return ${goInitPath}, but got ${goBinaryPath}: please ensure that the correct version of Go is installed before the \`codeql-action/init\` Action is used.`);
                (0, diagnostics_1.addDiagnostic)(config, languages_1.Language.go, (0, diagnostics_1.makeDiagnostic)("go/workflow/go-installed-after-codeql-init", "Go was installed after the `codeql-action/init` Action was run", {
                    markdownMessage: "To avoid interfering with the CodeQL analysis, perform all installation steps before calling the `github/codeql-action/init` Action.",
                    visibility: {
                        statusPage: true,
                        telemetry: true,
                        cliSummaryTable: true,
                    },
                    severity: "warning",
                }));
            }
        }
        await runAutobuildIfLegacyGoWorkflow(config, logger);
        dbCreationTimings = await (0, analyze_1.runFinalize)(outputDir, threads, memory, config, logger, features);
        if (actionsUtil.getRequiredInput("skip-queries") !== "true") {
            runStats = await (0, analyze_1.runQueries)(outputDir, memory, util.getAddSnippetsFlag(actionsUtil.getRequiredInput("add-snippets")), threads, actionsUtil.getOptionalInput("category"), config, logger, features);
        }
        if (actionsUtil.getOptionalInput("cleanup-level") !== "none") {
            await (0, analyze_1.runCleanup)(config, actionsUtil.getOptionalInput("cleanup-level") || "brutal", logger);
        }
        const dbLocations = {};
        for (const language of config.languages) {
            dbLocations[language] = util.getCodeQLDatabasePath(config, language);
        }
        core.setOutput("db-locations", dbLocations);
        core.setOutput("sarif-output", path_1.default.resolve(outputDir));
        const uploadInput = actionsUtil.getOptionalInput("upload");
        if (runStats && actionsUtil.getUploadValue(uploadInput) === "always") {
            uploadResult = await uploadLib.uploadFromActions(outputDir, actionsUtil.getRequiredInput("checkout_path"), actionsUtil.getOptionalInput("category"), logger, { considerInvalidRequestUserError: false });
            core.setOutput("sarif-id", uploadResult.sarifID);
        }
        else {
            logger.info("Not uploading results");
        }
        // Possibly upload the database bundles for remote queries
        await (0, database_upload_1.uploadDatabases)(repositoryNwo, config, apiDetails, logger);
        // Possibly upload the TRAP caches for later re-use
        const trapCacheUploadStartTime = perf_hooks_1.performance.now();
        const codeql = await (0, codeql_1.getCodeQL)(config.codeQLCmd);
        didUploadTrapCaches = await (0, trap_caching_1.uploadTrapCaches)(codeql, config, logger);
        trapCacheUploadTime = perf_hooks_1.performance.now() - trapCacheUploadStartTime;
        // We don't upload results in test mode, so don't wait for processing
        if (util.isInTestMode()) {
            core.debug("In test mode. Waiting for processing is disabled.");
        }
        else if (uploadResult !== undefined &&
            actionsUtil.getRequiredInput("wait-for-processing") === "true") {
            await uploadLib.waitForProcessing((0, repository_1.parseRepositoryNwo)(util.getRequiredEnvParam("GITHUB_REPOSITORY")), uploadResult.sarifID, (0, logging_1.getActionsLogger)());
        }
        // If we did not throw an error yet here, but we expect one, throw it.
        if (actionsUtil.getOptionalInput("expect-error") === "true") {
            core.setFailed(`expect-error input was set to true but no error was thrown.`);
        }
        core.exportVariable(environment_1.EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
    }
    catch (unwrappedError) {
        const error = (0, util_1.wrapError)(unwrappedError);
        if (actionsUtil.getOptionalInput("expect-error") !== "true" ||
            hasBadExpectErrorInput()) {
            core.setFailed(error.message);
        }
        if (error instanceof analyze_1.CodeQLAnalysisError) {
            const stats = { ...error.queriesStatusReport };
            await sendStatusReport(startedAt, config, stats, error, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger);
        }
        else {
            await sendStatusReport(startedAt, config, undefined, error, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger);
        }
        return;
    }
    if (runStats && uploadResult) {
        await sendStatusReport(startedAt, config, {
            ...runStats,
            ...uploadResult.statusReport,
        }, undefined, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger);
    }
    else if (runStats) {
        await sendStatusReport(startedAt, config, { ...runStats }, undefined, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger);
    }
    else {
        await sendStatusReport(startedAt, config, undefined, undefined, trapCacheUploadTime, dbCreationTimings, didUploadTrapCaches, logger);
    }
}
exports.runPromise = run();
async function runWrapper() {
    try {
        await exports.runPromise;
    }
    catch (error) {
        core.setFailed(`analyze action failed: ${(0, util_1.wrapError)(error).message}`);
    }
    await (0, util_1.checkForTimeout)();
}
void runWrapper();
//# sourceMappingURL=analyze-action.js.map