release: WRNexusJS 0.3.0
This commit is contained in:
@@ -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 "";
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user