/** * Runs after `tsc`: * 1. Copies `src/base.css` into `dist/` (tsc only emits TS output). * 2. Adds file extensions to relative imports (see addExtensions). * 3. Prepends `"use client"` to component/hook modules so they work as * client components under the Next.js App Router. Pure modules (utils, * types, the barrel) are left alone so they stay usable on the server. */ import { Glob } from "bun"; import path from "node:path"; const root = path.resolve(import.meta.dir, ".."); const dist = path.join(root, "dist"); await Bun.write( path.join(dist, "base.css"), Bun.file(path.join(root, "src", "base.css")), ); /** * tsc keeps extensionless relative specifiers ("./layout/Button"), which only * bundlers resolve. Rewrite them to full paths so plain Node ESM (e.g. SSR * with the package left external) can load the output too. */ async function resolveSpecifier(fromFile: string, specifier: string) { if (/\.(js|css|json)$/.test(specifier)) return specifier; const base = path.resolve(path.dirname(fromFile), specifier); if (await Bun.file(`${base}.js`).exists()) return `${specifier}.js`; if (await Bun.file(path.join(base, "index.js")).exists()) { return `${specifier}/index.js`; } return specifier; } const RELATIVE_IMPORT = /(\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)(["'])(\.{1,2}\/[^"']*)\2/g; async function addExtensions(filePath: string, content: string) { let result = ""; let lastIndex = 0; for (const match of content.matchAll(RELATIVE_IMPORT)) { const [full, prefix, quote, specifier] = match; const resolved = await resolveSpecifier(filePath, specifier!); result += content.slice(lastIndex, match.index); result += `${prefix}${quote}${resolved}${quote}`; lastIndex = match.index! + full.length; } return result + content.slice(lastIndex); } const SERVER_SAFE = [/^index\.js$/, /^types\.js$/, /^utils\//, /^\(utils\)\//]; let count = 0; for (const file of new Glob("**/*.{js,d.ts}").scanSync(dist)) { const filePath = path.join(dist, file); let content = await addExtensions( filePath, await Bun.file(filePath).text(), ); const isClientModule = file.endsWith(".js") && !SERVER_SAFE.some((pattern) => pattern.test(file)) && !content.startsWith('"use client"'); if (isClientModule) { content = `"use client";\n${content}`; count++; } await Bun.write(filePath, content); } console.log(`post-compile: copied base.css, marked ${count} client modules`);