Package library as @moduletrace/twui npm package

- 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]>
This commit is contained in:
2026-10-18 10:17:54 +01:00
co-authored by Claude Opus 5.5
parent d19d412066
commit 11da9b89e7
148 changed files with 937 additions and 500 deletions
+80
View File
@@ -0,0 +1,80 @@
/**
* 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`);