diff --git a/docs/public-api-0.8.json b/docs/public-api-0.8.json index 20ab60f9..e0691583 100644 --- a/docs/public-api-0.8.json +++ b/docs/public-api-0.8.json @@ -1316,6 +1316,7 @@ "createOutputProxy", "createServerProxy", "getActionRuntime", + "getComponentControllerRuntime", "getNavRuntime", "getReactiveRuntime", "getRealtimeRuntime", diff --git a/examples/inter-app-api-showcase/apps/admin/package.json b/examples/inter-app-api-showcase/apps/admin/package.json index 070fe721..26370e04 100644 --- a/examples/inter-app-api-showcase/apps/admin/package.json +++ b/examples/inter-app-api-showcase/apps/admin/package.json @@ -53,11 +53,11 @@ "@iconify-json/lucide": "^1.2.118", "@iconify/tailwind4": "^1.2.3", "@tailwindcss/cli": "^4.0.0", - "@types/bun": "latest", + "@types/bun": "^1.3.14", "eslint": "^9.0.0", - "prettier": "latest", + "prettier": "^3.9.6", "tailwindcss": "^4.0.0", "typescript": "^5.5.0", - "typescript-eslint": "latest" + "typescript-eslint": "^8.65.0" } } diff --git a/examples/inter-app-api-showcase/apps/web/package.json b/examples/inter-app-api-showcase/apps/web/package.json index c1608733..d7c5fa72 100644 --- a/examples/inter-app-api-showcase/apps/web/package.json +++ b/examples/inter-app-api-showcase/apps/web/package.json @@ -53,11 +53,11 @@ "@iconify-json/lucide": "^1.2.118", "@iconify/tailwind4": "^1.2.3", "@tailwindcss/cli": "^4.0.0", - "@types/bun": "latest", + "@types/bun": "^1.3.14", "eslint": "^9.0.0", - "prettier": "latest", + "prettier": "^3.9.6", "tailwindcss": "^4.0.0", "typescript": "^5.5.0", - "typescript-eslint": "latest" + "typescript-eslint": "^8.65.0" } } diff --git a/examples/inter-app-api-showcase/package.json b/examples/inter-app-api-showcase/package.json index 1733ed14..3730770d 100644 --- a/examples/inter-app-api-showcase/package.json +++ b/examples/inter-app-api-showcase/package.json @@ -23,10 +23,10 @@ "devDependencies": { "@wrnexus/cli": "0.8.6", "@eslint/js": "^9.0.0", - "@types/bun": "latest", + "@types/bun": "^1.3.14", "eslint": "^9.0.0", - "prettier": "latest", + "prettier": "^3.9.6", "typescript": "^5.5.0", - "typescript-eslint": "latest" + "typescript-eslint": "^8.65.0" } } diff --git a/package.json b/package.json index 513eea99..2bb01659 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,8 @@ "format": "prettier . --write", "format:check": "prettier . --check", "check": "bun run typecheck && bun run lint && bun run check:component-imports && bun run test && bun run format:check", - "verify:0.3": "node scripts/verify-0.3.mjs", - "verify:0.4": "node scripts/verify-0.4.mjs", - "validate:0.4": "node scripts/validate-0.4.mjs", "auth:dev": "bun run --cwd examples/auth-showcase dev", "auth:check": "bun run --cwd packages/auth check", - "validate:auth": "node scripts/validate-auth.mjs", "validate:0.6": "node scripts/validate-0.6.mjs", "validate:0.7": "node scripts/validate-0.7.mjs", "security:framework": "node scripts/security-performance-audit.mjs", @@ -50,7 +46,7 @@ "check:ui-visual": "node scripts/check-ui-visual-contract.mjs", "generate:ui-visual": "node scripts/check-ui-visual-contract.mjs --write", "sbom": "node scripts/generate-sbom.mjs", - "benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs", + "benchmark:framework": "node --experimental-transform-types scripts/benchmark-framework.mjs --write", "check:production": "bun run check:workspace && bun run check:public-api && bun run check:ui-visual && bun run validate:0.8 && bun run security:framework && bun run security:asvs && bun run check:editor-compiler && bun run check:editor-language-server && bun run check:editor-extension && bun run check", "validate:staging": "node --experimental-transform-types scripts/test-package-integrity.mjs", "stage:packages": "bun run scripts/publish-packages.ts", diff --git a/scripts/benchmark-framework.mjs b/scripts/benchmark-framework.mjs index 543fdb19..e732f307 100644 --- a/scripts/benchmark-framework.mjs +++ b/scripts/benchmark-framework.mjs @@ -9,6 +9,7 @@ import { secureJsonStringify } from "../packages/security/src/serialization.ts"; import { parse } from "../packages/syntax/src/index.ts"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const writeReport = process.argv.includes("--write"); const frameworkVersion = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version; const componentSource = readFileSync( join(root, "packages", "ui", "components", "button.wrn"), @@ -60,12 +61,14 @@ const report = { environment: { runtime: process.version, platform: process.platform, arch: process.arch }, benchmarks: benchmarks.map(({ result, budget }) => ({ result, budget })), }; -const reportDir = join(root, ".wrnexus", "reports"); -mkdirSync(reportDir, { recursive: true }); -writeFileSync( - join(reportDir, `benchmark-${frameworkVersion}.json`), - `${JSON.stringify(report, null, 2)}\n`, -); +if (writeReport) { + const reportDir = join(root, ".wrnexus", "reports"); + mkdirSync(reportDir, { recursive: true }); + writeFileSync( + join(reportDir, `benchmark-${frameworkVersion}.json`), + `${JSON.stringify(report, null, 2)}\n`, + ); +} console.log( JSON.stringify( report.benchmarks.map(({ result }) => ({ diff --git a/scripts/install-captcha.mjs b/scripts/install-captcha.mjs index 24929fe6..1712a26e 100644 --- a/scripts/install-captcha.mjs +++ b/scripts/install-captcha.mjs @@ -12,11 +12,19 @@ const args = process.argv.slice(2); const positional = args.filter((arg) => !arg.startsWith("--")); const target = resolve(positional[0] ?? process.cwd()); const flags = new Set(args.filter((arg) => arg.startsWith("--"))); +const install = flags.has("--install"); const force = flags.has("--force"); const withShowcase = flags.has("--with-showcase"); const withManaged = flags.has("--with-managed-service"); const runChecks = flags.has("--check"); +if (!install) { + console.log( + "Dry run: captcha installation would update the target repository. Re-run with --install to apply it.", + ); + process.exit(0); +} + async function exists(path) { try { await stat(path); diff --git a/scripts/reset-ui-components.mjs b/scripts/reset-ui-components.mjs deleted file mode 100644 index f71df4a6..00000000 --- a/scripts/reset-ui-components.mjs +++ /dev/null @@ -1,447 +0,0 @@ -import { readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; -import process from "node:process"; - -const root = join(import.meta.dirname, ".."); -const uiRoot = join(root, "packages", "ui"); -const componentsDir = join(uiRoot, "components"); -const preservedButton = readFileSync(join(componentsDir, "Button.wrn"), "utf8"); -const preservedAdvancedSelect = readFileSync(join(componentsDir, "AdvancedSelect.wrn"), "utf8"); - -const groups = { - layout: [ - "Container", - "Columns", - "Grid", - "LayoutSplitter", - "Typography", - "Image", - "Link", - "Divider", - "Kbd", - "CustomScrollbar", - ], - base: [ - "Accordion", - "Alert", - "Avatar", - "AvatarGroup", - "Badge", - "Blockquote", - "Button", - "ButtonGroup", - "Card", - "ChatBubble", - "Carousel", - "Collapse", - "DatePicker", - "DeviceFrame", - "List", - "ListGroup", - "LegendIndicator", - "Progress", - "FileUploadProgress", - "Rating", - "Skeleton", - "Spinner", - "StyledIcon", - "Toast", - "Timeline", - "TreeView", - "Marquee", - ], - navigation: [ - "Navbar", - "MegaMenu", - "Nav", - "Tabs", - "Sidebar", - "Scrollspy", - "Breadcrumb", - "Pagination", - "Stepper", - ], - forms: [ - "Input", - "InputGroup", - "Textarea", - "FileInput", - "Checkbox", - "Radio", - "Switch", - "Select", - "RangeSlider", - "ColorPicker", - "TimePicker", - ], - "advanced-forms": [ - "AdvancedSelect", - "Combobox", - "SearchBox", - "InputNumber", - "StrongPassword", - "TogglePassword", - "ToggleCount", - "CopyMarkup", - "PinInput", - ], - overlays: ["Dropdown", "ContextMenu", "Modal", "Drawer", "Popover", "Tooltip"], - tables: ["DataTable"], - integrations: [ - "AdvancedRangeSlider", - "AdvancedDatePicker", - "Chart", - "Clipboard", - "Confetti", - "DataMap", - "DragAndDrop", - "FileUpload", - "Map", - "ToastNotifications", - "WysiwygEditor", - ], -}; - -const displayName = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1 $2"); -const kebab = (name) => displayName(name).replace(/\s+/g, "-").toLowerCase(); - -function layoutComponent(name) { - if (name === "Image") - return `component Image { - props { - src = "" - alt = "" - width = "" - height = "" - loading = "lazy" - rounded = false - class = "" - } - view { -
- {alt} - -
- } -} -`; - if (name === "Link") - return `component Link { - props { - label = "Link" - href = "#" - target = "" - rel = "" - external = false - class = "" - } - view { - {label}{#if external}{/if} - } -} -`; - if (name === "Divider") - return `component Divider { - props { - label = "" - orientation = "horizontal" - class = "" - } - view { - - } -} -`; - if (name === "Kbd") - return `component Kbd { - props { - label = "⌘ K" - class = "" - } - view { {label} } -} -`; - const tag = name === "Typography" ? "article" : "div"; - return `component ${name} { - props { - columns = 2 - gap = "md" - maxWidth = "xl" - class = "" - } - view { - <${tag} class="wire-next wire-next--${kebab(name)} wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"> - } -} -`; -} - -function collectionComponent(name) { - if (name === "Accordion" || name === "Collapse") - return `component ${name} { - props { - items = [] - multiple = false - class = "" - } - view { -
- {#each items as item}
{item.label}
{item.content}
{/each} - -
- } -} -`; - if (name === "Progress" || name === "FileUploadProgress") - return `component ${name} { - props { - label = "Progress" - value = 50 - max = 100 - showValue = true - class = "" - } - view { -
-
{label}{#if showValue}{value}%{/if}
- -
- } -} -`; - if (name === "Spinner" || name === "Skeleton") - return `component ${name} { - props { - label = "Loading" - size = "md" - lines = 3 - class = "" - } - view { -
- {#if "${name}" === "Skeleton"}{#each Array(lines).fill(0) as line}{/each}{:else}{/if} -
- } -} -`; - return `component ${name} { - props { - title = "${displayName(name)}" - description = "" - items = [] - variant = "default" - class = "" - } - view { -
- {#if title}{title}{/if} - {#if description}

{description}

{/if} - {#if items}
{#each items as item}{item.label}{/each}
{/if} - -
- } -} -`; -} - -function navigationComponent(name) { - return `component ${name} { - props { - label = "${displayName(name)}" - items = [] - active = "" - orientation = "horizontal" - class = "" - } - view { - - } -} -`; -} - -function formComponent(name) { - const typeByName = { - Input: "text", - SearchBox: "search", - InputNumber: "number", - StrongPassword: "password", - TogglePassword: "password", - PinInput: "text", - DatePicker: "date", - AdvancedDatePicker: "date", - TimePicker: "time", - ColorPicker: "color", - RangeSlider: "range", - AdvancedRangeSlider: "range", - FileInput: "file", - }; - if (name === "Textarea" || name === "WysiwygEditor") - return `component ${name} { - props { - label = "${displayName(name)}" - name = "" - value = "" - placeholder = "" - rows = 5 - disabled = false - required = false - class = "" - } - view { - - } -} -`; - if (name === "Checkbox" || name === "Radio" || name === "Switch") - return `component ${name} { - props { - label = "${displayName(name)}" - name = "" - value = "on" - checked = false - disabled = false - class = "" - } - view { - - } -} -`; - if (name.includes("Select") || name === "Combobox") - return `component ${name} { - props { - label = "${displayName(name)}" - name = "" - value = "" - options = [] - placeholder = "Select an option" - multiple = false - disabled = false - class = "" - } - view { - - } -} -`; - const inputType = typeByName[name] ?? "text"; - return `component ${name} { - props { - label = "${displayName(name)}" - name = "" - value = "" - placeholder = "" - type = "${inputType}" - min = "" - max = "" - step = "" - disabled = false - required = false - class = "" - } - view { - - } -} -`; -} - -function overlayComponent(name) { - const modal = name === "Modal" || name === "Drawer"; - return `component ${name} { - props { - title = "${displayName(name)}" - description = "" - open = false - placement = "bottom" - closeLabel = "Close" - class = "" - } - view { -
-
{title}
- {#if description}

{description}

{/if} - -
- } -} -`; -} - -function dataComponent(name) { - if (name === "DataTable") - return `component ${name} { - props { - caption = "${displayName(name)}" - columns = [] - rows = [] - striped = true - class = "" - } - view { -
{#each columns as column}{/each}{#each rows as row}{#each columns as column}{/each}{/each}
{caption}
{column.label}
{row[column.key]}
- } -} -`; - return collectionComponent(name); -} - -function sourceFor(name, category) { - if (name === "Button") return preservedButton; - if (name === "AdvancedSelect") return preservedAdvancedSelect; - if (category === "layout") return layoutComponent(name); - if (category === "navigation") return navigationComponent(name); - if (category === "forms" || category === "advanced-forms") return formComponent(name); - if (category === "overlays") return overlayComponent(name); - if (category === "tables" || category === "integrations") return dataComponent(name); - if (name === "DatePicker") return formComponent(name); - return collectionComponent(name); -} - -function withUniversalDesignProps(source) { - let next = source; - if (!/^\s+color\s*=/m.test(next)) { - next = next.replace(/(\s+props\s*\{\r?\n)/, '$1 color = "primary"\n'); - } - if (!/^\s+size\s*=/m.test(next)) { - next = next.replace(/(\s+props\s*\{\r?\n)/, '$1 size = "default"\n'); - } - return next.replace( - /class="wire-next(?![^"]*wire-next--color-)/, - 'class="wire-next wire-next--color-{color} wire-next--size-{size}', - ); -} - -const entries = Object.entries(groups).flatMap(([category, names]) => - names.map((name) => ({ - name, - category, - purpose: `Theme-aware, responsive ${displayName(name).toLowerCase()} component.`, - })), -); - -for (const file of readdirSync(componentsDir)) { - if (file.endsWith(".wrn")) unlinkSync(join(componentsDir, file)); -} -for (const entry of entries) { - writeFileSync( - join(componentsDir, `${entry.name}.wrn`), - withUniversalDesignProps(sourceFor(entry.name, entry.category)), - "utf8", - ); -} - -writeFileSync( - join(uiRoot, "component-catalog.json"), - `${JSON.stringify({ sourceDocuments: ["Screenshot-directed WRNexus UI catalog reset"], components: entries }, null, 2)}\n`, -); -writeFileSync( - join(uiRoot, "component-migrations.json"), - `${JSON.stringify({ generatedFrom: "full component catalog reset", removedCount: 0, replacements: {} }, null, 2)}\n`, -); - -process.stdout.write(`Reset @wrnexus/ui to ${entries.length} canonical components.\n`); diff --git a/scripts/validate-0.4.mjs b/scripts/validate-0.4.mjs deleted file mode 100644 index d657aaf5..00000000 --- a/scripts/validate-0.4.mjs +++ /dev/null @@ -1,28 +0,0 @@ -/* global console */ -import { spawnSync } from "node:child_process"; -import process from "node:process"; - -const root = process.cwd(); -const bun = process.platform === "win32" ? "bun.exe" : "bun"; - -function run(command, args) { - console.log(`\n> ${command} ${args.join(" ")}`); - const result = spawnSync(command, args, { - cwd: root, - stdio: "inherit", - env: process.env, - }); - if (result.error) throw result.error; - if (result.status !== 0) process.exit(result.status ?? 1); -} - -run(process.execPath, ["scripts/verify-0.4.mjs"]); -run(bun, ["run", "check"]); -run(bun, ["test", "integration"]); -run(bun, ["run", "--cwd", "examples/basic-app", "test"]); -run(bun, ["run", "--cwd", "examples/basic-app", "build"]); -run(bun, ["run", "--cwd", "examples/component-showcase", "check"]); -run(bun, ["run", "--cwd", "examples/captcha-showcase", "check"]); -run(bun, ["test", "services/managed-captcha"]); - -console.log("\n✓ WRNexusJS 0.4.0 complete validation passed.\n"); diff --git a/scripts/validate-0.6.mjs b/scripts/validate-0.6.mjs index a89369ca..26170a9f 100644 --- a/scripts/validate-0.6.mjs +++ b/scripts/validate-0.6.mjs @@ -335,8 +335,8 @@ for (const [label, condition] of [ [ "peer client-function bindings", clientCodegenSource.includes("context.functions") && - clientCodegenSource.includes("const scopedContext = { ...context, functions }") && - clientCodegenSource.includes("handler(scopedContext, ...args)"), + clientCodegenSource.includes("context.functions[name](...args)") && + clientCodegenSource.includes("(...__wrnexusPeerArgs) => context.functions"), ], [ "drift-free ToggleCount animation scheduling", diff --git a/scripts/validate-auth.mjs b/scripts/validate-auth.mjs deleted file mode 100644 index f09a830c..00000000 --- a/scripts/validate-auth.mjs +++ /dev/null @@ -1,24 +0,0 @@ -/* global console */ -import { spawnSync } from "node:child_process"; -import process from "node:process"; - -const root = process.cwd(); -const bun = process.platform === "win32" ? "bun.exe" : "bun"; - -function run(command, args) { - console.log(`\n> ${command} ${args.join(" ")}`); - const result = spawnSync(command, args, { - cwd: root, - stdio: "inherit", - env: process.env, - }); - if (result.error) throw result.error; - if (result.status !== 0) process.exit(result.status ?? 1); -} - -run(process.execPath, ["scripts/verify-auth.mjs"]); -run(bun, ["run", "typecheck"]); -run(bun, ["test", "packages/auth"]); -run(bun, ["run", "--cwd", "examples/auth-showcase", "check"]); - -console.log("\n✓ @wrnexus/auth validation passed.\n"); diff --git a/scripts/verify-0.3.mjs b/scripts/verify-0.3.mjs deleted file mode 100644 index 88abcca9..00000000 --- a/scripts/verify-0.3.mjs +++ /dev/null @@ -1,134 +0,0 @@ -import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; -import { join, relative } from "node:path"; -import process from "node:process"; - -const root = process.cwd(); -const VERSION = "0.3.0"; -const errors = []; -const notes = []; -const readJson = (file) => JSON.parse(readFileSync(file, "utf8")); - -function walk(dir, name = "package.json") { - const out = []; - if (!existsSync(dir)) return out; - for (const entry of readdirSync(dir)) { - if (entry === "node_modules" || entry === ".git" || entry === "dist") continue; - const path = join(dir, entry); - const stat = statSync(path); - if (stat.isDirectory()) out.push(...walk(path, name)); - else if (entry === name) out.push(path); - } - return out; -} - -const packageFiles = [join(root, "package.json"), ...walk(join(root, "packages"))]; -const packages = new Map(); -for (const file of packageFiles) { - const pkg = readJson(file); - if (pkg.name) packages.set(pkg.name, { file, pkg }); - if (file.includes(`${join(root, "packages")}/`) && pkg.version !== VERSION) { - errors.push(`${relative(root, file)} has version ${pkg.version ?? ""}`); - } -} -if (readJson(join(root, "package.json")).version !== VERSION) { - errors.push(`root package version is not ${VERSION}`); -} - -for (const { file, pkg } of packages.values()) { - for (const field of ["dependencies", "devDependencies", "peerDependencies"]) { - for (const [name, range] of Object.entries(pkg[field] ?? {})) { - if (String(range).startsWith("workspace:") && !packages.has(name)) { - errors.push(`${relative(root, file)} references missing workspace ${name}`); - } - } - } -} - -for (const required of [ - "packages/syntax/src/index.ts", - "packages/plugin/src/index.ts", - "scripts/build-editor-compiler.mjs", - "docs/WRN-LANGUAGE-SPEC-1.0.md", - "docs/ARCHITECTURE-0.3.md", - "docs/UPGRADE-0.3.md", - "docs/40-POINT-IMPLEMENTATION-0.3.md", - "docs/TEST-CHECKLIST-0.3.md", - "docs/AUDIT-0.3.md", -]) { - if (!existsSync(join(root, required))) errors.push(`missing ${required}`); -} - -const updateSource = readFileSync(join(root, "packages/cli/src/update.ts"), "utf8"); -const migrationPairs = [ - ...updateSource.matchAll(/version:\s*"([^"]+)"[\s\S]*?id:\s*"([^"]+)"/g), -].map((match) => ({ version: match[1], id: match[2] })); -const ids = new Set(); -for (const migration of migrationPairs) { - if (ids.has(migration.id)) errors.push(`duplicate migration id ${migration.id}`); - ids.add(migration.id); -} -if (!migrationPairs.some((migration) => migration.version === VERSION)) { - errors.push(`missing ${VERSION} updater migration`); -} - -for (const jsonFile of [ - "editors/vscode/package.json", - "editors/vscode/package-lock.json", - "editors/vscode/snippets/wrn.json", - "editors/vscode/syntaxes/wrn.tmLanguage.json", -]) { - try { - readJson(join(root, jsonFile)); - } catch (error) { - errors.push(`${jsonFile} is invalid JSON: ${error instanceof Error ? error.message : error}`); - } -} - -try { - const lockText = readFileSync(join(root, "bun.lock"), "utf8").replace(/,\s*([}\]])/g, "$1"); - const lock = JSON.parse(lockText); - for (const workspace of ["packages/syntax", "packages/plugin"]) { - if (!lock.workspaces?.[workspace]) errors.push(`bun.lock missing ${workspace}`); - } - for (const name of ["@wrnexus/syntax", "@wrnexus/plugin"]) { - if (!lock.packages?.[name]) errors.push(`bun.lock missing workspace link ${name}`); - } -} catch (error) { - errors.push(`bun.lock structure is invalid: ${error instanceof Error ? error.message : error}`); -} - -const compilerParser = readFileSync(join(root, "packages/compiler/src/parser.ts"), "utf8"); -if (!compilerParser.includes("@wrnexus/syntax")) { - errors.push("compiler parser is not a compatibility re-export of @wrnexus/syntax"); -} - -const tsconfig = readJson(join(root, "tsconfig.json")); -for (const name of packages.keys()) { - if (name.startsWith("@wrnexus/") && !tsconfig.compilerOptions?.paths?.[name]) { - errors.push(`tsconfig paths missing ${name}`); - } -} - -notes.push(`${packages.size} named packages checked`); -notes.push(`${migrationPairs.length} migration entries checked`); -notes.push("editor JSON and Bun workspace lock structure checked"); - -if (errors.length) { - process.stderr.write( - [ - `WRNexusJS ${VERSION} verification failed:`, - ...errors.map((error) => ` - ${error}`), - "", - ].join("\n"), - ); - - process.exitCode = 1; -} else { - process.stdout.write( - [ - `WRNexusJS ${VERSION} structural verification passed.`, - ...notes.map((note) => ` ✓ ${note}`), - "", - ].join("\n"), - ); -} diff --git a/scripts/verify-0.4.mjs b/scripts/verify-0.4.mjs deleted file mode 100644 index 5db0cab7..00000000 --- a/scripts/verify-0.4.mjs +++ /dev/null @@ -1,218 +0,0 @@ -import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; -import { join, relative } from "node:path"; -import process from "node:process"; - -const root = process.cwd(); -const VERSION = "0.4.0"; -const errors = []; -const notes = []; -const read = (file) => readFileSync(join(root, file), "utf8"); -const readJson = (file) => JSON.parse(read(file)); - -function files(dir, filename = "package.json") { - const output = []; - if (!existsSync(dir)) return output; - for (const entry of readdirSync(dir)) { - if (["node_modules", ".git", ".publish", "dist", ".wrnexus"].includes(entry)) { - continue; - } - const path = join(dir, entry); - const stat = statSync(path); - if (stat.isDirectory()) output.push(...files(path, filename)); - else if (entry === filename) output.push(path); - } - return output; -} - -const rootPackage = readJson("package.json"); -if (rootPackage.version !== VERSION) { - errors.push(`root package version is ${rootPackage.version ?? ""}, expected ${VERSION}`); -} - -const packageFiles = files(join(root, "packages")); -const packages = new Map(); -for (const file of packageFiles) { - const manifest = JSON.parse(readFileSync(file, "utf8")); - if (!manifest.name?.startsWith("@wrnexus/")) continue; - packages.set(manifest.name, { file, manifest }); - if (manifest.version !== VERSION) { - errors.push(`${relative(root, file)} has version ${manifest.version ?? ""}`); - } -} -if (packages.size !== 30) errors.push(`expected 30 framework packages, found ${packages.size}`); - -const serviceManifest = readJson("services/managed-captcha/package.json"); -if (serviceManifest.version !== VERSION) { - errors.push(`managed CAPTCHA service has version ${serviceManifest.version ?? ""}`); -} - -for (const { file, manifest } of packages.values()) { - for (const field of [ - "dependencies", - "devDependencies", - "peerDependencies", - "optionalDependencies", - ]) { - for (const [name, range] of Object.entries(manifest[field] ?? {})) { - if (String(range).startsWith("workspace:") && !packages.has(name)) { - errors.push(`${relative(root, file)} references missing workspace ${name}`); - } - } - } -} - -for (const required of [ - "packages/plugin/src/types.ts", - "packages/plugin/src/manifest.ts", - "packages/plugin/src/discovery.ts", - "packages/dev-server/src/plugin-assets.ts", - "packages/dev-server/src/plugin-migrations.ts", - "packages/cli/src/inspect.ts", - "packages/cli/src/system.ts", - "packages/captcha/src/plugin.ts", - "packages/captcha/assets/client/captcha.js", - "packages/captcha/components/Captcha.wrn", - "integration/platform-upgrade.test.ts", - "docs/ARCHITECTURE-0.4.md", - "docs/UPGRADE-0.4.md", - "docs/PACKAGE-RUNTIMES-0.4.md", - "docs/TEST-CHECKLIST-0.4.md", - "CHANGELOG-0.4.0.md", -]) { - if (!existsSync(join(root, required))) errors.push(`missing ${required}`); -} - -const updateSource = read("packages/cli/src/update.ts"); -const migrations = [...updateSource.matchAll(/version:\s*"([^"]+)"[\s\S]*?id:\s*"([^"]+)"/g)].map( - (match) => ({ version: match[1], id: match[2] }), -); -const migrationIds = new Set(); -for (const migration of migrations) { - if (migrationIds.has(migration.id)) errors.push(`duplicate migration id ${migration.id}`); - migrationIds.add(migration.id); -} -if (!migrations.some((migration) => migration.version === VERSION)) { - errors.push(`missing ${VERSION} update migration`); -} - -const captchaComponent = read("packages/captcha/components/Captcha.wrn"); -if (!captchaComponent.includes('data-wrnexus-runtime="captcha"')) { - errors.push("Captcha.wrn does not declare the captcha client runtime marker"); -} -if (/ component.name === "Captcha")) { - errors.push("Captcha is still duplicated in the core UI component reference"); -} - -const tsconfig = readJson("tsconfig.json"); -for (const name of packages.keys()) { - if (!tsconfig.compilerOptions?.paths?.[name]) errors.push(`tsconfig paths missing ${name}`); -} - -try { - const lock = JSON.parse(read("bun.lock").replace(/,\s*([}\]])/g, "$1")); - for (const { file, manifest } of packages.values()) { - const workspace = relative(root, file) - .replace(/\\/g, "/") - .replace(/\/package\.json$/, ""); - if (lock.workspaces?.[workspace]?.version !== VERSION) { - errors.push(`bun.lock has a stale version for ${workspace}`); - } - if (!lock.packages?.[manifest.name]) errors.push(`bun.lock missing ${manifest.name}`); - } -} catch (error) { - errors.push(`bun.lock structure is invalid: ${error instanceof Error ? error.message : error}`); -} - -const editorManifest = readJson("editors/vscode/package.json"); -if (editorManifest.version !== VERSION) { - errors.push(`VS Code extension has version ${editorManifest.version ?? ""}`); -} -const editorLock = readJson("editors/vscode/package-lock.json"); -if (editorLock.version !== VERSION || editorLock.packages?.[""]?.version !== VERSION) { - errors.push("VS Code extension package-lock version is stale"); -} - -for (const obsolete of [ - ".publish", - "AUDIO-PLAYBACK-FIX.md", - "CAPTCHA-FIX8-README.md", - "CAPTCHA-FIXED7-INSTRUCTIONS.md", - "WRNexusJS-0.3.0-test-fixes.patch", -]) { - if (existsSync(join(root, obsolete))) - errors.push(`obsolete release artifact remains: ${obsolete}`); -} - -for (const file of [ - "editors/vscode/package.json", - "editors/vscode/package-lock.json", - "editors/vscode/snippets/wrn.json", - "editors/vscode/syntaxes/wrn.tmLanguage.json", -]) { - try { - readJson(file); - } catch (error) { - errors.push(`${file} is invalid JSON: ${error instanceof Error ? error.message : error}`); - } -} - -notes.push(`${packages.size} framework packages aligned to ${VERSION}`); -notes.push(`${migrations.length} unique updater migrations checked`); -notes.push("automatic component/runtime/asset/migration discovery checked"); -notes.push("CAPTCHA has no copied component or public JavaScript requirement"); -notes.push("Bun workspace lock and editor JSON checked"); - -if (errors.length) { - process.stderr.write( - [ - `WRNexusJS ${VERSION} verification failed:`, - ...errors.map((error) => ` - ${error}`), - "", - ].join("\n"), - ); - process.exitCode = 1; -} else { - process.stdout.write( - [ - `WRNexusJS ${VERSION} structural verification passed.`, - ...notes.map((note) => ` ✓ ${note}`), - "", - ].join("\n"), - ); -} diff --git a/scripts/verify-auth.mjs b/scripts/verify-auth.mjs deleted file mode 100644 index 275fa2e9..00000000 --- a/scripts/verify-auth.mjs +++ /dev/null @@ -1,43 +0,0 @@ -/* global console */ -import { existsSync, readFileSync, readdirSync } from "node:fs"; -import { join } from "node:path"; -import process from "node:process"; - -const root = process.cwd(); -const required = [ - "packages/auth/package.json", - "packages/auth/src/engine.ts", - "packages/auth/src/plugin.ts", - "packages/auth/src/routes/api.ts", - "packages/auth/src/stores/memory.ts", - "packages/auth/src/stores/sql.ts", - "packages/auth/assets/client/auth.js", - "packages/auth/migrations/001_auth.sql", - "examples/auth-showcase/package.json", -]; -for (const file of required) { - if (!existsSync(join(root, file))) throw new Error(`Missing authentication file: ${file}`); -} -const manifest = JSON.parse(readFileSync(join(root, "packages/auth/package.json"), "utf8")); -if (manifest.name !== "@wrnexus/auth") throw new Error("Invalid authentication package name"); -if (manifest.version !== "0.5.0") throw new Error("Invalid authentication package version"); -const components = readdirSync(join(root, "packages/auth/components")).filter((file) => - file.endsWith(".wrn"), -); -if (components.length < 16) - throw new Error(`Expected at least 16 auth components, received ${components.length}`); -const plugin = readFileSync(join(root, "packages/auth/src/plugin.ts"), "utf8"); -for (const expected of [ - "clientRuntimes", - "routeEntries", - "middleware", - "migrations", - "devToolbarPanels", -]) { - if (!plugin.includes(expected)) throw new Error(`Auth plugin is missing ${expected}`); -} -const runtime = readFileSync(join(root, "packages/auth/assets/client/auth.js"), "utf8"); -if (!runtime.includes("navigator.credentials")) throw new Error("Passkey runtime is incomplete"); -if (runtime.includes("document.write")) - throw new Error("Passkey runtime contains unsafe document.write"); -console.log(`@wrnexus/auth structural verification passed (${components.length} components).`); diff --git a/services/managed-captcha/package.json b/services/managed-captcha/package.json index a3c4221d..351341ed 100644 --- a/services/managed-captcha/package.json +++ b/services/managed-captcha/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/managed-captcha-service", - "version": "0.8.0", + "version": "0.8.6", "private": true, "type": "module", "scripts": {