Skip to content

Releases: Pyra-js/Pyra

v0.30.10

10 Mar 02:25

Choose a tag to compare

Fixed

  • All 5 example apps were unrunnable - every pyra.config.ts in examples/ was missing adapter: createReactAdapter(), causing pyra dev/build/start to fail immediately with a missing-adapter error; all configs now import and set the adapter correctly
  • Example package.json files missing @pyra-js/adapter-react - all 5 example apps now declare @pyra-js/adapter-react: workspace:* as a dependency so the adapter resolves correctly when running from the monorepo
  • HMR error overlay - compilation and route-scan errors are now broadcast to the browser as { type: "error" } WebSocket messages; an in-browser overlay (dark card, error badge, file pill, stack trace, ESC to dismiss) replaces silent console-only failures; overlay auto-dismisses on the next successful update or reload
  • Parallel compilation guard - concurrent requests to the same uncached route no longer each spawn a duplicate esbuild process; a pendingBundles map in bundler.ts and a pendingServerCompiles map in dev-compiler.ts deduplicate in-flight compiles so late arrivals await the same Promise
  • Event loop blocking in static file handler - dev-static.ts servePublicFile() switched from fs.readFileSync to fs.promises.readFile; dev-server.ts CSS and static file handlers likewise converted to fs.promises.stat and fs.promises.readFile, unblocking concurrent requests during large file reads

Changed

  • <Link> used for all internal navigation in examples - all layout nav links and intra-app page links across pyra-blog, routing-showcase, ssg-cache, and middleware-auth now use <Link> from @pyra-js/adapter-react instead of raw <a href>, enabling client-side SPA navigation without full page reloads
  • Dead artifacts removed from examples/ - removed Kubo/ (orphaned dist artifact), basic-ts/ (pre-routing SPA era), test-config/ (internal test fixture), example.sh (empty file), and 6 stale pyra.config.*.ts files (basic, full, library, minimal, mode, react) that described a superseded config API; QUICK_START.md and USAGE.md also removed as they documented the same obsolete format
  • examples/pyra.config.reference.ts added — single accurate reference file documenting every real PyraConfig field with inline comments, including a defineConfigFn mode-aware example at the bottom

v0.30.6

06 Mar 08:00

Choose a tag to compare

Added

  • pyraFramerMotion() plugin in @pyra-js/core - solves the Framer Motion + SSR incompatibility at the framework level
    • Implements the new headInjection plugin hook to inject <style id="__pyra_fm">[data-projection-id]{opacity:1!important;transform:none!important}</style> into every SSR page's <head>, keeping all Framer Motion elements visible during SSR and before hydration regardless of their initial prop
  • <FramerMotionReady> component in @pyra-js/adapter-react, companion component for pyraFramerMotion(); place it in your root layout to remove the SSR override after React hydrates and Framer Motion's own useLayoutEffect has initialized, using requestAnimationFrame to ensure correct sequencing
  • headInjection?(): string hook on the PyraPlugin interface in @pyra-js/shared - allows plugins to inject arbitrary HTML into <head> on every SSR page render, prepended before the adapter's own head tags; wired into both the streaming and buffered render paths in the dev server (dev-ssr.ts) and production server (prod/prod-server.ts)
  • Dev server compile cache warming - on startup, the dev server now pre-compiles all route and layout files in the background using Promise.allSettled, eliminating the ~280ms cold-start delay on the first browser request
  • SSR-safe hooks in @pyra-js/adapter-react - useLocation(), useParams(), and useRouteError() now return safe defaults during server-side rendering instead of crashing with window is not defined; useLocation() returns { pathname: '/', search: '', hash: '', searchParams: new URLSearchParams() } on the server, useParams() returns {}, and useRouteError() returns null

v0.27.12

06 Mar 05:17

Choose a tag to compare

Full details: CHANGELOG.md

v0.27.8

05 Mar 06:31

Choose a tag to compare

Added

  • React Fast Refresh - component-level hot updates in the dev server replace full-page reloads, preserving React component state, scroll position, and app context on every file save
    • New fast-refresh-plugin.ts in @pyra-js/adapter-react - esbuild onLoad plugin that runs react-refresh/babel via @babel/core on every .tsx/.jsx/.ts/.js file outside node_modules, inserting $RefreshReg$ and $RefreshSig$ registration calls; JSX and TypeScript transforms are still handled by esbuild after the plugin returns the modified source
    • getHMRPreamble?(): string added to the PyraAdapter interface in @pyra-js/shared - adapters return a <script type="module"> tag that initialises the HMR runtime in the browser before the hydration script's static imports execute, ensuring globals are in place when component registration calls fire
    • /__pyra_refresh_runtime endpoint in the dev server - lazily resolves react-refresh/runtime from the user's project root via createRequire, bundles it to ESM with esbuild once per session, and serves it with Cache-Control: no-cache; returns 404 if react-refresh is not installed
    • canFastRefresh(filePath, routesDir) helper in dev-hmr.ts - returns true for .tsx/.jsx/.ts/.js files that are not server-only route files (route.*, middleware.*); drives the update vs reload decision in the file watcher
    • window.__pyra_hmr_modules - a regular <script> tag (synchronous, runs before deferred module scripts) injected per page in dev-ssr.ts listing all client module URLs (/__pyra/modules/...) for the current page and its layouts; kept in sync after client-side navigations in the hydration script
    • window.__pyra_refresh — set by the RFR preamble to the react-refresh/runtime instance so the HMR client can call performReactRefresh() without importing the runtime again
    • @babel/core ^7.24.0 and react-refresh ^0.14.0 added as dependencies of @pyra-js/adapter-react
  • <Head> component in @pyra-js/adapter-react — manage document head tags (title, meta, link, etc.) from within any page or layout component
  • <ClientOnly> component in @pyra-js/adapter-react — suppresses SSR rendering of its children; useful for components that depend on browser-only APIs and would otherwise throw during renderToString()
  • <Form> component in @pyra-js/adapter-react — enhanced form element with type-safe props
  • <NavLink> component in @pyra-js/adapter-react — like <Link> but automatically applies an active CSS class when its href matches the current pathname; drops in as a replacement for <Link> in nav bars and sidebars
  • Routing hooks and utilities in @pyra-js/adapter-react
    • useLocation() - returns the current URL as a URL object, reactive to client-side navigations
    • useRouter() - exposes navigate(href) and the current params object; thin wrapper around window.__pyra
    • Additional routing helpers exported from the package index
  • Deployment documentation (docs/deployment.md)

Changed

  • Adapter-agnostic core - DevServer, build(), and ProdServer no longer have any knowledge of React; the adapter must be supplied by the application via pyra.config.ts
    • @pyra-js/adapter-react removed from @pyra-js/cli dependencies - the CLI no longer bundles the React adapter; projects must install it separately (all templates already do)
    • Passing adapter: createReactAdapter() in pyra.config.ts is now the only way to opt into React SSR; omitting adapter is an error for pyra dev, pyra build, and pyra start (existing behaviour, now enforced without a React fallback)
  • HMR file watcher now distinguishes between two update strategies for the change event
    • .tsx/.jsx/.ts/.js files that are not server-only route files → broadcasts { type: 'update' } (triggers React Fast Refresh in the browser)
    • Everything else (CSS, JSON, route.*, middleware.*, config files) → broadcasts { type: 'reload' } (full page reload, same as before)
    • File add events always broadcast { type: 'reload' } (route graph may have changed)
  • HMR client script updated to handle { type: 'update' } messages - re-imports all tracked page and layout modules with a ?__hmr=<timestamp> cache-bust query, then calls window.__pyra_refresh.performReactRefresh(); falls back to window.location.reload() if window.__pyra_refresh is not defined (non-React adapter) or if the re-import throws
  • bundleFile() in packages/core/src/bundler.ts accepts an optional extraPlugins: esbuild.Plugin[] fourth parameter - prepended before the PostCSS plugin; used to pass adapter-provided esbuild plugins (e.g. the RFR transform) to all client-side bundles from both /__pyra/modules/* and /__pyra/styles/* endpoints

Fixed

  • FormProps type error in @pyra-js/adapter-react - corrected incorrect prop type annotation that caused a TypeScript compile error when using the <Form> component
  • Type errors in PyraAdapter interface resolved - corrected mismatched signatures that surfaced after the adapter-agnostic refactor

v0.25.2

04 Mar 04:48

Choose a tag to compare

Added

  • Client-side navigation (CSN) - same-layout routes now transition without a full page reload
    • New /_pyra/navigate?path=... JSON endpoint in both dev and prod servers, runs middleware + load(), returns { data, clientEntry, layoutClientEntries, routeId }
    • Dev server: synthetic fakeReq via Object.create(req) ensures ctx.url reflects the navigated path so load() reads the correct query params
    • Prod server: manifest-driven, same response shape as dev
    • Middleware redirects (3xx) returned as { redirect: location } so the client performs a full navigation to the correct destination
    • <Link> component added to @pyra-js/adapter-react - intercepts same-origin clicks, passes through modifier-key clicks (Cmd/Ctrl/Shift/Alt) for native browser behavior, calls window.__pyra.navigate() for client-side transitions
    • getHydrationScript() in the React adapter now generates a persistent PyraApp shell component using useState/useEffect instead of a one-shot hydrateRoot() call
      • useState(() => InitialComponent) + useState(initialData) hold the current page component and data
      • useEffect registers window.__pyra.navigate(href) and a popstate handler for Back/Forward button support
      • Layout chain comparison: if nav.layoutClientEntries differs from the boot-time pageLayouts, falls back to location.href (full reload) - keeps layout boundaries clean without complex diffing
      • window.scrollTo(0, 0) on each client-side navigation

Changed

  • React Compiler wizard prompt updated from "Enable React Compiler?" to "Enable React Compiler? (beta - adds Babel build step, benefits are client-side only)" - makes the tradeoffs visible before the user opts in

Fixed

  • Tailwind CSS scaffolding in create-pyra for full-stack projects now prepends @tailwind directives to the existing style.css instead of creating a separate index.css - preserves all custom template styles (nav, cards, hero, etc.) that were previously lost
  • Tailwind index.css was previously written to src/ but full-stack layouts import from src/routes/, file is no longer created separately; directives are merged into the existing style.css in src/routes/

v0.24.0

03 Mar 06:39

Choose a tag to compare

Added

  • Router and React Compiler options in create-pyra wizard, new post-copy patching system in packages/create-pyra/src/patches.ts applies targeted file modifications after the base template is copied, avoiding a explosion of template directories
    • React SPA mode now prompts for a client-side router: None, React Router v7 (react-router: ^7.0.0), or TanStack Router (@tanstack/react-router: ^1.0.0)
    • React Router patch: rewrites main.tsx with createBrowserRouter, converts App.tsx into a layout shell with <Outlet />, and creates src/pages/Home.tsx
    • TanStack Router patch: rewrites main.tsx with a fully code-based router, removes App.tsx (root route replaces it)
    • React (both SSR and SPA) now prompts to enable the React Compiler, adds babel-plugin-react-compiler and esbuild-plugin-babel devDependencies and rewrites pyra.config.ts/js with a working Pyra plugin block
  • Separator lines in request trace terminal output - each toDetailedLog() block is now wrapped with a ────────────────────────────────────── rule above and below, making individual requests visually distinct in busy dev server output
  • Compat shim packages (packages/compat-pyrajs-*/) - new versions of the old pyrajs-* package names that re-export from @pyra-js/* so existing projects continue to work without code changes
    • pyrajs-shared → re-exports from @pyra-js/shared
    • pyrajs-core → re-exports from @pyra-js/core
    • pyrajs-adapter-react → re-exports from @pyra-js/adapter-react
    • pyrajs-cli → re-exports from @pyra-js/cli; includes a bin/pyra.mjs shim so the pyra binary continues to resolve
    • Each compat package ships a README.md with a deprecation notice and a migration guide

Changed

  • All four packages renamed to the @pyra-js npm scope
    • pyrajs-shared@pyra-js/shared
    • pyrajs-core@pyra-js/core
    • pyrajs-adapter-react@pyra-js/adapter-react
    • pyrajs-cli@pyra-js/cli
  • defineConfig and user-facing types (RequestContext, Middleware, ErrorPageProps, CacheConfig, PrerenderConfig) are now re-exported from @pyra-js/cli - application developers only ever need to import from @pyra-js/cli and never from @pyra-js/shared directly
  • All generated project templates (create-pyra and packages/cli) updated to import defineConfig from @pyra-js/cli

Fixed

  • babel-plugin-react-compiler version corrected from ^19.0.0 (non-existent) to ^1.0.0 - React Compiler reached stable 1.0 in October 2025 under its own versioning scheme

v0.21.23

27 Feb 07:05

Choose a tag to compare

Fixed

  • pyra doctor no longer reports "Entry point not found: src/index.ts" in full-stack (file-based routing) projects, the entry check is now skipped when a routes directory is detected, since file-based projects do not use a single entry file
  • pyra doctor TypeScript check no longer triggers Node.js DEP0190 deprecation warning ("Passing args to a child process with shell option true"), runTscCheck() now spawns cmd.exe /c tsc.cmd --noEmit on Windows instead of using shell: true, eliminating both the warning and the follow-on EINVAL spawn error

Security

  • Patched GHSA-mw96-cpmx-2vgc (high severity) - Rollup arbitrary file write via path traversal in versions >=4.0.0 <4.59.0; resolved by adding "rollup": "^4.59.0" to pnpm.overrides in the root package.json

Removed

  • Deprecated framework field removed from PyraConfig in packages/shared/src/types.ts - the adapter field fully replaces it and has been the intended API since v0.3

Changed

  • build.splitting is now read from config.build?.splitting instead of being hardcoded to true, applies to both the SSR client build (build-orchestrator.ts) and the SPA build (buildSPA.ts); server build remains hardcoded to false as Node.js ESM cannot load split chunks dynamically

Refactored

  • packages/cli/src/bin.ts extracted into per-command files under packages/cli/src/commands/
    • commands/dev.ts - exports DevOptions and devCommand()
    • commands/build.ts - exports BuildOptions and buildCommand()
    • commands/start.ts - exports StartOptions and startCommand()
    • commands/init.ts - exports InitOptions and initCommand()
    • bin.ts reduced from ~625 lines to ~155 lines; now a pure wiring file that resolves silent/color and delegates to each command function
    • Follows the same pattern already used by commands/graph.ts and commands/doctor.ts
  • packages/core/src/build.ts split into focused modules under packages/core/src/build/
    • build-utils.ts - routeIdToSafeName(), getAncestorDirIds() pure string utilities
    • build-client.ts - buildClientOutputMap(), buildClientLayoutOutputMap() esbuild metafile correlation
    • build-server.ts - buildServerOutputMap(), buildServerMwLayoutOutputMap() server entry path resolution
    • build-manifest.ts - assembleManifest(), buildEmptyManifest(), getMimeType(), DEFAULT_SHELL
    • build-prerender.ts - buildPrerenderAssetTags() SSG asset tag generation
    • build-report.ts - printBuildReport() and private helpers (estimateGzipSize, getSharedChunks, formatSize, countFilesRecursive)
    • build-orchestrator.ts - main build() function
    • build.ts replaced with a 3-line re-export shim; all external imports remain unchanged
  • packages/core/src/prod-server.ts split into focused modules under packages/core/src/prod/
    • prod-matcher.ts - MatchResult, buildMatcher() trie-based manifest route matcher
    • prod-assets.ts - buildCacheControlHeader(), getCacheControl(), getContentType(), buildAssetTags() static asset helpers
    • prod-html.ts - DEFAULT_SHELL, getErrorHTML(), get404HTML() HTML string generators
    • prod-server.ts - ProdServer class importing from the three helpers above
    • prod-server.ts at src/ root replaced with a 4-line re-export shim; all external imports remain unchanged

v0.19.0

26 Feb 19:26

Choose a tag to compare

Added

  • CORS support - configure cross-origin resource sharing via the cors field in pyra.config.ts
    • New CorsConfig type in packages/shared/src/types.ts: origin, methods, allowedHeaders, exposedHeaders, credentials, maxAge fields
    • packages/core/src/cors.tsbuildCORSHeaders() and applyCors() helpers; origin resolution supports wildcard, string, and array allow-lists; automatic OPTIONS preflight handling
    • Dev server and production server both apply CORS headers via applyCors() before routing
  • Static HTML CSS injection in dev server — prerendered/static HTML pages now receive <link rel="stylesheet"> tags for co-located CSS imports, consistent with SSR pages
  • HMR client CSS injection - extracted CSS is now injected into the HMR client bundle so hot-reloaded pages receive stylesheet updates

Refactored

  • packages/core/src/dev-server.ts split into eight focused modules under packages/core/src/dev/
    • dev-compiler.ts — on-demand esbuild compilation
    • dev-hmr.ts — WebSocket HMR server and client injection
    • dev-dashboard.ts/_pyra dashboard and API endpoint handlers
    • dev-static.ts — static file and public/ directory serving
    • dev-api.ts — API route dispatch and method validation
    • dev-routes.ts — page route matching and SSR pipeline entry
    • dev-errors.ts — error boundary rendering and 404 handling
    • dev-ssr.ts — core SSR assembly (load → render → shell → inject assets)
    • Dead code removed; dev-server.ts now re-exports and wires the modules together

Fixed

  • TypeScript typecheck errors in Image.test.tsx — corrected mockContext, renderToHTML, adapter, and React type annotations across multiple test helpers
  • Image plugin tests - added missing manifest fixture to image-plugin-tests.ts
  • Middleware tests - corrected assertions after middleware refactor

v0.18.4

23 Feb 03:26

Choose a tag to compare

Added

  • Module resolution and path alias support via ResolveConfig is now wired to esbuild in both dev and production builds
    • bundler.ts: new buildEsbuildResolveOptions() helper translates ResolveConfig to esbuild alias, resolveExtensions, and mainFields options; alias values are resolved to absolute paths automatically
    • dev-server.ts: all four bundleFile() call sites now pass config.resolve
    • build.ts: buildEsbuildResolveOptions() spread into both client and server esbuild.build() calls
  • RequestTracer.setDetail(detail) method - annotates the most recently closed stage with a detail string without opening a new span; used to attach the matched route ID to the route-match stage after the router returns
  • MetricsStore.isActiveBuild() method - returns true when startBuild() has been called without a matching finishBuild(); used to coordinate build lifecycle across the file watcher and request handler
  • Build metrics now fully wired in dev server
    • startBuild() called in the change and add watcher handlers when a source file is saved
    • Orphaned builds (two rapid saves with no intervening request) are closed before the next startBuild() via isActiveBuild()
    • finishBuild() called after the response is sent for the first routed request following a file change; totalDuration spans the full file-save-to-response cycle
  • Dashboard "Recent Requests" section - live table polling /_pyra/api/traces every 2 seconds
    • Color-coded method badges (GET, POST, PUT, PATCH, DELETE)
    • Color-coded status badges by class (2xx, 3xx, 4xx, 5xx)
    • Per-request pipeline stage breakdown with slow-stage highlighting (yellow above 50% of total, red above 80%)
    • Relative timestamps ("3s ago", "1m ago")
    • Matched route ID shown below the URL when the pattern differs from the path
  • Docs: docs/dashboard.md, docs/request-context.md, docs/cookies.md, docs/plugins.mdx

Fixed

  • Double route-match entry in request traces - the dev server was opening two separate spans for route matching; the second (zero-duration) span is now replaced with tracer.setDetail(), attaching the route ID to the first span and keeping extractRouteId() correct
  • Dashboard empty state messages used em dashes; replaced with commas for consistency

Changed

  • Dashboard stat cards show -- instead of 0 when no build data exists yet, avoiding misleading zeros on initial load
  • Build history rows now include a file count column
  • Dashboard "Recent Requests" and "Build History" empty states distinguish between the two conditions with separate messages

v0.16.1

20 Feb 08:25

Choose a tag to compare

Added

  • CSS pipeline fix in dev server - import './style.css' in layout/page files now works without Flash of Unstyled Content

    • bundler.ts: new cssOutputCache stores CSS extracted by esbuild separately from the JS bundle; removed the old document.createElement('style') injection that caused FOUC
    • New getCSSOutput(filePath) export returns cached CSS for a given entry file
    • dev-server.ts: new /__pyra/styles/* endpoint serves extracted CSS as proper text/css responses
    • handlePageRouteInner eagerly calls bundleFile() for each layout + page before assembly, then injects <link rel="stylesheet" href="/__pyra/styles/..."> tags into <head> via <!--pyra-head-->
    • style.css moved back to src/routes/ (co-located with the layout) in all full-stack templates; import './style.css' restored as a standard ES module import
  • Package READMEs: packages/adapter-react/README.md, packages/core/README.md, packages/shared/README.md - each tailored to its audience (end-user, internal/contributor, type reference)

  • .vscode/settings.json - excludes packages/cli/templates/** from Tailwind CSS extension scanning, preventing false "unable to load config" errors caused by the extension attempting CommonJS require() on ESM-only template config files

Changed

  • create-pyra rendering mode prompt labels updated from "SSR / SPA" to "Full-stack / Frontend (SPA)" - value fields ("ssr" / "spa") are unchanged so template selection is unaffected
  • packages/cli dev:link script changed from pnpm link -g to npm link - now consistent with create-pyra's dev:link, making npm link pyrajs-cli work correctly in generated test projects
  • Full-stack template CSS redesigned across all six templates (create-pyra React/Preact TS/JS and pyrajs-cli React TS/JS fullstack) to match the Pyra visual identity

Fixed

  • vitest.workspace.ts: removed defineWorkspace import that broke TypeScript in Vitest 4 (the export was removed in Vitest 4; workspace files now export the array directly)