import path from "path"; import AppNames from "./grab-app-names"; /** * Env var holding the dist directory. The CLI sets it from `--dist-dir` or * `dist_dir` in bunext.config.ts before any other module loads: most * modules read the directory names once, at import time. Child processes * (dev/start servers) inherit it. */ export const DistDirEnvName = "BUNEXT_DIST_DIR"; const ReservedDirs = ["src", "public", "node_modules"]; /** * # Resolve the dist directory * Returns the absolute path of the build artifact directory. `dist_dir` is * relative to the project root (or absolute) and must sit inside it: the * dev watcher only watches the project root, and checks it is alive through * a probe file in the dist directory. Its relative path is also the URL * path of the client bundles, so it is limited to URL-safe characters. */ export function resolveDistDir({ dist_dir, root_dir }) { const resolved = path.resolve(root_dir, dist_dir?.trim() || AppNames.defaultDistDir); if (!isInsideDir(resolved, root_dir)) { throw new Error(`Invalid dist directory \`${dist_dir}\`: it must be a directory ` + `inside the project root (${root_dir}).`); } const url_path = path .relative(root_dir, resolved) .split(path.sep) .join("/"); if (!/^[\w.\-\/]+$/.test(url_path)) { throw new Error(`Invalid dist directory \`${dist_dir}\`: use only letters, ` + `digits, \`.\`, \`_\`, \`-\` and \`/\`.`); } for (const reserved of ReservedDirs) { const reserved_dir = path.join(root_dir, reserved); if (resolved === reserved_dir || isInsideDir(resolved, reserved_dir)) { throw new Error(`Invalid dist directory \`${dist_dir}\`: it can't be ` + `\`${reserved}/\` or inside it.`); } } return resolved; } /** * Whether `target` is strictly inside `dir` (not `dir` itself) */ function isInsideDir(target, dir) { const relative = path.relative(dir, target); return (Boolean(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative)); }