Files
bunext/dist/functions/server/cleanup-artifacts.js
T
tbenandClaude Opus 5.5 96e392f556 1.4.0: not_found, server-only response options, compression
- Server functions can return not_found: true to render the 404 page
  with status 404.
- Page components and window.__PAGE_PROPS__ (and HMR updates) only get
  props, query, url and error. response_options, redirect, cache settings
  and res_transform stay on the server, so cookies and private headers no
  longer end up in the HTML. BunextPageProps is narrowed to match
  (breaking for code that read server-only fields from component props).
- response_options headers go through a Headers object, so repeated
  headers such as several Set-Cookie survive.
- Redirects: status_code wins over permanent; response_options headers
  and res_transform apply to redirects too.
- Gzip for compressible text responses; compress: false to turn it off.
- Files the router matches but never bundles (.server.ts, __root,
  excluded paths and directories) return 404 instead of looping through
  dev rebuild retries.
- Missing public files return 404 rather than 403, so dotted page paths
  like /users/john.doe fall through to pages again.
- importFresh detects rewrites within the filesystem's mtime granularity.
- README: self-hosted React (not esm.sh), snake_case option names, static
  file serving, cache headers, the new options.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 04:14:56 +01:00

93 lines
3.1 KiB
JavaScript

import { log } from "../../utils/log";
import path from "path";
import grabDirNames from "../../utils/grab-dir-names";
import { existsSync, readdirSync, statSync, unlinkSync } from "fs";
const { ROOT_DIR } = grabDirNames();
// esbuild's content hashes are 8-char base64-ish (e.g. `CMSI7VSQ`). Entry
// outputs are `<dir>/<hash>.js` / `<dir>/<hash>.css`. Shared chunks are
// `chunk-*.js` and MUST be kept: any current entry may still reference an
// older chunk (esbuild reuses unchanged chunks across rebuilds), and
// verifying references would require parsing every entry's imports.
const ENTRY_HASH_FILE = /^[A-Za-z0-9]{8}\.(js|css)$/;
/**
* Deletes orphaned entry outputs left behind by previous rebuilds.
*
* After a rebuild the page/SSR artifact maps only reference the newest
* hashed entry files; every previously hashed entry for the same directory
* is provably unreferenced (generated HTML always uses the current map) and
* is removed. Chunks, non-hashed fallback module files and `map.json` are
* left untouched.
*/
export default async function cleanupArtifacts({ paradigm }) {
const { HYDRATION_DST_DIR, BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
const is_hydration = paradigm === "hydration";
const root_dir = is_hydration
? HYDRATION_DST_DIR
: BUNX_CWD_MODULE_CACHE_DIR;
const map = is_hydration
? global.BUNEXT_BUNDLER_CTX_MAP
: global.BUNEXT_SSR_BUNDLER_CTX_MAP;
if (!map || !existsSync(root_dir)) {
return;
}
try {
const keep_by_dir = new Map();
for (const entry of Object.values(map)) {
if (!entry?.path)
continue;
const full_path = path.join(ROOT_DIR, entry.path);
const dir_path = path.dirname(full_path);
let keep = keep_by_dir.get(dir_path);
if (!keep) {
keep = new Set();
keep_by_dir.set(dir_path, keep);
}
keep.add(path.basename(full_path));
if (entry.css_path) {
keep.add(path.basename(entry.css_path));
}
}
sweepDir(root_dir, keep_by_dir);
}
catch (error) {
log.error(`Cleanup Artifacts Error: ${error?.message || error}`);
}
}
function sweepDir(dir_path, keep_by_dir) {
let entries;
try {
entries = readdirSync(dir_path);
}
catch (error) {
return;
}
for (const name of entries) {
const full_path = path.join(dir_path, name);
let is_dir = false;
try {
is_dir = statSync(full_path).isDirectory();
}
catch (error) {
continue;
}
if (is_dir) {
sweepDir(full_path, keep_by_dir);
continue;
}
if (!name.match(ENTRY_HASH_FILE)) {
continue;
}
if (name.startsWith("chunk-")) {
continue;
}
const keep = keep_by_dir.get(path.dirname(full_path));
if (keep?.has(name)) {
continue;
}
try {
unlinkSync(full_path);
}
catch (error) { }
}
}