diff --git a/packages/cli/src/update.ts b/packages/cli/src/update.ts index 9ac10dd2..4a837d55 100644 --- a/packages/cli/src/update.ts +++ b/packages/cli/src/update.ts @@ -120,20 +120,6 @@ interface Migration { apply: (ctx: MigrationCtx) => void; } -const VSCODE_EXTENSIONS = `{ - "recommendations": ["wrnexus.wrnexus", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] -} -`; - -const LEGACY_VSCODE_EXTENSIONS = - JSON.stringify( - { - recommendations: ["wrnexus.wrnexus", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"], - }, - null, - 2, - ) + "\n"; - function walkProjectFiles(dir: string, extension: string): string[] { if (!existsSync(dir)) return []; const out: string[] = []; @@ -380,21 +366,6 @@ export function migrateV060WrnSource( return migrateWrnSource(next); } -function updateV060Config(ctx: MigrationCtx): void { - const candidates = ["wrnexus.config.ts", "wrnexus.config.js", "wrnexus.config.mjs"]; - const file = candidates.map((name) => join(ctx.appRoot, name)).find(existsSync); - if (!file) return; - const current = readFileSync(file, "utf8"); - 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 = - current.slice(0, index).replace(/,?\s*$/, "") + "," + insertion + "\n" + current.slice(index); - ctx.log(`~ ${file.slice(ctx.appRoot.length + 1)}: v0.6 compatibility configuration`); - if (!ctx.dryRun) writeFileSync(file, next, "utf8"); -} - interface V060SymbolIndex { components: Map; layouts: Map; @@ -578,1330 +549,12 @@ function migrateImportedLayout( return insertImports(source.replace(match[0], `layout = ${symbol}`), [statement]); } -function writeMigrationReport(ctx: MigrationCtx): void { - const file = join(ctx.appRoot, ".wrnexus", "migrations", "0.6.0-report.json"); - ctx.log(`+ .wrnexus/migrations/0.6.0-report.json`); - if (ctx.dryRun) return; - mkdirSync(dirname(file), { recursive: true }); - writeFileSync( - file, - JSON.stringify( - { - version: "0.6.0", - from: ctx.from, - to: ctx.to, - appliedAt: new Date().toISOString(), - ...ctx.report, - }, - null, - 2, - ) + "\n", - "utf8", - ); -} - /** * Versioned, idempotent upgrade steps. Each MUST be safe to re-run. Source * migrations must preserve semantics and are protected by update backups. */ -function updateV070Config(ctx: MigrationCtx): void { - const candidates = ["wrnexus.config.ts", "wrnexus.config.js", "wrnexus.config.mjs"]; - const file = candidates.map((name) => join(ctx.appRoot, name)).find(existsSync); - if (!file) return; - const current = readFileSync(file, "utf8"); - const additions: string[] = []; - if (!/\bnavigation\s*:/.test(current)) { - additions.push(` navigation: { - mode: "auto", - }`); - } - if (!/\bsecurity\s*:/.test(current)) { - additions.push(` security: { - headers: true, - requestLimits: { - maxUrlLength: 8192, - maxHeaderCount: 100, - maxHeaderBytes: 32768, - maxQueryParameters: 100, - maxBodyBytes: 10485760, - timeoutMs: 30000, - maxConcurrent: 1000, - fetchMetadata: true, - }, - contentSecurityPolicy: { enabled: true, useDefaults: true }, - trustedTypes: { enabled: true, requireForScript: true }, - hsts: { enabled: true, maxAge: 63072000, includeSubDomains: true, preload: true }, - }`); - } - if (!/\bperformance\s*:/.test(current)) { - additions.push(` performance: { - enforcement: "warn", - analyze: true, - budgets: { - routeJsBytes: 51200, - routeCssBytes: 25600, - htmlBytes: 204800, - hydrationMs: 200, - serverRenderMs: 500, - lcpMs: 2500, - inpMs: 200, - cls: 0.1, - ttfbMs: 800, - }, - }`); - } - if (!/\bobservability\s*:/.test(current)) { - additions.push(` observability: { - enabled: true, - serverTiming: true, - sampleRate: 0.1, - exporter: "none", - webVitals: true, - }`); - } - if (!additions.length) return; - const index = current.lastIndexOf("}"); - if (index < 0) return; - const before = current.slice(0, index).replace(/,?\s*$/, ""); - const next = `${before},\n${additions.join(",\n")}\n${current.slice(index)}`; - ctx.log(`~ ${file.slice(ctx.appRoot.length + 1)}: security/performance production defaults`); - if (!ctx.dryRun) writeFileSync(file, next, "utf8"); -} - const MIGRATIONS: Migration[] = [ - { - version: "0.2.8", - id: "gitignore-artifacts", - description: "Ensure .gitignore covers framework build artifacts", - apply(ctx) { - const file = join(ctx.appRoot, ".gitignore"); - const want = ["node_modules/", "dist/", ".wrnexus/", "*.db"]; - const current = existsSync(file) ? readFileSync(file, "utf8") : ""; - const have = new Set(current.split(/\r?\n/).map((l) => l.trim())); - const missing = want.filter((w) => !have.has(w)); - if (missing.length === 0) return; - ctx.log(`+ .gitignore: ${missing.join(", ")}`); - if (ctx.dryRun) return; - const next = current.replace(/\s*$/, "") + "\n" + missing.join("\n") + "\n"; - writeFileSync(file, next.replace(/^\n+/, ""), "utf8"); - }, - }, - { - version: "0.2.14", - id: "complete-gitignore", - description: "Add the complete WRNexusJS generated-project ignore set", - apply(ctx) { - const file = join(ctx.appRoot, ".gitignore"); - const want = [ - "node_modules/", - "dist/", - ".wrnexus/", - "**/.wrnexus/", - "coverage/", - ".env", - ".env.*", - "!.env.example", - "*.log", - "*.db", - "*.db-shm", - "*.db-wal", - "*.sqlite", - "*.sqlite3", - "mobile/android/", - "mobile/ios/", - "mobile/.expo/", - ".idea/", - ".vscode/", - ".DS_Store", - "Thumbs.db", - "*.tsbuildinfo", - ".eslintcache", - ]; - const current = existsSync(file) ? readFileSync(file, "utf8") : ""; - const have = new Set(current.split(/\r?\n/).map((line) => line.trim())); - const missing = want.filter((entry) => !have.has(entry)); - if (!missing.length) return; - ctx.log(`+ .gitignore: ${missing.join(", ")}`); - if (!ctx.dryRun) { - const next = `${current.replace(/\s*$/, "")}\n${missing.join("\n")}\n`.replace(/^\n+/, ""); - writeFileSync(file, next, "utf8"); - } - }, - }, - { - version: "0.2.14", - id: "production-scripts", - description: "Add standard production build and start scripts", - apply(ctx) { - // Workspace roots are manifests, not runnable applications. - if (!existsSync(join(ctx.appRoot, "app", "pages"))) return; - const file = join(ctx.appRoot, "package.json"); - const pkg = JSON.parse(readFileSync(file, "utf8")) as Record; - const scripts = (pkg.scripts ??= {}); - const additions: Record = {}; - if (!scripts.build) additions.build = "wrnexus build ."; - if (!scripts.start) additions.start = "bun dist/server.js"; - if (!scripts.production) additions.production = "bun run build && bun run start"; - if (!Object.keys(additions).length) return; - Object.assign(scripts, additions); - ctx.log(`+ package scripts: ${Object.keys(additions).join(", ")}`); - if (!ctx.dryRun) writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8"); - }, - }, - { - version: "0.2.18", - id: "add-helpers-package", - description: "Add @wrnexus/helpers to existing runnable applications", - apply(ctx) { - // Workspace roots do not consume application Context helpers directly. - if (!existsSync(join(ctx.appRoot, "app", "pages"))) return; - const file = join(ctx.appRoot, "package.json"); - const pkg = JSON.parse(readFileSync(file, "utf8")) as Record; - const dependencies = (pkg.dependencies ??= {} as Record); - if (dependencies["@wrnexus/helpers"]) return; - dependencies["@wrnexus/helpers"] = `^${ctx.to}`; - ctx.log(`+ @wrnexus/helpers ^${ctx.to}`); - if (!ctx.dryRun) writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8"); - }, - }, - { - version: "0.2.19", - id: "vscode-formatting", - description: "Add safe VS Code format-on-save defaults and extension recommendations", - apply(ctx) { - const vscode = join(ctx.appRoot, ".vscode"); - const settings = join(vscode, "settings.json"); - const extensions = join(vscode, "extensions.json"); - const prettierIgnore = join(ctx.appRoot, ".prettierignore"); - const gitignore = join(ctx.appRoot, ".gitignore"); - - const files: Array<[string, string, string]> = [ - [ - settings, - JSON.stringify( - { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, - "prettier.requireConfig": true, - "[wrn]": { - "editor.defaultFormatter": "wrnexus.wrnexus", - "editor.formatOnSave": true, - }, - }, - null, - 2, - ) + "\n", - ".vscode/settings.json", - ], - [extensions, VSCODE_EXTENSIONS, ".vscode/extensions.json"], - ]; - - for (const [file, contents, label] of files) { - if (existsSync(file)) continue; - ctx.log(`+ ${label}`); - if (!ctx.dryRun) { - mkdirSync(vscode, { recursive: true }); - writeFileSync(file, contents, "utf8"); - } - } - - const prettier = existsSync(prettierIgnore) ? readFileSync(prettierIgnore, "utf8") : ""; - if (!new Set(prettier.split(/\r?\n/).map((line) => line.trim())).has("CLAUDE.md")) { - ctx.log("+ .prettierignore: CLAUDE.md"); - if (!ctx.dryRun) { - writeFileSync(prettierIgnore, `${prettier.replace(/\s*$/, "")}\nCLAUDE.md\n`, "utf8"); - } - } - - if (existsSync(gitignore)) { - const current = readFileSync(gitignore, "utf8"); - const lines = current.split(/\r?\n/); - const vscodeIgnore = lines.findIndex((line) => line.trim() === ".vscode/"); - const needed = [".vscode/*", "!.vscode/settings.json", "!.vscode/extensions.json"]; - if (vscodeIgnore >= 0 || needed.some((entry) => !lines.includes(entry))) { - ctx.log("~ .gitignore: track shared VS Code configuration"); - if (!ctx.dryRun) { - const filtered = lines.filter((line) => line.trim() !== ".vscode/"); - const have = new Set(filtered.map((line) => line.trim())); - for (const entry of needed) if (!have.has(entry)) filtered.push(entry); - writeFileSync(gitignore, filtered.join("\n").replace(/\n*$/, "\n"), "utf8"); - } - } - } - }, - }, - { - version: "0.2.20", - id: "normalize-vscode-extensions", - description: "Normalize the framework-generated VS Code recommendations file", - apply(ctx) { - const file = join(ctx.appRoot, ".vscode", "extensions.json"); - if (!existsSync(file) || readFileSync(file, "utf8") !== LEGACY_VSCODE_EXTENSIONS) return; - ctx.log("~ .vscode/extensions.json formatting normalized"); - if (!ctx.dryRun) writeFileSync(file, VSCODE_EXTENSIONS, "utf8"); - }, - }, - { - version: "0.2.21", - id: "in-process-hmr-runtime", - description: "No project-file changes; the in-process HMR behavior is runtime-only", - apply() { - // Explicit no-op: updating @wrnexus/dev-server is sufficient. - }, - }, - { - version: "0.2.22", - id: "nested-forward-auth-origin", - description: "No project-file changes; nested forward-auth headers are fixed at runtime", - apply() { - // Explicit no-op: updating @wrnexus/dev-server and helpers is sufficient. - }, - }, - { - version: "0.2.23", - id: "custom-required-validation-message", - description: "No project-file changes; custom required messages are provided at runtime", - apply() { - // Explicit no-op: updating @wrnexus/validation is sufficient. - }, - }, - { - version: "0.2.24", - id: "package-usage-documentation", - description: "No project-file changes; package usage documentation is expanded", - apply() { - // Explicit no-op: this release updates package and portal documentation only. - }, - }, - { - version: "0.2.25", - id: "reactive-state-attributes", - description: "No project-file changes; state expressions in attributes are reactive at runtime", - apply() { - // Explicit no-op: updating @wrnexus/compiler and @wrnexus/csr is sufficient. - }, - }, - { - version: "0.2.26", - id: "class-improvements-attributes", - description: - "Adds conditional class directives, route state parameters, formatter improvements, and HMR fixes.", - apply() { - // No project file migration is required for this framework release. - }, - }, - { - version: "0.2.27", - id: "component-improvements-tag", - description: "Adds Component renders system allowed 2 ways.", - apply() { - // No project file migration is required for this framework release. - }, - }, - { - version: "0.2.28", - id: "layout-component-improvements", - description: "Added new block layout for component and improved component rendering.", - apply() { - // No project file migration is required for this framework release. - }, - }, - { - version: "0.2.29", - id: "workspace-config-improvements", - description: - "Adds environment-aware workspace domains, app URL helpers, component tag syntax, layouts, and editor improvements.", - apply() { - // No generated project files require automatic changes. - // Existing applications remain compatible with this release. - }, - }, - { - version: "0.2.30", - id: "workspace-config-auth-improvements", - description: - "Adds dynamic workspace origins, workspace forward-auth resolution, layout support, component tag syntax, and editor improvements.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.31", - id: "lifecycle-improvements", - description: - "Adds component functions, lifecycle hooks, state watchers, optimized behavior cleanup, and CSR navigation lifecycle support.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.32", - id: "fix-runtime-bug-0-2-32", - description: - "Fixes safe encoding and browser parsing of multiline component behavior metadata.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.33", - id: "fix-runtime-bug-0-2-33", - description: - "Fixes safe encoding and browser parsing of multiline component behavior metadata.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.34", - id: "reactive-bug", - description: - "Fixes component behavior encoding, lifecycle and watcher metadata parsing, CSR cleanup, and reactive form control property updates.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.35", - id: "class-bug", - description: - "Fixes component behavior encoding, lifecycle and watcher metadata parsing, CSR cleanup, and reactive form control property updates.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.36", - id: "attribute-bug", - description: - "Adds support for Tailwind arbitrary-value class directives and improves reactive class bindings.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.37", - id: "loop-condition-fix", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.38", - id: "loop-condition-bug", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.39", - id: "server-function-bug", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.40", - id: "conditions-bug-0-2-40", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.41", - id: "conditions-bug-0-2-41", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.42", - id: "conditions-bug-0-2-42", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.43", - id: "conditions-bug-0-2-43", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.44", - id: "conditions-bug-0-2-44", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.45", - id: "conditions-bug-0-2-45", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.46", - id: "conditions-bug-0-2-46", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.47", - id: "conditions-bug-0-2-47", - description: - "Adds server-rendered component each/if blocks and fixes dynamic component rendering.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.48", - id: "components-update-0-2-48", - description: "Adds reactive hydration support for server-rendered each loop locals.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.49", - id: "components-update-0-2-49", - description: "Adds reactive hydration support for server-rendered each loop locals.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.50", - id: "components-update-0-2-50", - description: "Adds reactive hydration support for server-rendered each loop locals.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.51", - id: "ui-package-repair", - description: "Repairs bundled UI component syntax and app-local stylesheet processing.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.52", - id: "shared-marketing-components", - description: "Adds reusable FAQ, announcement, and back-to-top UI components.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.53", - id: "faq-accordion-tailwind-safelist", - description: "Ensures conditional FAQ accordion layout utilities are generated by Tailwind.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.54", - id: "essential-ui-component-system", - description: "Adds the PDF-defined minimum and essential shared UI component system.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.55", - id: "expanded-ui-foundation-catalog", - description: - "Adds the expanded typography, layout, action, surface, card, and indicator catalog.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.56", - id: "ui-motion-system", - description: - "Adds consistent, accessible motion and modern interactions across the shared UI catalog.", - apply() { - // No generated application files require automatic migration. - }, - }, - { - version: "0.2.57", - id: "semantic-theme-palettes", - description: - "Adds eight semantic UI palettes and validated complete custom palette configuration.", - apply() { - // Existing theme configuration remains compatible; blue is the default palette. - }, - }, - { - version: "0.2.58", - id: "complete-ui-component-catalog", - description: - "Completes the PDF-aligned UI catalog and ships generated props, slots, and events metadata.", - apply() { - // UI components remain auto-discovered; no application files require migration. - }, - }, - { - version: "0.2.59", - id: "typed-wrn-declarations", - description: - "Adds explicit prop and state types, local type declarations, and typed component runtime validation.", - apply() { - // Existing inferred declarations remain compatible; typed syntax is opt-in. - }, - }, - { - version: "0.2.60", - id: "page-owned-shell-backgrounds", - description: "Lets pages own their backgrounds by keeping shared page shells transparent.", - apply() { - // The UI stylesheet update applies automatically. - }, - }, - { - version: "0.2.61", - id: "spacing-neutral-component-boundaries", - description: - "Removes default outer component spacing and standardizes root class customization.", - apply() { - // The UI component and stylesheet updates apply automatically. - }, - }, - { - version: "0.2.62", - id: "application-theme-contract-and-public-ui", - description: - "Exposes semantic theme helpers to application UI and promotes the public-site components into @wrnexus/ui.", - apply() { - // Package auto-discovery and the root-level theme aliases apply automatically. - }, - }, - { - version: "0.2.63", - id: "polished-linked-catalog-cards", - description: - "Fixes visually hidden full-card links and improves channel, integration, and campaign card hierarchy and interaction.", - apply() { - // The shared UI stylesheet update applies automatically. - }, - }, - { - version: "0.2.64", - id: "production-workspace-orchestrator", - description: - "Adds a first-class production command that builds, migrates, and serves every registered workspace app.", - apply() { - // Existing workspaces can invoke `wrnexus production` without generated-file changes. - }, - }, - { - version: "0.2.65", - id: "private-production-app-listeners", - description: - "Restricts workspace-managed production app servers to loopback so external clients must use the gateway.", - apply() { - // The production gateway applies the private bind address automatically. - }, - }, - { - version: "0.2.66", - id: "named-workspace-environments-and-reusable-public-chrome", - description: - "Adds top-level named workspace environment commands and data-driven public announcement, header, and footer components.", - apply() { - // Existing workspaces gain the CLI command aliases and optional UI props automatically. - }, - }, - { - version: "0.2.67", - id: "configurable-workspace-environment-runtime", - description: - "Adds per-environment runtime, HMR, build, migration, profile, hostname, and port controls to workspace commands.", - apply() { - // Existing workspace configurations remain compatible; new controls are opt-in. - }, - }, - { - version: "0.2.68", - id: "production-request-error-logging", - description: - "Logs production request exceptions, forward-auth verifier failures, proxy failures, and unexpected app exits with actionable context.", - apply() { - // Runtime diagnostics improve automatically after updating and redeploying. - }, - }, - { - version: "0.2.69", - id: "centralized-production-error-diagnostics", - description: - "Relays bounded internal app error diagnostics to gateway logs and strips them from public responses.", - apply() { - // Runtime diagnostics improve automatically after updating and rebuilding. - }, - }, - { - version: "0.2.70", - id: "direct-production-runtime-dependency", - description: - "Declares @wrnexus/dev-server directly in runnable apps so production bundles resolve the matching runtime release.", - apply(ctx) { - if (!existsSync(join(ctx.appRoot, "app", "pages"))) return; - const file = join(ctx.appRoot, "package.json"); - const pkg = JSON.parse(readFileSync(file, "utf8")) as Record; - const dependencies = (pkg.dependencies ??= {} as Record); - if (dependencies["@wrnexus/dev-server"] === `^${ctx.to}`) return; - dependencies["@wrnexus/dev-server"] = `^${ctx.to}`; - ctx.log(`+ @wrnexus/dev-server ^${ctx.to}`); - if (!ctx.dryRun) writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8"); - }, - }, - { - version: "0.2.71", - id: "public-forward-auth-redirect-origins", - description: - "Builds SSO redirects and returnTo URLs from configured public app origins instead of internal gateway listeners.", - apply() { - // Public URL handling improves automatically after updating and rebuilding. - }, - }, - { - version: "0.2.72", - id: "fresh-linked-ui-compilation", - description: - "Refreshes the shared UI catalog and makes WRN compilation caches follow source content across package updates and linked dependencies.", - apply() { - // Component and cache behavior improves automatically after updating. - }, - }, - { - version: "0.2.73", - id: "polished-auth-component-visuals", - description: - "Updates shared authentication actions and notices with reliable built-in icons, semantic color treatments, and polished sizing.", - apply() { - // Shared UI visuals improve automatically after updating and rebuilding. - }, - }, - { - version: "0.2.74", - id: "wrn-imports-and-dynamic-public-shell", - description: - "Adds top-level WRN imports and data-driven public header and footer navigation, actions, links, and labels.", - apply() { - // Language and shared UI behavior improve automatically after updating. - }, - }, - { - version: "0.2.75", - id: "dev-toolbar", - description: "Dev toolbar for optimization and developer.", - apply() { - // Language and shared UI behavior improve automatically after updating. - }, - }, - { - version: "0.2.76", - id: "dev-toolbar-fix", - description: "Dev toolbar for optimization and developer.", - apply() { - // Language and shared UI behavior improve automatically after updating. - }, - }, - { - version: "0.2.77", - id: "ui-components-fix", - description: "Dev toolbar for optimization and developer.", - apply() { - // Language and shared UI behavior improve automatically after updating. - }, - }, - { - version: "0.2.78", - id: "ui-components-fix-2", - description: "Dev toolbar for optimization and developer.", - apply() { - // Language and shared UI behavior improve automatically after updating. - }, - }, - { - version: "0.2.79", - id: "ui-components-fix-3", - description: "Dev toolbar for optimization and developer.", - apply() { - // Language and shared UI behavior improve automatically after updating. - }, - }, - { - version: "0.3.0", - id: "language-foundation-and-compatible-platform-upgrade", - description: - "Adds the shared syntax/AST package, stable diagnostics, plugins, partial hydration, advanced routing, build analysis, tracing, typed data APIs, and safe WRN source normalization.", - apply(ctx) { - const runnable = existsSync(join(ctx.appRoot, "app", "pages")); - const packageFile = join(ctx.appRoot, "package.json"); - const pkg = JSON.parse(readFileSync(packageFile, "utf8")) as Record; - const scripts = (pkg.scripts ??= {}); - const dependencies = (pkg.dependencies ??= {}); - const addedScripts: string[] = []; - const addedDependencies: string[] = []; - if (runnable) { - for (const [name, command] of Object.entries({ - doctor: "wrnexus doctor .", - "config:explain": "wrnexus config . --explain", - analyze: "wrnexus analyze .", - "update:preview": "wrnexus update . --dry-run", - })) { - if (!scripts[name]) { - scripts[name] = command; - addedScripts.push(name); - } - } - for (const name of ["@wrnexus/syntax", "@wrnexus/plugin"]) { - if (!dependencies[name]) { - dependencies[name] = `^${ctx.to}`; - addedDependencies.push(name); - } - } - } - if (addedScripts.length) ctx.log(`+ package scripts: ${addedScripts.join(", ")}`); - if (addedDependencies.length) ctx.log(`+ dependencies: ${addedDependencies.join(", ")}`); - if (!ctx.dryRun && (addedScripts.length || addedDependencies.length)) { - writeFileSync(packageFile, JSON.stringify(pkg, null, 2) + "\n", "utf8"); - } - - const changedFiles: string[] = []; - for (const file of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const before = readFileSync(file, "utf8"); - const after = migrateWrnSource(before); - if (after === before) continue; - changedFiles.push(file.slice(ctx.appRoot.length + 1).replace(/\\/g, "/")); - ctx.log(`~ normalized ${changedFiles[changedFiles.length - 1]}`); - if (!ctx.dryRun) writeFileSync(file, after, "utf8"); - } - - const reportFile = join(ctx.appRoot, ".wrnexus", "migrations", "0.3.0.json"); - ctx.log(`+ .wrnexus/migrations/0.3.0.json (${changedFiles.length} source files normalized)`); - if (!ctx.dryRun) { - mkdirSync(dirname(reportFile), { recursive: true }); - writeFileSync( - reportFile, - JSON.stringify( - { - version: "0.3.0", - from: ctx.from, - appliedAt: new Date().toISOString(), - changedFiles, - compatibility: { - legacyDataFor: true, - legacyEachBlocks: true, - legacyComponentMounts: true, - quotedDynamicAttributes: true, - }, - }, - null, - 2, - ) + "\n", - "utf8", - ); - } - }, - }, - { - version: "0.3.1", - id: "pending-update-marker-verification-fix", - description: - "Allows warning-level doctor checks during pre-commit update verification so successfully verified migrations can commit their new version marker.", - apply() { - // CLI-only transaction fix. No generated application files require migration. - }, - }, - { - version: "0.3.2", - id: "ssr-page-state-control-scope", - description: "Makes page state variables available to server-rendered if and each expressions.", - apply() { - // Compiler-only fix. Existing WRN source files require no migration. - }, - }, - { - version: "0.3.3", - id: "component-fix-0-3-3", - description: "UI component fixes.", - apply() { - // Compiler-only fix. Existing WRN source files require no migration. - }, - }, - { - version: "0.3.4", - id: "component-fix-0-3-4", - description: "UI component fixes.", - apply() { - // Compiler-only fix. Existing WRN source files require no migration. - }, - }, - { - version: "0.3.5", - id: "new-component-added-0-3-5", - description: "UI component Added.", - apply() { - // Compiler-only fix. Existing WRN source files require no migration. - }, - }, - { - version: "0.3.6", - id: "new-component-added-0-3-6", - description: "UI component Added.", - apply() { - // Compiler-only fix. Existing WRN source files require no migration. - }, - }, - { - version: "0.4.0", - id: "package-runtime-and-asset-platform", - description: - "Enables automatic package discovery, package components/routes/migrations, and page-scoped client runtime injection without manually copied JavaScript assets.", - apply(ctx) { - const packageFile = join(ctx.appRoot, "package.json"); - const pkg = JSON.parse(readFileSync(packageFile, "utf8")) as Record; - const scripts = (pkg.scripts ??= {}); - const additions: Record = { - "inspect:plugins": "wrnexus inspect plugins .", - "inspect:runtimes": "wrnexus inspect runtimes .", - "inspect:assets": "wrnexus inspect assets .", - "inspect:routes": "wrnexus inspect routes .", - }; - const addedScripts: string[] = []; - for (const [name, command] of Object.entries(additions)) { - if (!scripts[name]) { - scripts[name] = command; - addedScripts.push(name); - } - } - if (addedScripts.length) ctx.log(`+ package scripts: ${addedScripts.join(", ")}`); - if (!ctx.dryRun && addedScripts.length) { - writeFileSync(packageFile, JSON.stringify(pkg, null, 2) + "\n", "utf8"); - } - - const changedFiles: string[] = []; - const legacyCaptchaScript = - /\s*]*\bsrc\s*=\s*["']\/(?:__wrnexus\/captcha|assets\/wrnexus\/captcha)\.js(?:\?[^"']*)?["'][^>]*>(?:\s*<\/script>)?\s*/gi; - for (const file of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const before = readFileSync(file, "utf8"); - const after = before.replace(legacyCaptchaScript, "\n"); - if (after === before) continue; - const relativeFile = file.slice(ctx.appRoot.length + 1).replace(/\\/g, "/"); - changedFiles.push(relativeFile); - ctx.log(`~ removed legacy CAPTCHA script tag from ${relativeFile}`); - if (!ctx.dryRun) writeFileSync(file, after, "utf8"); - } - - const movedAssets: string[] = []; - for (const relativeFile of [ - "public/assets/wrnexus/captcha.js", - "public/__wrnexus/captcha.js", - ]) { - const source = join(ctx.appRoot, relativeFile); - if (!existsSync(source)) continue; - const destination = join(ctx.appRoot, ".wrnexus", "legacy-assets", "0.4.0", relativeFile); - movedAssets.push(relativeFile); - ctx.log(`~ archived legacy ${relativeFile}`); - if (!ctx.dryRun) { - mkdirSync(dirname(destination), { recursive: true }); - cpSync(source, destination); - rmSync(source, { force: true }); - } - } - - const reportFile = join(ctx.appRoot, ".wrnexus", "migrations", "0.4.0.json"); - ctx.log( - `+ .wrnexus/migrations/0.4.0.json (${changedFiles.length} source files, ${movedAssets.length} legacy assets)`, - ); - if (!ctx.dryRun) { - mkdirSync(dirname(reportFile), { recursive: true }); - writeFileSync( - reportFile, - JSON.stringify( - { - version: "0.4.0", - from: ctx.from, - appliedAt: new Date().toISOString(), - changedFiles, - movedAssets, - packageRuntimeDiscovery: true, - manualCaptchaRuntimeRequired: false, - }, - null, - 2, - ) + "\n", - "utf8", - ); - } - }, - }, - { - version: "0.5.0", - id: "custom-ui-components-and-auth-platform", - description: - "Adds the authentication package, custom form controls, expanded UI component events, and reliable development hot reloading.", - apply() { - // Package, component, and development-runtime improvements apply automatically. - }, - }, - { - version: "0.5.1", - id: "native-wrn-json-expressions", - description: - "Adds native array and object state initializers plus JSX-style expression props in WRN component markup.", - apply() { - // Syntax and compiler support are package-managed; existing applications remain compatible. - }, - }, - { - version: "0.5.2", - id: "multiline-wrn-json-and-stable-gateway-runtime", - description: - "Supports formatted multiline array and object state initializers and keeps gateway children on the matching framework runtime.", - apply() { - // Syntax, compiler, and gateway fixes are package-managed; no application rewrite is required. - }, - }, - { - version: "0.5.3", - id: "public-workspace-origins-and-schema-backed-auth-forms", - description: - "Keeps cross-app redirects on configured public origins and enables schema-backed auth forms with active navigation states.", - apply() { - // Gateway, helper, validation, and UI improvements are package-managed. - }, - }, - { - version: "0.5.10", - id: "latest-release-after-upgrade", - description: "Latest New Packages and release ready packages", - apply() { - // Gateway, helper, validation, and UI improvements are package-managed. - }, - }, - { - version: "0.5.11", - id: "latest-release-after-upgrade", - description: "Latest New Packages and release ready packages", - apply() { - // Gateway, helper, validation, and UI improvements are package-managed. - }, - }, - { - version: "0.5.11", - id: "latest-release-after-upgrade", - description: "Latest New Packages and release ready packages", - apply() { - // Gateway, helper, validation, and UI improvements are package-managed. - }, - }, - { - version: "0.5.12", - id: "latest-release-after-upgrade", - description: "Latest New Packages and release ready packages", - apply() { - // Gateway, helper, validation, and UI improvements are package-managed. - }, - }, - { - version: "0.5.13", - id: "latest-release-after-upgrade", - description: "Latest New Packages and release ready packages", - apply() { - // Gateway, helper, validation, and UI improvements are package-managed. - }, - }, - { - version: "0.5.13", - id: "preserve-theme-accent-during-hmr", - description: "Preserves the user's active theme and accent palette during HMR morph updates.", - apply() { - // Runtime-only framework fix. No project files require migration. - }, - }, - { - version: "0.5.14", - id: "preserve-theme-accent-during-hmr", - description: "Preserves the user's active theme and accent palette during HMR morph updates.", - apply() { - // Runtime-only framework fix. No project files require migration. - }, - }, - { - version: "0.5.15", - id: "wrn-style-block-head-pipeline", - description: - "Promotes page, layout, and component style blocks into the document head after global CSS with CSP, HMR, and CSR navigation support.", - apply() { - // Compiler, SSR, dev-server, and CSR runtime changes apply automatically. - }, - }, - { - version: "0.6.0", - id: "0.6.0-01-parser-and-language", - description: - "Enables the v0.6 parser, AST, typed state, imports, stores, outputs, and runtime function grammar.", - apply() {}, - }, - { - version: "0.6.0", - id: "0.6.0-02-function-runtime", - description: - "Classifies unambiguous legacy functions as client, server, or shared while preserving ambiguous behavior.", - apply(ctx) { - for (const file of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const before = readFileSync(file, "utf8"); - const relative = file.slice(ctx.appRoot.length + 1).replace(/\\/g, "/"); - const after = classifyLegacyFunctions(before, ctx.report, relative); - if (after === before) continue; - ctx.log(`~ ${relative}: runtime function modifiers`); - ctx.report.changedAutomatically.push(relative); - if (!ctx.dryRun) writeFileSync(file, after, "utf8"); - } - }, - }, - { - version: "0.6.0", - id: "0.6.0-03-typed-outputs", - description: - "Converts legacy @event declarations to typed outputs with safe unknown payload fallbacks.", - apply(ctx) { - for (const file of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const before = readFileSync(file, "utf8"); - const relative = file.slice(ctx.appRoot.length + 1).replace(/\\/g, "/"); - const legacy = collectLegacyEvents(before); - const after = ensureOutputsBlock(legacy.source, legacy.outputs, ctx.report, relative); - if (after === before) continue; - ctx.log(`~ ${relative}: typed outputs`); - ctx.report.changedAutomatically.push(relative); - if (!ctx.dryRun) writeFileSync(file, after, "utf8"); - } - }, - }, - { - version: "0.6.0", - id: "0.6.0-04-remove-emit", - description: - "Migrates static $emit calls and event.detail consumers to output.name(payload) and payload.", - apply(ctx) { - for (const file of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const before = readFileSync(file, "utf8"); - const relative = file.slice(ctx.appRoot.length + 1).replace(/\\/g, "/"); - const after = convertLegacyEmit(before, ctx.report, relative).replace( - /\bevent\.detail\b/g, - "payload", - ); - if (after === before) continue; - ctx.log(`~ ${relative}: callable outputs`); - ctx.report.changedAutomatically.push(relative); - if (!ctx.dryRun) writeFileSync(file, after, "utf8"); - } - }, - }, - { - version: "0.6.0", - id: "0.6.0-05-explicit-imports", - description: - "Enables compatible explicit imports and records unresolved symbols without guessing.", - apply(ctx) { - if (!ctx.explicitImports) return; - const index = buildV060SymbolIndex(ctx.appRoot); - for (const path of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const relativeFile = path.slice(ctx.appRoot.length + 1).replace(/\\/g, "/"); - const before = readFileSync(path, "utf8"); - const after = addExplicitImportsToSource( - before, - relativeFile, - ctx.appRoot, - index, - ctx.report, - ); - if (after === before) continue; - ctx.log(`~ ${relativeFile}: explicit imports`); - if (!ctx.report.changedAutomatically.includes(relativeFile)) - ctx.report.changedAutomatically.push(relativeFile); - if (!ctx.dryRun) writeFileSync(path, after, "utf8"); - } - }, - }, - { - version: "0.6.0", - id: "0.6.0-06-layout-imports", - description: - "Converts safely resolvable string layouts to imported symbols while retaining compatibility.", - apply(ctx) { - if (!ctx.explicitImports) return; - const index = buildV060SymbolIndex(ctx.appRoot); - for (const path of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const relativeFile = path.slice(ctx.appRoot.length + 1).replace(/\\/g, "/"); - const before = readFileSync(path, "utf8"); - const after = migrateImportedLayout(before, relativeFile, ctx.appRoot, index, ctx.report); - if (after === before) continue; - ctx.log(`~ ${relativeFile}: imported layout`); - if (!ctx.report.changedAutomatically.includes(relativeFile)) - ctx.report.changedAutomatically.push(relativeFile); - if (!ctx.dryRun) writeFileSync(path, after, "utf8"); - } - }, - }, - { - version: "0.6.0", - id: "0.6.0-07-component-contracts", - description: "Enables generated component, output, function, and declaration contracts.", - apply() {}, - }, - { - version: "0.6.0", - id: "0.6.0-08-store-support", - description: "Adds @wrnexus/store and @wrnexus/typecheck to runnable applications.", - apply(ctx) { - if (!existsSync(join(ctx.appRoot, "app", "pages"))) return; - const file = join(ctx.appRoot, "package.json"); - const pkg = JSON.parse(readFileSync(file, "utf8")) as Record; - const dependencies = (pkg.dependencies ??= {}); - const added: string[] = []; - for (const name of ["@wrnexus/store", "@wrnexus/typecheck"]) { - if (dependencies[name] === `^${ctx.to}`) continue; - dependencies[name] = `^${ctx.to}`; - added.push(name); - } - if (added.length) ctx.log(`+ dependencies: ${added.join(", ")}`); - if (!ctx.dryRun && added.length) - writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8"); - }, - }, - { - version: "0.6.0", - id: "0.6.0-09-type-config", - description: - "Adds compatible imports, type checking, stores, and legacy behavior configuration.", - apply(ctx) { - // Workspace manifests are updated alongside each configured application, - // but they are not themselves runnable apps. Keep app-owned declarations - // under the individual app roots instead of creating `/app`. - if (!existsSync(join(ctx.appRoot, "app", "pages"))) return; - updateV060Config(ctx); - const types = join(ctx.appRoot, "app", "types"); - if (!existsSync(types)) { - ctx.log("+ app/types/global.d.ts"); - if (!ctx.dryRun) { - mkdirSync(types, { recursive: true }); - writeFileSync( - join(types, "global.d.ts"), - "// Application-wide ambient WRNexusJS types.\nexport {};\n", - "utf8", - ); - } - } - }, - }, - { - version: "0.6.0", - id: "0.6.0-10-showcase-and-ui", - description: "Finalizes v0.6 source normalization and writes the detailed migration report.", - apply(ctx) { - for (const file of walkProjectFiles(join(ctx.appRoot, "app"), ".wrn")) { - const before = readFileSync(file, "utf8"); - const relative = file.slice(ctx.appRoot.length + 1).replace(/\\/g, "/"); - const after = migrateWrnSource(before); - if (after === before) continue; - ctx.log(`~ ${relative}: final v0.6 normalization`); - if (!ctx.report.changedAutomatically.includes(relative)) - ctx.report.changedAutomatically.push(relative); - if (!ctx.dryRun) writeFileSync(file, after, "utf8"); - } - writeMigrationReport(ctx); - }, - }, - { - version: "0.7.0", - id: "0.7.0-01-security-performance-foundation", - description: - "Adds secure-by-default request limits, caching, image optimization, observability, benchmarks, and production hardening packages.", - apply(ctx) { - const file = join(ctx.appRoot, "package.json"); - if (existsSync(file)) { - const pkg = JSON.parse(readFileSync(file, "utf8")) as Record; - const dependencies = (pkg.dependencies ??= {}); - const devDependencies = (pkg.devDependencies ??= {}); - const added: string[] = []; - for (const name of [ - "@wrnexus/security", - "@wrnexus/cache", - "@wrnexus/image", - "@wrnexus/observability", - ]) { - if (dependencies[name] === `^${ctx.to}`) continue; - dependencies[name] = `^${ctx.to}`; - added.push(name); - } - if (devDependencies["@wrnexus/benchmark"] !== `^${ctx.to}`) { - devDependencies["@wrnexus/benchmark"] = `^${ctx.to}`; - added.push("@wrnexus/benchmark (dev)"); - } - if (added.length) { - ctx.log(`+ security/performance dependencies: ${added.join(", ")}`); - if (!ctx.dryRun) writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8"); - } - } - updateV070Config(ctx); - const gitignore = join(ctx.appRoot, ".gitignore"); - const currentIgnore = existsSync(gitignore) ? readFileSync(gitignore, "utf8") : ""; - const ignoreEntries = [".env", ".env.*", "!.env.example", "!.env.*.example"]; - const knownIgnore = new Set(currentIgnore.split(/\r?\n/).map((line) => line.trim())); - const missingIgnore = ignoreEntries.filter((entry) => !knownIgnore.has(entry)); - if (missingIgnore.length) { - ctx.log(`+ .gitignore secure environment templates: ${missingIgnore.join(", ")}`); - if (!ctx.dryRun) { - writeFileSync( - gitignore, - `${currentIgnore.replace(/\s*$/, "")}\n${missingIgnore.join("\n")}\n`.replace( - /^\n+/, - "", - ), - "utf8", - ); - } - } - const review = - "Review WRNexusJS 0.7 security policy, CSP allowlists, tenant authorization, upload scanners, cache adapters, and production performance budgets."; - if (!ctx.report.needsReview.includes(review)) ctx.report.needsReview.push(review); - }, - }, { version: "0.8.0", id: "0.8.0-01-package-kits", diff --git a/packages/cli/test/update-migration-floor.test.ts b/packages/cli/test/update-migration-floor.test.ts new file mode 100644 index 00000000..8e61f3bc --- /dev/null +++ b/packages/cli/test/update-migration-floor.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +test("no migration targets a version below 0.8.0", () => { + const source = readFileSync(join(import.meta.dir, "../src/update.ts"), "utf8"); + const versions = [...source.matchAll(/version:\s*"([0-9.]+)"/g)].map((match) => match[1]!); + + expect(versions.length).toBeGreaterThan(0); + + const belowFloor = versions.filter((version) => { + const [major, minor] = version.split(".").map(Number); + return major! === 0 && minor! < 8; + }); + + expect(belowFloor).toEqual([]); +}); diff --git a/packages/cli/test/update.test.ts b/packages/cli/test/update.test.ts index 73d0270a..2f8413d0 100644 --- a/packages/cli/test/update.test.ts +++ b/packages/cli/test/update.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { @@ -35,153 +35,6 @@ test("dependency updates use each package's independently published version", () expect(changes).toHaveLength(2); }); -test("updateApp migrates project files without marking an unverified update complete", () => { - const root = mkdtempSync(join(tmpdir(), "wrnexus-update-")); - mkdirSync(join(root, "app", "pages"), { recursive: true }); - writeFileSync( - join(root, "package.json"), - JSON.stringify({ - name: "legacy-app", - scripts: { dev: "wrnexus dev ." }, - dependencies: { "@wrnexus/core": "0.2.13" }, - wrnexus: { version: "0.2.13" }, - }), - ); - writeFileSync(join(root, ".gitignore"), "node_modules/\n"); - - try { - const result = updateApp(root, "0.2.14", false); - expect(result).not.toBeNull(); - const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); - expect(pkg.dependencies["@wrnexus/core"]).toBe("^0.2.14"); - expect(pkg.scripts.build).toBe("wrnexus build ."); - expect(pkg.scripts.start).toBe("bun dist/server.js"); - expect(pkg.scripts.production).toBe("bun run build && bun run start"); - expect(pkg.wrnexus.version).toBe("0.2.13"); - - const gitignore = readFileSync(join(root, ".gitignore"), "utf8"); - expect(gitignore).toContain("!.env.example"); - expect(gitignore).toContain("mobile/android/"); - expect(readFileSync(join(root, "public", "llms.txt"), "utf8")).toContain("# WrNexus"); - } finally { - rmSync(root, { recursive: true, force: true }); - } -}); - -test("0.2.18 migration adds helpers to existing apps and is idempotent", () => { - const root = mkdtempSync(join(tmpdir(), "wrnexus-update-")); - mkdirSync(join(root, "app", "pages"), { recursive: true }); - writeFileSync( - join(root, "package.json"), - JSON.stringify({ - name: "existing-app", - dependencies: { "@wrnexus/core": "^0.2.17" }, - wrnexus: { version: "0.2.17" }, - }), - ); - - try { - updateApp(root, "0.2.18", false); - updateApp(root, "0.2.18", false); - const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); - expect(pkg.dependencies["@wrnexus/helpers"]).toBe("^0.2.18"); - expect( - Object.keys(pkg.dependencies).filter((name) => name === "@wrnexus/helpers"), - ).toHaveLength(1); - } finally { - rmSync(root, { recursive: true, force: true }); - } -}); - -test("0.2.70 migration pins the production runtime directly in runnable apps", () => { - const root = mkdtempSync(join(tmpdir(), "wrnexus-update-")); - mkdirSync(join(root, "app", "pages"), { recursive: true }); - writeFileSync( - join(root, "package.json"), - JSON.stringify({ - name: "existing-app", - dependencies: { "@wrnexus/core": "^0.2.69" }, - wrnexus: { version: "0.2.69" }, - }), - ); - - try { - updateApp(root, "0.2.70", false); - updateApp(root, "0.2.70", false); - const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); - expect(pkg.dependencies["@wrnexus/dev-server"]).toBe("^0.2.70"); - expect( - Object.keys(pkg.dependencies).filter((name) => name === "@wrnexus/dev-server"), - ).toHaveLength(1); - } finally { - rmSync(root, { recursive: true, force: true }); - } -}); - -test("0.2.19 migration adds VS Code formatting defaults without overwriting settings", () => { - const root = mkdtempSync(join(tmpdir(), "wrnexus-update-")); - mkdirSync(join(root, "app", "pages"), { recursive: true }); - mkdirSync(join(root, ".vscode"), { recursive: true }); - writeFileSync( - join(root, "package.json"), - JSON.stringify({ name: "editor-app", wrnexus: { version: "0.2.18" } }), - ); - writeFileSync(join(root, ".gitignore"), ".vscode/\n"); - writeFileSync(join(root, ".prettierignore"), "node_modules/\n"); - writeFileSync(join(root, ".vscode", "settings.json"), '{"editor.tabSize":4}\n'); - - try { - updateApp(root, "0.2.19", false); - updateApp(root, "0.2.19", false); - - expect(readFileSync(join(root, ".vscode", "settings.json"), "utf8")).toBe( - '{"editor.tabSize":4}\n', - ); - expect(readFileSync(join(root, ".vscode", "extensions.json"), "utf8")).toContain( - "wrnexus.wrnexus", - ); - expect(readFileSync(join(root, ".vscode", "extensions.json"), "utf8")).toBe( - '{\n "recommendations": ["wrnexus.wrnexus", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]\n}\n', - ); - expect(readFileSync(join(root, ".prettierignore"), "utf8")).toBe("node_modules/\nCLAUDE.md\n"); - const gitignore = readFileSync(join(root, ".gitignore"), "utf8"); - expect(gitignore).not.toContain(".vscode/\n"); - expect(gitignore).toContain("!.vscode/settings.json"); - expect(gitignore).toContain("!.vscode/extensions.json"); - } finally { - rmSync(root, { recursive: true, force: true }); - } -}); - -test("0.2.20 migration repairs only the legacy generated recommendations layout", () => { - const root = mkdtempSync(join(tmpdir(), "wrnexus-update-")); - mkdirSync(join(root, "app", "pages"), { recursive: true }); - mkdirSync(join(root, ".vscode"), { recursive: true }); - writeFileSync( - join(root, "package.json"), - JSON.stringify({ name: "editor-app", wrnexus: { version: "0.2.19" } }), - ); - writeFileSync( - join(root, ".vscode", "extensions.json"), - JSON.stringify( - { - recommendations: ["wrnexus.wrnexus", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"], - }, - null, - 2, - ) + "\n", - ); - - try { - updateApp(root, "0.2.20", false); - expect(readFileSync(join(root, ".vscode", "extensions.json"), "utf8")).toBe( - '{\n "recommendations": ["wrnexus.wrnexus", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]\n}\n', - ); - } finally { - rmSync(root, { recursive: true, force: true }); - } -}); - test("update verification formats before checking and building", () => { expect( verificationCommands({ @@ -200,30 +53,6 @@ test("update verification skips unavailable scripts", () => { expect(verificationCommands({ check: "bun run lint" })).toEqual([["run", "check"]]); }); -test("0.6 type declarations are created only inside runnable apps", () => { - const workspaceRoot = mkdtempSync(join(tmpdir(), "wrnexus-update-workspace-")); - const appRoot = join(workspaceRoot, "apps", "web"); - mkdirSync(join(appRoot, "app", "pages"), { recursive: true }); - writeFileSync( - join(workspaceRoot, "package.json"), - JSON.stringify({ name: "workspace", wrnexus: { version: "0.5.9" } }), - ); - writeFileSync( - join(appRoot, "package.json"), - JSON.stringify({ name: "web", wrnexus: { version: "0.5.9" } }), - ); - - try { - updateApp(workspaceRoot, "0.6.0", false); - updateApp(appRoot, "0.6.0", false); - - expect(existsSync(join(workspaceRoot, "app", "types", "global.d.ts"))).toBe(false); - expect(existsSync(join(appRoot, "app", "types", "global.d.ts"))).toBe(true); - } finally { - rmSync(workspaceRoot, { recursive: true, force: true }); - } -}); - test("0.3 WRN source normalization is conservative and idempotent", async () => { const { migrateWrnSource } = await import("../src/update.ts"); const source = `component Card { @@ -282,66 +111,6 @@ test("update verification still blocks real doctor errors", () => { ).toEqual([fatal]); }); -test("0.4 migration removes manual CAPTCHA runtime wiring and archives copied assets", () => { - const root = mkdtempSync(join(tmpdir(), "wrnexus-update-")); - mkdirSync(join(root, "app", "pages"), { recursive: true }); - mkdirSync(join(root, "public", "assets", "wrnexus"), { recursive: true }); - writeFileSync( - join(root, "package.json"), - JSON.stringify({ - name: "captcha-app", - scripts: { dev: "wrnexus dev ." }, - dependencies: { "@wrnexus/captcha": "^0.3.6" }, - wrnexus: { version: "0.3.6" }, - }), - ); - writeFileSync( - join(root, "app", "pages", "index.wrn"), - `page Home { - view { - - - } - }`, - ); - writeFileSync( - join(root, "public", "assets", "wrnexus", "captcha.js"), - "window.legacyCaptcha = true;\n", - ); - - try { - updateApp(root, "0.4.0", false); - const page = readFileSync(join(root, "app", "pages", "index.wrn"), "utf8"); - const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8")); - expect(page).toContain(" { const root = mkdtempSync(join(tmpdir(), "wrnexus-update-current-source-")); mkdirSync(join(root, "app", "components"), { recursive: true });