release: WRNexusJS 0.4.0

This commit is contained in:
2026-07-27 12:42:18 +05:30
parent 8b728a3e5d
commit 30e5721e84
250 changed files with 10065 additions and 3923 deletions
+58 -13
View File
@@ -58,35 +58,40 @@ export {
import type { Mode, StyleProcessContext, StylesConfig } from "./config.ts";
import { bundleCss } from "./styles.ts";
import { mkdir, writeFile } from "node:fs/promises";
import { isAbsolute, join, relative } from "node:path";
/**
* Produce the final CSS for an entry: run the config's custom processor if one
* is provided (Tailwind/PostCSS/Sass), otherwise use the built-in Bun bundler.
*
* If a custom processor throws (e.g. Tailwind can't resolve `tailwindcss`
* because deps aren't installed), we DON'T crash every request — we log a clear,
* actionable message and fall back to best-effort CSS so the app keeps serving.
* Development can fall back to best-effort CSS. Production throws by default so
* a deployment cannot silently ship unprocessed Tailwind/PostCSS directives.
*/
export async function renderStyles(
ctx: StyleProcessContext,
styles?: StylesConfig,
): Promise<string> {
if (!ctx.entryPath) return "";
if (!ctx.entryPath && !ctx.entries?.length) return "";
const processContext =
styles?.includePackageSources === false ? ctx : await packageAwareStyleContext(ctx);
if (!processContext.entryPath) return "";
if (styles?.process) {
try {
return String(await styles.process(ctx));
return String(await styles.process(processContext));
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.error(
`\n[wrnexus] Stylesheet processing failed — serving un-processed CSS.\n` +
` ${msg.split("\n")[0]}\n` +
` If you use Tailwind, run \`bun install\` inside the app so \`tailwindcss\`\n` +
` is available (an app created inside another project may not have it).\n`,
);
return await fallbackCss(ctx);
const details =
`[wrnexus] Stylesheet processing failed.\n` +
` ${msg.split("\n")[0]}\n` +
" If you use Tailwind, run `bun install` in the application workspace so the CLI and plugins are available.";
const failureMode = styles.failureMode ?? (ctx.mode === "production" ? "throw" : "fallback");
if (failureMode === "throw") throw new Error(details, { cause: err });
console.error(`\n${details}\n Serving best-effort unprocessed CSS in development.\n`);
return await fallbackCss(processContext);
}
}
return bundleCss(ctx.entryPath, ctx.mode);
return bundleCss(processContext.entryPath!, processContext.mode);
}
/**
@@ -107,4 +112,44 @@ async function fallbackCss(ctx: StyleProcessContext): Promise<string> {
}
}
async function packageAwareStyleContext(ctx: StyleProcessContext): Promise<StyleProcessContext> {
const sources = [...new Set((ctx.sources ?? []).filter(Boolean))];
const entries = [...new Set((ctx.entries ?? []).filter(Boolean))];
if (sources.length === 0 && entries.length === 0) return ctx;
if (!ctx.entryPath && entries.length === 0) return ctx;
const outputDir = join(ctx.appRoot, ".wrnexus", "styles");
const wrapper = join(outputDir, "global.with-package-sources.css");
await mkdir(outputDir, { recursive: true });
const localPath = (value: string): string => {
const path = isAbsolute(value) ? relative(outputDir, value) : value;
const normalized = path.replace(/\\/g, "/");
return normalized.startsWith(".") ? normalized : `./${normalized}`;
};
const quote = (value: string) => localPath(value).replace(/"/g, '\\"');
const imports = [...(ctx.entryPath ? [ctx.entryPath] : []), ...entries]
.map((value) => `@import "${quote(value)}";`)
.join("\n");
const directives = sources.map((value) => `@source "${quote(value)}";`).join("\n");
await writeFile(
wrapper,
`${imports}\n\n/* WRNexus package scan sources */\n${directives}\n`,
"utf8",
);
return {
...ctx,
originalEntryPath: ctx.entryPath,
entryPath: wrapper,
sources,
entries,
};
}
export type { Mode as StylesMode };
export {
auditWireTokens,
normalizeStyleSources,
tailwindSourceDirectives,
contrast,
} from "./audit.ts";
export type { CssTokenAudit, StyleSource, ContrastResult } from "./audit.ts";