Compare commits

...
Author SHA1 Message Date
ClintchizandClaude Opus 5 e819c5739e Revert "chore(example): regenerate basic-app queries"
Quality / quality (ubuntu-latest) (push) Failing after 12m38s
Quality / quality (windows-latest) (push) Canceled after 0s
The generator's placeholder style is not deterministic across runs: the
same command emitted $1 once and ? the next time, depending on the
database dialect active in the environment. Restoring the committed
output and reverting my earlier regeneration, which was environment
churn rather than an intended change.

Worth a look on its own: a generator whose output depends on ambient
environment makes check:generated-types environment-sensitive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:26:07 +05:30
ClintchizandClaude Opus 5 ac164789bd fix(dev): serve the HMR client as an external script
A document's CSP nonce is fixed at load, so an inline script delivered by
a later response can never carry a nonce that document accepts. The HMR
client is now served at /__wrnexus/hmr-client.js, which script-src 'self'
already covers and which needs no nonce at all.

This removes one of the two inline scripts CSP was blocking in
development. The i18n data script is still blocked and needs the same
treatment; it is shared with the CSR navigation and HMR parsers, so
moving it spans @wrnexus/i18n, csr, and dev-server.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:25:40 +05:30
ClintchizandClaude Opus 5 88ec3a5cb6 chore(example): regenerate basic-app queries
Regenerated output for the committed query generator: positional
placeholders now render as $1 rather than ?.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:17:15 +05:30
ClintchizandClaude Opus 5 28085b5a43 chore(db,scripts): pending migration and packaging tweaks
Pre-existing working-tree changes to migration SQL parsing, query
generation, and the packaging scripts. Committed as-is rather than
authored here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:17:02 +05:30
ClintchizandClaude Opus 5 5d7bd81601 fix(dev-toolbar): report development-appropriate budgets
Excludes the toolbar's own bundle from the JavaScript budget, raises the
development thresholds, and skips WRNexus UI and theme stylesheets when
measuring CSS coverage, so unminified development modules and framework
styles stop reading as application problems.

Pre-existing working-tree change, committed as-is rather than authored
here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:17:01 +05:30
ClintchizandClaude Opus 5 5e114d867f feat(gateway): forward application identity on WebSocket upgrades
Adds gatewayWebSocketBackendHeaders so proxied upgrades carry application
identity while Bun keeps ownership of WebSocket framing.

Pre-existing working-tree change, committed as-is rather than authored
here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:16:54 +05:30
ClintchizandClaude Opus 5 10421465df fix(styles): stop scanning every component for utility sources
Component discovery is not utility-source discovery: scanning all
built-in and plugin component directories made Tailwind/Iconify generate
rules for components the app never renders. Packages that need scanning
opt in through styles.source.

Pre-existing working-tree change, committed as-is rather than authored
here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:16:53 +05:30
ClintchizandClaude Opus 5 52cce2c628 style(docs): apply Prettier to the React islands spec and plan
Formatting only; no content change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:16:45 +05:30
ClintchizandClaude Opus 5 afa2a8c093 chore(deps): move packages to TypeScript 6.0.3
Raises the typescript devDependency across the workspace, bumps package
versions, re-adds ignoreDeprecations, and repoints the @wrnexus registry.

These were pre-existing working-tree changes, committed as-is rather than
authored here. The .npmrc change redirects @wrnexus publishes from
registry.npmjs.org to registry.workroot.in — confirm that is intended
before publishing from this branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:16:34 +05:30
45 changed files with 317 additions and 209 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
@wrnexus:registry=https://registry.npmjs.org/
@wrnexus:registry=https://registry.workroot.in/repository/npm/
audit=true
fund=false
+5 -5
View File
@@ -272,7 +272,7 @@
},
"packages/cli": {
"name": "@wrnexus/cli",
"version": "0.8.36",
"version": "0.8.41",
"bin": {
"wrnexus": "src/index.ts",
},
@@ -287,6 +287,7 @@
"@wrnexus/mcp": "workspace:*",
"@wrnexus/playground": "workspace:*",
"@wrnexus/plugin": "workspace:*",
"@wrnexus/react": "workspace:*",
"@wrnexus/router": "workspace:*",
"@wrnexus/security": "workspace:*",
"@wrnexus/styles": "workspace:*",
@@ -299,9 +300,8 @@
},
"packages/compiler": {
"name": "@wrnexus/compiler",
"version": "0.8.10",
"version": "0.8.11",
"dependencies": {
"@wrnexus/core": "workspace:*",
"@wrnexus/csr": "workspace:*",
"@wrnexus/store": "workspace:*",
"@wrnexus/syntax": "workspace:*",
@@ -337,7 +337,7 @@
},
"packages/dev-server": {
"name": "@wrnexus/dev-server",
"version": "0.8.33",
"version": "0.8.37",
"dependencies": {
"@wrnexus/authz": "workspace:*",
"@wrnexus/cache": "workspace:*",
@@ -364,7 +364,7 @@
},
"packages/dev-toolbar": {
"name": "@wrnexus/dev-toolbar",
"version": "0.8.10",
"version": "0.8.12",
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^6.0.3",
@@ -30,26 +30,26 @@
**New package `packages/react/`:**
| File | Responsibility |
|---|---|
| `package.json` | Package manifest; optional peer deps |
| `src/snapshot-cache.ts` | Referentially-stable snapshot + selector caching. No React import. |
| `src/store-bridge.ts` | `useWrnStore` hook over `useSyncExternalStore` |
| `src/error-boundary.tsx` | Per-island React error boundary |
| `src/island-runtime.ts` | Mount/unmount, strategies, root registry |
| `src/runtime-source.ts` | `getIslandRuntime()` returning browser JS (mirrors `@wrnexus/csr`) |
| `src/index.ts` | Public exports |
| File | Responsibility |
| ------------------------ | ------------------------------------------------------------------ |
| `package.json` | Package manifest; optional peer deps |
| `src/snapshot-cache.ts` | Referentially-stable snapshot + selector caching. No React import. |
| `src/store-bridge.ts` | `useWrnStore` hook over `useSyncExternalStore` |
| `src/error-boundary.tsx` | Per-island React error boundary |
| `src/island-runtime.ts` | Mount/unmount, strategies, root registry |
| `src/runtime-source.ts` | `getIslandRuntime()` returning browser JS (mirrors `@wrnexus/csr`) |
| `src/index.ts` | Public exports |
**Modified:**
| File | Change |
|---|---|
| `packages/compiler/src/import-resolver.ts` | Resolve `.tsx`; tag `kind: "island"` |
| `packages/compiler/src/island-codegen.ts` *(new)* | Marker emission, props serialization, diagnostics |
| `packages/compiler/src/island-bundle.ts` *(new)* | Island entry generation + `Bun.build` with shared React chunk |
| `packages/dev-server/src/assets.ts` | Serve island routes in dev |
| `packages/dev-server/src/prod.ts` | Serve island routes in prod |
| `packages/cli/src/build.ts` | Emit island assets in static build |
| File | Change |
| ------------------------------------------------- | ------------------------------------------------------------- |
| `packages/compiler/src/import-resolver.ts` | Resolve `.tsx`; tag `kind: "island"` |
| `packages/compiler/src/island-codegen.ts` _(new)_ | Marker emission, props serialization, diagnostics |
| `packages/compiler/src/island-bundle.ts` _(new)_ | Island entry generation + `Bun.build` with shared React chunk |
| `packages/dev-server/src/assets.ts` | Serve island routes in dev |
| `packages/dev-server/src/prod.ts` | Serve island routes in prod |
| `packages/cli/src/build.ts` | Emit island assets in static build |
---
@@ -58,11 +58,13 @@
The load-bearing piece. `readonlySnapshot` in `@wrnexus/store` returns a fresh `Object.freeze(clone(state))` on every call; `useSyncExternalStore` requires a stable reference or it throws and infinite-loops. This task builds the cache with **no React dependency**, so it is testable in isolation.
**Files:**
- Create: `packages/react/package.json`
- Create: `packages/react/src/snapshot-cache.ts`
- Test: `packages/react/test/snapshot-cache.test.ts`
**Interfaces:**
- Consumes: nothing.
- Produces:
- `createSnapshotCache<S extends object>(source: SnapshotSource<S>): SnapshotCache<S>`
@@ -193,9 +195,7 @@ export interface SnapshotCache<S extends object> {
* and spins if given a fresh object each call, which `@wrnexus/store`'s
* `readonlySnapshot` does by design.
*/
export function createSnapshotCache<S extends object>(
source: SnapshotSource<S>,
): SnapshotCache<S> {
export function createSnapshotCache<S extends object>(source: SnapshotSource<S>): SnapshotCache<S> {
let cached: Readonly<S> | undefined;
let dirty = true;
@@ -259,12 +259,14 @@ git commit -m "feat(react): add referentially-stable snapshot and selector cache
### Task 2: Store bridge hook
**Files:**
- Create: `packages/react/src/store-bridge.ts`
- Create: `packages/react/src/index.ts`
- Modify: `package.json` (root) — add `react`, `react-dom` to devDependencies for tests
- Test: `packages/react/test/store-bridge.test.ts`
**Interfaces:**
- Consumes: `createSnapshotCache`, `createSelectorCache` from Task 1.
- Produces:
- `useWrnStore<S extends object, R = Readonly<S>>(name: string, selector?: (state: Readonly<S>) => R): R`
@@ -436,10 +438,12 @@ git commit -m "feat(react): add useWrnStore bridge over useSyncExternalStore"
### Task 3: Island marker codegen
**Files:**
- Create: `packages/compiler/src/island-codegen.ts`
- Test: `packages/compiler/test/island-codegen.test.ts`
**Interfaces:**
- Consumes: `escapeHtml` from `@wrnexus/core` (`packages/core/src/security.ts`).
- Produces:
- `type IslandStrategy = "only" | "load" | "visible" | "idle"`
@@ -596,10 +600,12 @@ git commit -m "feat(compiler): add island marker codegen and props contract"
### Task 4: Resolve `.tsx` imports as islands
**Files:**
- Modify: `packages/compiler/src/import-resolver.ts`
- Test: `packages/compiler/test/island-resolution.test.ts`
**Interfaces:**
- Consumes: nothing from prior tasks.
- Produces: `ResolvedImport` gains an optional `kind?: "island"` field, set when the resolved path ends in `.tsx`.
@@ -705,12 +711,12 @@ export interface ResolvedImport {
Then change the success return inside `resolveWrnImport` from `return { declaration, resolved: realpathSync(found) };` to:
```ts
if (found) {
const resolved = realpathSync(found);
return resolved.endsWith(".tsx")
? { declaration, resolved, kind: "island" }
: { declaration, resolved };
}
if (found) {
const resolved = realpathSync(found);
return resolved.endsWith(".tsx")
? { declaration, resolved, kind: "island" }
: { declaration, resolved };
}
```
- [ ] **Step 5: Run test to verify it passes**
@@ -735,10 +741,12 @@ git commit -m "feat(compiler): resolve .tsx imports and tag them as islands"
### Task 5: Island error boundary
**Files:**
- Create: `packages/react/src/error-boundary.tsx`
- Test: `packages/react/test/error-boundary.test.tsx`
**Interfaces:**
- Consumes: nothing from prior tasks.
- Produces: `IslandErrorBoundary` — a React component with props `{ name: string; development: boolean; children: ReactNode }`.
@@ -857,11 +865,13 @@ git commit -m "feat(react): add per-island error boundary"
### Task 6: Island runtime — mount, strategies, unmount
**Files:**
- Create: `packages/react/src/island-runtime.ts`
- Modify: `packages/react/src/index.ts`
- Test: `packages/react/test/island-runtime.test.ts`
**Interfaces:**
- Consumes: `IslandErrorBoundary` (Task 5), `setStoreResolver` (Task 2).
- Produces:
- `mountIslands(root: ParentNode, options: MountOptions): Promise<void>`
@@ -1070,10 +1080,12 @@ git commit -m "feat(react): add island mount strategies and navigation-safe unmo
### Task 7: Island bundling with a shared React chunk
**Files:**
- Create: `packages/compiler/src/island-bundle.ts`
- Test: `packages/compiler/test/island-bundle.test.ts`
**Interfaces:**
- Consumes: nothing from prior tasks.
- Produces:
- `generateIslandEntry(input: { name: string; sourcePath: string }): string`
@@ -1226,6 +1238,7 @@ git commit -m "feat(compiler): bundle islands with a shared React chunk"
### Task 8: Serve island assets in dev, prod, and static build
**Files:**
- Create: `packages/react/src/runtime-source.ts`
- Modify: `packages/dev-server/src/assets.ts`
- Modify: `packages/dev-server/src/prod.ts`
@@ -1233,6 +1246,7 @@ git commit -m "feat(compiler): bundle islands with a shared React chunk"
- Test: `packages/react/test/runtime-source.test.ts`
**Interfaces:**
- Consumes: `mountIslands`, `unmountIslands` (Task 6).
- Produces: `getIslandRuntime(development?: boolean): string` — browser JS served at `/__wrnexus/islands.js`, mirroring `getReactiveRuntime` in `@wrnexus/csr`.
@@ -1323,15 +1337,15 @@ import { getIslandRuntime } from "@wrnexus/react/runtime";
Then inside `serve(pathname)`, next to the other `/__wrnexus/*.js` lines:
```ts
if (pathname === "/__wrnexus/islands.js") return jsResponse(getIslandRuntime(true));
if (pathname === "/__wrnexus/islands.js") return jsResponse(getIslandRuntime(true));
```
The bootstrap dynamically imports `/__wrnexus/island/runtime.js` (the bundled mount runtime) and `/__wrnexus/island/<name>.js` (per-island bundles). Both live under the `/__wrnexus/island/` prefix, so add one prefix handler beside the existing `/__wrnexus/client/` handler at the top of `serve(pathname)`:
```ts
if (pathname.startsWith("/__wrnexus/island/")) {
return serveIslandArtifact(pathname) ?? new Response("Not Found", { status: 404 });
}
if (pathname.startsWith("/__wrnexus/island/")) {
return serveIslandArtifact(pathname) ?? new Response("Not Found", { status: 404 });
}
```
where `serveIslandArtifact` reads from the island build output directory produced by `buildIslands` (Task 7), mirroring how `serveWrnBrowserArtifact` serves `/__wrnexus/client/`.
@@ -1341,8 +1355,8 @@ where `serveIslandArtifact` reads from the island build output directory produce
In `packages/dev-server/src/prod.ts`, add the same import, then beside the existing `getReactiveRuntime()` route:
```ts
if (pathname === "/__wrnexus/islands.js")
return new Response(getIslandRuntime(), { headers: JS_HEADERS });
if (pathname === "/__wrnexus/islands.js")
return new Response(getIslandRuntime(), { headers: JS_HEADERS });
```
Add the same `/__wrnexus/island/` prefix handler here, serving the built island assets from the production output directory.
@@ -1375,10 +1389,12 @@ git commit -m "feat(islands): serve the island runtime in dev, prod, and static
A route containing an island is no longer zero-JS static — it is static-interactive. Without this, the framework's own performance reporting is wrong.
**Files:**
- Modify: `packages/compiler/src/analysis.ts`
- Test: `packages/compiler/test/island-classification.test.ts`
**Interfaces:**
- Consumes: `ResolvedImport.kind` (Task 4).
- Produces: `routeNeedsIslands(imports: ResolvedImport[]): boolean`, exported from `analysis.ts`.
@@ -1457,10 +1473,12 @@ git commit -m "feat(compiler): classify island routes as static-interactive"
Two tests protecting the project's core promise. These must fail loudly if a future change regresses them.
**Files:**
- Create: `examples/basic-app/app/islands/Counter.tsx`
- Test: `packages/compiler/test/island-integration.test.ts`
**Interfaces:**
- Consumes: `buildIslands` (Task 7), `renderIslandMarker` (Task 3), `routeNeedsIslands` (Task 9).
- Produces: nothing consumed downstream.
@@ -1493,10 +1511,7 @@ import { join, resolve } from "node:path";
import { buildIslands } from "../src/island-bundle.ts";
import { routeNeedsIslands } from "../src/analysis.ts";
const COUNTER = resolve(
import.meta.dir,
"../../../examples/basic-app/app/islands/Counter.tsx",
);
const COUNTER = resolve(import.meta.dir, "../../../examples/basic-app/app/islands/Counter.tsx");
test("a route with no islands ships zero framework JavaScript", async () => {
const outDir = mkdtempSync(join(tmpdir(), "wrnexus-nojs-"));
@@ -1522,8 +1537,8 @@ test("a page with multiple islands ships React exactly once", async () => {
.filter((file) => file.endsWith(".js"))
.map((file) => readFileSync(join(outDir, file), "utf8"));
const withReactInternals = bundles.filter((source) =>
source.includes("react.development") || source.includes("REACT_ELEMENT_TYPE"),
const withReactInternals = bundles.filter(
(source) => source.includes("react.development") || source.includes("REACT_ELEMENT_TYPE"),
);
expect(result.assets).toHaveLength(2);
@@ -1564,12 +1579,14 @@ git commit -m "test(islands): guard zero-JS routes and single-React bundling"
The spec's one author-facing rule — writes only from handlers or effects — is enforced in dev, not merely documented. React's own warning for this is too generic to diagnose quickly.
**Files:**
- Create: `packages/react/src/render-phase.ts`
- Modify: `packages/react/src/store-bridge.ts` (wrap actions in `useWrnActions`)
- Modify: `packages/react/src/index.ts`
- Test: `packages/react/test/render-phase.test.ts`
**Interfaces:**
- Consumes: `useWrnActions` (Task 2).
- Produces:
- `beginRenderPhase(): void`
@@ -1730,12 +1747,14 @@ git commit -m "feat(react): fail loudly on store writes during island render"
On island source change, unmount the root and re-mount with the new bundle. Component state resets on edit; that is the accepted v1 trade-off, and the concrete trigger for reconsidering Fast Refresh later.
**Files:**
- Modify: `packages/react/src/island-runtime.ts`
- Modify: `packages/react/src/runtime-source.ts`
- Modify: `packages/react/src/index.ts`
- Test: `packages/react/test/island-hmr.test.ts`
**Interfaces:**
- Consumes: `mountIslands`, `unmountIslands` (Task 6).
- Produces: `remountIslands(root: ParentNode, options: MountOptions): Promise<void>`
@@ -1751,8 +1770,7 @@ import { islandRootCount, mountIslands, remountIslands } from "../src/island-run
test("remount replaces island output without leaking roots", async () => {
const window = new Window();
window.document.body.innerHTML =
`<div data-wrn-island="Chart" data-wrn-island-strategy="only"
window.document.body.innerHTML = `<div data-wrn-island="Chart" data-wrn-island-strategy="only"
data-wrn-island-props='{}'></div>`;
(globalThis as any).window = window;
(globalThis as any).document = window.document;
@@ -28,14 +28,14 @@ byte-for-byte unchanged, and a route with no islands must ship no React.
## Decisions
| Question | Decision |
|---|---|
| Purpose | npm ecosystem access |
| Server rendering | Client-only by default; SSR opt-in deferred to v2 |
| Authoring | `import Chart from "./Chart.tsx"` in `.wrn` frontmatter, used as `<Chart client:only />` |
| Data flow | Two-way store access via `useSyncExternalStore` (read + write through actions) |
| Bundling | Extend the existing Bun pipeline |
| Packaging | New isolated package `@wrnexus/react` |
| Question | Decision |
| ---------------- | ---------------------------------------------------------------------------------------- |
| Purpose | npm ecosystem access |
| Server rendering | Client-only by default; SSR opt-in deferred to v2 |
| Authoring | `import Chart from "./Chart.tsx"` in `.wrn` frontmatter, used as `<Chart client:only />` |
| Data flow | Two-way store access via `useSyncExternalStore` (read + write through actions) |
| Bundling | Extend the existing Bun pipeline |
| Packaging | New isolated package `@wrnexus/react` |
## Architecture
@@ -100,7 +100,7 @@ separate island hydration channel is introduced.
`readonlySnapshot` in `packages/store/src/index.ts` returns `Object.freeze(clone(state))` — a
**new reference on every call**. `useSyncExternalStore` requires `getSnapshot()` to return a
referentially identical value when nothing has changed; otherwise React throws
*"The result of getSnapshot should be cached to avoid an infinite loop"* and spins.
_"The result of getSnapshot should be cached to avoid an infinite loop"_ and spins.
**The cache lives in the `@wrnexus/react` adapter, not in `@wrnexus/store`.** The adapter holds
one cached snapshot per store instance, returns the same reference until the store's `subscribe`
@@ -182,15 +182,15 @@ point.
## Error handling
| Condition | Behavior |
|---|---|
| `react`/`react-dom` not installed | Compiler diagnostic `WRN-ISLAND-REACT-MISSING`, naming the install command — not a raw module-resolution failure |
| Island throws during render | Per-island error boundary. Dev: render error in place with component name and stack. Prod: log, render nothing, leave surrounding server HTML intact |
| Island bundle fails to load | Placeholder remains, warning logged; page stays functional because everything else was server-rendered |
| Non-serializable props | Compile-time `WRN-ISLAND-PROPS` |
| Unknown store name | Dev: throw, listing available store names. Prod: warn, return undefined |
| Action fired during render | Left to React. See "Dropped: the write-during-render guard" below. |
| Cleanup throws on unmount | Caught and logged; navigation must not break |
| Condition | Behavior |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `react`/`react-dom` not installed | Compiler diagnostic `WRN-ISLAND-REACT-MISSING`, naming the install command — not a raw module-resolution failure |
| Island throws during render | Per-island error boundary. Dev: render error in place with component name and stack. Prod: log, render nothing, leave surrounding server HTML intact |
| Island bundle fails to load | Placeholder remains, warning logged; page stays functional because everything else was server-rendered |
| Non-serializable props | Compile-time `WRN-ISLAND-PROPS` |
| Unknown store name | Dev: throw, listing available store names. Prod: warn, return undefined |
| Action fired during render | Left to React. See "Dropped: the write-during-render guard" below. |
| Cleanup throws on unmount | Caught and logged; navigation must not break |
Islands failing **locally** is the most valuable property of this model: a crashed chart leaves
the rest of the page working.
+5 -5
View File
@@ -19,10 +19,10 @@
"@wrnexus/validation": "workspace:*"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.0",
"@iconify/tailwind4": "^1.0.0",
"@tailwindcss/cli": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.9.2"
"@iconify-json/lucide": "^1.2.123",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.3.3",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3"
}
}
+6 -6
View File
@@ -23,12 +23,12 @@
"devDependencies": {
"@wrnexus/test": "workspace:*",
"@eslint/js": "^10.0.1",
"@iconify-json/lucide": "^1.2.118",
"@iconify-json/lucide": "^1.2.123",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"eslint": "^10.8.0",
"prettier": "^3.9.4",
"tailwindcss": "^4.0.0",
"typescript-eslint": "^8.65.0"
"@tailwindcss/cli": "^4.3.3",
"eslint": "^10.8.1",
"prettier": "^3.9.6",
"tailwindcss": "^4.3.3",
"typescript-eslint": "^8.67.0"
}
}
+5 -5
View File
@@ -16,10 +16,10 @@
"@wrnexus/validation": "workspace:*"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.0",
"@iconify/tailwind4": "^1.0.0",
"@tailwindcss/cli": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.9.2"
"@iconify-json/lucide": "^1.2.123",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.3.3",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3"
}
}
+4 -4
View File
@@ -18,10 +18,10 @@
"@wrnexus/ui": "workspace:*"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.118",
"@iconify-json/lucide": "^1.2.123",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0"
"@tailwindcss/cli": "^4.3.3",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3"
}
}
+4 -4
View File
@@ -16,10 +16,10 @@
"@wrnexus/ui": "workspace:*"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.118",
"@iconify-json/lucide": "^1.2.123",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.9.2"
"@tailwindcss/cli": "^4.3.3",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3"
}
}
@@ -24,40 +24,40 @@
"check": "bun run typecheck && bun run lint && bun run test && bun run format:check"
},
"dependencies": {
"@wrnexus/ai": "0.8.6",
"@wrnexus/auth": "0.8.6",
"@wrnexus/captcha": "0.8.6",
"@wrnexus/core": "0.8.6",
"@wrnexus/csr": "0.8.6",
"@wrnexus/db": "0.8.6",
"@wrnexus/dev-server": "0.8.6",
"@wrnexus/encryption": "0.8.6",
"@wrnexus/helpers": "0.8.6",
"@wrnexus/i18n": "0.8.6",
"@wrnexus/image": "0.8.6",
"@wrnexus/jwt": "0.8.6",
"@wrnexus/observability": "0.8.6",
"@wrnexus/realtime": "0.8.6",
"@wrnexus/security": "0.8.6",
"@wrnexus/store": "0.8.6",
"@wrnexus/styles": "0.8.6",
"@wrnexus/tracking": "0.8.6",
"@wrnexus/ui": "0.8.6",
"@wrnexus/uploader": "0.8.6",
"@wrnexus/validation": "0.8.6",
"@wrnexus/authz": "0.8.6"
"@wrnexus/ai": "^0.8.9",
"@wrnexus/auth": "^0.8.12",
"@wrnexus/captcha": "^0.8.11",
"@wrnexus/core": "^0.8.9",
"@wrnexus/csr": "^0.8.21",
"@wrnexus/db": "^0.8.15",
"@wrnexus/dev-server": "^0.8.33",
"@wrnexus/encryption": "^0.8.9",
"@wrnexus/helpers": "^0.8.8",
"@wrnexus/i18n": "^0.8.11",
"@wrnexus/image": "^0.8.10",
"@wrnexus/jwt": "^0.8.9",
"@wrnexus/observability": "^0.8.8",
"@wrnexus/realtime": "^0.8.10",
"@wrnexus/security": "^0.8.8",
"@wrnexus/store": "^0.8.8",
"@wrnexus/styles": "^0.8.15",
"@wrnexus/tracking": "^0.8.8",
"@wrnexus/ui": "^0.8.19",
"@wrnexus/uploader": "^0.8.10",
"@wrnexus/validation": "^0.8.10",
"@wrnexus/authz": "^0.8.9"
},
"devDependencies": {
"@wrnexus/cli": "0.8.6",
"@eslint/js": "^9.0.0",
"@iconify-json/lucide": "^1.2.118",
"@wrnexus/cli": "^0.8.36",
"@eslint/js": "^10.0.1",
"@iconify-json/lucide": "^1.2.123",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@tailwindcss/cli": "^4.3.3",
"@types/bun": "^1.3.14",
"eslint": "^9.0.0",
"eslint": "^10.8.1",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0"
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3",
"typescript-eslint": "^8.67.0"
}
}
@@ -24,40 +24,40 @@
"check": "bun run typecheck && bun run lint && bun run test && bun run format:check"
},
"dependencies": {
"@wrnexus/ai": "0.8.6",
"@wrnexus/auth": "0.8.6",
"@wrnexus/captcha": "0.8.6",
"@wrnexus/core": "0.8.6",
"@wrnexus/csr": "0.8.6",
"@wrnexus/db": "0.8.6",
"@wrnexus/dev-server": "0.8.6",
"@wrnexus/encryption": "0.8.6",
"@wrnexus/helpers": "0.8.6",
"@wrnexus/i18n": "0.8.6",
"@wrnexus/image": "0.8.6",
"@wrnexus/jwt": "0.8.6",
"@wrnexus/observability": "0.8.6",
"@wrnexus/realtime": "0.8.6",
"@wrnexus/security": "0.8.6",
"@wrnexus/store": "0.8.6",
"@wrnexus/styles": "0.8.6",
"@wrnexus/tracking": "0.8.6",
"@wrnexus/ui": "0.8.6",
"@wrnexus/uploader": "0.8.6",
"@wrnexus/validation": "0.8.6",
"@wrnexus/authz": "0.8.6"
"@wrnexus/ai": "^0.8.9",
"@wrnexus/auth": "^0.8.12",
"@wrnexus/captcha": "^0.8.11",
"@wrnexus/core": "^0.8.9",
"@wrnexus/csr": "^0.8.21",
"@wrnexus/db": "^0.8.15",
"@wrnexus/dev-server": "^0.8.33",
"@wrnexus/encryption": "^0.8.9",
"@wrnexus/helpers": "^0.8.8",
"@wrnexus/i18n": "^0.8.11",
"@wrnexus/image": "^0.8.10",
"@wrnexus/jwt": "^0.8.9",
"@wrnexus/observability": "^0.8.8",
"@wrnexus/realtime": "^0.8.10",
"@wrnexus/security": "^0.8.8",
"@wrnexus/store": "^0.8.8",
"@wrnexus/styles": "^0.8.15",
"@wrnexus/tracking": "^0.8.8",
"@wrnexus/ui": "^0.8.19",
"@wrnexus/uploader": "^0.8.10",
"@wrnexus/validation": "^0.8.10",
"@wrnexus/authz": "^0.8.9"
},
"devDependencies": {
"@wrnexus/cli": "0.8.6",
"@eslint/js": "^9.0.0",
"@iconify-json/lucide": "^1.2.118",
"@wrnexus/cli": "^0.8.36",
"@eslint/js": "^10.0.1",
"@iconify-json/lucide": "^1.2.123",
"@iconify/tailwind4": "^1.2.3",
"@tailwindcss/cli": "^4.0.0",
"@tailwindcss/cli": "^4.3.3",
"@types/bun": "^1.3.14",
"eslint": "^9.0.0",
"eslint": "^10.8.1",
"prettier": "^3.9.6",
"tailwindcss": "^4.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0"
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3",
"typescript-eslint": "^8.67.0"
}
}
+5 -5
View File
@@ -21,12 +21,12 @@
"check": "bun run typecheck && bun run lint && bun run test && bun run format:check"
},
"devDependencies": {
"@wrnexus/cli": "0.8.6",
"@eslint/js": "^9.0.0",
"@wrnexus/cli": "^0.8.36",
"@eslint/js": "^10.0.1",
"@types/bun": "^1.3.14",
"eslint": "^9.0.0",
"eslint": "^10.8.1",
"prettier": "^3.9.6",
"typescript": "^5.5.0",
"typescript-eslint": "^8.65.0"
"typescript": "^6.0.3",
"typescript-eslint": "^8.67.0"
}
}
@@ -12,6 +12,6 @@
"test": "bun test"
},
"dependencies": {
"@wrnexus/pubsub": "0.8.6"
"@wrnexus/pubsub": "^0.8.9"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/auth",
"version": "0.8.11",
"version": "0.8.12",
"description": "Complete authentication, account security, MFA, passkeys, recovery, devices, risk, and audit system for WRNexusJS.",
"type": "module",
"sideEffects": false,
@@ -54,7 +54,7 @@
"devDependencies": {
"@types/bun": "^1.3.14",
"@wrnexus/syntax": "workspace:*",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
},
"wrnexus": {
"plugin": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/captcha",
"version": "0.8.10",
"version": "0.8.11",
"description": "First-class CAPTCHA challenges, providers, verification guards, page gates, and WRNexusJS UI.",
"type": "module",
"sideEffects": false,
@@ -48,7 +48,7 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2",
"typescript": "^6.0.3",
"@wrnexus/syntax": "workspace:*"
},
"wrnexus": {
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/cli",
"version": "0.8.36",
"version": "0.8.41",
"type": "module",
"main": "src/index.ts",
"exports": {
@@ -28,6 +28,7 @@
"@wrnexus/db": "workspace:*",
"@wrnexus/authz": "workspace:*",
"@wrnexus/plugin": "workspace:*",
"@wrnexus/react": "workspace:*",
"@wrnexus/syntax": "workspace:*",
"@wrnexus/typecheck": "workspace:*",
"@wrnexus/security": "workspace:*",
+8 -4
View File
@@ -620,10 +620,14 @@ export async function runBuild(appRoot: string): Promise<void> {
appDir,
appRoot: root,
mode: "production",
sources: [
...componentDirs,
...pluginContributions.styles.flatMap((style) => (style.source ? [style.source] : [])),
],
// Component discovery is not style discovery. Built-in and plugin
// components own their CSS; scanning every available component makes
// Tailwind/Iconify generate rules for packages and components the app
// never renders. Packages that intentionally use app utilities opt in
// through an explicit styles.source contribution.
sources: pluginContributions.styles.flatMap((style) =>
style.source ? [style.source] : [],
),
entries: pluginContributions.styles.flatMap((style) => (style.entry ? [style.entry] : [])),
},
config.styles,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/compiler",
"version": "0.8.10",
"version": "0.8.11",
"type": "module",
"main": "src/index.ts",
"exports": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/content",
"version": "0.8.8",
"version": "0.8.9",
"type": "module",
"description": "Typed Markdown content collections, loaders, indexes, feeds, and preview workflows for WRNexusJS.",
"main": "./src/index.ts",
@@ -14,6 +14,6 @@
],
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/db",
"version": "0.8.14",
"version": "0.8.15",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -26,6 +26,6 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
}
}
+1 -3
View File
@@ -226,9 +226,7 @@ export function generateQueriesFile(
);
}
const imports = [
`import type { Db${usesExecResult ? ", ExecResult" : ""} } from "@wrnexus/db";`,
];
const imports = [`import type { Db${usesExecResult ? ", ExecResult" : ""} } from "@wrnexus/db";`];
if (usedModels.size > 0) {
imports.push(`import { ${[...usedModels].sort().join(", ")} } from "./schema.ts";`);
}
+4 -3
View File
@@ -91,9 +91,10 @@ function additiveColumnTarget(sql: string): { table: string; column: string } |
.replace(/\/\*[\s\S]*?\*\//g, " ")
.replace(/--[^\r\n]*/g, " ")
.trim();
const match = /^ALTER\s+TABLE\s+([A-Za-z_][A-Za-z0-9_]*)\s+ADD\s+COLUMN\s+([A-Za-z_][A-Za-z0-9_]*)\b[\s\S]*;?\s*$/i.exec(
executable,
);
const match =
/^ALTER\s+TABLE\s+([A-Za-z_][A-Za-z0-9_]*)\s+ADD\s+COLUMN\s+([A-Za-z_][A-Za-z0-9_]*)\b[\s\S]*;?\s*$/i.exec(
executable,
);
return match ? { table: match[1]!, column: match[2]! } : undefined;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-server",
"version": "0.8.33",
"version": "0.8.37",
"type": "module",
"main": "src/index.ts",
"exports": {
+2
View File
@@ -34,6 +34,7 @@ import type { AssetServer } from "./runtime.ts";
import { servePublicAsset } from "./public.ts";
import { servePluginAsset, type ServedPluginAsset } from "./plugin-assets.ts";
import { serveIslandArtifact, serveWrnBrowserArtifact } from "./pipeline.ts";
import { HMR_CLIENT_HREF, HMR_CLIENT_JS } from "./runtime.ts";
/** Style inputs the dev asset server needs to build `/__wrnexus/styles.css`. */
export interface DevStyles {
@@ -99,6 +100,7 @@ export function createDevAssetServer(
if (pathname.startsWith("/__wrnexus/island/")) {
return serveIslandArtifact(pathname) ?? new Response("Not Found", { status: 404 });
}
if (pathname === HMR_CLIENT_HREF) return jsResponse(HMR_CLIENT_JS);
if (pathname === "/__wrnexus/islands.js") return jsResponse(getIslandRuntime(true));
if (pathname === "/__wrnexus/reactive.js") return jsResponse(getReactiveRuntime(true));
if (pathname === "/__wrnexus/controllers.js")
+37 -1
View File
@@ -113,6 +113,7 @@ interface Target extends GatewayApp {
interface WsBridge {
origin: string;
path: string;
headers: Record<string, string>;
backend?: WebSocket;
queue: Array<string | ArrayBuffer>;
maxMessageBytes: number;
@@ -433,6 +434,30 @@ export function gatewayProxyHeaders(
return headers;
}
/** Forward application identity to the child while leaving WebSocket framing to Bun. */
export function gatewayWebSocketBackendHeaders(
req: Request,
url: URL,
ip: string,
forwardedHeaders: boolean,
backendOrigin: string,
): Record<string, string> {
const headers = stripUntrustedInternalHeaders(
gatewayProxyHeaders(req, url, ip, forwardedHeaders),
);
headers.delete("host");
headers.delete("connection");
headers.delete("upgrade");
headers.delete("accept-encoding");
for (const name of [...headers.keys()]) {
if (name.startsWith("sec-websocket-")) headers.delete(name);
}
// The public origin was validated at the gateway edge. The child receives a
// new, trusted same-origin connection from its private gateway listener.
headers.set("origin", backendOrigin);
return Object.fromEntries(headers);
}
/** Remove headers that only a direct workspace-to-app request may supply. */
export function stripUntrustedInternalHeaders(headers: Headers): Headers {
const sanitized = new Headers(headers);
@@ -692,6 +717,13 @@ export async function startGateway(opts: GatewayOptions): Promise<RunningGateway
data: {
origin: target.origin,
path: url.pathname + url.search,
headers: gatewayWebSocketBackendHeaders(
req,
url,
ip,
forwardedHeaders,
target.origin,
),
queue: [],
maxMessageBytes: websocketSecurity.maxMessageBytes ?? 64 * 1024,
maxQueuedMessages: websocketSecurity.maxQueuedMessages ?? 100,
@@ -753,7 +785,11 @@ export async function startGateway(opts: GatewayOptions): Promise<RunningGateway
websocket: {
open(ws) {
const backendUrl = ws.data.origin.replace(/^http/, "ws") + ws.data.path;
const backend = new WebSocket(backendUrl);
const BackendWebSocket = WebSocket as unknown as new (
url: string,
options: Bun.WebSocketOptions,
) => WebSocket;
const backend = new BackendWebSocket(backendUrl, { headers: ws.data.headers });
ws.data.backend = backend;
backend.addEventListener("open", () => {
for (const m of ws.data.queue) backend.send(m);
+3 -4
View File
@@ -440,10 +440,9 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
config: opts.stylesConfig,
appRoot,
publicDir: join(appRoot, "public"),
sources: [
...componentDirs,
...pluginContributions.styles.flatMap((style) => (style.source ? [style.source] : [])),
],
// Component discovery is separate from utility-source discovery.
// Packages that need Tailwind scanning opt in via styles.source.
sources: pluginContributions.styles.flatMap((style) => (style.source ? [style.source] : [])),
entries: pluginContributions.styles.flatMap((style) => (style.entry ? [style.entry] : [])),
},
theme,
+13 -2
View File
@@ -878,8 +878,19 @@ function randomNonce(): string {
}
/** The dev HMR client as a nonce-tagged inline script (strict-CSP friendly). */
function hmrClientTag(nonce: string): string {
return `<script nonce="${nonce}">${HMR_CLIENT_JS}</script>`;
/** Path the dev asset server publishes the HMR client on. */
export const HMR_CLIENT_HREF = "/__wrnexus/hmr-client.js";
/**
* The HMR client is served as an external module rather than inlined.
*
* A document's CSP nonce is fixed at load, so an inline script arriving from a
* later response which is exactly what an HMR reload produces can never
* carry a nonce this document accepts. An external file is covered by
* script-src 'self' and needs no nonce at all.
*/
function hmrClientTag(_nonce: string): string {
return `<script src="${HMR_CLIENT_HREF}"></script>`;
}
/** 403 for a rejected cross-site WebSocket handshake. */
+30
View File
@@ -5,6 +5,7 @@ import {
forwardAuthFailure,
forwardAuthHeaders,
gatewayProxyHeaders,
gatewayWebSocketBackendHeaders,
stripUntrustedInternalHeaders,
gatewayRestartDelay,
internalError,
@@ -37,6 +38,35 @@ test("gateway disables compression for its internal proxy hop", () => {
expect(headers.get("x-forwarded-for")).toBe("127.0.0.1");
});
test("gateway WebSocket bridge forwards validated application identity", () => {
const request = new Request("http://web.localhost:3000/__wrnexus/hmr", {
headers: {
host: "web.localhost:3000",
origin: "http://web.localhost:3000",
cookie: "session=abc",
connection: "Upgrade",
upgrade: "websocket",
"sec-websocket-key": "test-key",
},
});
const headers = gatewayWebSocketBackendHeaders(
request,
new URL(request.url),
"127.0.0.1",
true,
"http://127.0.0.1:3001",
);
expect(headers.origin).toBe("http://127.0.0.1:3001");
expect(headers.cookie).toBe("session=abc");
expect(headers["x-forwarded-host"]).toBe("web.localhost:3000");
expect(headers["x-forwarded-proto"]).toBe("http");
expect(headers.host).toBeUndefined();
expect(headers.connection).toBeUndefined();
expect(headers.upgrade).toBeUndefined();
expect(headers["sec-websocket-key"]).toBeUndefined();
});
test("gateway proxy headers do not preserve the RPC internal marker", () => {
const request = new Request("http://localhost:3000/path", {
headers: { "x-wrnexus-internal": "1" },
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/dev-toolbar",
"version": "0.8.9",
"version": "0.8.12",
"private": true,
"type": "module",
"sideEffects": false,
@@ -20,6 +20,6 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
}
}
+2 -2
View File
@@ -43,8 +43,8 @@ export const DEV_TOOLBAR_RUNTIME = String.raw`(() => {
try{for(let i=0;i<localStorage.length;i++){const key=localStorage.key(i)||"";if(/(?:token|secret|password|session|credential|authorization)/i.test(key))found.push(issue("security/sensitive-local-storage","security","error","Sensitive value may be stored in localStorage","Storage key “"+key+"” looks authentication- or secret-related.",null,"Keep sessions and credentials in Secure, HttpOnly cookies."));}}catch{}
if(document.documentElement.scrollWidth>innerWidth+2)found.push(issue("responsive/document-overflow","responsive","error","Page has horizontal overflow","Document width exceeds the viewport.",null,"Inspect fixed widths, long text and overflowing media."));
q("body *").filter(visible).slice(0,2500).forEach(el=>{const r=el.getBoundingClientRect();if((r.right>innerWidth+8||r.left<-8)&&found.filter(x=>x.ruleId==="responsive/element-overflow").length<20)found.push(issue("responsive/element-overflow","responsive","warning","Element extends outside the viewport","Element bounds exceed the current viewport.",el,"Use fluid sizing, wrapping, max-width or an intentional scroll container."));});
const resources=performance.getEntriesByType("resource");const total=resources.reduce((s,e)=>s+(e.transferSize||0),0);const jsBytes=resources.filter(e=>/(?:\.m?js)(?:\?|$)/i.test(e.name)).reduce((sum,e)=>sum+(e.transferSize||0),0);if(resources.length>150)found.push(issue("performance/resource-count","performance","warning","Page loads many resources","Found "+resources.length+" resource requests.",null,"Remove duplicates and defer non-critical resources."));if(total>5000000)found.push(issue("performance/transfer-size","performance",total>10000000?"error":"warning","Page transfer size is large","Observed transfer size is about "+(total/1000000).toFixed(2)+" MB.",null,"Compress and optimize page resources."));if(jsBytes>150000)found.push(issue("performance/javascript-budget","javascript",jsBytes>300000?"error":"warning","JavaScript budget exceeded","JavaScript transfer is about "+(jsBytes/1000).toFixed(1)+" KB.",null,"Split routes and defer optional hydration."));const hydrationRoots=q("[data-wrn-client-module],[data-wrn-hydrate]");if(hydrationRoots.length>50)found.push(issue("performance/hydration-count","runtime","warning","Many components hydrate",hydrationRoots.length+" hydration boundaries were found.",null,"Use visible, idle or interaction hydration."));if(state.runtimeMetrics.longTasks.length)found.push(issue("performance/long-tasks","javascript","warning","Long main-thread tasks detected",state.runtimeMetrics.longTasks.length+" task(s) exceeded 50 ms.",null,"Split expensive work and reduce hydration.","high",{longestMs:Math.max(...state.runtimeMetrics.longTasks)}));
let checkedSelectors=0,unusedSelectors=0;for(const sheet of [...document.styleSheets]){let rules;try{rules=[...(sheet.cssRules||[])]}catch{continue}for(const rule of rules){if(checkedSelectors>=2000)break;const selector=rule.selectorText;if(!selector||selector.includes(":"))continue;checkedSelectors++;try{if(!document.querySelector(selector))unusedSelectors++}catch{}}}if(unusedSelectors)found.push(issue("css/unused-selectors","css","suggestion","Potentially unused CSS",unusedSelectors+" of "+checkedSelectors+" inspected selectors do not match this page.",null,"Review across routes before removing selectors.","medium",{checkedSelectors,unusedSelectors}));const memory=performance.memory;if(memory&&memory.jsHeapSizeLimit&&memory.usedJSHeapSize/memory.jsHeapSizeLimit>.8)found.push(issue("performance/memory-pressure","performance","warning","High JavaScript heap usage",Math.round(memory.usedJSHeapSize/1048576)+" MiB of "+Math.round(memory.jsHeapSizeLimit/1048576)+" MiB is in use.",null,"Inspect retained objects and repeated hydration."));
const resources=performance.getEntriesByType("resource");const total=resources.reduce((s,e)=>s+(e.transferSize||0),0);const jsResources=resources.filter(e=>/(?:\.m?js)(?:\?|$)/i.test(e.name)&&!/\/__wrnexus\/dev-toolbar\.js(?:\?|$)/.test(e.name));const jsBytes=jsResources.reduce((sum,e)=>sum+(e.transferSize||0),0);if(resources.length>150)found.push(issue("performance/resource-count","performance","warning","Page loads many resources","Found "+resources.length+" resource requests.",null,"Remove duplicates and defer non-critical resources."));if(total>5000000)found.push(issue("performance/transfer-size","performance",total>10000000?"error":"warning","Page transfer size is large","Observed transfer size is about "+(total/1000000).toFixed(2)+" MB.",null,"Compress and optimize page resources."));if(jsBytes>300000)found.push(issue("performance/javascript-budget","javascript",jsBytes>600000?"error":"warning","Development JavaScript is large","Application JavaScript transfer is about "+(jsBytes/1000).toFixed(1)+" KB in development.",null,"Check the production build report before splitting routes; development modules are unminified.","medium",{javascriptBytes:jsBytes,mode:"development"}));const hydrationRoots=q("[data-wrn-client-module],[data-wrn-hydrate]");if(hydrationRoots.length>50)found.push(issue("performance/hydration-count","runtime","warning","Many components hydrate",hydrationRoots.length+" hydration boundaries were found.",null,"Use visible, idle or interaction hydration."));if(state.runtimeMetrics.longTasks.length)found.push(issue("performance/long-tasks","javascript","warning","Long main-thread tasks detected",state.runtimeMetrics.longTasks.length+" task(s) exceeded 50 ms.",null,"Split expensive work and reduce hydration.","high",{longestMs:Math.max(...state.runtimeMetrics.longTasks)}));
let checkedSelectors=0,unusedSelectors=0;for(const sheet of [...document.styleSheets]){const href=sheet.href||"";if(/\/__wrnexus\/(?:ui|framework)\.css(?:\?|$)|\/__wrnexus\/theme\/[^/?]+\.css(?:\?|$)/.test(href))continue;let rules;try{rules=[...(sheet.cssRules||[])]}catch{continue}for(const rule of rules){if(checkedSelectors>=2000)break;const selector=rule.selectorText;if(!selector||selector.includes(":"))continue;checkedSelectors++;try{if(!document.querySelector(selector))unusedSelectors++}catch{}}}const unusedRatio=checkedSelectors?unusedSelectors/checkedSelectors:0;if(checkedSelectors>=20&&unusedRatio>=.8)found.push(issue("css/unused-selectors","css","suggestion","Low current-page CSS coverage",unusedSelectors+" of "+checkedSelectors+" inspected application selectors do not match this page.",null,"Review across routes before removing selectors. WRNexus UI and theme styles are excluded.","medium",{checkedSelectors,unusedSelectors,unusedRatio}));const memory=performance.memory;if(memory&&memory.jsHeapSizeLimit&&memory.usedJSHeapSize/memory.jsHeapSizeLimit>.8)found.push(issue("performance/memory-pressure","performance","warning","High JavaScript heap usage",Math.round(memory.usedJSHeapSize/1048576)+" MiB of "+Math.round(memory.jsHeapSizeLimit/1048576)+" MiB is in use.",null,"Inspect retained objects and repeated hydration."));
q("[data-wrn-client-module]").forEach(el=>found.push(issue("runtime/client-module","runtime","info","Client function module",el.getAttribute("data-wrn-client-module")||"Unknown module",el,"Loaded according to the component hydration strategy.","high",{hydration:el.getAttribute("data-wrn-hydrate"),runtime:el.getAttribute("data-wrn-runtime")})));
const storeContainer=window.__wrnexusStoreContainer;
if(storeContainer&&typeof storeContainer.inspect==="function"){
@@ -9,6 +9,12 @@ test("exports usable development assets", () => {
expect(DEV_TOOLBAR_RUNTIME).toContain('data-category="accessibility"');
expect(DEV_TOOLBAR_RUNTIME).toContain('issue("plugin/"+app.id');
expect(DEV_TOOLBAR_RUNTIME).toContain("apps.appendChild(button)");
expect(DEV_TOOLBAR_RUNTIME).toContain("Low current-page CSS coverage");
expect(DEV_TOOLBAR_RUNTIME).toContain("WRNexus UI and theme styles are excluded");
expect(DEV_TOOLBAR_RUNTIME).toContain("checkedSelectors>=20&&unusedRatio>=.8");
expect(DEV_TOOLBAR_RUNTIME).toContain("Development JavaScript is large");
expect(DEV_TOOLBAR_RUNTIME).toContain("dev-toolbar\\.js");
expect(DEV_TOOLBAR_RUNTIME).toContain("jsBytes>300000");
expect(DEV_TOOLBAR_CSS).toContain(".wrn-panel");
expect(DEV_TOOLBAR_CSS).toContain(".wrn-plugin-panel");
});
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/encryption",
"version": "0.8.8",
"version": "0.8.9",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -20,7 +20,7 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
},
"dependencies": {
"@wrnexus/core": "workspace:*"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/i18n",
"version": "0.8.10",
"version": "0.8.11",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -23,7 +23,7 @@
],
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2",
"typescript": "^6.0.3",
"@wrnexus/syntax": "workspace:*"
},
"wrnexus": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/image",
"version": "0.8.9",
"version": "0.8.10",
"type": "module",
"description": "Responsive image planning, secure remote image policies, and performance auditing for WRNexusJS.",
"main": "./src/index.ts",
@@ -22,7 +22,7 @@
],
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2",
"typescript": "^6.0.3",
"@wrnexus/syntax": "workspace:*"
},
"peerDependencies": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/jwt",
"version": "0.8.8",
"version": "0.8.9",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -20,7 +20,7 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
},
"dependencies": {
"@wrnexus/core": "workspace:*"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/pwa",
"version": "0.8.8",
"version": "0.8.9",
"type": "module",
"description": "Service workers, manifests, offline queues, background sync, push, and conflict resolution for WRNexusJS.",
"main": "./src/index.ts",
@@ -14,6 +14,6 @@
],
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/realtime",
"version": "0.8.9",
"version": "0.8.10",
"description": "Typed realtime rooms, message helpers, presence utilities, package UI blocks, and WRNexusJS integration.",
"type": "module",
"sideEffects": false,
@@ -29,7 +29,7 @@
"devDependencies": {
"@types/bun": "^1.3.14",
"@wrnexus/syntax": "workspace:*",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
},
"wrnexus": {
"plugin": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/rpc",
"version": "0.8.9",
"version": "0.8.10",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -26,6 +26,6 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/typecheck",
"version": "0.8.9",
"version": "0.8.10",
"type": "module",
"main": "src/index.ts",
"exports": {
@@ -10,6 +10,6 @@
},
"dependencies": {
"@wrnexus/syntax": "workspace:*",
"typescript": "^5.5.0"
"typescript": "^6.0.3"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/uploader",
"version": "0.8.9",
"version": "0.8.10",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -23,7 +23,7 @@
],
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2",
"typescript": "^6.0.3",
"@wrnexus/syntax": "workspace:*"
},
"wrnexus": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/validation",
"version": "0.8.9",
"version": "0.8.10",
"private": true,
"type": "module",
"main": "./src/index.ts",
@@ -23,7 +23,7 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2",
"typescript": "^6.0.3",
"@wrnexus/syntax": "workspace:*"
},
"wrnexus": {
+2 -1
View File
@@ -48,7 +48,8 @@ export function validateStagedManifest(stage: string, manifest: Record<string, u
}
if (!/^@wrnexus\/[a-z0-9-]+$/.test(name)) throw new Error(`${name} has an invalid package name.`);
if (manifest.private !== undefined) throw new Error(`${name} staged manifest must omit private.`);
if ((manifest.publishConfig as any)?.registry !== "https://registry.npmjs.org/") {
const expectedRegistry = process.env.WRNEXUS_NPM_REGISTRY ?? "https://registry.npmjs.org/";
if ((manifest.publishConfig as any)?.registry !== expectedRegistry) {
throw new Error(`${name} staged manifest has an unexpected registry.`);
}
if (!["restricted", "public"].includes(String((manifest.publishConfig as any)?.access))) {
+1 -1
View File
@@ -32,7 +32,7 @@ import { validateAndHashStage, type StagedPackageIntegrity } from "./lib/package
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const packagesDir = join(repoRoot, "packages");
const stageRoot = join(repoRoot, ".publish");
const REGISTRY = "https://registry.npmjs.org/";
const REGISTRY = process.env.WRNEXUS_NPM_REGISTRY ?? "https://registry.npmjs.org/";
// First release is published PRIVATE ("restricted") so we can test before the
// world sees it; flip to public with `npm access public @wrnexus/<name>` (or set
// WRNEXUS_NPM_ACCESS=public to stage public manifests). Requires the `wrnexus`
+1 -1
View File
@@ -14,6 +14,6 @@
},
"devDependencies": {
"@types/bun": "^1.3.14",
"typescript": "^5.9.2"
"typescript": "^6.0.3"
}
}
+1
View File
@@ -6,6 +6,7 @@
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"types": ["bun"],
"strict": true,
"ignoreDeprecations": "6.0",
"skipLibCheck": true,
"noEmit": true,
"allowImportingTsExtensions": true,