"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createServer = void 0;
const os_1 = __importDefault(require("os"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const connect_1 = __importDefault(require("connect"));
const cors_1 = __importDefault(require("cors"));
const chalk_1 = __importDefault(require("chalk"));
const chokidar_1 = __importDefault(require("chokidar"));
const config_1 = require("../config");
const pluginContainer_1 = require("../server/pluginContainer");
const https_1 = require("../server/https");
const ws_1 = require("../server/ws");
const proxy_1 = require("./middlewares/proxy");
const transform_1 = require("./middlewares/transform");
const indexHtml_1 = require("./middlewares/indexHtml");
const connect_history_api_fallback_1 = __importDefault(require("connect-history-api-fallback"));
const static_1 = require("./middlewares/static");
const time_1 = require("./middlewares/time");
const moduleGraph_1 = require("./moduleGraph");
const utils_1 = require("../utils");
const error_1 = require("./middlewares/error");
const hmr_1 = require("./hmr");
const openBrowser_1 = require("./openBrowser");
const launch_editor_middleware_1 = __importDefault(require("launch-editor-middleware"));
const transformRequest_1 = require("./transformRequest");
const esbuild_1 = require("../plugins/esbuild");
const logger_1 = require("../logger");
const optimizer_1 = require("../optimizer");
async function createServer(inlineConfig = {}, configPath) {
    const mode = inlineConfig.mode || 'development';
    const resolvedConfig = await config_1.resolveConfig(inlineConfig, 'serve', mode, configPath);
    const root = resolvedConfig.root;
    const logger = logger_1.createLogger(resolvedConfig.logLevel);
    const serverConfig = resolvedConfig.server || {};
    const app = connect_1.default();
    const httpServer = await resolveHttpServer(serverConfig, app);
    const ws = ws_1.createWebSocketServer(httpServer, logger);
    const watchOptions = serverConfig.watch || {};
    const watcher = chokidar_1.default.watch(root, {
        ignored: [
            '**/node_modules/**',
            '**/.git/**',
            ...(watchOptions.ignored || [])
        ],
        ignoreInitial: true,
        ignorePermissionErrors: true,
        ...watchOptions
    });
    const plugins = resolvedConfig.plugins;
    const container = await pluginContainer_1.createPluginContainer(resolvedConfig, watcher);
    const moduleGraph = new moduleGraph_1.ModuleGraph(container);
    const closeHttpServer = createSeverCloseFn(httpServer);
    const server = {
        config: resolvedConfig,
        app,
        httpServer,
        watcher,
        pluginContainer: container,
        ws,
        moduleGraph,
        optimizeDepsMetadata: null,
        transformWithEsbuild: esbuild_1.transformWithEsbuild,
        transformRequest(url) {
            return transformRequest_1.transformRequest(url, server);
        },
        listen(port) {
            return startServer(server, port);
        },
        async close() {
            await Promise.all([
                watcher.close(),
                ws.close(),
                container.close(),
                closeHttpServer()
            ]);
        }
    };
    if (serverConfig.hmr !== false) {
        watcher.on('change', async (file) => {
            file = utils_1.normalizePath(file);
            // invalidate module graph cache on file change
            moduleGraph.onFileChange(file);
            try {
                await hmr_1.handleHMRUpdate(file, server);
            }
            catch (err) {
                ws.send({
                    type: 'error',
                    err: error_1.prepareError(err)
                });
            }
        });
    }
    // apply server configuration hooks from plugins
    const postHooks = [];
    for (const plugin of plugins) {
        if (plugin.configureServer) {
            postHooks.push(await plugin.configureServer(server));
        }
    }
    // Internal middlewares ------------------------------------------------------
    // request timer
    if (process.env.DEBUG) {
        app.use(time_1.timeMiddleware(root));
    }
    // cors (enabled by default)
    const { cors } = serverConfig;
    if (cors !== false) {
        app.use(cors_1.default(typeof cors === 'boolean' ? {} : cors));
    }
    // proxy
    const { proxy } = serverConfig;
    if (proxy) {
        app.use(proxy_1.proxyMiddleware(server));
    }
    // open in editor support
    app.use('/__open-in-editor', launch_editor_middleware_1.default());
    // serve static files under /public
    // this applies before the transform middleware so that these files are served
    // as-is without transforms.
    app.use(static_1.serveStaticMiddleware(path_1.default.join(root, 'public')));
    // main transform middleware
    app.use(transform_1.transformMiddleware(server));
    // serve static files
    app.use(static_1.rawFsStaticMiddleware());
    app.use(static_1.serveStaticMiddleware(root, resolvedConfig));
    // spa fallback
    app.use(connect_history_api_fallback_1.default({
        logger: utils_1.createDebugger('vite:spa-fallback'),
        // support /dir/ without explicit index.html
        rewrites: [
            {
                from: /\/$/,
                to({ parsedUrl }) {
                    const rewritten = parsedUrl.pathname + 'index.html';
                    if (fs_1.default.existsSync(path_1.default.join(root, rewritten))) {
                        return rewritten;
                    }
                    else {
                        return `/index.html`;
                    }
                }
            }
        ]
    }));
    // run post config hooks
    // This is applied before the html middleware so that user middleware can
    // serve custom content instead of index.html.
    postHooks.forEach((fn) => fn && fn());
    // transform index.html
    app.use(indexHtml_1.indexHtmlMiddleware(server, plugins));
    // handle 404s
    app.use((_, res) => {
        res.statusCode = 404;
        res.end();
    });
    // error handler
    app.use(error_1.errorMiddleware(server));
    // overwrite listen to run optimizer before server start
    const listen = httpServer.listen.bind(httpServer);
    httpServer.listen = (async (port, ...args) => {
        await container.buildStart({});
        if (resolvedConfig.optimizeCacheDir) {
            // run optimizer
            await optimizer_1.optimizeDeps(resolvedConfig);
            // after optimization, read updated optimization metadata
            const dataPath = path_1.default.resolve(resolvedConfig.optimizeCacheDir, 'metadata.json');
            if (fs_1.default.existsSync(dataPath)) {
                server.optimizeDepsMetadata = JSON.parse(fs_1.default.readFileSync(dataPath, 'utf-8'));
            }
        }
        return listen(port, ...args);
    });
    httpServer.once('listening', () => {
        // update actual port since this may be different from initial value
        serverConfig.port = httpServer.address().port;
    });
    return server;
}
exports.createServer = createServer;
async function resolveHttpServer({ https = false, proxy }, app) {
    if (!https) {
        return require('http').createServer(app);
    }
    const httpsOptions = await https_1.resolveHttpsConfig(typeof https === 'boolean' ? {} : https);
    if (proxy) {
        // #484 fallback to http1 when proxy is needed.
        return require('https').createServer(httpsOptions, app);
    }
    else {
        return require('http2').createSecureServer({
            ...httpsOptions,
            allowHTTP1: true
        }, app);
    }
}
async function startServer(server, inlinePort) {
    const options = server.config.server || {};
    let port = inlinePort || options.port || 3000;
    let hostname = options.host || 'localhost';
    const protocol = options.https ? 'https' : 'http';
    const httpServer = server.httpServer;
    const info = server.config.logger.info;
    return new Promise((resolve, reject) => {
        const onError = (e) => {
            if (e.code === 'EADDRINUSE') {
                info(`Port ${port} is in use, trying another one...`);
                httpServer.listen(++port);
            }
            else {
                httpServer.removeListener('error', onError);
                reject(e);
            }
        };
        httpServer.on('error', onError);
        httpServer.listen(port, () => {
            httpServer.removeListener('error', onError);
            info(`\n  Vite dev server running at:\n`, { clear: true });
            const interfaces = os_1.default.networkInterfaces();
            Object.keys(interfaces).forEach((key) => (interfaces[key] || [])
                .filter((details) => details.family === 'IPv4')
                .map((detail) => {
                return {
                    type: detail.address.includes('127.0.0.1')
                        ? 'Local:   '
                        : 'Network: ',
                    host: detail.address.replace('127.0.0.1', hostname)
                };
            })
                .forEach(({ type, host }) => {
                const url = `${protocol}://${host}:${chalk_1.default.bold(port)}/`;
                info(`  > ${type} ${chalk_1.default.cyan(url)}`);
            }));
            // @ts-ignore
            if (global.__vite_start_time) {
                info(chalk_1.default.cyan(
                // @ts-ignore
                `\n  ready in ${Date.now() - global.__vite_start_time}ms.\n`));
            }
            // @ts-ignore
            const profileSession = global.__vite_profile_session;
            if (profileSession) {
                profileSession.post('Profiler.stop', (err, { profile }) => {
                    // Write profile to disk, upload, etc.
                    if (!err) {
                        const outPath = path_1.default.resolve('./vite-profile.cpuprofile');
                        fs_1.default.writeFileSync(outPath, JSON.stringify(profile));
                        info(chalk_1.default.yellow(`  CPU profile written to ${chalk_1.default.white.dim(outPath)}\n`));
                    }
                    else {
                        throw err;
                    }
                });
            }
            if (options.open) {
                openBrowser_1.openBrowser(`${protocol}://${hostname}:${port}`, options.open, server.config.logger);
            }
            resolve(server);
        });
    });
}
function createSeverCloseFn(server) {
    const openSockets = new Set();
    server.on('connection', (socket) => {
        openSockets.add(socket);
        socket.on('close', () => {
            openSockets.delete(socket);
        });
    });
    return () => new Promise((resolve, reject) => {
        openSockets.forEach((s) => s.destroy());
        server.close((err) => {
            if (err) {
                reject(err);
            }
            else {
                resolve();
            }
        });
    });
}
//# sourceMappingURL=index.js.map