"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.angularHandler = exports.toPosix = exports.getAngularJson = exports.getAngularCliModules = exports.getTempDir = exports.generateTsConfig = exports.getAngularBuildOptions = exports.getProjectConfig = void 0;
const tslib_1 = require("tslib");
const fs = tslib_1.__importStar(require("fs-extra"));
const os_1 = require("os");
const path = tslib_1.__importStar(require("path"));
const semver_1 = require("semver");
const dynamic_import_1 = require("../dynamic-import");
const sourceRelativeWebpackModules_1 = require("./sourceRelativeWebpackModules");
const debug_1 = tslib_1.__importDefault(require("debug"));
const debugPrefix = 'cypress:webpack-dev-server:angularHandler';
const debug = (0, debug_1.default)(debugPrefix);
async function getProjectConfig(projectRoot) {
    var _a;
    const angularJson = await getAngularJson(projectRoot);
    let { defaultProject } = angularJson;
    if (!defaultProject) {
        defaultProject = Object.keys(angularJson.projects).find((name) => angularJson.projects[name].projectType === 'application');
        if (!defaultProject) {
            throw new Error('Could not find a project with projectType "application" in "angular.json". Visit https://docs.cypress.io/guides/references/configuration#Options-API to see how to pass in a custom project configuration');
        }
    }
    const defaultProjectConfig = angularJson.projects[defaultProject];
    const { architect, root, sourceRoot } = defaultProjectConfig;
    const { build } = architect;
    return {
        root,
        sourceRoot,
        buildOptions: Object.assign(Object.assign({}, build.options), ((_a = build.configurations) === null || _a === void 0 ? void 0 : _a.development) || {}),
    };
}
exports.getProjectConfig = getProjectConfig;
function getAngularBuildOptions(buildOptions, tsConfig) {
    // Default options are derived from the @angular-devkit/build-angular browser builder, with some options from
    // the serve builder thrown in for development.
    // see: https://github.com/angular/angular-cli/blob/main/packages/angular_devkit/build_angular/src/builders/browser/schema.json
    return Object.assign(Object.assign({ outputPath: 'dist/angular-app', assets: [], styles: [], scripts: [], fileReplacements: [], inlineStyleLanguage: 'css', stylePreprocessorOptions: { includePaths: [] }, resourcesOutputPath: undefined, commonChunk: true, baseHref: undefined, deployUrl: undefined, verbose: false, progress: false, i18nMissingTranslation: 'warning', i18nDuplicateTranslation: 'warning', localize: undefined, watch: true, poll: undefined, deleteOutputPath: true, preserveSymlinks: undefined, showCircularDependencies: false, subresourceIntegrity: false, serviceWorker: false, ngswConfigPath: undefined, statsJson: false, webWorkerTsConfig: undefined, crossOrigin: 'none', allowedCommonJsDependencies: [], buildOptimizer: false, optimization: false, vendorChunk: true, extractLicenses: false, sourceMap: true, namedChunks: true }, buildOptions), { tsConfig, aot: false, outputHashing: 'none', budgets: undefined });
}
exports.getAngularBuildOptions = getAngularBuildOptions;
async function generateTsConfig(devServerConfig, buildOptions) {
    var _a;
    const { cypressConfig } = devServerConfig;
    const { projectRoot } = cypressConfig;
    const { workspaceRoot = projectRoot } = buildOptions;
    const specPattern = Array.isArray(cypressConfig.specPattern) ? cypressConfig.specPattern : [cypressConfig.specPattern];
    const getProjectFilePath = (...fileParts) => (0, exports.toPosix)(path.join(...fileParts));
    const includePaths = [...specPattern.map((pattern) => getProjectFilePath(projectRoot, pattern))];
    if (cypressConfig.supportFile) {
        includePaths.push((0, exports.toPosix)(cypressConfig.supportFile));
    }
    if (buildOptions.polyfills) {
        const polyfills = Array.isArray(buildOptions.polyfills)
            ? buildOptions.polyfills.filter((p) => { var _a, _b; return ((_a = devServerConfig.options) === null || _a === void 0 ? void 0 : _a.projectConfig.sourceRoot) && p.startsWith((_b = devServerConfig.options) === null || _b === void 0 ? void 0 : _b.projectConfig.sourceRoot); })
            : [buildOptions.polyfills];
        includePaths.push(...polyfills.map((p) => getProjectFilePath(workspaceRoot, p)));
    }
    const typeRoots = [
        getProjectFilePath(workspaceRoot, 'node_modules'),
    ];
    const types = ['cypress'];
    const tsConfigContent = JSON.stringify({
        extends: getProjectFilePath(projectRoot, (_a = buildOptions.tsConfig) !== null && _a !== void 0 ? _a : 'tsconfig.json'),
        compilerOptions: {
            outDir: getProjectFilePath(projectRoot, 'out-tsc/cy'),
            allowSyntheticDefaultImports: true,
            skipLibCheck: true,
            types,
            typeRoots,
        },
        include: includePaths,
    }, null, 2);
    const tsConfigPath = path.join(await getTempDir(), 'tsconfig.json');
    await fs.writeFile(tsConfigPath, tsConfigContent);
    return tsConfigPath;
}
exports.generateTsConfig = generateTsConfig;
async function getTempDir() {
    const cypressTempDir = path.join((0, os_1.tmpdir)(), 'cypress-angular-ct');
    await fs.ensureDir(cypressTempDir);
    return cypressTempDir;
}
exports.getTempDir = getTempDir;
async function getAngularCliModules(projectRoot) {
    let angularVersion;
    try {
        angularVersion = await getInstalledPackageVersion('@angular-devkit/core', projectRoot);
    }
    catch (_a) {
        throw new Error(`Could not resolve "@angular-devkit/core". Do you have it installed?`);
    }
    const angularCLiModules = [
        '@angular-devkit/build-angular/src/utils/webpack-browser-config.js',
        // in Angular 16.1 the locations of these files below were changed
        ...((0, semver_1.gte)(angularVersion, '16.1.0')
            ? ['@angular-devkit/build-angular/src/tools/webpack/configs/common.js', '@angular-devkit/build-angular/src/tools/webpack/configs/styles.js']
            : ['@angular-devkit/build-angular/src/webpack/configs/common.js', '@angular-devkit/build-angular/src/webpack/configs/styles.js']),
        '@angular-devkit/core/src/index.js',
    ];
    const [{ generateBrowserWebpackConfigFromContext }, { getCommonConfig }, { getStylesConfig }, { logging },] = await Promise.all(angularCLiModules.map((dep) => {
        try {
            const depPath = require.resolve(dep, { paths: [projectRoot] });
            return (0, dynamic_import_1.dynamicAbsoluteImport)(depPath);
        }
        catch (e) {
            throw new Error(`Could not resolve "${dep}". Do you have "@angular-devkit/build-angular" and "@angular-devkit/core" installed?`);
        }
    }));
    return {
        generateBrowserWebpackConfigFromContext,
        getCommonConfig,
        getStylesConfig,
        logging,
    };
}
exports.getAngularCliModules = getAngularCliModules;
async function getInstalledPackageVersion(pkgName, projectRoot) {
    const packageJsonPath = require.resolve(`${pkgName}/package.json`, { paths: [projectRoot] });
    const { version } = JSON.parse(await fs.readFile(packageJsonPath, { encoding: 'utf-8' }));
    return version;
}
async function getAngularJson(projectRoot) {
    const { findUp } = await (0, dynamic_import_1.dynamicImport)('find-up');
    const angularJsonPath = await findUp('angular.json', { cwd: projectRoot });
    if (!angularJsonPath) {
        throw new Error(`Could not find angular.json. Looked in ${projectRoot} and up.`);
    }
    const angularJson = await fs.readFile(angularJsonPath, 'utf8');
    return JSON.parse(angularJson);
}
exports.getAngularJson = getAngularJson;
function createFakeContext(projectRoot, defaultProjectConfig, logging) {
    const logger = new logging.Logger(debugPrefix);
    // Proxy all logging calls through to the debug logger
    logger.forEach((value) => {
        debug(JSON.stringify(value));
    });
    const context = {
        target: {
            project: 'angular',
        },
        workspaceRoot: projectRoot,
        getProjectMetadata: () => {
            return {
                root: defaultProjectConfig.root,
                sourceRoot: defaultProjectConfig.sourceRoot,
                projectType: 'application',
            };
        },
        logger,
    };
    return context;
}
const toPosix = (filePath) => filePath.split(path.sep).join(path.posix.sep);
exports.toPosix = toPosix;
async function getAngularCliWebpackConfig(devServerConfig) {
    var _a;
    const { projectRoot } = devServerConfig.cypressConfig;
    const { generateBrowserWebpackConfigFromContext, getCommonConfig, getStylesConfig, logging, } = await getAngularCliModules(projectRoot);
    // normalize
    const projectConfig = ((_a = devServerConfig.options) === null || _a === void 0 ? void 0 : _a.projectConfig) || await getProjectConfig(projectRoot);
    const tsConfig = await generateTsConfig(devServerConfig, projectConfig.buildOptions);
    const buildOptions = getAngularBuildOptions(projectConfig.buildOptions, tsConfig);
    const context = createFakeContext(projectConfig.buildOptions.workspaceRoot || projectRoot, projectConfig, logging);
    const { config } = await generateBrowserWebpackConfigFromContext(buildOptions, context, (wco) => {
        // Starting in Angular 16, the `getStylesConfig` function returns a `Promise`.
        // We wrap it with `Promise.resolve` so we support older version of Angular
        // returning a non-Promise result.
        const stylesConfig = Promise.resolve(getStylesConfig(wco)).then((config) => {
            // We modify resolve-url-loader and set `root` to be `projectRoot` + `sourceRoot` to ensure
            // imports in scss, sass, etc are correctly resolved.
            // https://github.com/cypress-io/cypress/issues/24272
            config.module.rules.forEach((rule) => {
                var _a;
                (_a = rule.rules) === null || _a === void 0 ? void 0 : _a.forEach((ruleSet) => {
                    if (!Array.isArray(ruleSet.use)) {
                        return;
                    }
                    ruleSet.use.map((loader) => {
                        var _a;
                        if (typeof loader !== 'object' || typeof loader.options !== 'object' || !((_a = loader.loader) === null || _a === void 0 ? void 0 : _a.includes('resolve-url-loader'))) {
                            return;
                        }
                        const root = projectConfig.buildOptions.workspaceRoot || path.join(devServerConfig.cypressConfig.projectRoot, projectConfig.sourceRoot);
                        debug('Adding root %s to resolve-url-loader options', root);
                        loader.options.root = root;
                    });
                });
            });
            return config;
        });
        return [getCommonConfig(wco), stylesConfig];
    });
    delete config.entry.main;
    return config;
}
function removeSourceMapPlugin(config) {
    var _a;
    config.plugins = (_a = config.plugins) === null || _a === void 0 ? void 0 : _a.filter((plugin) => {
        var _a;
        return ((_a = plugin === null || plugin === void 0 ? void 0 : plugin.constructor) === null || _a === void 0 ? void 0 : _a.name) !== 'SourceMapDevToolPlugin';
    });
}
async function angularHandler(devServerConfig) {
    const webpackConfig = await getAngularCliWebpackConfig(devServerConfig);
    removeSourceMapPlugin(webpackConfig);
    return { frameworkConfig: webpackConfig, sourceWebpackModulesResult: (0, sourceRelativeWebpackModules_1.sourceDefaultWebpackDependencies)(devServerConfig) };
}
exports.angularHandler = angularHandler;
