import { log } from "../../utils/log"; import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler"; import serverPostBuildFn from "./server-post-build-fn"; import { restartWatcher } from "./watcher-esbuild-ctx"; import { logBuildErrorsOnce } from "../../utils/build-error-log"; import disposeBundlerContexts from "../bundler/dispose-bundler-contexts"; import isDevelopment from "../../utils/is-development"; // Single-flight: error recovery paths (grab-page-component retries, the // watcher, post-build HMR pushes) all call fullRebuild, and each rebuild // triggers serverPostBuildFn which can re-enter error handling. Without this // guard one broken page could fan out into cascading, overlapping rebuilds // that leave the dev server unresponsive. Concurrent callers await the same // in-flight rebuild instead of starting a new one. let rebuild_in_progress; export default async function fullRebuild(params) { if (rebuild_in_progress) { if (params?.msg) { log.watch(`Rebuild already in progress, skipping: ${params.msg}`); } return rebuild_in_progress; } const run = doFullRebuild(params).finally(() => { if (rebuild_in_progress === run) { rebuild_in_progress = undefined; } }); rebuild_in_progress = run; return run; } async function doFullRebuild(params) { try { const { msg } = params || {}; global.BUNEXT_RECOMPILING = true; if (msg) { log.watch(msg); } global.BUNEXT_ROUTER.reload(); await disposeBundlerContexts(); await allPagesESBuildContextBundler({ post_build_fn: async (params) => { await serverPostBuildFn(params); }, }); // Production never rebuilds incrementally; don't keep esbuild's // build state alive for the rest of the process. if (!isDevelopment()) { await disposeBundlerContexts(); } } catch (error) { logBuildErrorsOnce([{ text: error?.message || String(error) }], (line) => log.error(line)); } finally { global.BUNEXT_RECOMPILING = false; global.BUNEXT_IS_SERVER_COMPONENT = false; } restartWatcher(); }