Refresh UI components and showcase; release updated framework packages
This commit is contained in:
1 parent
d800228585
commit
c77f5e1cec
293 files changed
+22544
-35778
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/cli",
|
||||
"version": "0.8.70",
|
||||
"version": "0.8.71",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -60,6 +60,7 @@ import { loadLocales, resolveI18n } from "@wrnexus/i18n";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { checkPerformanceBudgets } from "@wrnexus/core";
|
||||
import { currentCliVersion } from "./update-notifier.ts";
|
||||
import { shouldInlineCss } from "./inline-css.ts";
|
||||
import {
|
||||
createPluginRunner,
|
||||
discoverPlugins,
|
||||
@@ -73,7 +74,6 @@ import {
|
||||
// the generated entry resolves whether @wrnexus/dev-server is a workspace or an
|
||||
// installed dependency. Bun.build bundles it into a self-contained server.js.
|
||||
const PROD_MODULE = "@wrnexus/dev-server";
|
||||
const INLINE_CSS_LIMIT_BYTES = 4096;
|
||||
|
||||
const fwd = (p: string) => p.replace(/\\/g, "/");
|
||||
|
||||
@@ -648,7 +648,7 @@ export async function runBuild(appRoot: string): Promise<void> {
|
||||
// theme stylesheet is selected from the request cookie and loaded first.
|
||||
writeFileSync(join(distDir, "styles.css"), combinedCss, "utf8");
|
||||
hasStyles = true;
|
||||
if (Buffer.byteLength(combinedCss, "utf8") <= INLINE_CSS_LIMIT_BYTES) {
|
||||
if (shouldInlineCss(combinedCss, config.styles?.inlineMaxBytes)) {
|
||||
inlineStyles = combinedCss;
|
||||
}
|
||||
console.log(`✓ Styles: ${join(distDir, "styles.css")}`);
|
||||
@@ -709,7 +709,16 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
|
||||
const runtimeConfigPath = ["wrnexus.config.ts", "wrnexus.config.js", "wrnexus.config.mjs"]
|
||||
.map((name) => resolve(appRoot, name))
|
||||
.find(existsSync);
|
||||
if (runtimeConfigPath) imports.push(`import ${JSON.stringify(fwd(runtimeConfigPath))};`);
|
||||
if (runtimeConfigPath && config.auth) {
|
||||
// Importing config creates the engine, but does not run plugin.configure.
|
||||
// Initialize route options before route/middleware modules are evaluated.
|
||||
// Credentials are read by the config at server startup, never serialized here.
|
||||
writeFileSync(join(distDir, ".auth-runtime.ts"),
|
||||
`import config from ${JSON.stringify(fwd(runtimeConfigPath))};\n` +
|
||||
`import { configureAuthRuntime } from "@wrnexus/auth/plugin";\n` +
|
||||
`configureAuthRuntime(config.auth);\n`, "utf8");
|
||||
imports.push(`import "./.auth-runtime.ts";`);
|
||||
} else if (runtimeConfigPath) imports.push(`import ${JSON.stringify(fwd(runtimeConfigPath))};`);
|
||||
|
||||
const manifestRoutes = (routes: Route[]): string => {
|
||||
const parts = routes.map((r) => {
|
||||
@@ -796,6 +805,7 @@ applyAuthzManifestEarly([${authzSetupEntries}]);
|
||||
const entry = `// AUTO-GENERATED production server entry — do not edit.
|
||||
import { join } from "node:path";
|
||||
import { createProductionServer } from ${JSON.stringify(PROD_MODULE)};
|
||||
import { productionConnections } from "@wrnexus/cli/runtime-connections";
|
||||
${imports.join("\n")}
|
||||
|
||||
await createProductionServer(
|
||||
@@ -855,6 +865,7 @@ await createProductionServer(
|
||||
tenancy: ${JSON.stringify(config.tenancy ?? {})},
|
||||
navigation: ${JSON.stringify(config.navigation ?? {})},
|
||||
developmentRuntime: process.env.WRNEXUS_PRODUCTION_DEV === "1",
|
||||
...productionConnections(),
|
||||
},
|
||||
);
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/** Keep the default conservative; sites can opt in for a measured first-load improvement. */
|
||||
export function shouldInlineCss(css: string, configuredLimit: number | undefined): boolean {
|
||||
const limit = configuredLimit ?? 4096;
|
||||
if (!Number.isSafeInteger(limit) || limit < 0) {
|
||||
throw new Error("styles.inlineMaxBytes must be a non-negative safe integer");
|
||||
}
|
||||
if (!limit || Buffer.byteLength(css, "utf8") > limit) return false;
|
||||
// Moving CSS into HTML must not change the base of relative asset URLs.
|
||||
for (const match of css.matchAll(/url\(\s*([^)]+)\s*\)/gi)) {
|
||||
const url = match[1]!.trim().replace(/^["']|["']$/g, "");
|
||||
if (!/^(?:\/|https?:|data:)/i.test(url)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/** Deployment-time connection overrides for immutable production images. */
|
||||
export function productionConnections(value = process.env.WRNEXUS_RUNTIME_CONNECTIONS) {
|
||||
if (!value) return {};
|
||||
const parsed = JSON.parse(value);
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
||||
throw new Error("WRNEXUS_RUNTIME_CONNECTIONS must be a JSON object");
|
||||
const allowed = new Set(["db", "databases", "realtime", "queue", "storage"]);
|
||||
for (const key of Object.keys(parsed)) {
|
||||
if (!allowed.has(key)) throw new Error(`Unsupported runtime connection: ${key}`);
|
||||
if (!parsed[key] || typeof parsed[key] !== "object" || Array.isArray(parsed[key]))
|
||||
throw new Error(`Runtime connection ${key} must be an object`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { shouldInlineCss } from "../src/inline-css.ts";
|
||||
|
||||
test("production CSS inlining uses a configurable UTF-8 byte budget", () => {
|
||||
expect(shouldInlineCss("x".repeat(4096), undefined)).toBe(true);
|
||||
expect(shouldInlineCss("x".repeat(4097), undefined)).toBe(false);
|
||||
expect(shouldInlineCss("x".repeat(5000), 6000)).toBe(true);
|
||||
expect(shouldInlineCss("é".repeat(10), 10)).toBe(false);
|
||||
expect(shouldInlineCss("", 0)).toBe(false);
|
||||
for (const value of [-1, NaN, Infinity, 1.5]) expect(() => shouldInlineCss("", value)).toThrow();
|
||||
});
|
||||
|
||||
test("relative asset URLs stay external so their base does not change", () => {
|
||||
for (const url of ['./font.woff2', '../font.woff2', 'font.woff2', '#mask']) {
|
||||
expect(shouldInlineCss(`a{background:url("${url}")}`, undefined)).toBe(false);
|
||||
}
|
||||
for (const url of ['/font.woff2', 'https://cdn.example/font.woff2', 'data:image/svg+xml;base64,AAAA']) {
|
||||
expect(shouldInlineCss(`a{background:url('${url}')}`, undefined)).toBe(true);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { productionConnections } from "../src/runtime-connections.ts";
|
||||
|
||||
test("deployment connections override build placeholders without accepting server settings", () => {
|
||||
const db = { driver: "postgres", url: "postgres://runtime:secret@db/app" };
|
||||
expect(productionConnections(JSON.stringify({ db, realtime: { scale: true } }))).toEqual({ db, realtime: { scale: true } });
|
||||
expect(productionConnections("")).toEqual({});
|
||||
for (const value of ["null", "[]", "invalid", '{"port":80}', '{"db":null}', '{"db":[]}'])
|
||||
expect(() => productionConnections(value)).toThrow();
|
||||
});
|
||||
Reference in new issue
Block a user