"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadEnv = exports.sortUserPlugins = exports.resolveConfig = exports.defineConfig = void 0;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const build_1 = require("./build");
const utils_1 = require("./utils");
const plugins_1 = require("./plugins");
const chalk_1 = __importDefault(require("chalk"));
const esbuild_1 = require("./plugins/esbuild");
const dotenv_1 = __importDefault(require("dotenv"));
const dotenv_expand_1 = __importDefault(require("dotenv-expand"));
const constants_1 = require("./constants");
const resolve_1 = require("./plugins/resolve");
const logger_1 = require("./logger");
const pluginutils_1 = require("@rollup/pluginutils");
const asset_1 = require("./plugins/asset");
const debug = utils_1.createDebugger('vite:config');
/**
 * Type helper to make it easier to use vite.config.ts
 * accepts a direct {@link UserConfig} object, or a function that returns it.
 * The function receives a {@link ConfigEnv} object that exposes two properties:
 * `command` (either `'build'` or `'serve'`), and `mode`.
 */
function defineConfig(config) {
    return config;
}
exports.defineConfig = defineConfig;
async function resolveConfig(inlineConfig, command, mode, configPath) {
    let config = inlineConfig;
    if (configPath !== false) {
        const loadResult = await loadConfigFromFile({
            mode,
            command
        }, configPath, inlineConfig.root, config.logLevel);
        if (loadResult) {
            config = mergeConfig(loadResult.config, config);
            configPath = loadResult.path;
        }
    }
    // resolve plugins
    const rawUserPlugins = (config.plugins || []).flat().filter((p) => {
        return !p.apply || p.apply === command;
    });
    const [prePlugins, postPlugins, normalPlugins] = sortUserPlugins(rawUserPlugins);
    // run config hooks
    const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins];
    userPlugins.forEach((p) => {
        if (p.config) {
            const res = p.config(config);
            if (res) {
                config = mergeConfig(config, res);
            }
        }
    });
    // resolve root
    const resolvedRoot = utils_1.normalizePath(config.root ? path_1.default.resolve(config.root) : process.cwd());
    // resolve alias with internal client alias
    const resolvedAlias = mergeAlias([{ find: /^\/@vite\//, replacement: constants_1.CLIENT_DIR + '/' }], config.alias || []);
    // load .env files
    const userEnv = loadEnv(mode, resolvedRoot);
    // Note it is possible for user to have a custom mode, e.g. `staging` where
    // production-like behavior is expected. This is indicated by NODE_ENV=production
    // loaded from `.staging.env` and set by us as VITE_USER_NODE_ENV
    const resolvedMode = process.env.VITE_USER_NODE_ENV || mode;
    const isProduction = resolvedMode === 'production';
    const resolvedBuildOptions = build_1.resolveBuildOptions(config.build);
    // resolve optimizer cache directory
    const pkgPath = utils_1.lookupFile(resolvedRoot, [`package.json`], true /* pathOnly */);
    const optimizeCacheDir = pkgPath && path_1.default.join(path_1.default.dirname(pkgPath), `node_modules/${constants_1.DEP_CACHE_DIR}`);
    const assetsFilter = config.assetsInclude
        ? pluginutils_1.createFilter(config.assetsInclude)
        : () => false;
    const resolved = {
        ...config,
        configPath: configPath ? utils_1.normalizePath(configPath) : undefined,
        inlineConfig,
        root: resolvedRoot,
        command,
        mode,
        isProduction,
        optimizeCacheDir,
        alias: resolvedAlias,
        plugins: userPlugins,
        server: config.server || {},
        build: resolvedBuildOptions,
        env: {
            ...userEnv,
            BASE_URL: command === 'build' ? resolvedBuildOptions.base : '/',
            MODE: mode,
            DEV: !isProduction,
            PROD: isProduction
        },
        assetsInclude(file) {
            return constants_1.DEFAULT_ASSETS_RE.test(file) || assetsFilter(file);
        },
        logger: logger_1.createLogger(config.logLevel),
        publicUrls: command == 'serve' ? asset_1.checkPublicFiles(resolvedRoot) : {}
    };
    resolved.plugins = await plugins_1.resolvePlugins(resolved, prePlugins, normalPlugins, postPlugins);
    // call configResolved hooks
    userPlugins.forEach((p) => {
        if (p.configResolved) {
            p.configResolved(resolved);
        }
    });
    if (process.env.DEBUG) {
        debug(`using resolved config: %O`, {
            ...resolved,
            plugins: resolved.plugins.map((p) => p.name)
        });
    }
    return resolved;
}
exports.resolveConfig = resolveConfig;
function mergeConfig(a, b, isRoot = true) {
    const merged = { ...a };
    for (const key in b) {
        const value = b[key];
        if (value == null) {
            continue;
        }
        const existing = merged[key];
        if (Array.isArray(existing) && Array.isArray(value)) {
            merged[key] = [...existing, ...value];
            continue;
        }
        if (utils_1.isObject(existing) && utils_1.isObject(value)) {
            merged[key] = mergeConfig(existing, value, false);
            continue;
        }
        // root fields that require special handling
        if (existing != null && isRoot) {
            if (key === 'alias') {
                merged[key] = mergeAlias(existing, value);
                continue;
            }
            else if (key === 'assetsInclude') {
                merged[key] = [].concat(existing, value);
                continue;
            }
        }
        merged[key] = value;
    }
    return merged;
}
function mergeAlias(a = [], b = []) {
    return [...normalizeAlias(a), ...normalizeAlias(b)];
}
function normalizeAlias(o) {
    return utils_1.isObject(o)
        ? Object.keys(o).map((find) => ({
            find,
            replacement: o[find]
        }))
        : o;
}
function sortUserPlugins(plugins) {
    const prePlugins = [];
    const postPlugins = [];
    const normalPlugins = [];
    if (plugins) {
        plugins.flat().forEach((p) => {
            if (p.enforce === 'pre')
                prePlugins.push(p);
            else if (p.enforce === 'post')
                postPlugins.push(p);
            else
                normalPlugins.push(p);
        });
    }
    return [prePlugins, postPlugins, normalPlugins];
}
exports.sortUserPlugins = sortUserPlugins;
async function loadConfigFromFile(configEnv, configPath, configRoot = process.cwd(), logLevel) {
    const start = Date.now();
    let resolvedPath;
    let isTS = false;
    let isMjs = false;
    function checkMjs() {
        // check package.json for type: "module" and set `isMjs` to true
        try {
            const pkg = utils_1.lookupFile(configRoot, ['package.json']);
            if (pkg && JSON.parse(pkg).type === 'module') {
                isMjs = true;
            }
        }
        catch (e) { }
    }
    if (configPath) {
        // explicit config path is always resolved from cwd
        resolvedPath = path_1.default.resolve(configPath);
        if (configPath.endsWith('.js'))
            checkMjs();
    }
    else {
        // implicit config file loaded from inline root (if present)
        // otherwise from cwd
        const jsConfigPath = path_1.default.resolve(configRoot, 'vite.config.js');
        if (fs_1.default.existsSync(jsConfigPath)) {
            resolvedPath = jsConfigPath;
            checkMjs();
        }
        if (!resolvedPath) {
            const mjsConfigPath = path_1.default.resolve(configRoot, 'vite.config.mjs');
            if (fs_1.default.existsSync(mjsConfigPath)) {
                resolvedPath = mjsConfigPath;
                isMjs = true;
            }
        }
        if (!resolvedPath) {
            const tsConfigPath = path_1.default.resolve(configRoot, 'vite.config.ts');
            if (fs_1.default.existsSync(tsConfigPath)) {
                resolvedPath = tsConfigPath;
                isTS = true;
            }
        }
    }
    if (!resolvedPath) {
        debug('no config file found.');
        return null;
    }
    try {
        let userConfig;
        if (isMjs) {
            // using eval to avoid this from being compiled away by TS/Rollup
            // append a query so that we force reload fresh config in case of
            // server restart
            userConfig = (await eval(`import(resolvedPath + '?t=${Date.now()}')`))
                .default;
            debug(`native esm config loaded in ${Date.now() - start}ms`);
        }
        if (!userConfig && !isTS && !isMjs) {
            // 1. try to directly require the module (assuming commonjs)
            try {
                // clear cache in case of server restart
                delete require.cache[resolvedPath];
                userConfig = require(resolvedPath);
                debug(`cjs config loaded in ${Date.now() - start}ms`);
            }
            catch (e) {
                const ignored = /Cannot use import statement|Unexpected token 'export'|Must use import to load ES Module/;
                if (!ignored.test(e.message)) {
                    throw e;
                }
            }
        }
        if (!userConfig) {
            // 2. if we reach here, the file is ts or using es import syntax, or
            // the user has type: "module" in their package.json (#917)
            // transpile es import syntax to require syntax using rollup.
            // lazy require rollup (it's actually in dependencies)
            const rollup = require('rollup');
            // node-resolve must be imported since it's bundled
            const bundle = await rollup.rollup({
                external: (id) => (id[0] !== '.' && !path_1.default.isAbsolute(id)) ||
                    id.slice(-5, id.length) === '.json',
                input: resolvedPath,
                treeshake: false,
                plugins: [
                    // use esbuild + node-resolve to support .ts files
                    esbuild_1.esbuildPlugin({ target: 'es2019' }),
                    resolve_1.resolvePlugin({
                        root: path_1.default.dirname(resolvedPath),
                        isBuild: true,
                        asSrc: false
                    })
                ]
            });
            const { output: [{ code }] } = await bundle.generate({
                exports: 'named',
                format: 'cjs'
            });
            userConfig = await loadConfigFromBundledFile(resolvedPath, code);
            debug(`bundled config file loaded in ${Date.now() - start}ms`);
        }
        const config = typeof userConfig === 'function' ? userConfig(configEnv) : userConfig;
        if (!utils_1.isObject(config)) {
            throw new Error(`config must export or return an object.`);
        }
        return {
            path: resolvedPath,
            config
        };
    }
    catch (e) {
        logger_1.createLogger(logLevel).error(chalk_1.default.red(`failed to load config from ${resolvedPath}`));
        throw e;
    }
}
async function loadConfigFromBundledFile(fileName, bundledCode) {
    const extension = path_1.default.extname(fileName);
    const defaultLoader = require.extensions[extension];
    require.extensions[extension] = (module, filename) => {
        if (filename === fileName) {
            ;
            module._compile(bundledCode, filename);
        }
        else {
            defaultLoader(module, filename);
        }
    };
    delete require.cache[fileName];
    const raw = require(fileName);
    const config = raw.__esModule ? raw.default : raw;
    require.extensions[extension] = defaultLoader;
    return config;
}
function loadEnv(mode, root, prefix = 'VITE_') {
    if (mode === 'local') {
        throw new Error(`"local" cannot be used as a mode name because it conflicts with ` +
            `the .local postfix for .env files.`);
    }
    const env = {};
    const envFiles = [
        /** mode local file */ `.env.${mode}.local`,
        /** mode file */ `.env.${mode}`,
        /** local file */ `.env.local`,
        /** default file */ `.env`
    ];
    for (const file of envFiles) {
        const path = utils_1.lookupFile(root, [file], true);
        if (path) {
            const parsed = dotenv_1.default.parse(fs_1.default.readFileSync(path), {
                debug: !!process.env.DEBUG || undefined
            });
            // let environment variables use each other
            dotenv_expand_1.default({
                parsed,
                // prevent process.env mutation
                ignoreProcessEnv: true
            });
            // only keys that start with prefix are exposed to client
            for (const [key, value] of Object.entries(parsed)) {
                if (key.startsWith(prefix) && env[key] === undefined) {
                    env[key] = value;
                }
                else if (key === 'NODE_ENV') {
                    // NODE_ENV override in .env file
                    process.env.VITE_USER_NODE_ENV = value;
                }
            }
        }
    }
    return env;
}
exports.loadEnv = loadEnv;
//# sourceMappingURL=config.js.map