"use strict";
/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMinimizer = exports.getHttpsConfig = exports.compile = exports.applyConfigurePostCss = exports.applyConfigureWebpack = exports.getCustomizableJSLoader = exports.getBabelOptions = exports.getCustomBabelConfigFilePath = exports.getStyleLoaders = exports.printStatsWarnings = exports.formatStatsErrorMessage = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const crypto_1 = tslib_1.__importDefault(require("crypto"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const mini_css_extract_plugin_1 = tslib_1.__importDefault(require("mini-css-extract-plugin"));
const webpack_merge_1 = require("webpack-merge");
const webpack_1 = tslib_1.__importDefault(require("webpack"));
const terser_webpack_plugin_1 = tslib_1.__importDefault(require("terser-webpack-plugin"));
const css_minimizer_webpack_plugin_1 = tslib_1.__importDefault(require("css-minimizer-webpack-plugin"));
const formatWebpackMessages_1 = tslib_1.__importDefault(require("react-dev-utils/formatWebpackMessages"));
function formatStatsErrorMessage(statsJson) {
    if (statsJson?.errors?.length) {
        // TODO formatWebpackMessages does not print stack-traces
        // Also the error causal chain is lost here
        // We log the stacktrace inside serverEntry.tsx for now (not ideal)
        const { errors } = (0, formatWebpackMessages_1.default)(statsJson);
        return errors
            .map((str) => logger_1.default.red(str))
            .join(`\n\n${logger_1.default.yellow('--------------------------')}\n\n`);
    }
    return undefined;
}
exports.formatStatsErrorMessage = formatStatsErrorMessage;
function printStatsWarnings(statsJson) {
    if (statsJson?.warnings?.length) {
        statsJson.warnings?.forEach((warning) => {
            logger_1.default.warn(warning);
        });
    }
}
exports.printStatsWarnings = printStatsWarnings;
// Utility method to get style loaders
function getStyleLoaders(isServer, cssOptionsArg = {}) {
    const cssOptions = {
        // TODO turn esModule on later, see https://github.com/facebook/docusaurus/pull/6424
        esModule: false,
        ...cssOptionsArg,
    };
    if (isServer) {
        return cssOptions.modules
            ? [
                {
                    loader: require.resolve('css-loader'),
                    options: cssOptions,
                },
            ]
            : [
                {
                    loader: mini_css_extract_plugin_1.default.loader,
                    options: {
                        // Don't emit CSS files for SSR (previously used null-loader)
                        // See https://github.com/webpack-contrib/mini-css-extract-plugin/issues/90#issuecomment-811991738
                        emit: false,
                    },
                },
                {
                    loader: require.resolve('css-loader'),
                    options: cssOptions,
                },
            ];
    }
    return [
        {
            loader: mini_css_extract_plugin_1.default.loader,
            options: {
                esModule: true,
            },
        },
        {
            loader: require.resolve('css-loader'),
            options: cssOptions,
        },
        {
            // Options for PostCSS as we reference these options twice
            // Adds vendor prefixing based on your specified browser support in
            // package.json
            loader: require.resolve('postcss-loader'),
            options: {
                postcssOptions: {
                    // Necessary for external CSS imports to work
                    // https://github.com/facebook/create-react-app/issues/2677
                    ident: 'postcss',
                    plugins: [
                        // eslint-disable-next-line global-require
                        require('autoprefixer'),
                    ],
                },
            },
        },
    ];
}
exports.getStyleLoaders = getStyleLoaders;
async function getCustomBabelConfigFilePath(siteDir) {
    const customBabelConfigurationPath = path_1.default.join(siteDir, utils_1.BABEL_CONFIG_FILE_NAME);
    return (await fs_extra_1.default.pathExists(customBabelConfigurationPath))
        ? customBabelConfigurationPath
        : undefined;
}
exports.getCustomBabelConfigFilePath = getCustomBabelConfigFilePath;
function getBabelOptions({ isServer, babelOptions, } = {}) {
    if (typeof babelOptions === 'string') {
        return {
            babelrc: false,
            configFile: babelOptions,
            caller: { name: isServer ? 'server' : 'client' },
        };
    }
    return {
        ...(babelOptions ?? { presets: [require.resolve('../babel/preset')] }),
        babelrc: false,
        configFile: false,
        caller: { name: isServer ? 'server' : 'client' },
    };
}
exports.getBabelOptions = getBabelOptions;
// Name is generic on purpose
// we want to support multiple js loader implementations (babel + esbuild)
function getDefaultBabelLoader({ isServer, babelOptions, }) {
    return {
        loader: require.resolve('babel-loader'),
        options: getBabelOptions({ isServer, babelOptions }),
    };
}
const getCustomizableJSLoader = (jsLoader = 'babel') => ({ isServer, babelOptions, }) => jsLoader === 'babel'
    ? getDefaultBabelLoader({ isServer, babelOptions })
    : jsLoader(isServer);
exports.getCustomizableJSLoader = getCustomizableJSLoader;
/**
 * Helper function to modify webpack config
 * @param configureWebpack a webpack config or a function to modify config
 * @param config initial webpack config
 * @param isServer indicates if this is a server webpack configuration
 * @param jsLoader custom js loader config
 * @param content content loaded by the plugin
 * @returns final/ modified webpack config
 */
function applyConfigureWebpack(configureWebpack, config, isServer, jsLoader, content) {
    // Export some utility functions
    const utils = {
        getStyleLoaders,
        getJSLoader: (0, exports.getCustomizableJSLoader)(jsLoader),
    };
    if (typeof configureWebpack === 'function') {
        const { mergeStrategy, ...res } = configureWebpack(config, isServer, utils, content) ?? {};
        const customizeRules = mergeStrategy ?? {};
        return (0, webpack_merge_1.mergeWithCustomize)({
            customizeArray: (0, webpack_merge_1.customizeArray)(customizeRules),
            customizeObject: (0, webpack_merge_1.customizeObject)(customizeRules),
        })(config, res);
    }
    return config;
}
exports.applyConfigureWebpack = applyConfigureWebpack;
function applyConfigurePostCss(configurePostCss, config) {
    // Not ideal heuristic but good enough for our use-case?
    function isPostCssLoader(loader) {
        return !!loader?.options?.postcssOptions;
    }
    // Does not handle all edge cases, but good enough for now
    function overridePostCssOptions(entry) {
        if (isPostCssLoader(entry)) {
            entry.options.postcssOptions = configurePostCss(entry.options.postcssOptions);
        }
        else if (Array.isArray(entry.oneOf)) {
            entry.oneOf.forEach((r) => {
                if (r) {
                    overridePostCssOptions(r);
                }
            });
        }
        else if (Array.isArray(entry.use)) {
            entry.use
                .filter((u) => typeof u === 'object')
                .forEach((rule) => overridePostCssOptions(rule));
        }
    }
    config.module?.rules?.forEach((rule) => overridePostCssOptions(rule));
    return config;
}
exports.applyConfigurePostCss = applyConfigurePostCss;
function compile(config) {
    return new Promise((resolve, reject) => {
        const compiler = (0, webpack_1.default)(config);
        compiler.run((err, stats) => {
            if (err) {
                logger_1.default.error(err.stack ?? err);
                if (err.details) {
                    logger_1.default.error(err.details);
                }
                reject(err);
            }
            // Let plugins consume all the stats
            const errorsWarnings = stats?.toJson('errors-warnings');
            if (stats?.hasErrors()) {
                const statsErrorMessage = formatStatsErrorMessage(errorsWarnings);
                reject(new Error(`Failed to compile due to Webpack errors.\n${statsErrorMessage}`));
            }
            printStatsWarnings(errorsWarnings);
            // Webpack 5 requires calling close() so that persistent caching works
            // See https://github.com/webpack/webpack.js.org/pull/4775
            compiler.close((errClose) => {
                if (errClose) {
                    logger_1.default.error(`Error while closing Webpack compiler: ${errClose}`);
                    reject(errClose);
                }
                else {
                    resolve();
                }
            });
        });
    });
}
exports.compile = compile;
// Ensure the certificate and key provided are valid and if not
// throw an easy to debug error
function validateKeyAndCerts({ cert, key, keyFile, crtFile, }) {
    let encrypted;
    try {
        // publicEncrypt will throw an error with an invalid cert
        encrypted = crypto_1.default.publicEncrypt(cert, Buffer.from('test'));
    }
    catch (err) {
        logger_1.default.error `The certificate path=${crtFile} is invalid.`;
        throw err;
    }
    try {
        // privateDecrypt will throw an error with an invalid key
        crypto_1.default.privateDecrypt(key, encrypted);
    }
    catch (err) {
        logger_1.default.error `The certificate key path=${keyFile} is invalid.`;
        throw err;
    }
}
// Read file and throw an error if it doesn't exist
async function readEnvFile(file, type) {
    if (!(await fs_extra_1.default.pathExists(file))) {
        throw new Error(`You specified ${type} in your env, but the file "${file}" can't be found.`);
    }
    return fs_extra_1.default.readFile(file);
}
// Get the https config
// Return cert files if provided in env, otherwise just true or false
async function getHttpsConfig() {
    const appDirectory = await fs_extra_1.default.realpath(process.cwd());
    const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
    const isHttps = HTTPS === 'true';
    if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
        const crtFile = path_1.default.resolve(appDirectory, SSL_CRT_FILE);
        const keyFile = path_1.default.resolve(appDirectory, SSL_KEY_FILE);
        const config = {
            cert: await readEnvFile(crtFile, 'SSL_CRT_FILE'),
            key: await readEnvFile(keyFile, 'SSL_KEY_FILE'),
        };
        validateKeyAndCerts({ ...config, keyFile, crtFile });
        return config;
    }
    return isHttps;
}
exports.getHttpsConfig = getHttpsConfig;
// See https://github.com/webpack-contrib/terser-webpack-plugin#parallel
function getTerserParallel() {
    let terserParallel = true;
    if (process.env.TERSER_PARALLEL === 'false') {
        terserParallel = false;
    }
    else if (process.env.TERSER_PARALLEL &&
        parseInt(process.env.TERSER_PARALLEL, 10) > 0) {
        terserParallel = parseInt(process.env.TERSER_PARALLEL, 10);
    }
    return terserParallel;
}
function getMinimizer(useSimpleCssMinifier = false) {
    const minimizer = [
        new terser_webpack_plugin_1.default({
            parallel: getTerserParallel(),
            terserOptions: {
                parse: {
                    // We want uglify-js to parse ecma 8 code. However, we don't want it
                    // to apply any minification steps that turns valid ecma 5 code
                    // into invalid ecma 5 code. This is why the 'compress' and 'output'
                    // sections only apply transformations that are ecma 5 safe
                    // https://github.com/facebook/create-react-app/pull/4234
                    ecma: 2020,
                },
                compress: {
                    ecma: 5,
                },
                mangle: {
                    safari10: true,
                },
                output: {
                    ecma: 5,
                    comments: false,
                    // Turned on because emoji and regex is not minified properly using
                    // default. See https://github.com/facebook/create-react-app/issues/2488
                    ascii_only: true,
                },
            },
        }),
    ];
    if (useSimpleCssMinifier) {
        minimizer.push(new css_minimizer_webpack_plugin_1.default());
    }
    else {
        minimizer.push(
        // Using the array syntax to add 2 minimizers
        // see https://github.com/webpack-contrib/css-minimizer-webpack-plugin#array
        new css_minimizer_webpack_plugin_1.default({
            minimizerOptions: [
                // CssNano options
                {
                    preset: require.resolve('@docusaurus/cssnano-preset'),
                },
                // CleanCss options
                {
                    inline: false,
                    level: {
                        1: {
                            all: false,
                            removeWhitespace: true,
                        },
                        2: {
                            all: true,
                            restructureRules: true,
                            removeUnusedAtRules: false,
                        },
                    },
                },
            ],
            minify: [
                css_minimizer_webpack_plugin_1.default.cssnanoMinify,
                css_minimizer_webpack_plugin_1.default.cleanCssMinify,
            ],
        }));
    }
    return minimizer;
}
exports.getMinimizer = getMinimizer;
