"use strict";
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */
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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.coerceVersionNumber = coerceVersionNumber;
exports.checkCLIVersion = checkCLIVersion;
exports.getCLIUpdateRunnerVersion = getCLIUpdateRunnerVersion;
exports.runTempBinary = runTempBinary;
exports.shouldForcePackageManager = shouldForcePackageManager;
const node_child_process_1 = require("node:child_process");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const semver = __importStar(require("semver"));
const version_1 = require("../../../utilities/version");
const constants_1 = require("./constants");
/**
 * Coerces a string into a valid semantic version number.
 * @param version The version string to coerce.
 * @returns A valid semantic version string, or undefined if coercion fails.
 */
function coerceVersionNumber(version) {
    if (!version) {
        return undefined;
    }
    if (!/^\d{1,30}\.\d{1,30}\.\d{1,30}/.test(version)) {
        const match = version.match(/^\d{1,30}(\.\d{1,30})*/);
        if (!match) {
            return undefined;
        }
        if (!match[1]) {
            version = version.substring(0, match[0].length) + '.0.0' + version.substring(match[0].length);
        }
        else if (!match[2]) {
            version = version.substring(0, match[0].length) + '.0' + version.substring(match[0].length);
        }
        else {
            return undefined;
        }
    }
    return semver.valid(version) ?? undefined;
}
/**
 * Checks if the installed CLI version is compatible with the packages being updated.
 * @param packagesToUpdate The list of packages being updated.
 * @param logger The logger instance.
 * @param packageManager The package manager instance.
 * @param verbose Whether to log verbose output.
 * @param next Whether to check for the next version.
 * @returns The version of the CLI to install, or null if the current version is compatible.
 */
async function checkCLIVersion(packagesToUpdate, logger, packageManager, next = false) {
    const runnerVersion = getCLIUpdateRunnerVersion(packagesToUpdate, next);
    const manifest = await packageManager.getManifest(`@angular/cli@${runnerVersion}`);
    if (!manifest) {
        logger.warn(`Could not find @angular/cli version '${runnerVersion}'.`);
        return null;
    }
    const version = manifest.version;
    return version_1.VERSION.full === version ? null : version;
}
/**
 * Determines the version of the CLI to use for the update process.
 * @param packagesToUpdate The list of packages being updated.
 * @param next Whether to use the next version.
 * @returns The version or tag to use for the CLI update runner.
 */
function getCLIUpdateRunnerVersion(packagesToUpdate, next) {
    if (next) {
        return 'next';
    }
    const updatingAngularPackage = packagesToUpdate?.find((r) => constants_1.ANGULAR_PACKAGES_REGEXP.test(r));
    if (updatingAngularPackage) {
        // If we are updating any Angular package we can update the CLI to the target version because
        // migrations for @angular/core@13 can be executed using Angular/cli@13.
        // This is same behaviour as `npx @angular/cli@13 update @angular/core@13`.
        // `@angular/cli@13` -> ['', 'angular/cli', '13']
        // `@angular/cli` -> ['', 'angular/cli']
        const tempVersion = coerceVersionNumber(updatingAngularPackage.split('@')[2]);
        return semver.parse(tempVersion)?.major ?? 'latest';
    }
    // When not updating an Angular package we cannot determine which schematic runtime the migration should to be executed in.
    // Typically, we can assume that the `@angular/cli` was updated previously.
    // Example: Angular official packages are typically updated prior to NGRX etc...
    // Therefore, we only update to the latest patch version of the installed major version of the Angular CLI.
    // This is important because we might end up in a scenario where locally Angular v12 is installed, updating NGRX from 11 to 12.
    // We end up using Angular ClI v13 to run the migrations if we run the migrations using the CLI installed major version + 1 logic.
    return version_1.VERSION.major;
}
/**
 * Runs a binary from a temporary package installation.
 * @param packageName The name of the package to install and run.
 * @param packageManager The package manager instance.
 * @param args The arguments to pass to the binary.
 * @returns The exit code of the binary.
 */
async function runTempBinary(packageName, packageManager, args = []) {
    const { workingDirectory, cleanup } = await packageManager.acquireTempPackage(packageName);
    try {
        // Remove version/tag etc... from package name
        // Ex: @angular/cli@latest -> @angular/cli
        const packageNameNoVersion = packageName.substring(0, packageName.lastIndexOf('@'));
        const pkgLocation = (0, node_path_1.join)(workingDirectory, 'node_modules', packageNameNoVersion);
        const packageJsonPath = (0, node_path_1.join)(pkgLocation, 'package.json');
        // Get a binary location for this package
        let binPath;
        if ((0, node_fs_1.existsSync)(packageJsonPath)) {
            const content = await node_fs_1.promises.readFile(packageJsonPath, 'utf-8');
            if (content) {
                const { bin = {} } = JSON.parse(content);
                const binKeys = Object.keys(bin);
                if (binKeys.length) {
                    binPath = (0, node_path_1.resolve)(pkgLocation, bin[binKeys[0]]);
                }
            }
        }
        if (!binPath) {
            throw new Error(`Cannot locate bin for temporary package: ${packageNameNoVersion}.`);
        }
        const { status, error } = (0, node_child_process_1.spawnSync)(process.execPath, [binPath, ...args], {
            stdio: 'inherit',
            env: {
                ...process.env,
                NG_DISABLE_VERSION_CHECK: 'true',
                NG_CLI_ANALYTICS: 'false',
            },
        });
        if (status === null && error) {
            throw error;
        }
        return status ?? 0;
    }
    finally {
        await cleanup();
    }
}
/**
 * Determines whether to force the package manager to ignore peer dependency warnings.
 * @param packageManager The package manager instance.
 * @param logger The logger instance.
 * @param verbose Whether to log verbose output.
 * @returns True if the package manager should be forced, false otherwise.
 */
async function shouldForcePackageManager(packageManager, logger, verbose) {
    // npm 7+ can fail due to it incorrectly resolving peer dependencies that have valid SemVer
    // ranges during an update. Update will set correct versions of dependencies within the
    // package.json file. The force option is set to workaround these errors.
    if (packageManager.name === 'npm') {
        const version = await packageManager.getVersion();
        if (semver.gte(version, '7.0.0')) {
            if (verbose) {
                logger.info('NPM 7+ detected -- enabling force option for package installation');
            }
            return true;
        }
    }
    return false;
}
//# sourceMappingURL=cli-version.js.map