diff --git a/examples/basic-app/wrnexus.config.ts b/examples/basic-app/wrnexus.config.ts index cbb94a80..8842ba32 100644 --- a/examples/basic-app/wrnexus.config.ts +++ b/examples/basic-app/wrnexus.config.ts @@ -13,8 +13,6 @@ import type { AppConfig } from "@wrnexus/styles"; * framework-level headers and optional CORS. */ const config: AppConfig = { - frameworkBehaviour: 1, - compatibilityDate: "2026-08-02", head: [ // --- Use a CSS framework via CDN (uncomment one) --- // Bootstrap: diff --git a/examples/inter-app-api-showcase/apps/admin/wrnexus.config.ts b/examples/inter-app-api-showcase/apps/admin/wrnexus.config.ts index 315abed6..7b7a25b9 100644 --- a/examples/inter-app-api-showcase/apps/admin/wrnexus.config.ts +++ b/examples/inter-app-api-showcase/apps/admin/wrnexus.config.ts @@ -4,8 +4,6 @@ import { join } from "node:path"; import type { AppConfig } from "@wrnexus/styles"; const config: AppConfig = { - compatibilityDate: "2026-08-02", - frameworkBehaviour: 1, // v0.8 defaults: explicit imports, strict template types, safe stores, and // automatic progressive navigation. Package plugins are discovered from the // installed packages above; add custom plugins to this array when needed. @@ -19,14 +17,7 @@ const config: AppConfig = { checkComponentProps: true, generateDeclarations: true, }, - functions: { legacyDefaultRuntime: "current" }, stores: { strictMutations: true, persistence: true }, - compatibility: { - legacyEmit: false, - legacyEventProps: false, - legacyComponentDiscovery: false, - stringLayouts: false, - }, experimental: {}, performance: { diff --git a/examples/inter-app-api-showcase/apps/web/wrnexus.config.ts b/examples/inter-app-api-showcase/apps/web/wrnexus.config.ts index facc82aa..fe17faf7 100644 --- a/examples/inter-app-api-showcase/apps/web/wrnexus.config.ts +++ b/examples/inter-app-api-showcase/apps/web/wrnexus.config.ts @@ -4,8 +4,6 @@ import { join } from "node:path"; import type { AppConfig } from "@wrnexus/styles"; const config: AppConfig = { - compatibilityDate: "2026-08-02", - frameworkBehaviour: 1, // v0.8 defaults: explicit imports, strict template types, safe stores, and // automatic progressive navigation. Package plugins are discovered from the // installed packages above; add custom plugins to this array when needed. @@ -19,14 +17,7 @@ const config: AppConfig = { checkComponentProps: true, generateDeclarations: true, }, - functions: { legacyDefaultRuntime: "current" }, stores: { strictMutations: true, persistence: true }, - compatibility: { - legacyEmit: false, - legacyEventProps: false, - legacyComponentDiscovery: false, - stringLayouts: false, - }, experimental: {}, performance: { diff --git a/packages/cli/src/compatibility-command.ts b/packages/cli/src/compatibility-command.ts deleted file mode 100644 index e0ab89fc..00000000 --- a/packages/cli/src/compatibility-command.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; -import { basename, join, resolve } from "node:path"; -import { - CURRENT_COMPATIBILITY_DATE, - CURRENT_FRAMEWORK_BEHAVIOUR, - loadRawConfig, - resolveCompatibility, -} from "@wrnexus/styles"; - -const CONFIG_NAMES = ["wrnexus.config.ts", "wrnexus.config.js", "wrnexus.config.mjs"]; - -function configPath(root: string): string | undefined { - return CONFIG_NAMES.map((name) => join(root, name)).find(existsSync); -} - -export async function compatibilityReport(appRoot: string) { - return resolveCompatibility(await loadRawConfig(resolve(appRoot))); -} - -export function upgradeCompatibility(appRoot: string): { - file: string; - backup: string; - changed: boolean; -} { - const root = resolve(appRoot); - const file = configPath(root); - if (!file) throw new Error("WRN-COMPATIBILITY-NO-CONFIG: wrnexus.config.ts was not found."); - const source = readFileSync(file, "utf8"); - let updated = source; - const replace = (name: string, value: string) => { - const pattern = new RegExp(`(^\\s*${name}\\s*:\\s*)(?:["'][^"']*["']|\\d+)(\\s*,?)`, "m"); - if (pattern.test(updated)) updated = updated.replace(pattern, `$1${value}$2`); - else { - const object = /(?:const\s+config[^=]*=|defineConfig\s*\(|export\s+default)\s*\{/m; - if (!object.test(updated)) - throw new Error("WRN-COMPATIBILITY-CONFIG-SHAPE: unable to locate the root config object."); - updated = updated.replace(object, (match) => `${match}\n ${name}: ${value},`); - } - }; - replace("compatibilityDate", JSON.stringify(CURRENT_COMPATIBILITY_DATE)); - replace("frameworkBehaviour", String(CURRENT_FRAMEWORK_BEHAVIOUR)); - if (updated === source) return { file, backup: "", changed: false }; - const directory = join(root, ".wrnexus", "compatibility-backups"); - mkdirSync(directory, { recursive: true }); - const backup = join(directory, `${Date.now()}-${basename(file)}`); - copyFileSync(file, backup); - writeFileSync(file, updated, "utf8"); - return { file, backup, changed: true }; -} - -export async function runCompatibilityCommand( - appRoot: string, - command = "check", - args: string[] = [], -): Promise { - if (command === "upgrade") { - const result = upgradeCompatibility(appRoot); - if (args.includes("--json")) console.log(JSON.stringify(result, null, 2)); - else - console.log( - result.changed - ? `✓ Compatibility policy upgraded\n backup: ${result.backup}` - : "✓ Compatibility policy already current", - ); - } - const report = await compatibilityReport(appRoot); - if (args.includes("--json")) console.log(JSON.stringify(report, null, 2)); - else { - console.log(`Compatibility date: ${report.effectiveDate} (current ${report.currentDate})`); - console.log( - `Framework behaviour: ${report.effectiveBehaviour} (current ${report.currentBehaviour})`, - ); - for (const message of report.messages) console.log(`- ${message}`); - } - return !report.needsUpgrade && !report.future; -} diff --git a/packages/cli/src/create.ts b/packages/cli/src/create.ts index 58b8db27..339f33ac 100644 --- a/packages/cli/src/create.ts +++ b/packages/cli/src/create.ts @@ -260,8 +260,6 @@ trim_trailing_whitespace = true "wrnexus.config.ts": `import type { AppConfig } from "@wrnexus/styles"; const config: AppConfig = { - compatibilityDate: "2026-08-02", - frameworkBehaviour: 1, // v0.8 defaults: explicit imports, strict template types, safe stores, and // automatic progressive navigation. Package plugins are discovered from the // installed packages above; add custom plugins to this array when needed. @@ -275,14 +273,7 @@ const config: AppConfig = { checkComponentProps: true, generateDeclarations: true, }, - functions: { legacyDefaultRuntime: "current" }, stores: { strictMutations: true, persistence: true }, - compatibility: { - legacyEmit: false, - legacyEventProps: false, - legacyComponentDiscovery: false, - stringLayouts: false, - }, experimental: {}, performance: { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a9d3893a..13231c2c 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -72,8 +72,6 @@ Usage: Run unit | component | api | browser | visual | accessibility | performance wrnexus profiles [app-dir] List config profiles (dev/prod/uat/…) and their env files wrnexus doctor [app-dir] [--fix] Check project health; optionally apply safe repairs - wrnexus compatibility [app-dir] - Inspect or explicitly upgrade behavior defaults wrnexus contracts [app-dir] Detect breaking boundary contract changes wrnexus security [app-dir] @@ -292,16 +290,6 @@ async function main(): Promise { if (!healthy) process.exitCode = 1; break; } - case "compatibility": { - const { runCompatibilityCommand } = await import("./compatibility-command.ts"); - const subcommand = rest.find((value) => !value.startsWith("--")) ?? "check"; - const appRoot = rest.filter((value) => !value.startsWith("--"))[1] ?? "."; - if (!["check", "explain", "upgrade"].includes(subcommand)) - throw new Error(`WRN-COMPATIBILITY-COMMAND: unknown command '${subcommand}'.`); - const current = await runCompatibilityCommand(appRoot, subcommand, rest); - if (!current && subcommand !== "explain") process.exitCode = 1; - break; - } case "contracts": { const { runContractsCommand } = await import("./contracts-command.ts"); const subcommand = rest.find((value) => !value.startsWith("--")) ?? "check"; diff --git a/packages/cli/src/update.ts b/packages/cli/src/update.ts index fcbb9327..9ac10dd2 100644 --- a/packages/cli/src/update.ts +++ b/packages/cli/src/update.ts @@ -385,8 +385,8 @@ function updateV060Config(ctx: MigrationCtx): void { const file = candidates.map((name) => join(ctx.appRoot, name)).find(existsSync); if (!file) return; const current = readFileSync(file, "utf8"); - if (/\bimports\s*:/.test(current) && /\bcompatibility\s*:/.test(current)) return; - const insertion = `\n imports: { mode: "compatible", autoImport: true, aliases: { "@": "./app" } },\n types: { strict: false, noImplicitAny: false, strictNullChecks: true, checkTemplates: true, checkComponentProps: true, generateDeclarations: true, globalTypes: "./app/types/global.d.ts" },\n functions: { legacyDefaultRuntime: "current" },\n stores: { strictMutations: true, persistence: true },\n compatibility: { legacyEmit: true, legacyEventProps: true, legacyComponentDiscovery: true, stringLayouts: true },`; + if (/\bimports\s*:/.test(current)) return; + const insertion = `\n imports: { mode: "compatible", autoImport: true, aliases: { "@": "./app" } },\n types: { strict: false, noImplicitAny: false, strictNullChecks: true, checkTemplates: true, checkComponentProps: true, generateDeclarations: true, globalTypes: "./app/types/global.d.ts" },\n stores: { strictMutations: true, persistence: true },`; const index = current.lastIndexOf("}"); if (index < 0) return; const next = diff --git a/packages/cli/test/compatibility.test.ts b/packages/cli/test/compatibility.test.ts deleted file mode 100644 index bf39712d..00000000 --- a/packages/cli/test/compatibility.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { expect, test } from "bun:test"; -import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { compatibilityReport, upgradeCompatibility } from "../src/compatibility-command.ts"; - -test("compatibility upgrade is backed up, current, and idempotent", async () => { - const root = mkdtempSync(join(tmpdir(), "wrnexus-compatibility-")); - const file = join(root, "wrnexus.config.ts"); - writeFileSync(file, `export default { port: 3000 };\n`); - const first = upgradeCompatibility(root); - expect(first.changed).toBe(true); - expect(readFileSync(first.backup, "utf8")).toContain("port: 3000"); - expect(readFileSync(file, "utf8")).toContain('compatibilityDate: "2026-08-02"'); - expect(upgradeCompatibility(root).changed).toBe(false); - expect((await compatibilityReport(root)).needsUpgrade).toBe(false); -}); diff --git a/packages/cli/test/create.test.ts b/packages/cli/test/create.test.ts index c0a680b7..cc835c73 100644 --- a/packages/cli/test/create.test.ts +++ b/packages/cli/test/create.test.ts @@ -96,7 +96,6 @@ test("scaffoldApp includes the complete v0.8 configuration and starter structure "imports:", "types:", "stores:", - "compatibility:", "performance:", "observability:", "tenancy:", diff --git a/packages/styles/src/compatibility.ts b/packages/styles/src/compatibility.ts deleted file mode 100644 index 0722fb6c..00000000 --- a/packages/styles/src/compatibility.ts +++ /dev/null @@ -1,59 +0,0 @@ -export const CURRENT_COMPATIBILITY_DATE = "2026-08-02"; -export const CURRENT_FRAMEWORK_BEHAVIOUR = 1; - -export interface CompatibilityPolicy { - compatibilityDate?: string; - frameworkBehaviour?: number; -} - -export interface CompatibilityReport { - configuredDate?: string; - effectiveDate: string; - currentDate: string; - configuredBehaviour?: number; - effectiveBehaviour: number; - currentBehaviour: number; - needsUpgrade: boolean; - future: boolean; - messages: string[]; -} - -export function isCompatibilityDate(value: string): boolean { - if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false; - const date = new Date(`${value}T00:00:00.000Z`); - return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value; -} - -export function resolveCompatibility(policy: CompatibilityPolicy): CompatibilityReport { - const configuredDate = policy.compatibilityDate; - const configuredBehaviour = policy.frameworkBehaviour; - const effectiveDate = configuredDate ?? "1970-01-01"; - const effectiveBehaviour = configuredBehaviour ?? 0; - const future = - (configuredDate !== undefined && configuredDate > CURRENT_COMPATIBILITY_DATE) || - (configuredBehaviour !== undefined && configuredBehaviour > CURRENT_FRAMEWORK_BEHAVIOUR); - const needsUpgrade = - !future && - (effectiveDate < CURRENT_COMPATIBILITY_DATE || - effectiveBehaviour < CURRENT_FRAMEWORK_BEHAVIOUR); - const messages: string[] = []; - if (!configuredDate) messages.push("compatibilityDate is not configured; legacy defaults apply."); - if (!configuredBehaviour) - messages.push("frameworkBehaviour is not configured; behaviour version 0 applies."); - if (future) - messages.push("Configuration targets framework behavior newer than this CLI supports."); - else if (needsUpgrade) - messages.push("A newer compatibility policy is available; review it before upgrading."); - else messages.push("Compatibility policy matches the current framework behavior."); - return { - configuredDate, - effectiveDate, - currentDate: CURRENT_COMPATIBILITY_DATE, - configuredBehaviour, - effectiveBehaviour, - currentBehaviour: CURRENT_FRAMEWORK_BEHAVIOUR, - needsUpgrade, - future, - messages, - }; -} diff --git a/packages/styles/src/config.ts b/packages/styles/src/config.ts index ea9ab669..64a03387 100644 --- a/packages/styles/src/config.ts +++ b/packages/styles/src/config.ts @@ -17,11 +17,6 @@ import type { StorageConfig } from "@wrnexus/uploader"; import type { BrowserCookiesConfig, ThemeConfig } from "./theme.ts"; import type { FontConfig } from "./fonts.ts"; import { fontCspSources } from "./fonts.ts"; -import { - isCompatibilityDate, - resolveCompatibility, - type CompatibilityPolicy, -} from "./compatibility.ts"; export type Mode = "development" | "production"; @@ -230,23 +225,12 @@ export interface TypesConfig { globalTypes?: string; } -export interface FunctionsConfig { - legacyDefaultRuntime?: "current" | "client" | "server" | "shared"; -} - export interface StoresConfig { strictMutations?: boolean; persistence?: boolean; } -export interface CompatibilityConfig { - legacyEmit?: boolean; - legacyEventProps?: boolean; - legacyComponentDiscovery?: boolean; - stringLayouts?: boolean; -} - -export interface AppConfig extends CompatibilityPolicy { +export interface AppConfig { /** Ordered reusable configuration layers; the application always has final precedence. */ extends?: string | string[]; /** Compiler/dev/build plugins, resolved in deterministic pre/normal/post order. */ @@ -260,12 +244,8 @@ export interface AppConfig extends CompatibilityPolicy { imports?: ImportsConfig; /** TypeScript-backed .wrn type checking and declaration generation. */ types?: TypesConfig; - /** Legacy function runtime behavior for existing applications. */ - functions?: FunctionsConfig; /** Typed global/page store behavior. */ stores?: StoresConfig; - /** Temporary v0.5 syntax compatibility switches. */ - compatibility?: CompatibilityConfig; /** Opt-in APIs that are not yet covered by stable compatibility guarantees. */ experimental?: ExperimentalConfig; /** Route and asset budgets plus build analyzer behavior. */ @@ -597,32 +577,23 @@ export function defineConfig(config: AppConfig): AppConfig { return config; } +const REMOVED_CONFIG_KEYS = [ + "compatibilityDate", + "frameworkBehaviour", + "functions", + "compatibility", +] as const; + export function validateAppConfig(config: AppConfig): ConfigIssue[] { const issues: ConfigIssue[] = []; - if (config.compatibilityDate !== undefined && !isCompatibilityDate(config.compatibilityDate)) { - issues.push({ - path: "compatibilityDate", - severity: "error", - message: "must be a real ISO calendar date in YYYY-MM-DD format", - }); - } - if ( - config.frameworkBehaviour !== undefined && - (!Number.isInteger(config.frameworkBehaviour) || config.frameworkBehaviour < 1) - ) { - issues.push({ - path: "frameworkBehaviour", - severity: "error", - message: "must be a positive integer", - }); - } - const compatibility = resolveCompatibility(config); - if (compatibility.future) { - issues.push({ - path: "compatibilityDate", - severity: "error", - message: "targets framework behavior newer than this version supports", - }); + for (const key of REMOVED_CONFIG_KEYS) { + if ((config as Record)[key] !== undefined) { + issues.push({ + path: key, + severity: "error", + message: "was removed; delete it from the configuration", + }); + } } const sampleRate = config.observability?.sampleRate; if ( diff --git a/packages/styles/src/index.ts b/packages/styles/src/index.ts index 369f83d3..7bbb286f 100644 --- a/packages/styles/src/index.ts +++ b/packages/styles/src/index.ts @@ -36,13 +36,6 @@ export { resolveConfigLayers, validateAppConfig, } from "./config.ts"; -export { - CURRENT_COMPATIBILITY_DATE, - CURRENT_FRAMEWORK_BEHAVIOUR, - isCompatibilityDate, - resolveCompatibility, -} from "./compatibility.ts"; -export type { CompatibilityPolicy, CompatibilityReport } from "./compatibility.ts"; export { findStyleEntry, bundleCss } from "./styles.ts"; export type { FontConfig, GoogleFont, LocalFontFace, FontDisplay } from "./fonts.ts"; export { renderFontHead, renderProductionFontHead, fontCspSources } from "./fonts.ts"; diff --git a/packages/styles/test/config.test.ts b/packages/styles/test/config.test.ts index 89a903c2..d5d1a6f8 100644 --- a/packages/styles/test/config.test.ts +++ b/packages/styles/test/config.test.ts @@ -7,7 +7,6 @@ import { resolveProfile, loadEnv, renderStyles, - resolveCompatibility, validateAppConfig, explainAppConfig, } from "../src/index.ts"; @@ -16,18 +15,6 @@ afterEach(() => { delete process.env.WRNEXUS_PROFILE; }); -test("compatibility dates pin behavior and reject invalid or future policies", () => { - expect( - resolveCompatibility({ compatibilityDate: "2026-08-02", frameworkBehaviour: 1 }), - ).toMatchObject({ needsUpgrade: false, future: false, effectiveBehaviour: 1 }); - expect(validateAppConfig({ compatibilityDate: "2026-02-31" })).toContainEqual( - expect.objectContaining({ path: "compatibilityDate", severity: "error" }), - ); - expect(validateAppConfig({ frameworkBehaviour: 2 })).toContainEqual( - expect.objectContaining({ severity: "error" }), - ); -}); - test("observability config requires bounded sampling and a valid OTLP endpoint", () => { expect(validateAppConfig({ observability: { sampleRate: Number.NaN } })).toContainEqual( expect.objectContaining({ path: "observability.sampleRate", severity: "error" }), diff --git a/packages/styles/test/removed-config-keys.test.ts b/packages/styles/test/removed-config-keys.test.ts new file mode 100644 index 00000000..9cc71983 --- /dev/null +++ b/packages/styles/test/removed-config-keys.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from "bun:test"; +import { validateAppConfig } from "../src/config.ts"; + +// A stale config must fail loudly. Silently ignoring a removed key leaves +// someone believing a flag still applies. +const REMOVED = [ + { key: "compatibilityDate", config: { compatibilityDate: "2026-08-02" } }, + { key: "frameworkBehaviour", config: { frameworkBehaviour: 1 } }, + { key: "functions", config: { functions: { legacyDefaultRuntime: "current" } } }, + { key: "compatibility", config: { compatibility: { legacyEmit: false } } }, +]; + +for (const { key, config } of REMOVED) { + test(`a config still setting "${key}" is rejected with a message naming it`, () => { + const issues = validateAppConfig(config as never); + const match = issues.find((issue) => issue.path === key || issue.path.startsWith(`${key}.`)); + + expect(match).toBeDefined(); + expect(match!.severity).toBe("error"); + expect(match!.message.toLowerCase()).toContain("removed"); + }); +} + +test("a config without those keys is accepted", () => { + const issues = validateAppConfig({} as never); + + expect(issues.filter((issue) => issue.severity === "error")).toEqual([]); +});