"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractPackageFile = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const global_1 = require("../../../config/global");
const logger_1 = require("../../../logger");
const array_1 = require("../../../util/array");
const common_1 = require("../../../util/common");
const regex_1 = require("../../../util/regex");
const yaml_1 = require("../../../util/yaml");
const gitea_tags_1 = require("../../datasource/gitea-tags");
const github_runners_1 = require("../../datasource/github-runners");
const github_tags_1 = require("../../datasource/github-tags");
const dockerVersioning = tslib_1.__importStar(require("../../versioning/docker"));
const extract_1 = require("../dockerfile/extract");
const dockerActionRe = (0, regex_1.regEx)(/^\s+uses\s*: ['"]?docker:\/\/([^'"]+)\s*$/);
const actionRe = (0, regex_1.regEx)(/^\s+-?\s+?uses\s*: (?<replaceString>['"]?(?<registryUrl>https:\/\/[.\w-]+\/)?(?<depName>[\w-]+\/[.\w-]+)(?<path>\/.*)?@(?<currentValue>[^\s'"]+)['"]?(?:\s+#\s*(?:renovate\s*:\s*)?(?:pin\s+|tag\s*=\s*)?@?(?<tag>v?\d+(?:\.\d+(?:\.\d+)?)?))?)/);
// SHA1 or SHA256, see https://github.blog/2020-10-19-git-2-29-released/
const shaRe = (0, regex_1.regEx)(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
const shaShortRe = (0, regex_1.regEx)(/^[a-f0-9]{6,7}$/);
// detects if we run against a Github Enterprise Server and adds the URL to the beginning of the registryURLs for looking up Actions
// This reflects the behavior of how GitHub looks up Actions
// First on the Enterprise Server, then on GitHub.com
function detectCustomGitHubRegistryUrlsForActions() {
    const endpoint = global_1.GlobalConfig.get('endpoint');
    const registryUrls = ['https://github.com'];
    if (endpoint && global_1.GlobalConfig.get('platform') === 'github') {
        const parsedEndpoint = new URL(endpoint);
        if (parsedEndpoint.host !== 'github.com' &&
            parsedEndpoint.host !== 'api.github.com') {
            registryUrls.unshift(`${parsedEndpoint.protocol}//${parsedEndpoint.host}`);
            return { registryUrls };
        }
    }
    return {};
}
function extractWithRegex(content) {
    const customRegistryUrlsPackageDependency = detectCustomGitHubRegistryUrlsForActions();
    logger_1.logger.trace('github-actions.extractWithRegex()');
    const deps = [];
    for (const line of content.split(regex_1.newlineRegex)) {
        if (line.trim().startsWith('#')) {
            continue;
        }
        const dockerMatch = dockerActionRe.exec(line);
        if (dockerMatch) {
            const [, currentFrom] = dockerMatch;
            const dep = (0, extract_1.getDep)(currentFrom);
            dep.depType = 'docker';
            deps.push(dep);
            continue;
        }
        const tagMatch = actionRe.exec(line);
        if (tagMatch?.groups) {
            const { depName, currentValue, path = '', tag, replaceString, registryUrl = '', } = tagMatch.groups;
            let quotes = '';
            if (replaceString.indexOf("'") >= 0) {
                quotes = "'";
            }
            if (replaceString.indexOf('"') >= 0) {
                quotes = '"';
            }
            const dep = {
                depName,
                commitMessageTopic: '{{{depName}}} action',
                datasource: github_tags_1.GithubTagsDatasource.id,
                versioning: dockerVersioning.id,
                depType: 'action',
                replaceString,
                autoReplaceStringTemplate: `${quotes}${registryUrl}{{depName}}${path}@{{#if newDigest}}{{newDigest}}${quotes}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}${quotes}{{/unless}}`,
                ...(registryUrl
                    ? detectDatasource(registryUrl)
                    : customRegistryUrlsPackageDependency),
            };
            if (shaRe.test(currentValue)) {
                dep.currentValue = tag;
                dep.currentDigest = currentValue;
            }
            else if (shaShortRe.test(currentValue)) {
                dep.currentValue = tag;
                dep.currentDigestShort = currentValue;
            }
            else {
                dep.currentValue = currentValue;
                if (!dockerVersioning.api.isValid(currentValue)) {
                    dep.skipReason = 'invalid-version';
                }
            }
            deps.push(dep);
        }
    }
    return deps;
}
function detectDatasource(registryUrl) {
    const platform = (0, common_1.detectPlatform)(registryUrl);
    switch (platform) {
        case 'github':
            return { registryUrls: [registryUrl] };
        case 'gitea':
            return {
                registryUrls: [registryUrl],
                datasource: gitea_tags_1.GiteaTagsDatasource.id,
            };
    }
    return {
        skipReason: 'unsupported-url',
    };
}
function extractContainer(container) {
    if (is_1.default.string(container)) {
        return (0, extract_1.getDep)(container);
    }
    else if (is_1.default.plainObject(container) && is_1.default.string(container.image)) {
        return (0, extract_1.getDep)(container.image);
    }
    return undefined;
}
const runnerVersionRegex = (0, regex_1.regEx)(/^\s*(?<depName>[a-zA-Z]+)-(?<currentValue>[^\s]+)/);
function extractRunner(runner) {
    const runnerVersionGroups = runnerVersionRegex.exec(runner)?.groups;
    if (!runnerVersionGroups) {
        return null;
    }
    const { depName, currentValue } = runnerVersionGroups;
    if (!github_runners_1.GithubRunnersDatasource.isValidRunner(depName, currentValue)) {
        return null;
    }
    const dependency = {
        depName,
        currentValue,
        replaceString: `${depName}-${currentValue}`,
        depType: 'github-runner',
        datasource: github_runners_1.GithubRunnersDatasource.id,
        autoReplaceStringTemplate: '{{depName}}-{{newValue}}',
    };
    if (!dockerVersioning.api.isValid(currentValue)) {
        dependency.skipReason = 'invalid-version';
    }
    return dependency;
}
function extractRunners(runner) {
    const runners = [];
    if (is_1.default.string(runner)) {
        runners.push(runner);
    }
    else if (is_1.default.array(runner, is_1.default.string)) {
        runners.push(...runner);
    }
    return runners.map(extractRunner).filter(array_1.isNotNullOrUndefined);
}
function extractWithYAMLParser(content, packageFile) {
    logger_1.logger.trace('github-actions.extractWithYAMLParser()');
    const deps = [];
    let pkg;
    try {
        // TODO: use schema (#9610)
        pkg = (0, yaml_1.parseSingleYaml)(content, { json: true });
    }
    catch (err) {
        logger_1.logger.debug({ packageFile, err }, 'Failed to parse GitHub Actions Workflow YAML');
        return [];
    }
    for (const job of Object.values(pkg?.jobs ?? {})) {
        const dep = extractContainer(job?.container);
        if (dep) {
            dep.depType = 'container';
            deps.push(dep);
        }
        for (const service of Object.values(job?.services ?? {})) {
            const dep = extractContainer(service);
            if (dep) {
                dep.depType = 'service';
                deps.push(dep);
            }
        }
        deps.push(...extractRunners(job?.['runs-on']));
    }
    return deps;
}
function extractPackageFile(content, packageFile) {
    logger_1.logger.trace(`github-actions.extractPackageFile(${packageFile})`);
    const deps = [
        ...extractWithRegex(content),
        ...extractWithYAMLParser(content, packageFile),
    ];
    if (!deps.length) {
        return null;
    }
    return { deps };
}
exports.extractPackageFile = extractPackageFile;
//# sourceMappingURL=extract.js.map