release: WRNexusJS 0.3.0

This commit is contained in:
2026-07-22 17:29:08 +05:30
parent 13dfa31d19
commit 07d8fb59d6
145 changed files with 9664 additions and 3881 deletions
+128 -1
View File
@@ -10,7 +10,8 @@
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import type { SecurityConfig, SeoConfig } from "@wrnexus/core";
import type { PerformanceBudgets, SecurityConfig, SeoConfig } from "@wrnexus/core";
import type { PluginInput } from "@wrnexus/plugin";
import type { StorageConfig } from "@wrnexus/uploader";
import type { ThemeConfig } from "./theme.ts";
import type { FontConfig } from "./fonts.ts";
@@ -131,7 +132,59 @@ export interface DevToolbarConfig {
veryLargeImageBytes?: number;
}
export interface ExperimentalConfig {
serverComponents?: boolean;
streaming?: boolean;
partialHydration?: boolean;
typedRpc?: boolean;
pluginTransforms?: boolean;
[feature: string]: boolean | undefined;
}
export interface PerformanceConfig {
budgets?: PerformanceBudgets;
/** `warn` reports budget violations; `error` fails production builds. */
enforcement?: "off" | "warn" | "error";
analyze?: boolean;
}
export interface ObservabilityConfig {
enabled?: boolean;
serviceName?: string;
serverTiming?: boolean;
sampleRate?: number;
exporter?: "console" | "otlp" | "none";
endpoint?: string;
}
export interface TenancyConfig {
mode?: "subdomain" | "domain" | "path" | "custom";
required?: boolean;
rootDomains?: string[];
pathPrefix?: string;
}
export interface BuildConfig {
cache?: boolean;
cacheDir?: string;
sourceMaps?: boolean;
report?: boolean;
adapter?: "bun" | "node" | "static" | "serverless" | "edge" | string;
}
export interface AppConfig {
/** Compiler/dev/build plugins, resolved in deterministic pre/normal/post order. */
plugins?: PluginInput;
/** Opt-in APIs that are not yet covered by stable compatibility guarantees. */
experimental?: ExperimentalConfig;
/** Route and asset budgets plus build analyzer behavior. */
performance?: PerformanceConfig;
/** Request tracing, Server-Timing, and exporter configuration. */
observability?: ObservabilityConfig;
/** First-class tenant resolution defaults. */
tenancy?: TenancyConfig;
/** Build cache, source map, report, and deployment adapter settings. */
build?: BuildConfig;
/** Development-only page diagnostics toolbar. Enabled by default in development. */
devToolbar?: boolean | DevToolbarConfig;
/** Raw HTML appended to every page's `<head>` (e.g. CDN stylesheet links). */
@@ -237,6 +290,13 @@ export async function loadAppConfig(appRoot: string, profile?: string): Promise<
const merged: AppConfig = override ? deepMerge(base, override) : { ...base };
delete merged.profiles;
applyFontCsp(merged);
const issues = validateAppConfig(merged);
const errors = issues.filter((issue) => issue.severity === "error");
if (errors.length) {
throw new Error(
`Invalid wrnexus.config: ${errors.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`,
);
}
return merged;
}
@@ -306,6 +366,73 @@ function parseDotenv(content: string): Record<string, string> {
return out;
}
export interface ConfigIssue {
path: string;
severity: "error" | "warning";
message: string;
}
export function defineConfig(config: AppConfig): AppConfig {
return config;
}
export function validateAppConfig(config: AppConfig): ConfigIssue[] {
const issues: ConfigIssue[] = [];
const sampleRate = config.observability?.sampleRate;
if (sampleRate !== undefined && (sampleRate < 0 || sampleRate > 1)) {
issues.push({
path: "observability.sampleRate",
severity: "error",
message: "must be between 0 and 1",
});
}
const budgets = config.performance?.budgets;
if (budgets) {
for (const [name, value] of Object.entries(budgets)) {
if (value !== undefined && (!Number.isFinite(value) || value < 0)) {
issues.push({
path: `performance.budgets.${name}`,
severity: "error",
message: "must be a non-negative finite number",
});
}
}
}
if (config.tenancy?.mode === "path" && !config.tenancy.pathPrefix) {
issues.push({
path: "tenancy.pathPrefix",
severity: "warning",
message: "is recommended when tenancy.mode is path",
});
}
return issues;
}
export interface ExplainedConfig {
profile: string;
config: AppConfig;
issues: ConfigIssue[];
sources: string[];
}
export async function explainAppConfig(
appRoot: string,
profile?: string,
): Promise<ExplainedConfig> {
const active = profile ?? resolveProfile();
const config = await loadAppConfig(appRoot, active);
const sources = CONFIG_NAMES.filter((name) => existsSync(join(appRoot, name)));
const envSources = [".env", ".env.local", `.env.${active}`, `.env.${active}.local`].filter(
(name) => existsSync(join(appRoot, name)),
);
return {
profile: active,
config,
issues: validateAppConfig(config),
sources: [...sources, ...envSources],
};
}
/** Flatten a head config into a single HTML string. */
export function headToString(head?: string | string[]): string {
if (!head) return "";
+18 -1
View File
@@ -9,13 +9,30 @@
export type {
AppConfig,
BuildConfig,
ConfigIssue,
DevToolbarConfig,
ExplainedConfig,
ExperimentalConfig,
MobileConfig,
ObservabilityConfig,
PerformanceConfig,
TenancyConfig,
PwaConfig,
StylesConfig,
StyleProcessContext,
Mode,
} from "./config.ts";
export { loadAppConfig, loadRawConfig, headToString, resolveProfile, loadEnv } from "./config.ts";
export {
defineConfig,
explainAppConfig,
headToString,
loadAppConfig,
loadEnv,
loadRawConfig,
resolveProfile,
validateAppConfig,
} from "./config.ts";
export { findStyleEntry, bundleCss } from "./styles.ts";
export type { FontConfig, GoogleFont, LocalFontFace, FontDisplay } from "./fonts.ts";
export { renderFontHead, renderProductionFontHead, fontCspSources } from "./fonts.ts";