"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.reactScriptsFiveModifications = exports.cypressGlobals = exports.createReactAppHandler = void 0;
const tslib_1 = require("tslib");
const debug_1 = tslib_1.__importDefault(require("debug"));
const path_1 = tslib_1.__importDefault(require("path"));
const sourceRelativeWebpackModules_1 = require("./sourceRelativeWebpackModules");
const debug = (0, debug_1.default)('cypress:webpack-dev-server:create-react-app');
/**
 * Sourcing the config for create-react-app
 */
function createReactAppHandler(devServerConfig) {
    const sourceWebpackModulesResult = (0, sourceRelativeWebpackModules_1.sourceDefaultWebpackDependencies)(devServerConfig);
    // this is required because
    // 1) we use our own HMR and we don't need react-refresh transpiling overhead
    // 2) it doesn't work with process.env=test @see https://github.com/cypress-io/cypress-realworld-app/pull/832
    process.env.FAST_REFRESH = 'false';
    const webpackConfig = loadWebpackConfig(devServerConfig);
    addCypressToWebpackEslintRulesInPlace(webpackConfig);
    getTranspileFolders(devServerConfig.cypressConfig).forEach((folder) => {
        allowModuleSourceInPlace(folder, webpackConfig);
        addFolderToBabelLoaderTranspileInPlace(folder, webpackConfig);
    });
    if (sourceWebpackModulesResult.webpack.majorVersion === 5) {
        debug('Modifying configuration for react-scripts@5');
        reactScriptsFiveModifications(webpackConfig);
    }
    return {
        frameworkConfig: webpackConfig,
        sourceWebpackModulesResult,
    };
}
exports.createReactAppHandler = createReactAppHandler;
function loadWebpackConfig(devServerConfig) {
    let webpackConfigPath;
    const envName = 'test';
    process.env.NODE_ENV = envName;
    process.env.BABEL_ENV = envName;
    try {
        // Search for react-scripts webpack config
        webpackConfigPath = require.resolve('react-scripts/config/webpack.config.js', {
            paths: [devServerConfig.cypressConfig.projectRoot],
        });
        debug('Found react-scripts webpack config at %s', webpackConfigPath);
    }
    catch (err) {
        // Might be an ejected cra app, search for common webpack configs
        const ejectedWebpackConfigPath = path_1.default.join(devServerConfig.cypressConfig.projectRoot, 'config', 'webpack.config.js');
        try {
            webpackConfigPath = require.resolve(ejectedWebpackConfigPath);
        }
        catch (err) {
            throw new Error(`Failed to find webpack at ${ejectedWebpackConfigPath}. We looked in this location as we could not find the "react-scripts" dependency and are assuming an ejected create-react-app.`);
        }
    }
    try {
        let webpackConfig = require(webpackConfigPath);
        if (webpackConfig.default) {
            // we probably loaded TS file
            debug('loaded webpack options has .default - taking that as the config');
            webpackConfig = webpackConfig.default;
        }
        if (typeof webpackConfig === 'function') {
            debug('calling webpack function with environment "%s"', envName);
            webpackConfig = webpackConfig('development');
        }
        return webpackConfig;
    }
    catch (err) {
        throw new Error(`Failed to require webpack config at ${webpackConfigPath} with error: ${err}`);
    }
}
exports.cypressGlobals = ['cy', 'Cypress', 'before', 'after', 'context'];
function addCypressToWebpackEslintRulesInPlace(webpackConfig) {
    var _a, _b;
    const eslintPlugin = (_a = webpackConfig.plugins) === null || _a === void 0 ? void 0 : _a.find((plugin) => plugin.constructor.name === 'ESLintWebpackPlugin');
    if (eslintPlugin) {
        const cypressGlobalsRules = exports.cypressGlobals
            .reduce((acc, global) => (Object.assign(Object.assign({}, acc), { [global]: 'writable' })), {});
        eslintPlugin.options.baseConfig = Object.assign(Object.assign({}, eslintPlugin.options.baseConfig), { globals: Object.assign(Object.assign({}, (_b = eslintPlugin.options.baseConfig) === null || _b === void 0 ? void 0 : _b.globals), cypressGlobalsRules) });
        debug('Found ESLintWebpackPlugin, modified eslint config %o', eslintPlugin.options.baseConfig);
    }
}
function getTranspileFolders(cypressConfig) {
    var _a;
    const rawFolders = (_a = cypressConfig.addTranspiledFolders) !== null && _a !== void 0 ? _a : [];
    const folders = rawFolders.map((folder) => path_1.default.resolve(cypressConfig.projectRoot, folder));
    // ensure path is absolute
    const ensureAbs = (folder) => {
        if (!path_1.default.isAbsolute(folder)) {
            return path_1.default.resolve(folder);
        }
        return folder;
    };
    if (cypressConfig.fixturesFolder) {
        folders.push(ensureAbs(cypressConfig.fixturesFolder));
    }
    if (cypressConfig.supportFolder) {
        folders.push(ensureAbs(cypressConfig.supportFolder));
    }
    // attempt to add directories based on spec pattern
    let componentDirs = cypressConfig.specPattern || '';
    // can be string or array
    if (typeof componentDirs === 'string') {
        componentDirs = [componentDirs];
    }
    const dirsFromSpecPattern = componentDirs.reduce((acc, curr) => {
        // glob
        if (curr.includes('*')) {
            const parts = curr.slice(0, curr.indexOf('*') - 1);
            const joined = parts.split(path_1.default.sep);
            const dir = path_1.default.join(...joined);
            return acc.concat(path_1.default.resolve(cypressConfig.projectRoot, dir));
        }
        return acc;
    }, []);
    return folders.concat(...dirsFromSpecPattern, path_1.default.resolve('cypress'));
}
/**
 * Finds the ModuleScopePlugin plugin and adds given folder
 * to that list. This allows react-scripts to import folders
 * outside of the default "/src" folder.
 * WARNING modifies the input webpack options argument.
 * @see https://github.com/bahmutov/cypress-react-unit-test/issues/289
 * @param {string} folderName Folder to add, should be absolute
 */
function allowModuleSourceInPlace(folderName, webpackConfig) {
    if (!folderName) {
        return;
    }
    if (!webpackConfig.resolve) {
        return;
    }
    if (!Array.isArray(webpackConfig.resolve.plugins)) {
        return;
    }
    const moduleSourcePlugin = webpackConfig.resolve.plugins.find((plugin) => {
        return typeof plugin !== 'string' && Array.isArray(plugin.appSrcs);
    });
    if (!moduleSourcePlugin) {
        debug('cannot find module source plugin');
        return;
    }
    debug('found module source plugin %o', moduleSourcePlugin);
    if (!moduleSourcePlugin.appSrcs.includes(folderName)) {
        moduleSourcePlugin.appSrcs.push(folderName);
        debug('added folder %s to allowed sources', folderName);
    }
}
/**
 * Returns true if the provided loader path includes "babel-loader".
 * Uses current OS path separator to split the loader path correctly.
 */
const isBabelLoader = (loaderPath) => {
    if (!loaderPath) {
        return false;
    }
    const loaderPathParts = loaderPath.split(path_1.default.sep);
    return loaderPathParts.some((pathPart) => pathPart === 'babel-loader');
};
const findBabelRule = (webpackConfig) => {
    if (!webpackConfig) {
        return;
    }
    if (!webpackConfig.module) {
        return;
    }
    debug('webpackConfig.module %o', webpackConfig.module);
    if (!Array.isArray(webpackConfig.module.rules)) {
        return;
    }
    const oneOfRule = webpackConfig.module.rules.find((rule) => {
        return typeof rule !== 'string' && Array.isArray(rule.oneOf);
    });
    if (!oneOfRule || !oneOfRule.oneOf) {
        debug('could not find oneOfRule');
        return;
    }
    debug('looking through oneOf rules');
    debug('oneOfRule.oneOf %o', oneOfRule.oneOf);
    oneOfRule.oneOf.forEach((rule) => debug('rule %o', rule));
    const babelRule = oneOfRule.oneOf.find((rule) => rule.loader && isBabelLoader(rule.loader));
    return babelRule;
};
// see https://github.com/bahmutov/find-webpack/issues/7
const findBabelLoaderRule = (webpackConfig) => {
    debug('looking for babel-loader rule');
    if (!webpackConfig) {
        return;
    }
    if (!webpackConfig.module) {
        return;
    }
    debug('webpackConfig.module %o', webpackConfig.module);
    if (!Array.isArray(webpackConfig.module.rules)) {
        return;
    }
    debug('webpack module rules');
    webpackConfig.module.rules.forEach((rule) => {
        debug('rule %o', rule);
    });
    const babelRule = webpackConfig.module.rules.find((rule) => typeof rule !== 'string' && rule.loader === 'babel-loader');
    if (!babelRule || !babelRule.test) {
        debug('could not find babel rule');
        return;
    }
    debug('found Babel rule that applies to %s', babelRule.test.toString());
    return babelRule;
};
const findBabelLoaderUseRule = (webpackConfig) => {
    var _a;
    debug('looking for babel-loader rule with use property');
    if (!webpackConfig) {
        return;
    }
    if (!webpackConfig.module) {
        return;
    }
    debug('webpackConfig.module %o', webpackConfig.module);
    if (!Array.isArray(webpackConfig.module.rules)) {
        return;
    }
    debug('webpack module rules');
    webpackConfig.module.rules.forEach((rule) => {
        debug('rule %o', rule);
    });
    const isBabelLoader = (rule) => { var _a; return ((_a = rule === null || rule === void 0 ? void 0 : rule.use) === null || _a === void 0 ? void 0 : _a.loader) === 'babel-loader'; };
    const isNextBabelLoader = (rule) => { var _a; return ((_a = rule === null || rule === void 0 ? void 0 : rule.use) === null || _a === void 0 ? void 0 : _a.loader) === 'next-babel-loader'; };
    const babelRule = webpackConfig.module.rules.find((rule) => isBabelLoader(rule) || isNextBabelLoader(rule));
    if (!babelRule) {
        debug('could not find babel rule');
        return;
    }
    debug('found Babel use rule that applies to %s', (_a = babelRule.test) === null || _a === void 0 ? void 0 : _a.toString());
    return babelRule.use;
};
const findBabelRuleWrap = (webpackConfig) => {
    let babelRule = findBabelRule(webpackConfig);
    if (!babelRule) {
        debug('could not find Babel rule using oneOf');
        babelRule = findBabelLoaderRule(webpackConfig);
    }
    if (!babelRule) {
        debug('could not find Babel rule directly');
        babelRule = findBabelLoaderUseRule(webpackConfig);
    }
    if (!babelRule) {
        debug('could not find Babel rule');
        return;
    }
    return babelRule;
};
function addFolderToBabelLoaderTranspileInPlace(folder, webpackConfig) {
    if (!folder) {
        debug('no extra folder to transpile using Babel');
        return;
    }
    debug('trying to transpile additional folder %s using Babel', folder);
    const babelRule = findBabelRuleWrap(webpackConfig);
    if (!babelRule) {
        debug('could not find Babel rule');
        return;
    }
    debug('babel rule %o', babelRule);
    if (!babelRule.include) {
        debug('could not find Babel include condition');
        return;
    }
    if (typeof babelRule.include === 'string') {
        babelRule.include = [babelRule.include];
    }
    if (babelRule.include.includes(folder)) {
        // do not double include the same folder
        debug('babel includes rule for folder %s', folder);
        return;
    }
    babelRule.include.push(folder);
    debug('added folder %s to babel rules', folder);
}
function reactScriptsFiveModifications(webpackConfig) {
    var _a;
    // React-Scripts sets the webpack target to ["browserslist"] which tells
    // webpack to target the browsers found within the browserslist config
    // depending on the environment (process.env.NODE_ENV). Since we set
    // process.env.NODE_ENV = "test", webpack is unable to find any browsers and errors.
    // We set BROWSERSLIST_ENV = "development" to override the default NODE_ENV search of browsers.
    if (!process.env.BROWSERSLIST_ENV) {
        process.env.BROWSERSLIST_ENV = 'development';
    }
    // We use the "development" configuration of the react-scripts webpack config.
    // There is a conflict when settings process.env.NODE_ENV = "test" since DefinePlugin
    // uses the "development" configuration and expects process.env.NODE_ENV = "development".
    const definePlugin = (_a = webpackConfig.plugins) === null || _a === void 0 ? void 0 : _a.find((plugin) => plugin.constructor.name === 'DefinePlugin');
    if (definePlugin) {
        const processEnv = definePlugin.definitions['process.env'];
        processEnv.NODE_ENV = JSON.stringify('development');
        debug('Found "DefinePlugin", modified "process.env" definition %o', processEnv);
    }
}
exports.reactScriptsFiveModifications = reactScriptsFiveModifications;
