- Move components/ to src/ and compile to dist/ with tsc - Add root barrel export plus deep-import subpath exports and base.css export - Post-compile step: copy base.css, add .js import extensions, mark client modules - Add publish.sh and .npmrc for the Gitea npm registry - Fix type errors, guard TinyMCE env access, drop ace-builds type dependency - Rename clashing props type names, remove Next.js scaffolding and unused deps Co-Authored-By: Claude Opus 5.5 <[email protected]>
81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
/**
|
|
* 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`);
|