@idlesummer/pen 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import{a as e,f as t,i as n,n as r,r as i,t as a}from"./serialized-routes-DFDKf4sB.mjs";import{join as o,resolve as s}from"path";import{pathToFileURL as c}from"url";import{existsSync as l}from"fs";import{Command as u}from"commander";import{duration as d,fileList as f,pipe as p}from"@idlesummer/tasker";import{mkdir as m,writeFile as h}from"fs/promises";import{build as g}from"rolldown";import _ from"rollup-plugin-node-externals";var v=Object.create,y=Object.defineProperty,b=Object.getOwnPropertyDescriptor,x=Object.getOwnPropertyNames,S=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty,w=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),T=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=x(t),a=0,o=i.length,s;a<o;a++)s=i[a],!C.call(e,s)&&s!==n&&y(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=b(t,s))||r.enumerable});return e},E=(e,t,n)=>(n=e==null?{}:v(S(e)),T(t||!e||!e.__esModule?y(n,`default`,{value:e,enumerable:!0}):n,e));const D=`@idlesummer/pen`,O=`0.2.3`,k=`@idlesummer/pen`.split(`/`)?.[1]??`@idlesummer/pen`;var A=E(w(((e,t)=>{let n=process||{},r=n.argv||[],i=n.env||{},a=!(i.NO_COLOR||r.includes(`--no-color`))&&(!!i.FORCE_COLOR||r.includes(`--color`)||n.platform===`win32`||(n.stdout||{}).isTTY&&i.TERM!==`dumb`||!!i.CI),o=(e,t,n=e)=>r=>{let i=``+r,a=i.indexOf(t,e.length);return~a?e+s(i,t,n,a)+t:e+i+t},s=(e,t,n,r)=>{let i=``,a=0;do i+=e.substring(a,r)+n,a=r+t.length,r=e.indexOf(t,a);while(~r);return i+e.substring(a)},c=(e=a)=>{let t=e?o:()=>String;return{isColorSupported:e,reset:t(`\x1B[0m`,`\x1B[0m`),bold:t(`\x1B[1m`,`\x1B[22m`,`\x1B[22m\x1B[1m`),dim:t(`\x1B[2m`,`\x1B[22m`,`\x1B[22m\x1B[2m`),italic:t(`\x1B[3m`,`\x1B[23m`),underline:t(`\x1B[4m`,`\x1B[24m`),inverse:t(`\x1B[7m`,`\x1B[27m`),hidden:t(`\x1B[8m`,`\x1B[28m`),strikethrough:t(`\x1B[9m`,`\x1B[29m`),black:t(`\x1B[30m`,`\x1B[39m`),red:t(`\x1B[31m`,`\x1B[39m`),green:t(`\x1B[32m`,`\x1B[39m`),yellow:t(`\x1B[33m`,`\x1B[39m`),blue:t(`\x1B[34m`,`\x1B[39m`),magenta:t(`\x1B[35m`,`\x1B[39m`),cyan:t(`\x1B[36m`,`\x1B[39m`),white:t(`\x1B[37m`,`\x1B[39m`),gray:t(`\x1B[90m`,`\x1B[39m`),bgBlack:t(`\x1B[40m`,`\x1B[49m`),bgRed:t(`\x1B[41m`,`\x1B[49m`),bgGreen:t(`\x1B[42m`,`\x1B[49m`),bgYellow:t(`\x1B[43m`,`\x1B[49m`),bgBlue:t(`\x1B[44m`,`\x1B[49m`),bgMagenta:t(`\x1B[45m`,`\x1B[49m`),bgCyan:t(`\x1B[46m`,`\x1B[49m`),bgWhite:t(`\x1B[47m`,`\x1B[49m`),blackBright:t(`\x1B[90m`,`\x1B[39m`),redBright:t(`\x1B[91m`,`\x1B[39m`),greenBright:t(`\x1B[92m`,`\x1B[39m`),yellowBright:t(`\x1B[93m`,`\x1B[39m`),blueBright:t(`\x1B[94m`,`\x1B[39m`),magentaBright:t(`\x1B[95m`,`\x1B[39m`),cyanBright:t(`\x1B[96m`,`\x1B[39m`),whiteBright:t(`\x1B[97m`,`\x1B[39m`),bgBlackBright:t(`\x1B[100m`,`\x1B[49m`),bgRedBright:t(`\x1B[101m`,`\x1B[49m`),bgGreenBright:t(`\x1B[102m`,`\x1B[49m`),bgYellowBright:t(`\x1B[103m`,`\x1B[49m`),bgBlueBright:t(`\x1B[104m`,`\x1B[49m`),bgMagentaBright:t(`\x1B[105m`,`\x1B[49m`),bgCyanBright:t(`\x1B[106m`,`\x1B[49m`),bgWhiteBright:t(`\x1B[107m`,`\x1B[49m`)}};t.exports=c(),t.exports.createColors=c}))(),1);const j={name:`Building file tree`,onSuccess:(e,t)=>`Built file tree (${d(t)})`,run:async t=>({fileTree:e(t.appDir)})},M={name:`Building segment tree...`,onSuccess:(e,t)=>`Built segment tree (${d(t)})`,run:async e=>({segmentTree:n(e.fileTree)})},N={name:`Building route chain map`,onSuccess:(e,t)=>`Built route chain map (${d(t)})`,run:async e=>({routeChainMap:i(e.segmentTree,e.outDir)})},P={name:`Building component index map`,onSuccess:(e,t)=>`Built component map (${d(t)})`,run:async e=>({componentIdMap:r(e.routeChainMap)})},F=`route-chain-map.ts`,I={name:`Writing ${F}`,onSuccess:(e,t)=>`Saved ${F}} (${d(t)})`,run:async e=>{let t=o(e.outDir,`artifacts`),n=o(t,F),r=[`// Auto-generated by ${D}`,`// Do not manually edit this file`,`// NOTE: This file is kept for documentation/debugging.`,`// Runtime uses pre-built routes from routes.ts instead.`,``,`import type { RouteChainMap } from '${D}'`,``,`export const routes: RouteChainMap = ${JSON.stringify(e.routeChainMap,null,2)} as const`,``].join(`
3
- `);await m(t,{recursive:!0}),await h(n,r,`utf-8`)}},L={name:`Serializing routes`,onSuccess:(e,t)=>`Serialized routes (${d(t)})`,run:async e=>({serializedRoutes:a(e.routeChainMap,e.componentIdMap)})},R=`serialized-routes.ts`,z={name:`Writing ${R}`,onSuccess:(e,t)=>`Saved ${R} (${d(t)})`,run:async e=>{let t=o(e.outDir,`artifacts`),n=o(t,R),r=[`// Auto-generated by ${D}`,`// Do not manually edit this file`,`// NOTE: This file is kept for documentation/debugging.`,``,`import type { SerializedRouteTreeMap } from '${D}'`,``,`export const serializedRoutes: SerializedRouteTreeMap = ${JSON.stringify(e.serializedRoutes,null,2)} as const`,``].join(`
4
- `);await m(t,{recursive:!0}),await h(n,r,`utf-8`)}},B=`component-id-map.ts`,V={name:`Writing ${B}`,onSuccess:(e,t)=>`Saved ${B} (${d(t)})`,run:async e=>{let t=o(e.outDir,`artifacts`),n=o(t,B),r=e.componentIdMap,i=[`// Auto-generated by ${D}`,`// Do not manually edit this file`,``,`import type { ComponentIdMap } from '${D}'`,``,`export const componentIdMap: ComponentIdMap = ${JSON.stringify(r,null,2)} as const`,``].join(`
5
- `);await m(t,{recursive:!0}),await h(n,i,`utf-8`)}},H=`compiled-routes.ts`,U={name:`Writing ${H}`,onSuccess:(e,t)=>`Saved ${H} (${d(t)})`,run:async e=>{let t=o(e.outDir,`generated`),n=o(t,H),r=e.serializedRoutes,i=e.componentIdMap,a=Object.keys(i).map((e,t)=>`import Component${t} from '${e}'`).join(`
6
- `),s=[];for(let[e,t]of Object.entries(r)){let n=` ${W(t).replace(/\n/g,`
7
- `)}`;s.push(` '${e}':\n${n},`)}let c=[`// Auto-generated by ${D}`,`// Do not manually edit this file`,``,`import type { CompiledRoutes } from '${D}'`,`import { createElement } from 'react'`,``,a,``,`// Compiled route elements generated at build time`,`export const compiledRoutes: CompiledRoutes = {`,s.join(`
8
- `),`} as const`,``].join(`
9
- `);await m(t,{recursive:!0}),await h(n,c,`utf-8`)}};function W(e,t=0){let n=` `.repeat(t),r=Object.entries(e.props).map(([e,t])=>`${e}: ${t}`).join(`, `);if(!e.children)return`createElement(${e.component}, { ${r} })`;let i=W(e.children,t+1);return`createElement(${e.component}, { ${r} },\n${n} ${i}\n${n})`}const G=`entry.ts`,K={name:`Writing ${G}`,onSuccess:(e,t)=>`Saved ${G} (${d(t)})`,run:async e=>{let t=o(e.outDir,`generated`),n=o(t,G),r=[`// Auto-generated by ${D}`,`// Do not manually edit this file`,``,`import { createElement } from 'react'`,`import { render } from 'ink'`,`import { App } from '${D}'`,`import { compiledRoutes as routes } from './compiled-routes'`,``,`export async function run(initialUrl: string) {`,` const element = createElement(App, { initialUrl, routes })`,` const { waitUntilExit } = render(element)`,` await waitUntilExit()`,`}`,``].join(`
10
- `);await m(t,{recursive:!0}),await h(n,r,`utf-8`)}},q={name:`Compiling application`,onSuccess:(e,t)=>`Compiled application (${d(t)})`,onError:e=>`Compilation failed: ${e.message}`,run:async e=>{await g({input:s(o(e.outDir,`generated`,`entry.ts`)),platform:`node`,resolve:{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`]},plugins:[_()],output:{dir:s(o(e.outDir,`dist`)),format:`esm`,sourcemap:!0,minify:!0}})}},J={name:`build`,desc:`Build the routing structure and compile application`,action:async()=>{try{let{appDir:e,outDir:n,emitMetadata:r}=await t();console.log(A.default.cyan(` Starting production build...
11
- `)),console.log(A.default.bold(` ✎ ${k} v${O}\n`)),console.log(A.default.dim(` entry: ${e}`)),console.log(A.default.dim(` target: node24`)),console.log(A.default.dim(` output: ${n}`)),console.log();let{duration:i}=await p([j,M,N,P,L,r&&I,r&&z,r&&V,U,K,q]).run({appDir:e,outDir:n});console.log(),console.log(f(n,`**/*`)),console.log(),console.log(`${A.default.green(`✓`)} Built in ${A.default.bold(d(i))}`),console.log()}catch(e){console.error(`${A.default.red(`✗`)} Build failed`),console.log();let t=e instanceof Error?e.message:String(e);console.error(A.default.red(t)),console.log(),process.exit(1)}}},Y={name:`init`,desc:`Initialize a new Pen project`,action:async()=>{if(console.log(A.default.cyan(`\n Initializing ${k} project...\n`)),l(`pen.config.ts`)){console.error(A.default.yellow(`⚠`)+` Project already initialized`),console.error(A.default.dim(` pen.config.ts already exists`));return}await h(`pen.config.ts`,[`import { defineConfig } from '${D}'`,``,`export default defineConfig({`,` appDir: './src/app',`,` outDir: './.pen',`,`})`,``].join(`
12
- `),`utf-8`),console.log(A.default.green(`✓`)+` Created pen.config.ts`);let e=`./src/app`;await m(e,{recursive:!0});let t=[`import { Box, Text } from 'ink'`,`import type { ReactNode } from 'react'`,``,`export default function Layout({ children }: { children?: ReactNode }) {`,` return (`,` <Box flexDirection="column" padding={1}>`,` <Box marginBottom={1} borderStyle="round" borderColor="cyan" paddingX={2}>`,` <Text bold color="cyan">Welcome to Pen</Text>`,` </Box>`,` {children}`,` </Box>`,` )`,`}`,``].join(`
13
- `);await h(o(e,`layout.tsx`),t,`utf-8`),console.log(A.default.green(`✓`)+` Created src/app/layout.tsx`);let n=[`import { useState } from 'react'`,`import { Box, Text, useInput } from 'ink'`,``,`export default function Screen() {`,` const [count, setCount] = useState(0)`,``,` useInput((input) => {`,` if (input === ' ') setCount(c => c + 1)`,` })`,``,` return (`,` <Box flexDirection="column" gap={1}>`,` <Box>`,` <Text>Count: <Text bold color="green">{count}</Text></Text>`,` </Box>`,` <Box>`,` <Text dimColor>Press <Text bold>SPACE</Text> to increment</Text>`,` </Box>`,` </Box>`,` )`,`}`,``].join(`
14
- `);await h(o(e,`screen.tsx`),n,`utf-8`),console.log(A.default.green(`✓`)+` Created src/app/screen.tsx`),console.log(),console.log(A.default.green(`✓`)+` Project initialized!`),console.log(),console.log(A.default.bold(` Next steps:`)),console.log(),console.log(A.default.dim(` 1. Add scripts to package.json:`)),console.log(),console.log(` {`),console.log(` "scripts": {`),console.log(` "build": "${k} build",`),console.log(` "start": "${k} start"`),console.log(` }`),console.log(` }`),console.log(),console.log(A.default.dim(` 2. Run "npm run build" to build your app`)),console.log(A.default.dim(` 3. Run "npm run start" to start your app`)),console.log()}},X={name:`start`,desc:`Start the application`,action:async()=>{let{outDir:e}=await t(),n=s(e,`dist`,`entry.js`);if(!l(n))throw console.error(A.default.red(`✗`)+` Build not found`),console.error(A.default.dim(` Run "${k} build" first`)),Error(`Build not found`);let{run:r}=await import(c(n).href);await r(`/`)}};function Z(e){e.name(k).description(`File-based routing for React Ink apps (experimental)`).version(O);let t=[J,Y,X];for(let{name:n,desc:r,action:i}of t)e.command(n).description(r).action(i);return e}async function Q(e){let t=Z(new u);try{return await t.parseAsync(e),0}catch(e){let t=e instanceof Error?e.message:`Unknown error`;return console.error(t),1}}process.exit(await Q(process.argv));export{};
2
+ import{f as e,i as t,n,r,t as i}from"./route-tree-BbrKaika.mjs";import{join as a,resolve as o}from"path";import{pathToFileURL as s}from"url";import{existsSync as c}from"fs";import{Command as l}from"commander";import{duration as u,fileList as d,pipe as f}from"@idlesummer/tasker";import{mkdir as p,writeFile as m}from"fs/promises";import{build as h}from"rolldown";import g from"rollup-plugin-node-externals";var _=Object.create,v=Object.defineProperty,y=Object.getOwnPropertyDescriptor,b=Object.getOwnPropertyNames,x=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty,C=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),w=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=b(t),a=0,o=i.length,s;a<o;a++)s=i[a],!S.call(e,s)&&s!==n&&v(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=y(t,s))||r.enumerable});return e},T=(e,t,n)=>(n=e==null?{}:_(x(e)),w(t||!e||!e.__esModule?v(n,`default`,{value:e,enumerable:!0}):n,e));const E=`@idlesummer/pen`,D=`0.3.0`,O=`@idlesummer/pen`.split(`/`)?.[1]??`@idlesummer/pen`;var k=T(C(((e,t)=>{let n=process||{},r=n.argv||[],i=n.env||{},a=!(i.NO_COLOR||r.includes(`--no-color`))&&(!!i.FORCE_COLOR||r.includes(`--color`)||n.platform===`win32`||(n.stdout||{}).isTTY&&i.TERM!==`dumb`||!!i.CI),o=(e,t,n=e)=>r=>{let i=``+r,a=i.indexOf(t,e.length);return~a?e+s(i,t,n,a)+t:e+i+t},s=(e,t,n,r)=>{let i=``,a=0;do i+=e.substring(a,r)+n,a=r+t.length,r=e.indexOf(t,a);while(~r);return i+e.substring(a)},c=(e=a)=>{let t=e?o:()=>String;return{isColorSupported:e,reset:t(`\x1B[0m`,`\x1B[0m`),bold:t(`\x1B[1m`,`\x1B[22m`,`\x1B[22m\x1B[1m`),dim:t(`\x1B[2m`,`\x1B[22m`,`\x1B[22m\x1B[2m`),italic:t(`\x1B[3m`,`\x1B[23m`),underline:t(`\x1B[4m`,`\x1B[24m`),inverse:t(`\x1B[7m`,`\x1B[27m`),hidden:t(`\x1B[8m`,`\x1B[28m`),strikethrough:t(`\x1B[9m`,`\x1B[29m`),black:t(`\x1B[30m`,`\x1B[39m`),red:t(`\x1B[31m`,`\x1B[39m`),green:t(`\x1B[32m`,`\x1B[39m`),yellow:t(`\x1B[33m`,`\x1B[39m`),blue:t(`\x1B[34m`,`\x1B[39m`),magenta:t(`\x1B[35m`,`\x1B[39m`),cyan:t(`\x1B[36m`,`\x1B[39m`),white:t(`\x1B[37m`,`\x1B[39m`),gray:t(`\x1B[90m`,`\x1B[39m`),bgBlack:t(`\x1B[40m`,`\x1B[49m`),bgRed:t(`\x1B[41m`,`\x1B[49m`),bgGreen:t(`\x1B[42m`,`\x1B[49m`),bgYellow:t(`\x1B[43m`,`\x1B[49m`),bgBlue:t(`\x1B[44m`,`\x1B[49m`),bgMagenta:t(`\x1B[45m`,`\x1B[49m`),bgCyan:t(`\x1B[46m`,`\x1B[49m`),bgWhite:t(`\x1B[47m`,`\x1B[49m`),blackBright:t(`\x1B[90m`,`\x1B[39m`),redBright:t(`\x1B[91m`,`\x1B[39m`),greenBright:t(`\x1B[92m`,`\x1B[39m`),yellowBright:t(`\x1B[93m`,`\x1B[39m`),blueBright:t(`\x1B[94m`,`\x1B[39m`),magentaBright:t(`\x1B[95m`,`\x1B[39m`),cyanBright:t(`\x1B[96m`,`\x1B[39m`),whiteBright:t(`\x1B[97m`,`\x1B[39m`),bgBlackBright:t(`\x1B[100m`,`\x1B[49m`),bgRedBright:t(`\x1B[101m`,`\x1B[49m`),bgGreenBright:t(`\x1B[102m`,`\x1B[49m`),bgYellowBright:t(`\x1B[103m`,`\x1B[49m`),bgBlueBright:t(`\x1B[104m`,`\x1B[49m`),bgMagentaBright:t(`\x1B[105m`,`\x1B[49m`),bgCyanBright:t(`\x1B[106m`,`\x1B[49m`),bgWhiteBright:t(`\x1B[107m`,`\x1B[49m`)}};t.exports=c(),t.exports.createColors=c}))(),1);const A={name:`Building file tree`,onSuccess:(e,t)=>`Built file tree (${u(t)})`,run:async e=>({fileTree:t(e.appDir)})},j={name:`Building segment tree...`,onSuccess:(e,t)=>`Built segment tree (${u(t)})`,run:async e=>({segmentTree:r(e.fileTree)})},M={name:`Building route tree`,onSuccess:(e,t)=>`Built route tree (${u(t)})`,run:async e=>({routeTree:i(e.segmentTree,e.outDir)})},N={name:`Writing file-tree.ts`,onSuccess:(e,t)=>`Saved file-tree.ts (${u(t)})`,run:async e=>{let t=a(e.outDir,`artifacts`),n=a(t,`file-tree.ts`),r=[`// Auto-generated by ${E}`,`// Do not manually edit this file`,``,`import type { FileNode } from "${E}"`,``,`export const fileTree: FileNode = ${JSON.stringify(e.fileTree,null,2)} as const`,``].join(`
3
+ `);await p(t,{recursive:!0}),await m(n,r,`utf-8`)}},P={name:`Writing segment-tree.ts`,onSuccess:(e,t)=>`Saved segment-tree.ts (${u(t)})`,run:async e=>{let t=a(e.outDir,`artifacts`),n=a(t,`segment-tree.ts`),r=[`// Auto-generated by ${E}`,`// Do not manually edit this file`,``,`import type { SegmentNode } from "${E}"`,``,`export const segmentTree: SegmentNode = ${JSON.stringify(e.segmentTree,null,2)} as const`,``].join(`
4
+ `);await p(t,{recursive:!0}),await m(n,r,`utf-8`)}},F={name:`Writing route-tree.ts`,onSuccess:(e,t)=>`Saved route-tree.ts (${u(t)})`,run:async e=>{let t=a(e.outDir,`generated`),n=a(t,`route-tree.ts`),r=[`// Auto-generated by ${E}`,`// Do not manually edit this file`,``,`import type { RouteTreeNode } from "${E}"`,``,`export const routeTree: RouteTreeNode = ${JSON.stringify(e.routeTree,null,2)} as const`,``].join(`
5
+ `);await p(t,{recursive:!0}),await m(n,r,`utf-8`)}},I={name:`Writing path-component-map.ts`,onSuccess:(e,t)=>`Saved path-component-map.ts (${u(t)})`,run:async e=>{let t=a(e.outDir,`generated`),n=a(t,`path-component-map.ts`),r=L(e.routeTree),i=Array.from(r),o=i.map(R).join(`
6
+ `),s=i.map(z).join(`
7
+ `),c=[`// Auto-generated by ${E}`,`// Do not manually edit this file`,``,o,``,`export const componentMap = {`,s,`}`,``].join(`
8
+ `);await p(t,{recursive:!0}),await m(n,c,`utf-8`)}};function L(e){let t=new Set;function r(e){for(let r of n)e.roles?.[r]&&t.add(e.roles[r]);for(let t of e.children??[])r(t)}return r(e),t}const R=(e,t)=>`import Component${t} from "${e}"`,z=(e,t)=>` ${JSON.stringify(e)}: Component${t},`,B={name:`Writing entry.ts`,onSuccess:(e,t)=>`Saved entry.ts (${u(t)})`,run:async e=>{let t=a(e.outDir,`generated`),n=a(t,`entry.ts`),r=[`// Auto-generated by ${E}`,`// Do not manually edit this file`,``,`import { createElement } from "react"`,`import { render } from "ink"`,`import { App } from '${E}'`,`import { componentMap } from "./path-component-map"`,`import { routeTree } from "./route-tree"`,``,`export async function run(initialUrl: string) {`,` const routingTable = { routeTree, componentMap }`,` const element = createElement(App, { initialUrl, routingTable })`,` const { waitUntilExit } = render(element)`,` await waitUntilExit()`,`}`,``].join(`
9
+ `);await p(t,{recursive:!0}),await m(n,r,`utf-8`)}},V={name:`Compiling application`,onSuccess:(e,t)=>`Compiled application (${u(t)})`,onError:e=>`Compilation failed: ${e.message}`,run:async e=>{await h({input:o(a(e.outDir,`generated`,`entry.ts`)),platform:`node`,resolve:{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`]},plugins:[g()],output:{dir:o(a(e.outDir,`dist`)),format:`esm`,sourcemap:!0,minify:!0}})}},H={name:`build`,desc:`Build the routing structure and compile application`,action:async()=>{try{let{appDir:t,outDir:n,emitMetadata:r}=await e();console.log(k.default.cyan(` Starting production build...
10
+ `)),console.log(k.default.bold(` ✎ ${O} v${D}\n`)),console.log(k.default.dim(` entry: ${t}`)),console.log(k.default.dim(` target: node24`)),console.log(k.default.dim(` output: ${n}`)),console.log();let{duration:i}=await f([A,j,M,r&&N,r&&P,F,I,B,V]).run({appDir:t,outDir:n});console.log(),console.log(d(n,`**/*`)),console.log(),console.log(k.default.green(`Built in ${k.default.bold(u(i))}`)),console.log()}catch(e){console.error(k.default.red(`Build failed`)),console.log();let t=e instanceof Error?e.message:String(e);console.error(k.default.red(t)),console.log(),process.exit(1)}}},U={name:`init`,desc:`Initialize a new Pen project`,action:async()=>{if(console.log(k.default.cyan(`\n Initializing ${O} project...\n`)),c(`pen.config.ts`)){console.error(k.default.yellow(`⚠`)+` Project already initialized`),console.error(k.default.dim(` pen.config.ts already exists`));return}await m(`pen.config.ts`,[`import { defineConfig } from '${E}'`,``,`export default defineConfig({`,` appDir: './src/app',`,` outDir: './.pen',`,`})`,``].join(`
11
+ `),`utf-8`),console.log(k.default.green(`✓`)+` Created pen.config.ts`);let e=`./src/app`;await p(e,{recursive:!0});let t=[`import { Box, Text } from 'ink'`,`import type { ReactNode } from 'react'`,``,`export default function Layout({ children }: { children?: ReactNode }) {`,` return (`,` <Box flexDirection="column" padding={1}>`,` <Box marginBottom={1} borderStyle="round" borderColor="cyan" paddingX={2}>`,` <Text bold color="cyan">Welcome to Pen</Text>`,` </Box>`,` {children}`,` </Box>`,` )`,`}`,``].join(`
12
+ `);await m(a(e,`layout.tsx`),t,`utf-8`),console.log(k.default.green(`✓`)+` Created src/app/layout.tsx`);let n=[`import { useState } from 'react'`,`import { Box, Text, useInput } from 'ink'`,``,`export default function Screen() {`,` const [count, setCount] = useState(0)`,``,` useInput((input) => {`,` if (input === ' ') setCount(c => c + 1)`,` })`,``,` return (`,` <Box flexDirection="column" gap={1}>`,` <Box>`,` <Text>Count: <Text bold color="green">{count}</Text></Text>`,` </Box>`,` <Box>`,` <Text dimColor>Press <Text bold>SPACE</Text> to increment</Text>`,` </Box>`,` </Box>`,` )`,`}`,``].join(`
13
+ `);await m(a(e,`screen.tsx`),n,`utf-8`),console.log(k.default.green(`✓`)+` Created src/app/screen.tsx`),console.log(),console.log(k.default.green(`✓`)+` Project initialized!`),console.log(),console.log(k.default.bold(` Next steps:`)),console.log(),console.log(k.default.dim(` 1. Add scripts to package.json:`)),console.log(),console.log(` {`),console.log(` "scripts": {`),console.log(` "build": "${O} build",`),console.log(` "start": "${O} start"`),console.log(` }`),console.log(` }`),console.log(),console.log(k.default.dim(` 2. Run "npm run build" to build your app`)),console.log(k.default.dim(` 3. Run "npm run start" to start your app`)),console.log()}},W={name:`start`,desc:`Start the application`,action:async()=>{let{outDir:t}=await e(),n=o(t,`dist`,`entry.js`);if(!c(n))throw console.error(k.default.red(`✗`)+` Build not found`),console.error(k.default.dim(` Run "${O} build" first`)),Error(`Build not found`);let{run:r}=await import(s(n).href);await r(`/`)}};function G(e){e.name(O).description(`File-based routing for React Ink apps (experimental)`).version(D);let t=[H,U,W];for(let{name:n,desc:r,action:i}of t)e.command(n).description(r).action(i);return e}async function K(e){let t=G(new l);try{return await t.parseAsync(e),0}catch(e){let t=e instanceof Error?e.message:`Unknown error`;return console.error(t),1}}process.exit(await K(process.argv));export{};
15
14
  //# sourceMappingURL=bin.mjs.map
package/dist/bin.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.mjs","names":["filename","filename","filename","filename","rolldownBuild","build","pc","pc","pc","run","build"],"sources":["../src/pen/constants.ts","../node_modules/picocolors/picocolors.js","../src/cli/commands/build/tasks/build-file-tree.ts","../src/cli/commands/build/tasks/build-segment-tree.ts","../src/cli/commands/build/tasks/build-route-chain-map.ts","../src/cli/commands/build/tasks/build-component-id-map.ts","../src/cli/commands/build/tasks/write-route-chain-map-file.ts","../src/cli/commands/build/tasks/build-serialized-routes.ts","../src/cli/commands/build/tasks/write-serialized-routes-file.ts","../src/cli/commands/build/tasks/write-component-id-map-file.ts","../src/cli/commands/build/tasks/write-compiled-routes-file.ts","../src/cli/commands/build/tasks/write-entry-file.ts","../src/cli/commands/build/tasks/compile-application.ts","../src/cli/commands/build/index.ts","../src/cli/commands/init/index.ts","../src/cli/commands/start/index.ts","../src/cli/index.ts","../src/bin.ts"],"sourcesContent":["// Build-time globals injected by tsdown via `define`.\r\n// tsdown replaces these placeholders with actual values\r\n// from package.json at compile time.\r\n\r\ndeclare const __DESCRIPTION__: string\r\ndeclare const __PACKAGE_NAME__: string\r\ndeclare const __VERSION__: string\r\n\r\n// Package metadata\r\nexport const DESCRIPTION = __DESCRIPTION__\r\nexport const PACKAGE_NAME = __PACKAGE_NAME__\r\nexport const VERSION = __VERSION__\r\n\r\n// Framework metadata\r\nexport const CLI_NAME = __PACKAGE_NAME__.split('/')?.[1] ?? __PACKAGE_NAME__\r\n","let p = process || {}, argv = p.argv || [], env = p.env || {}\nlet isColorSupported =\n\t!(!!env.NO_COLOR || argv.includes(\"--no-color\")) &&\n\t(!!env.FORCE_COLOR || argv.includes(\"--color\") || p.platform === \"win32\" || ((p.stdout || {}).isTTY && env.TERM !== \"dumb\") || !!env.CI)\n\nlet formatter = (open, close, replace = open) =>\n\tinput => {\n\t\tlet string = \"\" + input, index = string.indexOf(close, open.length)\n\t\treturn ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close\n\t}\n\nlet replaceClose = (string, close, replace, index) => {\n\tlet result = \"\", cursor = 0\n\tdo {\n\t\tresult += string.substring(cursor, index) + replace\n\t\tcursor = index + close.length\n\t\tindex = string.indexOf(close, cursor)\n\t} while (~index)\n\treturn result + string.substring(cursor)\n}\n\nlet createColors = (enabled = isColorSupported) => {\n\tlet f = enabled ? formatter : () => String\n\treturn {\n\t\tisColorSupported: enabled,\n\t\treset: f(\"\\x1b[0m\", \"\\x1b[0m\"),\n\t\tbold: f(\"\\x1b[1m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[1m\"),\n\t\tdim: f(\"\\x1b[2m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[2m\"),\n\t\titalic: f(\"\\x1b[3m\", \"\\x1b[23m\"),\n\t\tunderline: f(\"\\x1b[4m\", \"\\x1b[24m\"),\n\t\tinverse: f(\"\\x1b[7m\", \"\\x1b[27m\"),\n\t\thidden: f(\"\\x1b[8m\", \"\\x1b[28m\"),\n\t\tstrikethrough: f(\"\\x1b[9m\", \"\\x1b[29m\"),\n\n\t\tblack: f(\"\\x1b[30m\", \"\\x1b[39m\"),\n\t\tred: f(\"\\x1b[31m\", \"\\x1b[39m\"),\n\t\tgreen: f(\"\\x1b[32m\", \"\\x1b[39m\"),\n\t\tyellow: f(\"\\x1b[33m\", \"\\x1b[39m\"),\n\t\tblue: f(\"\\x1b[34m\", \"\\x1b[39m\"),\n\t\tmagenta: f(\"\\x1b[35m\", \"\\x1b[39m\"),\n\t\tcyan: f(\"\\x1b[36m\", \"\\x1b[39m\"),\n\t\twhite: f(\"\\x1b[37m\", \"\\x1b[39m\"),\n\t\tgray: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\n\t\tbgBlack: f(\"\\x1b[40m\", \"\\x1b[49m\"),\n\t\tbgRed: f(\"\\x1b[41m\", \"\\x1b[49m\"),\n\t\tbgGreen: f(\"\\x1b[42m\", \"\\x1b[49m\"),\n\t\tbgYellow: f(\"\\x1b[43m\", \"\\x1b[49m\"),\n\t\tbgBlue: f(\"\\x1b[44m\", \"\\x1b[49m\"),\n\t\tbgMagenta: f(\"\\x1b[45m\", \"\\x1b[49m\"),\n\t\tbgCyan: f(\"\\x1b[46m\", \"\\x1b[49m\"),\n\t\tbgWhite: f(\"\\x1b[47m\", \"\\x1b[49m\"),\n\n\t\tblackBright: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\t\tredBright: f(\"\\x1b[91m\", \"\\x1b[39m\"),\n\t\tgreenBright: f(\"\\x1b[92m\", \"\\x1b[39m\"),\n\t\tyellowBright: f(\"\\x1b[93m\", \"\\x1b[39m\"),\n\t\tblueBright: f(\"\\x1b[94m\", \"\\x1b[39m\"),\n\t\tmagentaBright: f(\"\\x1b[95m\", \"\\x1b[39m\"),\n\t\tcyanBright: f(\"\\x1b[96m\", \"\\x1b[39m\"),\n\t\twhiteBright: f(\"\\x1b[97m\", \"\\x1b[39m\"),\n\n\t\tbgBlackBright: f(\"\\x1b[100m\", \"\\x1b[49m\"),\n\t\tbgRedBright: f(\"\\x1b[101m\", \"\\x1b[49m\"),\n\t\tbgGreenBright: f(\"\\x1b[102m\", \"\\x1b[49m\"),\n\t\tbgYellowBright: f(\"\\x1b[103m\", \"\\x1b[49m\"),\n\t\tbgBlueBright: f(\"\\x1b[104m\", \"\\x1b[49m\"),\n\t\tbgMagentaBright: f(\"\\x1b[105m\", \"\\x1b[49m\"),\n\t\tbgCyanBright: f(\"\\x1b[106m\", \"\\x1b[49m\"),\n\t\tbgWhiteBright: f(\"\\x1b[107m\", \"\\x1b[49m\"),\n\t}\n}\n\nmodule.exports = createColors()\nmodule.exports.createColors = createColors\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createFileTree } from '@/pen/compiler'\r\n\r\n// ===== Main Task =====\r\n\r\nexport const buildFileTree: Task<BuildContext> = {\r\n name: 'Building file tree',\r\n onSuccess: (_, dur) => `Built file tree (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n fileTree: createFileTree(ctx.appDir),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createSegmentTree } from '@/pen/compiler'\r\n\r\n// ===== Main Task =====\r\n\r\nexport const buildSegmentTree: Task<BuildContext> = {\r\n name: 'Building segment tree...',\r\n onSuccess: (_, dur) => `Built segment tree (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n segmentTree: createSegmentTree(ctx.fileTree!),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createRouteChainMap } from '@/pen/compiler'\r\n\r\n// ===== Main Task =====\r\n\r\nexport const buildRouteChainMap: Task<BuildContext> = {\r\n name: 'Building route chain map',\r\n onSuccess: (_, dur) => `Built route chain map (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n routeChainMap: createRouteChainMap(ctx.segmentTree!, ctx.outDir),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createComponentIdMap } from '@/pen/compiler'\r\n\r\nexport const buildComponentIdMap: Task<BuildContext> = {\r\n name: 'Building component index map',\r\n onSuccess: (_, dur) => `Built component map (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n componentIdMap: createComponentIdMap(ctx.routeChainMap!),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nconst filename = 'route-chain-map.ts'\r\n\r\nexport const writeRouteMapFile: Task<BuildContext> = {\r\n name: `Writing ${filename}`,\r\n onSuccess: (_, dur) => `Saved ${filename}} (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'artifacts')\r\n const outDir = join(genDir, filename)\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '// NOTE: This file is kept for documentation/debugging.',\r\n '// Runtime uses pre-built routes from routes.ts instead.',\r\n '',\r\n `import type { RouteChainMap } from '${PACKAGE_NAME}'`,\r\n '',\r\n `export const routes: RouteChainMap = ${JSON.stringify(ctx.routeChainMap!, null, 2)} as const`,\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createSerializedRoutes } from '@/pen/compiler'\r\n\r\nexport const buildSerializedRoutes: Task<BuildContext> = {\r\n name: 'Serializing routes',\r\n onSuccess: (_, dur) => `Serialized routes (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n serializedRoutes: createSerializedRoutes(ctx.routeChainMap!, ctx.componentIdMap!),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nconst filename = 'serialized-routes.ts'\r\n\r\nexport const writeSerializedTreeFile: Task<BuildContext> = {\r\n name: `Writing ${filename}`,\r\n onSuccess: (_, dur) => `Saved ${filename} (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'artifacts')\r\n const outDir = join(genDir, filename)\r\n\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '// NOTE: This file is kept for documentation/debugging.',\r\n '',\r\n `import type { SerializedRouteTreeMap } from '${PACKAGE_NAME}'`,\r\n '',\r\n `export const serializedRoutes: SerializedRouteTreeMap = ${JSON.stringify(ctx.serializedRoutes!, null, 2)} as const`,\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nconst filename = 'component-id-map.ts'\r\n\r\nexport const writeComponentIdMapFile: Task<BuildContext> = {\r\n name: `Writing ${filename}`,\r\n onSuccess: (_, dur) => `Saved ${filename} (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'artifacts')\r\n const outDir = join(genDir, filename)\r\n\r\n const componentIdMap = ctx.componentIdMap!\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n `import type { ComponentIdMap } from '${PACKAGE_NAME}'`,\r\n '',\r\n `export const componentIdMap: ComponentIdMap = ${JSON.stringify(componentIdMap, null, 2)} as const`,\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { SerializedTree } from '@/pen/compiler'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nconst filename = 'compiled-routes.ts'\r\n\r\nexport const writeCompiledRoutesFile: Task<BuildContext> = {\r\n name: `Writing ${filename}`,\r\n onSuccess: (_, dur) => `Saved ${filename} (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'generated')\r\n const outDir = join(genDir, filename)\r\n const serializedRoutes = ctx.serializedRoutes!\r\n const componentIdMap = ctx.componentIdMap!\r\n\r\n // Get sorted imports from component map\r\n const sortedImports = Object.keys(componentIdMap)\r\n\r\n // Generate component imports\r\n const importStatements = sortedImports\r\n .map((importPath, i) => `import Component${i} from '${importPath}'`)\r\n .join('\\n')\r\n\r\n // Generate pre-built route components from element trees\r\n const routeEntries: string[] = []\r\n for (const [url, tree] of Object.entries(serializedRoutes)) {\r\n const elementCode = ` ${serialize(tree).replace(/\\n/g, '\\n ')}`\r\n routeEntries.push(` '${url}':\\n${elementCode},`)\r\n }\r\n\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n `import type { CompiledRoutes } from '${PACKAGE_NAME}'`,\r\n 'import { createElement } from \\'react\\'',\r\n '',\r\n importStatements,\r\n '',\r\n '// Compiled route elements generated at build time',\r\n 'export const compiledRoutes: CompiledRoutes = {',\r\n routeEntries.join('\\n'),\r\n '} as const',\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n\r\n/** Serializes an SerializedTree to React.createElement() code string. */\r\nexport function serialize(tree: SerializedTree, indent = 0): string {\r\n const spaces = ' '.repeat(indent)\r\n\r\n // Props are already pre-serialized (strings have quotes, identifiers don't)\r\n const props = Object.entries(tree.props)\r\n .map(([key, value]) => `${key}: ${value}`)\r\n .join(', ')\r\n\r\n if (!tree.children)\r\n return `createElement(${tree.component}, { ${props} })`\r\n\r\n const childCode = serialize(tree.children, indent + 1)\r\n return `createElement(${tree.component}, { ${props} },\\n${spaces} ${childCode}\\n${spaces})`\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nconst filename = 'entry.ts'\r\n\r\nexport const writeEntryFile: Task<BuildContext> = {\r\n name: `Writing ${filename}`,\r\n onSuccess: (_, dur) => `Saved ${filename} (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'generated')\r\n const outDir = join(genDir, filename)\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n 'import { createElement } from \\'react\\'',\r\n 'import { render } from \\'ink\\'',\r\n `import { App } from '${PACKAGE_NAME}'`,\r\n 'import { compiledRoutes as routes } from \\'./compiled-routes\\'',\r\n '',\r\n 'export async function run(initialUrl: string) {',\r\n ' const element = createElement(App, { initialUrl, routes })',\r\n ' const { waitUntilExit } = render(element)',\r\n ' await waitUntilExit()',\r\n '}',\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n","import { join, resolve } from 'path'\r\nimport { build as rolldownBuild } from 'rolldown'\r\nimport nodeExternals from 'rollup-plugin-node-externals'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\n\r\nexport const compileApplication: Task<BuildContext> = {\r\n name: 'Compiling application',\r\n onSuccess: (_, dur) => `Compiled application (${duration(dur)})`,\r\n onError: (err) => `Compilation failed: ${err.message}`,\r\n run: async (ctx) => {\r\n await rolldownBuild({\r\n input: resolve(join(ctx.outDir, 'generated', 'entry.ts')), // entry point\r\n platform: 'node',\r\n resolve: {\r\n extensions: ['.ts', '.tsx', '.js', '.jsx'],\r\n },\r\n plugins: [nodeExternals()],\r\n output: {\r\n dir: resolve(join(ctx.outDir, 'dist')),\r\n format: 'esm',\r\n sourcemap: true,\r\n minify: true,\r\n },\r\n })\r\n },\r\n}\r\n","import type { CLICommand } from '../../types'\r\nimport pc from 'picocolors'\r\nimport { pipe, duration, fileList } from '@idlesummer/tasker'\r\nimport { loadConfig } from '@/pen/config'\r\nimport { CLI_NAME, VERSION } from '@/pen/constants'\r\n\r\n// Import individual tasks\r\nimport { buildFileTree } from './tasks/build-file-tree'\r\nimport { buildSegmentTree } from './tasks/build-segment-tree'\r\nimport { buildRouteChainMap } from './tasks/build-route-chain-map'\r\nimport { buildComponentIdMap } from './tasks/build-component-id-map'\r\nimport { writeRouteMapFile } from './tasks/write-route-chain-map-file'\r\nimport { buildSerializedRoutes } from './tasks/build-serialized-routes'\r\nimport { writeSerializedTreeFile } from './tasks/write-serialized-routes-file'\r\nimport { writeComponentIdMapFile } from './tasks/write-component-id-map-file'\r\nimport { writeCompiledRoutesFile } from './tasks/write-compiled-routes-file'\r\nimport { writeEntryFile } from './tasks/write-entry-file'\r\nimport { compileApplication } from './tasks/compile-application'\r\n\r\nexport const build: CLICommand = {\r\n name: 'build',\r\n desc: 'Build the routing structure and compile application',\r\n action: async () => {\r\n try {\r\n const { appDir, outDir, emitMetadata } = await loadConfig()\r\n console.log(pc.cyan(' Starting production build...\\n'))\r\n console.log(pc.bold(` ✎ ${CLI_NAME} v${VERSION}\\n`))\r\n console.log(pc.dim( ` entry: ${appDir}`))\r\n console.log(pc.dim( ' target: node24'))\r\n console.log(pc.dim( ` output: ${outDir}`))\r\n console.log()\r\n\r\n const pipeline = pipe([\r\n buildFileTree,\r\n buildSegmentTree,\r\n buildRouteChainMap,\r\n buildComponentIdMap,\r\n buildSerializedRoutes,\r\n\r\n // Conditionally add metadata file generation tasks\r\n emitMetadata && writeRouteMapFile,\r\n emitMetadata && writeSerializedTreeFile,\r\n emitMetadata && writeComponentIdMapFile,\r\n\r\n writeCompiledRoutesFile,\r\n writeEntryFile,\r\n compileApplication,\r\n ])\r\n\r\n const { duration: dur } = await pipeline.run({ appDir, outDir })\r\n console.log()\r\n console.log(fileList(outDir, '**/*'))\r\n console.log()\r\n console.log(`${pc.green('✓')} Built in ${pc.bold(duration(dur))}`)\r\n console.log()\r\n }\r\n\r\n catch (err) {\r\n console.error(`${pc.red('✗')} Build failed`)\r\n console.log()\r\n\r\n const message = err instanceof Error ? err.message : String(err)\r\n console.error(pc.red(message))\r\n console.log()\r\n process.exit(1)\r\n }\r\n },\r\n}\r\n","import { existsSync } from 'fs'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport pc from 'picocolors'\r\n\r\nimport { CLI_NAME, PACKAGE_NAME } from '@/pen/constants'\r\nimport type { CLICommand } from '../../types'\r\n\r\nexport const init: CLICommand = {\r\n name: 'init',\r\n desc: 'Initialize a new Pen project',\r\n action: async () => {\r\n console.log(pc.cyan(`\\n Initializing ${CLI_NAME} project...\\n`))\r\n\r\n // Check if already initialized\r\n if (existsSync('pen.config.ts')) {\r\n console.error(pc.yellow('⚠') + ' Project already initialized')\r\n console.error(pc.dim(' pen.config.ts already exists'))\r\n return\r\n }\r\n\r\n // Create pen.config.ts\r\n const configContent = [\r\n `import { defineConfig } from '${PACKAGE_NAME}'`,\r\n '',\r\n 'export default defineConfig({',\r\n ' appDir: \\'./src/app\\',',\r\n ' outDir: \\'./.pen\\',',\r\n '})',\r\n '',\r\n ].join('\\n')\r\n\r\n await writeFile('pen.config.ts', configContent, 'utf-8')\r\n console.log(pc.green('✓') + ' Created pen.config.ts')\r\n\r\n // Create src/app directory\r\n const appDir = './src/app'\r\n await mkdir(appDir, { recursive: true })\r\n\r\n // Create layout.tsx\r\n const layoutContent = [\r\n 'import { Box, Text } from \\'ink\\'',\r\n 'import type { ReactNode } from \\'react\\'',\r\n '',\r\n 'export default function Layout({ children }: { children?: ReactNode }) {',\r\n ' return (',\r\n ' <Box flexDirection=\"column\" padding={1}>',\r\n ' <Box marginBottom={1} borderStyle=\"round\" borderColor=\"cyan\" paddingX={2}>',\r\n ' <Text bold color=\"cyan\">Welcome to Pen</Text>',\r\n ' </Box>',\r\n ' {children}',\r\n ' </Box>',\r\n ' )',\r\n '}',\r\n '',\r\n ].join('\\n')\r\n\r\n await writeFile(join(appDir, 'layout.tsx'), layoutContent, 'utf-8')\r\n console.log(pc.green('✓') + ' Created src/app/layout.tsx')\r\n\r\n // Create screen.tsx\r\n const screenContent = [\r\n 'import { useState } from \\'react\\'',\r\n 'import { Box, Text, useInput } from \\'ink\\'',\r\n '',\r\n 'export default function Screen() {',\r\n ' const [count, setCount] = useState(0)',\r\n '',\r\n ' useInput((input) => {',\r\n ' if (input === \\' \\') setCount(c => c + 1)',\r\n ' })',\r\n '',\r\n ' return (',\r\n ' <Box flexDirection=\"column\" gap={1}>',\r\n ' <Box>',\r\n ' <Text>Count: <Text bold color=\"green\">{count}</Text></Text>',\r\n ' </Box>',\r\n ' <Box>',\r\n ' <Text dimColor>Press <Text bold>SPACE</Text> to increment</Text>',\r\n ' </Box>',\r\n ' </Box>',\r\n ' )',\r\n '}',\r\n '',\r\n ].join('\\n')\r\n\r\n await writeFile(join(appDir, 'screen.tsx'), screenContent, 'utf-8')\r\n console.log(pc.green('✓') + ' Created src/app/screen.tsx')\r\n\r\n // Success message with instructions\r\n console.log()\r\n console.log(pc.green('✓') + ' Project initialized!')\r\n console.log()\r\n console.log(pc.bold(' Next steps:'))\r\n console.log()\r\n console.log(pc.dim(' 1. Add scripts to package.json:'))\r\n console.log()\r\n console.log(' {')\r\n console.log(' \"scripts\": {')\r\n console.log(` \"build\": \"${CLI_NAME} build\",`)\r\n console.log(` \"start\": \"${CLI_NAME} start\"`)\r\n console.log(' }')\r\n console.log(' }')\r\n console.log()\r\n console.log(pc.dim(' 2. Run \"npm run build\" to build your app'))\r\n console.log(pc.dim(' 3. Run \"npm run start\" to start your app'))\r\n console.log()\r\n },\r\n}\r\n","import { existsSync } from 'fs'\r\nimport { resolve } from 'path'\r\nimport { pathToFileURL } from 'url'\r\nimport pc from 'picocolors'\r\n\r\nimport { loadConfig } from '@/pen/config'\r\nimport { CLI_NAME } from '@/pen/constants'\r\nimport type { CLICommand } from '../../types'\r\n\r\nexport const start: CLICommand = {\r\n name: 'start',\r\n desc: 'Start the application',\r\n action: async () => {\r\n const { outDir } = await loadConfig()\r\n const initialUrl = '/'\r\n const entryPath = resolve(outDir, 'dist', 'entry.js')\r\n\r\n // Check if build exists\r\n if (!existsSync(entryPath)) {\r\n console.error(pc.red('✗') + ' Build not found')\r\n console.error(pc.dim(` Run \"${CLI_NAME} build\" first`))\r\n throw new Error('Build not found') // Let Commander handle exit\r\n }\r\n\r\n // Import and run the bundled entry\r\n const entryUrl = pathToFileURL(entryPath).href\r\n const { run } = await import(entryUrl)\r\n\r\n // Run with initial URL\r\n await run(initialUrl)\r\n },\r\n}\r\n","import { Command } from 'commander'\r\nimport { CLI_NAME, DESCRIPTION, VERSION } from '@/pen/constants'\r\nimport { build } from './commands/build'\r\nimport { init } from './commands/init'\r\nimport { start } from './commands/start'\r\n\r\nfunction configureProgram(program: Command) {\r\n program\r\n .name(CLI_NAME)\r\n .description(DESCRIPTION)\r\n .version(VERSION)\r\n\r\n const commands = [build, init, start] as const\r\n for (const { name, desc, action } of commands) {\r\n program\r\n .command(name)\r\n .description(desc)\r\n .action(action)\r\n }\r\n return program\r\n}\r\n\r\nexport async function run(argv: string[]) {\r\n const command = new Command()\r\n const program = configureProgram(command)\r\n\r\n try {\r\n await program.parseAsync(argv)\r\n return 0\r\n }\r\n catch (err) {\r\n const message = err instanceof Error ? err.message : 'Unknown error'\r\n console.error(message)\r\n return 1\r\n }\r\n}\r\n","#!/usr/bin/env node\r\n\r\nimport { run } from './cli'\r\n\r\nprocess.exit(await run(process.argv))\r\n"],"x_google_ignoreList":[1],"mappings":"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"}
1
+ {"version":3,"file":"bin.mjs","names":["rolldownBuild","build","pc","pc","pc","run","build"],"sources":["../src/pen/constants.ts","../node_modules/picocolors/picocolors.js","../src/cli/commands/build/tasks/build-file-tree.ts","../src/cli/commands/build/tasks/build-segment-tree.ts","../src/cli/commands/build/tasks/build-route-tree.ts","../src/cli/commands/build/tasks/write-file-tree.ts","../src/cli/commands/build/tasks/write-segment-tree.ts","../src/cli/commands/build/tasks/write-route-tree.ts","../src/cli/commands/build/tasks/write-path-component-map.ts","../src/cli/commands/build/tasks/write-entry.ts","../src/cli/commands/build/tasks/compile-application.ts","../src/cli/commands/build/index.ts","../src/cli/commands/init/index.ts","../src/cli/commands/start/index.ts","../src/cli/index.ts","../src/bin.ts"],"sourcesContent":["// Build-time globals injected by tsdown via `define`.\r\n// tsdown replaces these placeholders with actual values\r\n// from package.json at compile time.\r\n\r\ndeclare const __DESCRIPTION__: string\r\ndeclare const __PACKAGE_NAME__: string\r\ndeclare const __VERSION__: string\r\n\r\n// Package metadata\r\nexport const DESCRIPTION = __DESCRIPTION__\r\nexport const PACKAGE_NAME = __PACKAGE_NAME__\r\nexport const VERSION = __VERSION__\r\n\r\n// Framework metadata\r\nexport const CLI_NAME = __PACKAGE_NAME__.split('/')?.[1] ?? __PACKAGE_NAME__\r\n","let p = process || {}, argv = p.argv || [], env = p.env || {}\nlet isColorSupported =\n\t!(!!env.NO_COLOR || argv.includes(\"--no-color\")) &&\n\t(!!env.FORCE_COLOR || argv.includes(\"--color\") || p.platform === \"win32\" || ((p.stdout || {}).isTTY && env.TERM !== \"dumb\") || !!env.CI)\n\nlet formatter = (open, close, replace = open) =>\n\tinput => {\n\t\tlet string = \"\" + input, index = string.indexOf(close, open.length)\n\t\treturn ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close\n\t}\n\nlet replaceClose = (string, close, replace, index) => {\n\tlet result = \"\", cursor = 0\n\tdo {\n\t\tresult += string.substring(cursor, index) + replace\n\t\tcursor = index + close.length\n\t\tindex = string.indexOf(close, cursor)\n\t} while (~index)\n\treturn result + string.substring(cursor)\n}\n\nlet createColors = (enabled = isColorSupported) => {\n\tlet f = enabled ? formatter : () => String\n\treturn {\n\t\tisColorSupported: enabled,\n\t\treset: f(\"\\x1b[0m\", \"\\x1b[0m\"),\n\t\tbold: f(\"\\x1b[1m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[1m\"),\n\t\tdim: f(\"\\x1b[2m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[2m\"),\n\t\titalic: f(\"\\x1b[3m\", \"\\x1b[23m\"),\n\t\tunderline: f(\"\\x1b[4m\", \"\\x1b[24m\"),\n\t\tinverse: f(\"\\x1b[7m\", \"\\x1b[27m\"),\n\t\thidden: f(\"\\x1b[8m\", \"\\x1b[28m\"),\n\t\tstrikethrough: f(\"\\x1b[9m\", \"\\x1b[29m\"),\n\n\t\tblack: f(\"\\x1b[30m\", \"\\x1b[39m\"),\n\t\tred: f(\"\\x1b[31m\", \"\\x1b[39m\"),\n\t\tgreen: f(\"\\x1b[32m\", \"\\x1b[39m\"),\n\t\tyellow: f(\"\\x1b[33m\", \"\\x1b[39m\"),\n\t\tblue: f(\"\\x1b[34m\", \"\\x1b[39m\"),\n\t\tmagenta: f(\"\\x1b[35m\", \"\\x1b[39m\"),\n\t\tcyan: f(\"\\x1b[36m\", \"\\x1b[39m\"),\n\t\twhite: f(\"\\x1b[37m\", \"\\x1b[39m\"),\n\t\tgray: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\n\t\tbgBlack: f(\"\\x1b[40m\", \"\\x1b[49m\"),\n\t\tbgRed: f(\"\\x1b[41m\", \"\\x1b[49m\"),\n\t\tbgGreen: f(\"\\x1b[42m\", \"\\x1b[49m\"),\n\t\tbgYellow: f(\"\\x1b[43m\", \"\\x1b[49m\"),\n\t\tbgBlue: f(\"\\x1b[44m\", \"\\x1b[49m\"),\n\t\tbgMagenta: f(\"\\x1b[45m\", \"\\x1b[49m\"),\n\t\tbgCyan: f(\"\\x1b[46m\", \"\\x1b[49m\"),\n\t\tbgWhite: f(\"\\x1b[47m\", \"\\x1b[49m\"),\n\n\t\tblackBright: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\t\tredBright: f(\"\\x1b[91m\", \"\\x1b[39m\"),\n\t\tgreenBright: f(\"\\x1b[92m\", \"\\x1b[39m\"),\n\t\tyellowBright: f(\"\\x1b[93m\", \"\\x1b[39m\"),\n\t\tblueBright: f(\"\\x1b[94m\", \"\\x1b[39m\"),\n\t\tmagentaBright: f(\"\\x1b[95m\", \"\\x1b[39m\"),\n\t\tcyanBright: f(\"\\x1b[96m\", \"\\x1b[39m\"),\n\t\twhiteBright: f(\"\\x1b[97m\", \"\\x1b[39m\"),\n\n\t\tbgBlackBright: f(\"\\x1b[100m\", \"\\x1b[49m\"),\n\t\tbgRedBright: f(\"\\x1b[101m\", \"\\x1b[49m\"),\n\t\tbgGreenBright: f(\"\\x1b[102m\", \"\\x1b[49m\"),\n\t\tbgYellowBright: f(\"\\x1b[103m\", \"\\x1b[49m\"),\n\t\tbgBlueBright: f(\"\\x1b[104m\", \"\\x1b[49m\"),\n\t\tbgMagentaBright: f(\"\\x1b[105m\", \"\\x1b[49m\"),\n\t\tbgCyanBright: f(\"\\x1b[106m\", \"\\x1b[49m\"),\n\t\tbgWhiteBright: f(\"\\x1b[107m\", \"\\x1b[49m\"),\n\t}\n}\n\nmodule.exports = createColors()\nmodule.exports.createColors = createColors\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createFileTree } from '@/pen/compiler'\r\n\r\n// ===== Main Task =====\r\n\r\nexport const buildFileTree: Task<BuildContext> = {\r\n name: 'Building file tree',\r\n onSuccess: (_, dur) => `Built file tree (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n fileTree: createFileTree(ctx.appDir),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createSegmentTree } from '@/pen/compiler'\r\n\r\n// ===== Main Task =====\r\n\r\nexport const buildSegmentTree: Task<BuildContext> = {\r\n name: 'Building segment tree...',\r\n onSuccess: (_, dur) => `Built segment tree (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n segmentTree: createSegmentTree(ctx.fileTree!),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { createRouteTree } from '@/pen/compiler'\r\n\r\nexport const buildRouteTree: Task<BuildContext> = {\r\n name: 'Building route tree',\r\n onSuccess: (_, dur) => `Built route tree (${duration(dur)})`,\r\n run: async (ctx) => ({\r\n routeTree: createRouteTree(ctx.segmentTree!, ctx.outDir),\r\n }),\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nexport const writeFileTree: Task<BuildContext> = {\r\n name: 'Writing file-tree.ts',\r\n onSuccess: (_, dur) => `Saved file-tree.ts (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'artifacts')\r\n const outDir = join(genDir, 'file-tree.ts')\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n `import type { FileNode } from \"${PACKAGE_NAME}\"`,\r\n '',\r\n `export const fileTree: FileNode = ${JSON.stringify(ctx.fileTree!, null, 2)} as const`,\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nexport const writeSegmentTree: Task<BuildContext> = {\r\n name: 'Writing segment-tree.ts',\r\n onSuccess: (_, dur) => `Saved segment-tree.ts (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'artifacts')\r\n const outDir = join(genDir, 'segment-tree.ts')\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n `import type { SegmentNode } from \"${PACKAGE_NAME}\"`,\r\n '',\r\n `export const segmentTree: SegmentNode = ${JSON.stringify(ctx.segmentTree!, null, 2)} as const`,\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nexport const writeRouteTree: Task<BuildContext> = {\r\n name: 'Writing route-tree.ts',\r\n onSuccess: (_, dur) => `Saved route-tree.ts (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'generated')\r\n const outPath = join(genDir, 'route-tree.ts')\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n `import type { RouteTreeNode } from \"${PACKAGE_NAME}\"`,\r\n '',\r\n `export const routeTree: RouteTreeNode = ${JSON.stringify(ctx.routeTree!, null, 2)} as const`,\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outPath, code, 'utf-8')\r\n },\r\n}\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport type { RouteTreeNode } from '@/pen/compiler'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { SEGMENT_ROLES } from '@/pen/compiler'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nexport const writePathComponentMap: Task<BuildContext> = {\r\n name: 'Writing path-component-map.ts',\r\n onSuccess: (_, dur) => `Saved path-component-map.ts (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'generated')\r\n const outPath = join(genDir, 'path-component-map.ts')\r\n\r\n // Collect all unique paths from routeTree\r\n const paths = collectComponentPaths(ctx.routeTree!)\r\n const entries = Array.from(paths)\r\n\r\n // Codegen for imports\r\n const imports = entries.map(toImportStatement).join('\\n')\r\n const map = entries.map(toMapEntry).join('\\n')\r\n\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n imports,\r\n '',\r\n 'export const componentMap = {',\r\n map,\r\n '}',\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outPath, code, 'utf-8')\r\n },\r\n}\r\n\r\nfunction collectComponentPaths(routeTree: RouteTreeNode) {\r\n const paths = new Set<string>()\r\n\r\n function traverse(node: RouteTreeNode) {\r\n for (const role of SEGMENT_ROLES) {\r\n if (node.roles?.[role]) paths.add(node.roles[role]!)\r\n }\r\n for (const child of node.children ?? []) traverse(child)\r\n }\r\n\r\n traverse(routeTree)\r\n return paths\r\n}\r\n\r\nconst toImportStatement = (p: string, i: number) => `import Component${i} from \"${p}\"`\r\nconst toMapEntry = (p: string, i: number) => ` ${JSON.stringify(p)}: Component${i},`\r\n","import type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport { PACKAGE_NAME } from '@/pen/constants'\r\n\r\nexport const writeEntry: Task<BuildContext> = {\r\n name: 'Writing entry.ts',\r\n onSuccess: (_, dur) => `Saved entry.ts (${duration(dur)})`,\r\n run: async (ctx) => {\r\n const genDir = join(ctx.outDir, 'generated')\r\n const outDir = join(genDir, 'entry.ts')\r\n const code = [\r\n `// Auto-generated by ${PACKAGE_NAME}`,\r\n '// Do not manually edit this file',\r\n '',\r\n 'import { createElement } from \"react\"',\r\n 'import { render } from \"ink\"',\r\n `import { App } from '${PACKAGE_NAME}'`,\r\n 'import { componentMap } from \"./path-component-map\"',\r\n 'import { routeTree } from \"./route-tree\"',\r\n '',\r\n 'export async function run(initialUrl: string) {',\r\n ' const routingTable = { routeTree, componentMap }',\r\n ' const element = createElement(App, { initialUrl, routingTable })',\r\n ' const { waitUntilExit } = render(element)',\r\n ' await waitUntilExit()',\r\n '}',\r\n '',\r\n ].join('\\n')\r\n\r\n await mkdir(genDir, { recursive: true })\r\n await writeFile(outDir, code, 'utf-8')\r\n },\r\n}\r\n","import { join, resolve } from 'path'\r\nimport { build as rolldownBuild } from 'rolldown'\r\nimport nodeExternals from 'rollup-plugin-node-externals'\r\nimport { duration } from '@idlesummer/tasker'\r\nimport type { Task } from '@idlesummer/tasker'\r\nimport type { BuildContext } from '../types'\r\n\r\nexport const compileApplication: Task<BuildContext> = {\r\n name: 'Compiling application',\r\n onSuccess: (_, dur) => `Compiled application (${duration(dur)})`,\r\n onError: (err) => `Compilation failed: ${err.message}`,\r\n run: async (ctx) => {\r\n await rolldownBuild({\r\n input: resolve(join(ctx.outDir, 'generated', 'entry.ts')), // entry point\r\n platform: 'node',\r\n resolve: {\r\n extensions: ['.ts', '.tsx', '.js', '.jsx'],\r\n },\r\n plugins: [nodeExternals()],\r\n output: {\r\n dir: resolve(join(ctx.outDir, 'dist')),\r\n format: 'esm',\r\n sourcemap: true,\r\n minify: true,\r\n },\r\n })\r\n },\r\n}\r\n","import type { CLICommand } from '../../types'\r\nimport pc from 'picocolors'\r\nimport { pipe, duration, fileList } from '@idlesummer/tasker'\r\nimport { loadConfig } from '@/pen/config'\r\nimport { CLI_NAME, VERSION } from '@/pen/constants'\r\n\r\n// Import individual tasks\r\nimport { buildFileTree } from './tasks/build-file-tree'\r\nimport { buildSegmentTree } from './tasks/build-segment-tree'\r\nimport { buildRouteTree } from './tasks/build-route-tree'\r\nimport { writeFileTree } from './tasks/write-file-tree'\r\nimport { writeSegmentTree } from './tasks/write-segment-tree'\r\nimport { writeRouteTree } from './tasks/write-route-tree'\r\nimport { writePathComponentMap } from './tasks/write-path-component-map'\r\nimport { writeEntry } from './tasks/write-entry'\r\nimport { compileApplication } from './tasks/compile-application'\r\n\r\nexport const build: CLICommand = {\r\n name: 'build',\r\n desc: 'Build the routing structure and compile application',\r\n action: async () => {\r\n try {\r\n const { appDir, outDir, emitMetadata } = await loadConfig()\r\n console.log(pc.cyan(' Starting production build...\\n'))\r\n console.log(pc.bold(` ✎ ${CLI_NAME} v${VERSION}\\n`))\r\n console.log(pc.dim( ` entry: ${appDir}`))\r\n console.log(pc.dim( ' target: node24'))\r\n console.log(pc.dim( ` output: ${outDir}`))\r\n console.log()\r\n\r\n const pipeline = pipe([\r\n buildFileTree,\r\n buildSegmentTree,\r\n buildRouteTree,\r\n emitMetadata && writeFileTree,\r\n emitMetadata && writeSegmentTree,\r\n writeRouteTree,\r\n writePathComponentMap,\r\n writeEntry,\r\n compileApplication,\r\n ])\r\n\r\n const { duration: dur } = await pipeline.run({ appDir, outDir })\r\n console.log()\r\n console.log(fileList(outDir, '**/*'))\r\n console.log()\r\n console.log(pc.green(`Built in ${pc.bold(duration(dur))}`))\r\n console.log()\r\n }\r\n\r\n catch (err) {\r\n console.error(pc.red('Build failed'))\r\n console.log()\r\n\r\n const message = err instanceof Error ? err.message : String(err)\r\n console.error(pc.red(message))\r\n console.log()\r\n process.exit(1)\r\n }\r\n },\r\n}\r\n","import { existsSync } from 'fs'\r\nimport { mkdir, writeFile } from 'fs/promises'\r\nimport { join } from 'path'\r\nimport pc from 'picocolors'\r\n\r\nimport { CLI_NAME, PACKAGE_NAME } from '@/pen/constants'\r\nimport type { CLICommand } from '../../types'\r\n\r\nexport const init: CLICommand = {\r\n name: 'init',\r\n desc: 'Initialize a new Pen project',\r\n action: async () => {\r\n console.log(pc.cyan(`\\n Initializing ${CLI_NAME} project...\\n`))\r\n\r\n // Check if already initialized\r\n if (existsSync('pen.config.ts')) {\r\n console.error(pc.yellow('⚠') + ' Project already initialized')\r\n console.error(pc.dim(' pen.config.ts already exists'))\r\n return\r\n }\r\n\r\n // Create pen.config.ts\r\n const configContent = [\r\n `import { defineConfig } from '${PACKAGE_NAME}'`,\r\n '',\r\n 'export default defineConfig({',\r\n ' appDir: \\'./src/app\\',',\r\n ' outDir: \\'./.pen\\',',\r\n '})',\r\n '',\r\n ].join('\\n')\r\n\r\n await writeFile('pen.config.ts', configContent, 'utf-8')\r\n console.log(pc.green('✓') + ' Created pen.config.ts')\r\n\r\n // Create src/app directory\r\n const appDir = './src/app'\r\n await mkdir(appDir, { recursive: true })\r\n\r\n // Create layout.tsx\r\n const layoutContent = [\r\n 'import { Box, Text } from \\'ink\\'',\r\n 'import type { ReactNode } from \\'react\\'',\r\n '',\r\n 'export default function Layout({ children }: { children?: ReactNode }) {',\r\n ' return (',\r\n ' <Box flexDirection=\"column\" padding={1}>',\r\n ' <Box marginBottom={1} borderStyle=\"round\" borderColor=\"cyan\" paddingX={2}>',\r\n ' <Text bold color=\"cyan\">Welcome to Pen</Text>',\r\n ' </Box>',\r\n ' {children}',\r\n ' </Box>',\r\n ' )',\r\n '}',\r\n '',\r\n ].join('\\n')\r\n\r\n await writeFile(join(appDir, 'layout.tsx'), layoutContent, 'utf-8')\r\n console.log(pc.green('✓') + ' Created src/app/layout.tsx')\r\n\r\n // Create screen.tsx\r\n const screenContent = [\r\n 'import { useState } from \\'react\\'',\r\n 'import { Box, Text, useInput } from \\'ink\\'',\r\n '',\r\n 'export default function Screen() {',\r\n ' const [count, setCount] = useState(0)',\r\n '',\r\n ' useInput((input) => {',\r\n ' if (input === \\' \\') setCount(c => c + 1)',\r\n ' })',\r\n '',\r\n ' return (',\r\n ' <Box flexDirection=\"column\" gap={1}>',\r\n ' <Box>',\r\n ' <Text>Count: <Text bold color=\"green\">{count}</Text></Text>',\r\n ' </Box>',\r\n ' <Box>',\r\n ' <Text dimColor>Press <Text bold>SPACE</Text> to increment</Text>',\r\n ' </Box>',\r\n ' </Box>',\r\n ' )',\r\n '}',\r\n '',\r\n ].join('\\n')\r\n\r\n await writeFile(join(appDir, 'screen.tsx'), screenContent, 'utf-8')\r\n console.log(pc.green('✓') + ' Created src/app/screen.tsx')\r\n\r\n // Success message with instructions\r\n console.log()\r\n console.log(pc.green('✓') + ' Project initialized!')\r\n console.log()\r\n console.log(pc.bold(' Next steps:'))\r\n console.log()\r\n console.log(pc.dim(' 1. Add scripts to package.json:'))\r\n console.log()\r\n console.log(' {')\r\n console.log(' \"scripts\": {')\r\n console.log(` \"build\": \"${CLI_NAME} build\",`)\r\n console.log(` \"start\": \"${CLI_NAME} start\"`)\r\n console.log(' }')\r\n console.log(' }')\r\n console.log()\r\n console.log(pc.dim(' 2. Run \"npm run build\" to build your app'))\r\n console.log(pc.dim(' 3. Run \"npm run start\" to start your app'))\r\n console.log()\r\n },\r\n}\r\n","import { existsSync } from 'fs'\r\nimport { resolve } from 'path'\r\nimport { pathToFileURL } from 'url'\r\nimport pc from 'picocolors'\r\n\r\nimport { loadConfig } from '@/pen/config'\r\nimport { CLI_NAME } from '@/pen/constants'\r\nimport type { CLICommand } from '../../types'\r\n\r\nexport const start: CLICommand = {\r\n name: 'start',\r\n desc: 'Start the application',\r\n action: async () => {\r\n const { outDir } = await loadConfig()\r\n const initialUrl = '/'\r\n const entryPath = resolve(outDir, 'dist', 'entry.js')\r\n\r\n // Check if build exists\r\n if (!existsSync(entryPath)) {\r\n console.error(pc.red('✗') + ' Build not found')\r\n console.error(pc.dim(` Run \"${CLI_NAME} build\" first`))\r\n throw new Error('Build not found') // Let Commander handle exit\r\n }\r\n\r\n // Import and run the bundled entry\r\n const entryUrl = pathToFileURL(entryPath).href\r\n const { run } = await import(entryUrl)\r\n\r\n // Run with initial URL\r\n await run(initialUrl)\r\n },\r\n}\r\n","import { Command } from 'commander'\r\nimport { CLI_NAME, DESCRIPTION, VERSION } from '@/pen/constants'\r\nimport { build } from './commands/build'\r\nimport { init } from './commands/init'\r\nimport { start } from './commands/start'\r\n\r\nfunction configureProgram(program: Command) {\r\n program\r\n .name(CLI_NAME)\r\n .description(DESCRIPTION)\r\n .version(VERSION)\r\n\r\n const commands = [build, init, start] as const\r\n for (const { name, desc, action } of commands) {\r\n program\r\n .command(name)\r\n .description(desc)\r\n .action(action)\r\n }\r\n return program\r\n}\r\n\r\nexport async function run(argv: string[]) {\r\n const command = new Command()\r\n const program = configureProgram(command)\r\n\r\n try {\r\n await program.parseAsync(argv)\r\n return 0\r\n }\r\n catch (err) {\r\n const message = err instanceof Error ? err.message : 'Unknown error'\r\n console.error(message)\r\n return 1\r\n }\r\n}\r\n","#!/usr/bin/env node\r\n\r\nimport { run } from './cli'\r\n\r\nprocess.exit(await run(process.argv))\r\n"],"x_google_ignoreList":[1],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,7 +1,50 @@
1
- import * as react0 from "react";
1
+ import * as react5 from "react";
2
2
  import { Component, ComponentType, PropsWithChildren, ReactElement } from "react";
3
- import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+ import * as react_jsx_runtime5 from "react/jsx-runtime";
4
4
 
5
+ //#region src/pen/api/NavigationProvider.d.ts
6
+ declare const NavigationContext: react5.Context<NavigationContextValue | null>;
7
+ type NavigationContextValue = {
8
+ url: string;
9
+ searchParams?: unknown;
10
+ history: readonly string[];
11
+ position: number;
12
+ push: (url: string, searchParams?: unknown) => void;
13
+ replace: (url: string) => void;
14
+ back: () => void;
15
+ forward: () => void;
16
+ };
17
+ type NavigationProviderProps = PropsWithChildren<{
18
+ initialUrl: string;
19
+ }>;
20
+ declare function NavigationProvider({
21
+ initialUrl,
22
+ children
23
+ }: NavigationProviderProps): react_jsx_runtime5.JSX.Element;
24
+ //#endregion
25
+ //#region src/pen/api/hooks/use-history.d.ts
26
+ declare function useHistory(): {
27
+ stack: readonly string[];
28
+ position: number;
29
+ };
30
+ //#endregion
31
+ //#region src/pen/api/hooks/use-navigate.d.ts
32
+ declare function useNavigate(): NavigationContextValue;
33
+ //#endregion
34
+ //#region src/pen/api/hooks/use-pathname.d.ts
35
+ declare function usePathname(): string;
36
+ //#endregion
37
+ //#region src/pen/api/hooks/use-router.d.ts
38
+ declare function useRouter(): {
39
+ push: (url: string, searchParams?: unknown) => void;
40
+ replace: (url: string) => void;
41
+ back: () => void;
42
+ forward: () => void;
43
+ };
44
+ //#endregion
45
+ //#region src/pen/api/hooks/use-search-params.d.ts
46
+ declare function useSearchParams(): unknown;
47
+ //#endregion
5
48
  //#region src/pen/config.d.ts
6
49
  /**
7
50
  * Resolved configuration with all fields guaranteed to be present.
@@ -38,14 +81,6 @@ declare function defineConfig(config: PenConfig): PenConfig;
38
81
  */
39
82
  declare function loadConfig(): Promise<ResolvedPenConfig>;
40
83
  //#endregion
41
- //#region src/pen/compiler/types.d.ts
42
- /**
43
- * Compiled route elements mapped by URL.
44
- * This represents the output format of the build process.
45
- * Routes are compiled once at build time via codegen.
46
- */
47
- type CompiledRoutes = Record<string, ReactElement>;
48
- //#endregion
49
84
  //#region src/pen/compiler/builders/file-tree.d.ts
50
85
  type FileNode = {
51
86
  name: string;
@@ -66,15 +101,14 @@ declare function createFileTree(appPath: string): FileNode;
66
101
  //#region src/pen/compiler/builders/segment-tree.d.ts
67
102
  declare const SEGMENT_ROLES: readonly ["layout", "screen", "error", "not-found"];
68
103
  type SegmentRole = typeof SEGMENT_ROLES[number];
69
- type SegmentRoles = Partial<Record<SegmentRole, string>>;
104
+ type SegmentLayer = Partial<Record<SegmentRole, string>>;
70
105
  type SegmentNode = {
71
- segment: string;
72
- url: `${string}/`;
73
- type: 'page' | 'group';
74
- roles: SegmentRoles;
75
- parent?: SegmentNode;
106
+ route: `${string}/`;
107
+ name: string;
108
+ param?: string;
109
+ type: 'page' | 'group' | 'dynamic';
110
+ roles?: SegmentLayer;
76
111
  children?: SegmentNode[];
77
- file: FileNode;
78
112
  };
79
113
  /**
80
114
  * Creates a segment tree from a file system tree.
@@ -86,47 +120,25 @@ type SegmentNode = {
86
120
  */
87
121
  declare function createSegmentTree(fileTree: FileNode): SegmentNode;
88
122
  //#endregion
89
- //#region src/pen/compiler/builders/route-chain-map.d.ts
90
- type RouteChainMap = Record<string, Route>;
91
- type Route = {
92
- url: string;
93
- chain: SegmentRoles[];
123
+ //#region src/pen/compiler/builders/route-tree.d.ts
124
+ type RouteTreeNode = {
125
+ name: string;
126
+ param?: string;
127
+ group?: true;
128
+ roles?: SegmentLayer;
129
+ children?: RouteTreeNode[];
94
130
  };
95
131
  /**
96
- * Builds a route manifest from a segment tree.
132
+ * Builds a route tree from a segment tree.
97
133
  *
98
- * Flattens the tree into a dictionary mapping URLs to route metadata.
99
- * Only includes routes that have screens.
100
- * Paths are stored as relative import paths from the generated directory.
134
+ * Strips all build-time metadata (FileNode refs, parent pointers, route strings)
135
+ * and rewrites absolute paths to relative `.js` import paths from the generated dir.
136
+ * The resulting tree is JSON-serializable and safe for runtime use.
101
137
  *
102
138
  * @param segmentTree - Segment tree with parent pointers
103
139
  * @param outDir - Output directory (to calculate relative import paths)
104
- * @returns Flat manifest ready for runtime composition
105
- */
106
- declare function createRouteChainMap(segmentTree: SegmentNode, outDir: string): RouteChainMap;
107
- //#endregion
108
- //#region src/pen/compiler/builders/component-id-map.d.ts
109
- /** Map from import path to component index (keys are in sorted order) */
110
- type ComponentIdMap = Record<string, number>;
111
- /**
112
- * Builds a mapping of import paths to component IDs from the manifest.
113
- * Collects all unique import paths and assigns them indices.
114
- * Keys are stored in sorted order for deterministic output.
115
- */
116
- declare function createComponentIdMap(routes: RouteChainMap): ComponentIdMap;
117
- //#endregion
118
- //#region src/pen/compiler/builders/serialized-routes.d.ts
119
- interface SerializedTree {
120
- component: string;
121
- props: Record<string, unknown>;
122
- children?: SerializedTree;
123
- }
124
- type SerializedRouteTreeMap = Record<string, SerializedTree>;
125
- /**
126
- * Creates element trees for all routeChain in the manifest.
127
- * Each tree represents the nested React component structure for a route.
128
140
  */
129
- declare function createSerializedRoutes(routeChain: RouteChainMap, componentIdMap: ComponentIdMap): SerializedRouteTreeMap;
141
+ declare function createRouteTree(segmentTree: SegmentNode, outDir: string): RouteTreeNode;
130
142
  //#endregion
131
143
  //#region src/pen/compiler/errors.d.ts
132
144
  /**
@@ -153,95 +165,28 @@ declare class DuplicateScreenError extends FileRouterError {
153
165
  constructor(url: string, files: string[]);
154
166
  }
155
167
  //#endregion
156
- //#region src/pen/router/RouterProvider.d.ts
157
- interface RouterContextValue {
158
- url: string;
159
- data: unknown | undefined;
160
- history: readonly string[];
161
- position: number;
162
- push: (url: string, data?: unknown) => void;
163
- replace: (url: string) => void;
164
- back: () => void;
165
- forward: () => void;
166
- }
167
- interface RouterProviderProps extends PropsWithChildren {
168
- initialUrl: string;
169
- }
170
- declare const RouterContext: react0.Context<RouterContextValue | null>;
171
- declare function RouterProvider({
172
- initialUrl,
173
- children
174
- }: RouterProviderProps): react_jsx_runtime0.JSX.Element;
175
- //#endregion
176
- //#region src/pen/router/hooks/use-history.d.ts
177
- /**
178
- * Get the navigation history (URLs only)
179
- *
180
- * @returns readonly array of visited URLs
181
- *
182
- * @example
183
- * ```tsx
184
- * const history = useHistory()
185
- * return (
186
- * <Box>
187
- * <Text>Visited pages:</Text>
188
- * {history.map((url, i) => (
189
- * <Text key={i}>{url}</Text>
190
- * ))}
191
- * </Box>
192
- * )
193
- * ```
194
- */
195
- declare function useHistory(): readonly string[];
196
- //#endregion
197
- //#region src/pen/router/hooks/use-navigate.d.ts
198
- /**
199
- * Get navigation functions without url/data
200
- */
201
- declare function useNavigate(): {
202
- push: (url: string, data?: unknown) => void;
203
- replace: (url: string) => void;
204
- back: () => void;
205
- forward: () => void;
168
+ //#region src/pen/runtime/routing/composer.d.ts
169
+ type PathComponentMap = Record<string, ComponentType>;
170
+ type RoutingTable = {
171
+ routeTree: RouteTreeNode;
172
+ componentMap: PathComponentMap;
206
173
  };
207
174
  //#endregion
208
- //#region src/pen/router/hooks/use-route-data.d.ts
209
- /**
210
- * Access route data passed via router.push()
211
- *
212
- * @example
213
- * ```tsx
214
- * const user = useRouteData<User>()
215
- * if (!user) return <Text>No user</Text>
216
- * return <Text>{user.name}</Text>
217
- * ```
218
- */
219
- declare function useRouteData<T = unknown>(): T | undefined;
220
- //#endregion
221
- //#region src/pen/router/hooks/use-router.d.ts
222
- declare function useRouter(): RouterContextValue;
223
- //#endregion
224
- //#region src/pen/router/hooks/use-url.d.ts
225
- /**
226
- * Get the current URL
227
- */
228
- declare function useUrl(): string;
229
- //#endregion
230
175
  //#region src/pen/runtime/App.d.ts
231
- interface AppProps {
176
+ type AppProps = {
232
177
  initialUrl: string;
233
- routes: CompiledRoutes;
234
- }
178
+ routingTable: RoutingTable;
179
+ };
235
180
  /**
236
181
  * Root application component.
237
182
  * Routes are compiled at build time via codegen - no runtime composition needed!
238
183
  */
239
184
  declare function App({
240
185
  initialUrl,
241
- routes
242
- }: AppProps): react_jsx_runtime0.JSX.Element;
186
+ routingTable
187
+ }: AppProps): react_jsx_runtime5.JSX.Element;
243
188
  //#endregion
244
- //#region src/pen/runtime/ui/ErrorBoundary.d.ts
189
+ //#region src/pen/runtime/components/ErrorBoundary.d.ts
245
190
  /** Props passed to error.tsx components */
246
191
  interface ErrorComponentProps {
247
192
  error: Error;
@@ -276,10 +221,10 @@ declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryS
276
221
  componentDidCatch(): void;
277
222
  /** Resets error state and attempts to re-render children. */
278
223
  reset(): void;
279
- render(): string | number | bigint | boolean | react_jsx_runtime0.JSX.Element | Iterable<react0.ReactNode> | Promise<string | number | bigint | boolean | react0.ReactPortal | react0.ReactElement<unknown, string | react0.JSXElementConstructor<any>> | Iterable<react0.ReactNode> | null | undefined> | null | undefined;
224
+ render(): string | number | bigint | boolean | react_jsx_runtime5.JSX.Element | Iterable<react5.ReactNode> | Promise<string | number | bigint | boolean | react5.ReactPortal | react5.ReactElement<unknown, string | react5.JSXElementConstructor<any>> | Iterable<react5.ReactNode> | null | undefined> | null | undefined;
280
225
  }
281
226
  //#endregion
282
- //#region src/pen/runtime/ui/NotFoundBoundary.d.ts
227
+ //#region src/pen/runtime/components/NotFoundBoundary.d.ts
283
228
  /** Props passed to not-found.tsx components */
284
229
  interface NotFoundComponentProps {
285
230
  url: string;
@@ -294,38 +239,57 @@ interface NotFoundBoundaryProps extends PropsWithChildren {
294
239
  declare function NotFoundBoundary({
295
240
  fallback,
296
241
  children
297
- }: NotFoundBoundaryProps): react_jsx_runtime0.JSX.Element;
242
+ }: NotFoundBoundaryProps): react_jsx_runtime5.JSX.Element;
298
243
  //#endregion
299
- //#region src/pen/runtime/ui/ErrorScreen.d.ts
244
+ //#region src/pen/runtime/components/FileRouter.d.ts
245
+ type FileRouterProps = {
246
+ routingTable: RoutingTable;
247
+ };
248
+ /**
249
+ * Router component that matches the current URL and renders the corresponding route.
250
+ * Lazily composes route elements on first visit and caches them for subsequent renders.
251
+ */
252
+ declare function FileRouter({
253
+ routingTable
254
+ }: FileRouterProps): ReactElement;
255
+ //#endregion
256
+ //#region src/pen/runtime/components/ErrorScreen.d.ts
300
257
  /** Global error fallback that always wraps the app. */
301
258
  declare function ErrorScreen({
302
259
  error,
303
260
  reset
304
- }: ErrorComponentProps): react_jsx_runtime0.JSX.Element;
261
+ }: ErrorComponentProps): react_jsx_runtime5.JSX.Element;
305
262
  //#endregion
306
- //#region src/pen/runtime/ui/NotFoundScreen.d.ts
263
+ //#region src/pen/runtime/components/NotFoundScreen.d.ts
307
264
  /**
308
265
  * Not found error screen displayed when a route is not found.
309
266
  * Shows the URL that was attempted.
310
267
  */
311
268
  declare function NotFoundScreen({
312
269
  url
313
- }: NotFoundComponentProps): react_jsx_runtime0.JSX.Element;
270
+ }: NotFoundComponentProps): react_jsx_runtime5.JSX.Element;
314
271
  //#endregion
315
- //#region src/pen/runtime/FileRouter.d.ts
316
- /**
317
- * Props for the FileRouter component.
318
- */
319
- interface FileRouterProps {
320
- routes: CompiledRoutes;
321
- }
272
+ //#region src/pen/runtime/providers/DynamicParamsProvider.d.ts
273
+ type DynamicParams = Record<string, string>;
274
+ declare const DynamicParamsContext: react5.Context<DynamicParams>;
275
+ type DynamicParamsProviderProps = PropsWithChildren<{
276
+ params: DynamicParams;
277
+ }>;
278
+ declare function DynamicParamsProvider({
279
+ params,
280
+ children
281
+ }: DynamicParamsProviderProps): react_jsx_runtime5.JSX.Element;
282
+ //#endregion
283
+ //#region src/pen/runtime/hooks/use-params.d.ts
322
284
  /**
323
- * Router component that renders compiled route elements.
324
- * Simply looks up the current URL in the compiled routes map.
285
+ * Returns the dynamic route params for the current URL.
286
+ *
287
+ * @example
288
+ * // With route file: app/users/[id]/screen.tsx
289
+ * // and URL: /users/42/
290
+ * const { id } = useParams() // { id: "42" }
325
291
  */
326
- declare function FileRouter({
327
- routes
328
- }: FileRouterProps): ReactElement;
292
+ declare function useParams(): DynamicParams;
329
293
  //#endregion
330
294
  //#region src/pen/runtime/errors.d.ts
331
295
  /** Special error thrown to trigger not-found.tsx. */
@@ -333,16 +297,6 @@ declare class NotFoundError extends Error {
333
297
  readonly url: string;
334
298
  constructor(url: string, message?: string);
335
299
  }
336
- /** Error thrown when a route has an empty chain. */
337
- declare class EmptyChainError extends Error {
338
- url: string;
339
- constructor(url: string);
340
- }
341
- /** Error thrown when a component is missing from the component map. */
342
- declare class ComponentNotFoundError extends Error {
343
- componentPath: string;
344
- constructor(componentPath: string);
345
- }
346
300
  //#endregion
347
301
  //#region src/pen/utils/use-memory-monitor.d.ts
348
302
  interface MemoryUsage {
@@ -371,5 +325,5 @@ interface MemoryUsage {
371
325
  */
372
326
  declare function useMemoryMonitor(intervalMs?: number): MemoryUsage;
373
327
  //#endregion
374
- export { App, type AppProps, type CompiledRoutes, type ComponentIdMap, ComponentNotFoundError, DirectoryNotFoundError, DuplicateScreenError, EmptyChainError, ErrorBoundary, type ErrorComponentProps, ErrorScreen, type FileNode, FileRouter, FileRouterError, type FileRouterProps, MemoryUsage, NotADirectoryError, NotFoundBoundary, type NotFoundComponentProps, NotFoundError, NotFoundScreen, PenConfig, ResolvedPenConfig, RootIsFileError, type Route, type RouteChainMap, RouterContext, type RouterContextValue, RouterProvider, type RouterProviderProps, type SegmentNode, type SegmentRoles, type SerializedRouteTreeMap, type SerializedTree, createComponentIdMap, createFileTree, createRouteChainMap, createSegmentTree, createSerializedRoutes, defineConfig, loadConfig, useHistory, useMemoryMonitor, useNavigate, useRouteData, useRouter, useUrl };
328
+ export { App, type AppProps, DirectoryNotFoundError, DuplicateScreenError, DynamicParamsContext, DynamicParamsProvider, ErrorBoundary, type ErrorComponentProps, ErrorScreen, FileNode, FileRouter, FileRouterError, type FileRouterProps, MemoryUsage, NavigationContext, type NavigationContextValue, NavigationProvider, type NavigationProviderProps, NotADirectoryError, NotFoundBoundary, type NotFoundComponentProps, NotFoundError, NotFoundScreen, PenConfig, ResolvedPenConfig, RootIsFileError, RouteTreeNode, SEGMENT_ROLES, SegmentLayer, SegmentNode, createFileTree, createRouteTree, createSegmentTree, defineConfig, loadConfig, useHistory, useMemoryMonitor, useNavigate, useParams, usePathname, useRouter, useSearchParams };
375
329
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/pen/config.ts","../src/pen/compiler/types.ts","../src/pen/compiler/builders/file-tree.ts","../src/pen/compiler/builders/segment-tree.ts","../src/pen/compiler/builders/route-chain-map.ts","../src/pen/compiler/builders/component-id-map.ts","../src/pen/compiler/builders/serialized-routes.ts","../src/pen/compiler/errors.ts","../src/pen/router/RouterProvider.tsx","../src/pen/router/hooks/use-history.ts","../src/pen/router/hooks/use-navigate.ts","../src/pen/router/hooks/use-route-data.ts","../src/pen/router/hooks/use-router.ts","../src/pen/router/hooks/use-url.ts","../src/pen/runtime/App.tsx","../src/pen/runtime/ui/ErrorBoundary.tsx","../src/pen/runtime/ui/NotFoundBoundary.tsx","../src/pen/runtime/ui/ErrorScreen.tsx","../src/pen/runtime/ui/NotFoundScreen.tsx","../src/pen/runtime/FileRouter.tsx","../src/pen/runtime/errors.ts","../src/pen/utils/use-memory-monitor.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/pen/api/NavigationProvider.tsx","../src/pen/api/hooks/use-history.ts","../src/pen/api/hooks/use-navigate.ts","../src/pen/api/hooks/use-pathname.ts","../src/pen/api/hooks/use-router.ts","../src/pen/api/hooks/use-search-params.ts","../src/pen/config.ts","../src/pen/compiler/builders/file-tree.ts","../src/pen/compiler/builders/segment-tree.ts","../src/pen/compiler/builders/route-tree.ts","../src/pen/compiler/errors.ts","../src/pen/runtime/routing/composer.ts","../src/pen/runtime/App.tsx","../src/pen/runtime/components/ErrorBoundary.tsx","../src/pen/runtime/components/NotFoundBoundary.tsx","../src/pen/runtime/components/FileRouter.tsx","../src/pen/runtime/components/ErrorScreen.tsx","../src/pen/runtime/components/NotFoundScreen.tsx","../src/pen/runtime/providers/DynamicParamsProvider.tsx","../src/pen/runtime/hooks/use-params.ts","../src/pen/runtime/errors.ts","../src/pen/utils/use-memory-monitor.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,r as c,s as l,t as u,u as d}from"./serialized-routes-DFDKf4sB.mjs";import{Component as f,createContext as p,useCallback as m,useContext as h,useEffect as g,useState as _}from"react";import{jsx as v,jsxs as y}from"react/jsx-runtime";import{Box as b,Text as x,useInput as S}from"ink";function C(e){return e.endsWith(`/`)?e:`${e}/`}function w(e,t,n){return{stack:[...e.stack.slice(0,e.position+1),{url:C(t),data:n}],position:e.position+1}}function T(e,t){let n=[...e.stack];return n[e.position]={url:C(t)},{...e,stack:n}}function E(e){return e.position>0?{...e,position:e.position-1}:e}function D(e){return e.position<e.stack.length-1?{...e,position:e.position+1}:e}const O=p(null);function k({initialUrl:e,children:t}){let n=C(e),[r,i]=_({stack:[{url:n}],position:0}),{url:a,data:o}=r.stack[r.position]??{url:n},s=m((e,t)=>{i(n=>w(n,e,t))},[]),c=m(e=>{i(t=>T(t,e))},[]),l=m(()=>{i(e=>E(e))},[]),u=m(()=>{i(e=>D(e))},[]);return v(O.Provider,{value:{url:a,data:o,history:r.stack.map(e=>e.url),position:r.position,push:s,replace:c,back:l,forward:u},children:t})}function A(){let e=h(O);if(!e)throw Error(`useRouter must be used within a RouterProvider`);return e}function j(){let{history:e}=A();return e}function M(){let{push:e,replace:t,back:n,forward:r}=A();return{push:e,replace:t,back:n,forward:r}}function N(){let{data:e}=A();return e}function P(){let{url:e}=A();return e}var F=class extends f{state;constructor(e){super(e),this.state={error:null},this.reset=this.reset.bind(this)}static getDerivedStateFromError(e){return{error:e}}componentDidCatch(){}reset(){this.setState({error:null})}render(){if(this.state.error){let e=this.props.fallback;return v(e,{error:this.state.error,reset:this.reset})}return this.props.children}},I=class extends Error{url;constructor(e,t=`Not Found`){super(t),this.name=`NotFoundError`,this.url=e}},L=class extends Error{constructor(e){super(`Route ${e} has an empty chain. This indicates a bug in manifest generation - routes must have at least one segment.`),this.url=e,this.name=`EmptyChainError`}},R=class extends Error{constructor(e){super(`Component not found: ${e}. This indicates the component map is out of sync with the manifest. Try running 'pen build' again.`),this.componentPath=e,this.name=`ComponentNotFoundError`}},z=class extends f{state;constructor(e){super(e),this.state={url:null}}static getDerivedStateFromError(e){return e instanceof I?{url:e.url}:null}componentDidCatch(e){if(!(e instanceof I))throw e}componentDidUpdate(e){e.url!==this.props.url&&this.state.url&&this.setState({url:null})}render(){if(this.state.url){let e=this.props.fallback;return v(e,{url:this.state.url})}return this.props.children}};function B({fallback:e,children:t}){let{url:n}=A();return v(z,{fallback:e,url:n,children:t})}function V({error:e,reset:t}){let[n,r]=_(!1);return S(e=>{e===`r`&&t(),e===`s`&&r(e=>!e),e===`q`&&process.exit(1)}),y(b,{flexDirection:`column`,padding:2,borderStyle:`double`,borderColor:`red`,children:[v(b,{marginBottom:1,children:v(x,{bold:!0,color:`red`,children:`💥 Critical Application Error`})}),v(b,{marginBottom:1,children:v(x,{color:`red`,children:`The application encountered a fatal error and cannot continue.`})}),y(b,{flexDirection:`column`,padding:1,borderStyle:`round`,borderColor:`yellow`,marginBottom:1,children:[v(x,{bold:!0,color:`yellow`,children:`Error Details:`}),v(x,{children:e.message})]}),n&&e.stack&&y(b,{flexDirection:`column`,padding:1,borderStyle:`single`,borderColor:`gray`,marginBottom:1,children:[v(x,{bold:!0,dimColor:!0,children:`Stack Trace:`}),v(x,{dimColor:!0,children:e.stack})]}),y(b,{flexDirection:`column`,marginTop:1,children:[v(x,{bold:!0,children:`Options:`}),v(x,{dimColor:!0,children:` [r] Retry [s] Toggle Stack Trace [q] Quit`})]}),v(b,{marginTop:1,children:v(x,{dimColor:!0,children:`If this error persists, please report it.`})})]})}function H({url:e}){let t=A();return S(e=>{e===`b`&&t.back(),e===`q`&&process.exit(0)}),y(b,{flexDirection:`column`,padding:2,borderStyle:`round`,borderColor:`red`,children:[y(b,{flexDirection:`column`,marginBottom:1,children:[v(x,{bold:!0,color:`red`,children:`Route Not Found`}),v(x,{dimColor:!0,children:`The router couldn't match the requested URL.`})]}),v(b,{flexDirection:`column`,paddingLeft:1,marginBottom:1,children:y(x,{children:[`Requested:`,` `,v(x,{color:`yellow`,bold:!0,children:e})]})}),y(b,{flexDirection:`column`,padding:1,borderStyle:`single`,borderColor:`gray`,marginBottom:1,children:[v(x,{bold:!0,children:`Actions`}),v(x,{dimColor:!0,children:` [b] Go back`}),v(x,{dimColor:!0,children:` [q] Quit`})]}),v(x,{dimColor:!0,children:`Tip: Check the route segment name or add a screen.tsx for this path.`})]})}function U({routes:e}){let{url:t}=A(),n=e[t];if(!n)throw new I(t);return n}function W({initialUrl:e,routes:t}){return v(F,{fallback:V,children:v(k,{initialUrl:e,children:v(B,{fallback:H,children:v(U,{routes:t})})})})}function G(e=1e3){let[t,n]=_({heapUsed:0,heapTotal:0,rss:0,external:0});return g(()=>{let t=()=>{let e=process.memoryUsage();n({heapUsed:Math.round(e.heapUsed/1024/1024),heapTotal:Math.round(e.heapTotal/1024/1024),rss:Math.round(e.rss/1024/1024),external:Math.round(e.external/1024/1024)})};t();let r=setInterval(t,e);return()=>clearInterval(r)},[e]),t}export{W as App,R as ComponentNotFoundError,s as DirectoryNotFoundError,l as DuplicateScreenError,L as EmptyChainError,F as ErrorBoundary,V as ErrorScreen,U as FileRouter,t as FileRouterError,a as NotADirectoryError,B as NotFoundBoundary,I as NotFoundError,H as NotFoundScreen,d as RootIsFileError,O as RouterContext,k as RouterProvider,o as createComponentIdMap,e as createFileTree,c as createRouteChainMap,i as createSegmentTree,u as createSerializedRoutes,n as defineConfig,r as loadConfig,j as useHistory,G as useMemoryMonitor,M as useNavigate,N as useRouteData,A as useRouter,P as useUrl};
1
+ import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,r as c,s as l,t as u,u as d}from"./route-tree-BbrKaika.mjs";import{Component as f,createContext as p,createElement as m,useContext as h,useEffect as g,useMemo as _,useReducer as v,useState as y}from"react";import{jsx as b,jsxs as x}from"react/jsx-runtime";import{Box as S,Text as C,useInput as w}from"ink";function T(e,t){let{history:n,position:r}=e;switch(t.type){case`push`:{let{url:e,searchParams:i}=t,a={url:D(e),searchParams:i};return{position:r+1,history:n.toSpliced(r+1,1/0,a)}}case`replace`:return{...e,history:n.with(r,{url:D(t.url)})};case`back`:return r>0?{...e,position:r-1}:e;case`forward`:return r<n.length-1?{...e,position:r+1}:e}}function E(e){return{history:[{url:D(e)}],position:0}}function D(e){return e.endsWith(`/`)?e:`${e}/`}const O=p(null);function k({initialUrl:e,children:t}){let[n,r]=v(T,e,E),{url:i,searchParams:a}=n.history[n.position];return b(O.Provider,{value:{url:i,searchParams:a,history:n.history.map(e=>e.url),position:n.position,push:(e,t)=>r({type:`push`,url:e,searchParams:t}),replace:e=>r({type:`replace`,url:e}),back:()=>r({type:`back`}),forward:()=>r({type:`forward`})},children:t})}function A(){let e=h(O);if(!e)throw Error(`useRouter must be used within a NavigationProvider`);return e}function j(){let{history:e,position:t}=A();return{stack:e,position:t}}function M(){let{url:e}=A();return e}function N(){let{push:e,replace:t,back:n,forward:r}=A();return{push:e,replace:t,back:n,forward:r}}function P(){let{searchParams:e}=A();return e}var F=class extends f{state;constructor(e){super(e),this.state={error:null},this.reset=this.reset.bind(this)}static getDerivedStateFromError(e){return{error:e}}componentDidCatch(){}reset(){this.setState({error:null})}render(){if(this.state.error){let e=this.props.fallback;return b(e,{error:this.state.error,reset:this.reset})}return this.props.children}},I=class extends Error{url;constructor(e,t=`Not Found`){super(t),this.name=`NotFoundError`,this.url=e}},L=class extends f{state;constructor(e){super(e),this.state={url:null}}static getDerivedStateFromError(e){return e instanceof I?{url:e.url}:null}componentDidCatch(e){if(!(e instanceof I))throw e}componentDidUpdate(e){e.url!==this.props.url&&this.state.url&&this.setState({url:null})}render(){if(this.state.url){let e=this.props.fallback;return b(e,{url:this.state.url})}return this.props.children}};function R({fallback:e,children:t}){return b(L,{fallback:e,url:M(),children:t})}function z({error:e,reset:t}){let[n,r]=y(!1);return w(e=>{e===`r`&&t(),e===`s`&&r(e=>!e),e===`q`&&process.exit(1)}),x(S,{flexDirection:`column`,padding:2,borderStyle:`double`,borderColor:`red`,children:[b(S,{marginBottom:1,children:b(C,{bold:!0,color:`red`,children:`💥 Critical Application Error`})}),b(S,{marginBottom:1,children:b(C,{color:`red`,children:`The application encountered a fatal error and cannot continue.`})}),x(S,{flexDirection:`column`,padding:1,borderStyle:`round`,borderColor:`yellow`,marginBottom:1,children:[b(C,{bold:!0,color:`yellow`,children:`Error Details:`}),b(C,{children:e.message})]}),n&&e.stack&&x(S,{flexDirection:`column`,padding:1,borderStyle:`single`,borderColor:`gray`,marginBottom:1,children:[b(C,{bold:!0,dimColor:!0,children:`Stack Trace:`}),b(C,{dimColor:!0,children:e.stack})]}),x(S,{flexDirection:`column`,marginTop:1,children:[b(C,{bold:!0,children:`Options:`}),b(C,{dimColor:!0,children:` [r] Retry [s] Toggle Stack Trace [q] Quit`})]}),b(S,{marginTop:1,children:b(C,{dimColor:!0,children:`If this error persists, please report it.`})})]})}function B({url:e}){let t=N();return w(e=>{e===`b`&&t.back(),e===`q`&&process.exit(0)}),x(S,{flexDirection:`column`,padding:2,borderStyle:`round`,borderColor:`red`,children:[x(S,{flexDirection:`column`,marginBottom:1,children:[b(C,{bold:!0,color:`red`,children:`Route Not Found`}),b(C,{dimColor:!0,children:`The router couldn't match the requested URL.`})]}),b(S,{flexDirection:`column`,paddingLeft:1,marginBottom:1,children:x(C,{children:[`Requested:`,` `,b(C,{color:`yellow`,bold:!0,children:e})]})}),x(S,{flexDirection:`column`,padding:1,borderStyle:`single`,borderColor:`gray`,marginBottom:1,children:[b(C,{bold:!0,children:`Actions`}),b(C,{dimColor:!0,children:` [b] Go back`}),b(C,{dimColor:!0,children:` [q] Quit`})]}),b(C,{dimColor:!0,children:`Tip: Check the route segment name or add a screen.tsx for this path.`})]})}const V=p({});function H({params:e,children:t}){return b(V.Provider,{value:e,children:t})}function U(e,t,n){let r=e[0]?.screen;return G(e,t,r?m(t[r],{key:r}):m(()=>{throw new I(n)}))}function W(e,t,n){return G(e,t,m(()=>{throw new I(n)}))}function G(e,t,n){let r=n;for(let n of e){if(n[`not-found`]){let e=t[n[`not-found`]];r=m(R,{key:n[`not-found`],fallback:e},r)}if(n.error){let e=t[n.error];r=m(F,{key:n.error,fallback:e},r)}if(n.layout){let e=t[n.layout];r=m(e,{key:n.layout},r)}}return r}function K(e){let t=[],n=e[e.length-1];n.roles&&Object.keys(n.roles).length&&t.push({...n.roles});for(let n=e.length-2;n>=0;n--){let{screen:r,...i}=e[n].roles??{};Object.keys(i).length&&t.push(i)}return t}function q(e,t){let n=[e],r=-1;return d({idx:0,path:n},{visit:({idx:e,path:i})=>e===t.length&&(n=i,r=-1,!0),expand:({idx:e,path:i})=>{let a=i[i.length-1],o=t[e],s=(a.children??[]).flatMap(t=>t.group?[{idx:e,path:i.concat(t)}]:t.param||t.name===o?[{idx:e+1,path:i.concat(t)}]:[]);return!s.length&&e>r&&(n=i,r=e),s}}),{routePath:n,hasMatch:r===-1}}function J({routeTree:e,componentMap:t}){let n={};return r=>{if(n[r])return n[r];let i=Y(r),{routePath:a,hasMatch:o}=q(e,i),s=K(a),c=X(a,i);if(o){let e=U(s,t,r);return n[r]=Object.keys(c).length?{element:e,params:c}:{element:e}}let l=s.findIndex(e=>e[`not-found`]);if(l!==-1){let e=W(s.slice(l),t,r);return n[r]=Object.keys(c).length?{element:e,params:c}:{element:e}}throw new I(r)}}function Y(e){let t=e.slice(1,-1);return t?t.split(`/`):[]}function X(e,t){let n={},r=0;for(let i=1;i<e.length;i++){let a=e[i];a.group||(a.param&&(n[a.param]=t[r]),r++)}return n}function Z({routingTable:e}){let{element:t,params:n}=_(()=>J(e),[e])(M());return b(H,{params:n??{},children:t})}function Q({initialUrl:e,routingTable:t}){return b(F,{fallback:z,children:b(k,{initialUrl:e,children:b(R,{fallback:B,children:b(Z,{routingTable:t})})})})}function $(){return h(V)}function ee(e=1e3){let[t,n]=y({heapUsed:0,heapTotal:0,rss:0,external:0});return g(()=>{let t=()=>{let e=process.memoryUsage();n({heapUsed:Math.round(e.heapUsed/1024/1024),heapTotal:Math.round(e.heapTotal/1024/1024),rss:Math.round(e.rss/1024/1024),external:Math.round(e.external/1024/1024)})};t();let r=setInterval(t,e);return()=>clearInterval(r)},[e]),t}export{Q as App,e as DirectoryNotFoundError,s as DuplicateScreenError,V as DynamicParamsContext,H as DynamicParamsProvider,F as ErrorBoundary,z as ErrorScreen,Z as FileRouter,l as FileRouterError,O as NavigationContext,k as NavigationProvider,t as NotADirectoryError,R as NotFoundBoundary,I as NotFoundError,B as NotFoundScreen,a as RootIsFileError,o as SEGMENT_ROLES,i as createFileTree,u as createRouteTree,c as createSegmentTree,n as defineConfig,r as loadConfig,j as useHistory,ee as useMemoryMonitor,A as useNavigate,$ as useParams,M as usePathname,N as useRouter,P as useSearchParams};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["push","actions.push","replace","actions.replace","back","actions.back","forward","actions.forward"],"sources":["../src/pen/router/actions.ts","../src/pen/router/RouterProvider.tsx","../src/pen/router/hooks/use-router.ts","../src/pen/router/hooks/use-history.ts","../src/pen/router/hooks/use-navigate.ts","../src/pen/router/hooks/use-route-data.ts","../src/pen/router/hooks/use-url.ts","../src/pen/runtime/ui/ErrorBoundary.tsx","../src/pen/runtime/errors.ts","../src/pen/runtime/ui/NotFoundBoundary.tsx","../src/pen/runtime/ui/ErrorScreen.tsx","../src/pen/runtime/ui/NotFoundScreen.tsx","../src/pen/runtime/FileRouter.tsx","../src/pen/runtime/App.tsx","../src/pen/utils/use-memory-monitor.ts"],"sourcesContent":["import type { NavigationHistory } from './types'\r\n\r\n/**\r\n * Normalizes a URL to include a trailing slash.\r\n * Routes are keyed with trailing slashes in the manifest.\r\n */\r\nexport function normalizeUrl(url: string): string {\r\n return url.endsWith('/') ? url : `${url}/`\r\n}\r\n\r\nexport function push(prev: NavigationHistory, newUrl: string, newData?: unknown): NavigationHistory {\r\n return {\r\n stack: [...prev.stack.slice(0, prev.position + 1), { url: normalizeUrl(newUrl), data: newData }],\r\n position: prev.position + 1,\r\n }\r\n}\r\n\r\nexport function replace(prev: NavigationHistory, newUrl: string): NavigationHistory {\r\n const newStack = [...prev.stack]\r\n newStack[prev.position] = { url: normalizeUrl(newUrl) }\r\n return { ...prev, stack: newStack }\r\n}\r\n\r\nexport function back(prev: NavigationHistory): NavigationHistory {\r\n return prev.position > 0\r\n ? { ...prev, position: prev.position - 1 }\r\n : prev\r\n}\r\n\r\nexport function forward(prev: NavigationHistory): NavigationHistory {\r\n return prev.position < prev.stack.length - 1\r\n ? { ...prev, position: prev.position + 1 }\r\n : prev\r\n}\r\n","import { createContext, useState, useCallback, type PropsWithChildren } from 'react'\r\nimport * as actions from './actions'\r\nimport { normalizeUrl } from './actions'\r\nimport type { NavigationHistory } from './types'\r\n\r\nexport interface RouterContextValue {\r\n url: string\r\n data: unknown | undefined\r\n history: readonly string[] // Expose as readonly\r\n position: number // Expose current position\r\n push: (url: string, data?: unknown) => void\r\n replace: (url: string) => void\r\n back: () => void\r\n forward: () => void\r\n}\r\n\r\nexport interface RouterProviderProps extends PropsWithChildren {\r\n initialUrl: string\r\n}\r\n\r\n// Step 1: Define what data exists on the channel\r\nexport const RouterContext = createContext<RouterContextValue | null>(null)\r\n\r\n// Step 2: Broadcast the data\r\nexport function RouterProvider({ initialUrl, children }: RouterProviderProps) {\r\n const normalizedInitialUrl = normalizeUrl(initialUrl)\r\n const [history, setHistory] = useState<NavigationHistory>({\r\n stack: [{ url: normalizedInitialUrl }],\r\n position: 0,\r\n })\r\n\r\n // Grab the current url and data\r\n const { url, data } = history.stack[history.position] ?? { url: normalizedInitialUrl }\r\n\r\n // Push new URL and data to history\r\n const push = useCallback((newUrl: string, newData?: unknown) => {\r\n setHistory(prev => actions.push(prev, newUrl, newData))\r\n }, [])\r\n\r\n // Replace current URL without adding to history\r\n const replace = useCallback((newUrl: string) => {\r\n setHistory(prev => actions.replace(prev, newUrl))\r\n }, [])\r\n\r\n // Navigate backwards\r\n const back = useCallback(() => {\r\n setHistory(prev => actions.back(prev))\r\n }, [])\r\n\r\n // Navigate forwards\r\n const forward = useCallback(() => {\r\n setHistory(prev => actions.forward(prev))\r\n }, [])\r\n\r\n return (\r\n <RouterContext.Provider\r\n value={{\r\n url,\r\n data,\r\n history: history.stack.map(entry => entry.url),\r\n position: history.position,\r\n push,\r\n replace,\r\n back,\r\n forward,\r\n }}\r\n >\r\n {children}\r\n </RouterContext.Provider>\r\n )\r\n}\r\n","import { useContext } from 'react'\r\nimport { RouterContext } from '../RouterProvider'\r\n\r\n// Step 3: Tune into the channel and receive the broadcast\r\nexport function useRouter() {\r\n const context = useContext(RouterContext)\r\n if (!context)\r\n throw new Error('useRouter must be used within a RouterProvider')\r\n return context\r\n}\r\n","import { useRouter } from './use-router'\r\n\r\n/**\r\n * Get the navigation history (URLs only)\r\n *\r\n * @returns readonly array of visited URLs\r\n *\r\n * @example\r\n * ```tsx\r\n * const history = useHistory()\r\n * return (\r\n * <Box>\r\n * <Text>Visited pages:</Text>\r\n * {history.map((url, i) => (\r\n * <Text key={i}>{url}</Text>\r\n * ))}\r\n * </Box>\r\n * )\r\n * ```\r\n */\r\nexport function useHistory(): readonly string[] {\r\n const { history } = useRouter()\r\n return history\r\n}\r\n","import { useRouter } from './use-router'\r\n\r\n/**\r\n * Get navigation functions without url/data\r\n */\r\nexport function useNavigate() {\r\n const { push, replace, back, forward } = useRouter()\r\n return { push, replace, back, forward }\r\n}\r\n","import { useRouter } from './use-router'\r\n\r\n/**\r\n * Access route data passed via router.push()\r\n *\r\n * @example\r\n * ```tsx\r\n * const user = useRouteData<User>()\r\n * if (!user) return <Text>No user</Text>\r\n * return <Text>{user.name}</Text>\r\n * ```\r\n */\r\nexport function useRouteData<T = unknown>() {\r\n const { data } = useRouter()\r\n return data as T | undefined\r\n}\r\n","import { useRouter } from './use-router'\r\n\r\n/**\r\n * Get the current URL\r\n */\r\nexport function useUrl(): string {\r\n const { url } = useRouter()\r\n return url\r\n}\r\n","import { Component, type ComponentType, type PropsWithChildren } from 'react'\r\n\r\n/** Props passed to error.tsx components */\r\nexport interface ErrorComponentProps {\r\n error: Error\r\n reset: () => void\r\n}\r\n\r\ninterface ErrorBoundaryProps extends PropsWithChildren {\r\n fallback: ComponentType<ErrorComponentProps>\r\n}\r\n\r\ninterface ErrorBoundaryState {\r\n error: Error | null\r\n}\r\n\r\n/**\r\n * React Error Boundary that catches errors in child components.\r\n * Renders the provided ErrorComponent when an error occurs.\r\n *\r\n * Catches:\r\n * - Errors during rendering\r\n * - Errors in lifecycle methods\r\n * - Errors in constructors\r\n *\r\n * Does NOT catch:\r\n * - Event handlers (use try-catch)\r\n * - Async code (use try-catch)\r\n * - Errors in the error boundary itself\r\n */\r\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\r\n state: ErrorBoundaryState\r\n\r\n constructor(props: ErrorBoundaryProps) {\r\n super(props)\r\n this.state = { error: null }\r\n this.reset = this.reset.bind(this) // Bind all methods that will be passed as callbacks\r\n }\r\n\r\n /** Called when a child component throws during render. */\r\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\r\n return { error }\r\n }\r\n\r\n /** Called after error is caught. */\r\n componentDidCatch() {\r\n // Error is passed to error.tsx component via props\r\n }\r\n\r\n /** Resets error state and attempts to re-render children. */\r\n reset() {\r\n this.setState({ error: null })\r\n }\r\n\r\n render() {\r\n if (this.state.error) {\r\n const ErrorComponent = this.props.fallback\r\n return <ErrorComponent error={this.state.error} reset={this.reset} />\r\n }\r\n return this.props.children\r\n }\r\n}\r\n","/** Special error thrown to trigger not-found.tsx. */\r\nexport class NotFoundError extends Error {\r\n public readonly url: string\r\n\r\n constructor(url: string, message: string = 'Not Found') {\r\n super(message)\r\n this.name = 'NotFoundError'\r\n this.url = url\r\n }\r\n}\r\n\r\n/** Error thrown when a route has an empty chain. */\r\nexport class EmptyChainError extends Error {\r\n constructor(public url: string) {\r\n super(\r\n `Route ${url} has an empty chain. ` +\r\n 'This indicates a bug in manifest generation - ' +\r\n 'routes must have at least one segment.',\r\n )\r\n this.name = 'EmptyChainError'\r\n }\r\n}\r\n\r\n/** Error thrown when a component is missing from the component map. */\r\nexport class ComponentNotFoundError extends Error {\r\n constructor(public componentPath: string) {\r\n super(\r\n `Component not found: ${componentPath}. ` +\r\n 'This indicates the component map is out of sync with the manifest. ' +\r\n 'Try running \\'pen build\\' again.',\r\n )\r\n this.name = 'ComponentNotFoundError'\r\n }\r\n}\r\n","import { Component, type ComponentType, type PropsWithChildren } from 'react'\r\nimport { useRouter } from '@/pen/router'\r\nimport { NotFoundError } from '../errors'\r\n\r\n/** Props passed to not-found.tsx components */\r\nexport interface NotFoundComponentProps {\r\n url: string\r\n}\r\n\r\ninterface NotFoundErrorBoundaryProps extends PropsWithChildren {\r\n fallback: ComponentType<NotFoundComponentProps>\r\n url: string\r\n}\r\n\r\ninterface NotFoundBoundaryState {\r\n url: string | null\r\n}\r\n\r\n/**\r\n * Catches NotFoundError thrown by notFound() function.\r\n * Renders fallback component when caught.\r\n *\r\n * Does NOT catch:\r\n * - Non-NotFoundError errors (they bubble)\r\n */\r\nclass NotFoundErrorBoundary extends Component<NotFoundErrorBoundaryProps, NotFoundBoundaryState> {\r\n state: NotFoundBoundaryState\r\n\r\n constructor(props: NotFoundErrorBoundaryProps) {\r\n super(props)\r\n this.state = { url: null }\r\n }\r\n\r\n /** Called when a child component throws during render. */\r\n static getDerivedStateFromError(error: Error) {\r\n // Only catch NotFoundError, other errors bubble\r\n return error instanceof NotFoundError ? { url: error.url } : null\r\n }\r\n\r\n /** Called after error is caught. */\r\n componentDidCatch(error: Error) {\r\n if (!(error instanceof NotFoundError)) // Only handle NotFoundError\r\n throw error // Re-throw other errors\r\n }\r\n\r\n /** Called after updates. Clears the not-found state when the URL prop changes. */\r\n componentDidUpdate(prevProps: NotFoundErrorBoundaryProps) {\r\n if (prevProps.url !== this.props.url && this.state.url) // URL changed → clear not-found state\r\n this.setState({ url: null })\r\n }\r\n\r\n render() {\r\n if (this.state.url) {\r\n const NotFoundComponent = this.props.fallback\r\n return <NotFoundComponent url={this.state.url} />\r\n }\r\n return this.props.children\r\n }\r\n}\r\n\r\nexport interface NotFoundBoundaryProps extends PropsWithChildren {\r\n fallback: ComponentType<NotFoundComponentProps>\r\n}\r\n\r\n/**\r\n * Router-aware wrapper for NotFoundErrorBoundary.\r\n * Injects the current URL so the boundary can reset on navigation.\r\n */\r\nexport function NotFoundBoundary({ fallback, children }: NotFoundBoundaryProps) {\r\n const { url } = useRouter()\r\n return (\r\n <NotFoundErrorBoundary fallback={fallback} url={url}>\r\n {children}\r\n </NotFoundErrorBoundary>\r\n )\r\n}\r\n","// src/core/router/components/GlobalErrorFallback.tsx\r\nimport { Box, Text, useInput } from 'ink'\r\nimport { useState } from 'react'\r\nimport { type ErrorComponentProps } from './ErrorBoundary'\r\n\r\n/** Global error fallback that always wraps the app. */\r\nexport function ErrorScreen({ error, reset }: ErrorComponentProps) {\r\n const [showStack, setShowStack] = useState(false)\r\n\r\n useInput((input) => {\r\n if (input === 'r') reset()\r\n if (input === 's') setShowStack(prev => !prev)\r\n if (input === 'q') process.exit(1)\r\n })\r\n\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n padding={2}\r\n borderStyle=\"double\"\r\n borderColor=\"red\"\r\n >\r\n <Box marginBottom={1}>\r\n <Text bold color=\"red\">💥 Critical Application Error</Text>\r\n </Box>\r\n\r\n <Box marginBottom={1}>\r\n <Text color=\"red\">The application encountered a fatal error and cannot continue.</Text>\r\n </Box>\r\n\r\n <Box\r\n flexDirection=\"column\"\r\n padding={1}\r\n borderStyle=\"round\"\r\n borderColor=\"yellow\"\r\n marginBottom={1}\r\n >\r\n <Text bold color=\"yellow\">Error Details:</Text>\r\n <Text>{error.message}</Text>\r\n </Box>\r\n\r\n {showStack && error.stack && (\r\n <Box\r\n flexDirection=\"column\"\r\n padding={1}\r\n borderStyle=\"single\"\r\n borderColor=\"gray\"\r\n marginBottom={1}\r\n >\r\n <Text bold dimColor>Stack Trace:</Text>\r\n <Text dimColor>{error.stack}</Text>\r\n </Box>\r\n )}\r\n\r\n <Box flexDirection=\"column\" marginTop={1}>\r\n <Text bold>Options:</Text>\r\n <Text dimColor> [r] Retry [s] Toggle Stack Trace [q] Quit</Text>\r\n </Box>\r\n\r\n <Box marginTop={1}>\r\n <Text dimColor>\r\n If this error persists, please report it.\r\n </Text>\r\n </Box>\r\n </Box>\r\n )\r\n}\r\n","import { Box, Text, useInput } from 'ink'\r\nimport { useRouter } from '@/pen/router'\r\nimport { type NotFoundComponentProps } from './NotFoundBoundary'\r\n\r\n/**\r\n * Not found error screen displayed when a route is not found.\r\n * Shows the URL that was attempted.\r\n */\r\nexport function NotFoundScreen({ url }: NotFoundComponentProps) {\r\n const router = useRouter()\r\n\r\n useInput((input) => {\r\n if (input === 'b') router.back()\r\n if (input === 'q') process.exit(0)\r\n })\r\n\r\n return (\r\n <Box flexDirection=\"column\" padding={2} borderStyle=\"round\" borderColor=\"red\">\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Text bold color=\"red\">Route Not Found</Text>\r\n <Text dimColor>{'The router couldn\\'t match the requested URL.'}</Text>\r\n </Box>\r\n\r\n <Box flexDirection=\"column\" paddingLeft={1} marginBottom={1}>\r\n <Text>\r\n Requested:{' '}\r\n <Text color=\"yellow\" bold>\r\n {url}\r\n </Text>\r\n </Text>\r\n </Box>\r\n\r\n <Box\r\n flexDirection=\"column\"\r\n padding={1}\r\n borderStyle=\"single\"\r\n borderColor=\"gray\"\r\n marginBottom={1}\r\n >\r\n <Text bold>Actions</Text>\r\n <Text dimColor> [b] Go back</Text>\r\n <Text dimColor> [q] Quit</Text>\r\n </Box>\r\n\r\n <Text dimColor>\r\n Tip: Check the route segment name or add a screen.tsx for this path.\r\n </Text>\r\n </Box>\r\n )\r\n}\r\n","import { useRouter } from '@/pen/router'\r\nimport { NotFoundError } from './errors'\r\n\r\nimport type { ReactElement } from 'react'\r\nimport type { CompiledRoutes } from '../compiler/types'\r\n\r\n/**\r\n * Props for the FileRouter component.\r\n */\r\nexport interface FileRouterProps {\r\n routes: CompiledRoutes\r\n}\r\n\r\n/**\r\n * Router component that renders compiled route elements.\r\n * Simply looks up the current URL in the compiled routes map.\r\n */\r\nexport function FileRouter({ routes }: FileRouterProps): ReactElement {\r\n const { url } = useRouter()\r\n const element = routes[url]\r\n\r\n if (!element) throw new NotFoundError(url)\r\n return element\r\n}\r\n","import { RouterProvider } from '@/pen/router'\r\nimport { ErrorBoundary } from './ui/ErrorBoundary'\r\nimport { NotFoundBoundary } from './ui/NotFoundBoundary'\r\nimport { ErrorScreen } from './ui/ErrorScreen'\r\nimport { NotFoundScreen } from './ui/NotFoundScreen'\r\nimport { FileRouter } from './FileRouter'\r\nimport type { CompiledRoutes } from '../compiler/types'\r\n\r\nexport interface AppProps {\r\n initialUrl: string\r\n routes: CompiledRoutes\r\n}\r\n\r\n/**\r\n * Root application component.\r\n * Routes are compiled at build time via codegen - no runtime composition needed!\r\n */\r\nexport function App({ initialUrl, routes }: AppProps) {\r\n return (\r\n <ErrorBoundary fallback={ErrorScreen}>\r\n <RouterProvider initialUrl={initialUrl}>\r\n <NotFoundBoundary fallback={NotFoundScreen}>\r\n <FileRouter routes={routes} />\r\n </NotFoundBoundary>\r\n </RouterProvider>\r\n </ErrorBoundary>\r\n )\r\n}\r\n","import { useEffect, useState } from 'react'\r\n\r\nexport interface MemoryUsage {\r\n /** Heap memory currently used (in MB) */\r\n heapUsed: number\r\n /** Total heap memory allocated (in MB) */\r\n heapTotal: number\r\n /** Resident Set Size - total memory allocated for the process (in MB) */\r\n rss: number\r\n /** Memory used by C++ objects bound to JavaScript (in MB) */\r\n external: number\r\n}\r\n\r\n/**\r\n * Hook to monitor Node.js memory usage in real-time\r\n *\r\n * @param intervalMs - Polling interval in milliseconds (default: 1000ms)\r\n * @returns Current memory usage statistics in megabytes\r\n *\r\n * @example\r\n * ```tsx\r\n * function MemoryDisplay() {\r\n * const { heapUsed, heapTotal, rss } = useMemoryMonitor()\r\n * return <Text>RAM: {heapUsed}/{heapTotal} MB (RSS: {rss} MB)</Text>\r\n * }\r\n * ```\r\n */\r\nexport function useMemoryMonitor(intervalMs = 1000): MemoryUsage {\r\n const [memory, setMemory] = useState<MemoryUsage>({\r\n heapUsed: 0,\r\n heapTotal: 0,\r\n rss: 0,\r\n external: 0,\r\n })\r\n\r\n useEffect(() => {\r\n const updateMemory = () => {\r\n const usage = process.memoryUsage()\r\n setMemory({\r\n heapUsed: Math.round(usage.heapUsed / 1024 / 1024),\r\n heapTotal: Math.round(usage.heapTotal / 1024 / 1024),\r\n rss: Math.round(usage.rss / 1024 / 1024),\r\n external: Math.round(usage.external / 1024 / 1024),\r\n })\r\n }\r\n\r\n // Get initial reading immediately\r\n updateMemory()\r\n\r\n const interval = setInterval(updateMemory, intervalMs)\r\n\r\n return () => clearInterval(interval)\r\n }, [intervalMs])\r\n\r\n return memory\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/pen/api/reducer.ts","../src/pen/api/NavigationProvider.tsx","../src/pen/api/hooks/use-navigate.ts","../src/pen/api/hooks/use-history.ts","../src/pen/api/hooks/use-pathname.ts","../src/pen/api/hooks/use-router.ts","../src/pen/api/hooks/use-search-params.ts","../src/pen/runtime/components/ErrorBoundary.tsx","../src/pen/runtime/errors.ts","../src/pen/runtime/components/NotFoundBoundary.tsx","../src/pen/runtime/components/ErrorScreen.tsx","../src/pen/runtime/components/NotFoundScreen.tsx","../src/pen/runtime/providers/DynamicParamsProvider.tsx","../src/pen/runtime/routing/composer.ts","../src/pen/runtime/routing/chainer.ts","../src/pen/runtime/routing/matcher.ts","../src/pen/runtime/routing/resolver.ts","../src/pen/runtime/components/FileRouter.tsx","../src/pen/runtime/App.tsx","../src/pen/runtime/hooks/use-params.ts","../src/pen/utils/use-memory-monitor.ts"],"sourcesContent":["/** Navigation entry with URL and optional searchParams */\r\ntype Location = {\r\n url: string\r\n searchParams?: unknown\r\n}\r\n\r\ntype NavigationState = {\r\n position: number\r\n history: Location[]\r\n}\r\n\r\ntype NavigationAction =\r\n | { type: 'push'; url: string; searchParams?: unknown }\r\n | { type: 'replace'; url: string }\r\n | { type: 'back' }\r\n | { type: 'forward' }\r\n\r\nexport function reducer(navigation: NavigationState, action: NavigationAction): NavigationState {\r\n const { history, position } = navigation\r\n\r\n switch (action.type) {\r\n case 'push': {\r\n const { url, searchParams } = action\r\n const location = { url: normalizeUrl(url), searchParams }\r\n return {\r\n position: position+1,\r\n history: history.toSpliced(position+1, Infinity, location),\r\n }\r\n }\r\n case 'replace': return {\r\n ...navigation,\r\n history: history.with(position, { url: normalizeUrl(action.url) }),\r\n }\r\n case 'back': return position > 0\r\n ? { ...navigation, position: position-1 }\r\n : navigation\r\n\r\n case 'forward': return position < history.length-1\r\n ? { ...navigation, position: position+1 }\r\n : navigation\r\n }\r\n}\r\n\r\nexport function createNavigationState(initialUrl: string): NavigationState {\r\n const history = [{ url: normalizeUrl(initialUrl) }]\r\n return { history, position: 0 }\r\n}\r\n\r\n/** Normalizes a URL to include a trailing slash. */\r\nfunction normalizeUrl(url: string): string {\r\n return url.endsWith('/') ? url : `${url}/`\r\n}\r\n","import type { PropsWithChildren } from 'react'\r\nimport { createContext, useReducer } from 'react'\r\nimport { createNavigationState, reducer } from './reducer'\r\n\r\nexport const NavigationContext = createContext<NavigationContextValue | null>(null)\r\nexport type NavigationContextValue = {\r\n url: string\r\n searchParams?: unknown\r\n history: readonly string[] // Expose as readonly\r\n position: number // Expose current position\r\n push: (url: string, searchParams?: unknown) => void\r\n replace: (url: string) => void\r\n back: () => void\r\n forward: () => void\r\n}\r\n\r\nexport type NavigationProviderProps = PropsWithChildren<{ initialUrl: string }>\r\nexport function NavigationProvider({ initialUrl, children }: NavigationProviderProps) {\r\n const [navigation, dispatch] = useReducer(reducer, initialUrl, createNavigationState)\r\n const { url, searchParams } = navigation.history[navigation.position]!\r\n\r\n return (\r\n <NavigationContext.Provider\r\n value={{\r\n url, searchParams,\r\n history: navigation.history.map(location => location.url),\r\n position: navigation.position,\r\n push: (url, searchParams) => dispatch({ type: 'push', url, searchParams }),\r\n replace: (url) => dispatch({ type: 'replace', url }),\r\n back: () => dispatch({ type: 'back' }),\r\n forward: () => dispatch({ type: 'forward' }),\r\n }}\r\n >\r\n {children}\r\n </NavigationContext.Provider>\r\n )\r\n}\r\n","import { useContext } from 'react'\r\nimport { NavigationContext } from '../NavigationProvider'\r\n\r\nexport function useNavigate() {\r\n const context = useContext(NavigationContext)\r\n if (!context) throw new Error('useRouter must be used within a NavigationProvider')\r\n return context\r\n}\r\n","import { useNavigate } from './use-navigate'\r\n\r\nexport function useHistory() {\r\n const { history: stack, position } = useNavigate()\r\n return { stack, position }\r\n}\r\n","import { useNavigate } from './use-navigate'\r\n\r\nexport function usePathname() {\r\n const { url } = useNavigate()\r\n return url\r\n}\r\n","import { useNavigate } from './use-navigate'\r\n\r\nexport function useRouter() {\r\n const { push, replace, back, forward } = useNavigate()\r\n return { push, replace, back, forward }\r\n}\r\n","import { useNavigate } from './use-navigate'\r\n\r\nexport function useSearchParams() {\r\n const { searchParams } = useNavigate()\r\n return searchParams\r\n}\r\n","import { Component, type ComponentType, type PropsWithChildren } from 'react'\r\n\r\n/** Props passed to error.tsx components */\r\nexport interface ErrorComponentProps {\r\n error: Error\r\n reset: () => void\r\n}\r\n\r\ninterface ErrorBoundaryProps extends PropsWithChildren {\r\n fallback: ComponentType<ErrorComponentProps>\r\n}\r\n\r\ninterface ErrorBoundaryState {\r\n error: Error | null\r\n}\r\n\r\n/**\r\n * React Error Boundary that catches errors in child components.\r\n * Renders the provided ErrorComponent when an error occurs.\r\n *\r\n * Catches:\r\n * - Errors during rendering\r\n * - Errors in lifecycle methods\r\n * - Errors in constructors\r\n *\r\n * Does NOT catch:\r\n * - Event handlers (use try-catch)\r\n * - Async code (use try-catch)\r\n * - Errors in the error boundary itself\r\n */\r\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\r\n state: ErrorBoundaryState\r\n\r\n constructor(props: ErrorBoundaryProps) {\r\n super(props)\r\n this.state = { error: null }\r\n this.reset = this.reset.bind(this) // Bind all methods that will be passed as callbacks\r\n }\r\n\r\n /** Called when a child component throws during render. */\r\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\r\n return { error }\r\n }\r\n\r\n /** Called after error is caught. */\r\n componentDidCatch() {\r\n // Error is passed to error.tsx component via props\r\n }\r\n\r\n /** Resets error state and attempts to re-render children. */\r\n reset() {\r\n this.setState({ error: null })\r\n }\r\n\r\n render() {\r\n if (this.state.error) {\r\n const ErrorComponent = this.props.fallback\r\n return <ErrorComponent error={this.state.error} reset={this.reset} />\r\n }\r\n return this.props.children\r\n }\r\n}\r\n","/** Special error thrown to trigger not-found.tsx. */\r\nexport class NotFoundError extends Error {\r\n public readonly url: string\r\n\r\n constructor(url: string, message: string = 'Not Found') {\r\n super(message)\r\n this.name = 'NotFoundError'\r\n this.url = url\r\n }\r\n}\r\n","import { Component, type ComponentType, type PropsWithChildren } from 'react'\r\nimport { usePathname } from '@/pen/api'\r\nimport { NotFoundError } from '../errors'\r\n\r\n/** Props passed to not-found.tsx components */\r\nexport interface NotFoundComponentProps {\r\n url: string\r\n}\r\n\r\ninterface NotFoundErrorBoundaryProps extends PropsWithChildren {\r\n fallback: ComponentType<NotFoundComponentProps>\r\n url: string\r\n}\r\n\r\ninterface NotFoundBoundaryState {\r\n url: string | null\r\n}\r\n\r\n/**\r\n * Catches NotFoundError thrown by notFound() function.\r\n * Renders fallback component when caught.\r\n *\r\n * Does NOT catch:\r\n * - Non-NotFoundError errors (they bubble)\r\n */\r\nclass NotFoundErrorBoundary extends Component<NotFoundErrorBoundaryProps, NotFoundBoundaryState> {\r\n state: NotFoundBoundaryState\r\n\r\n constructor(props: NotFoundErrorBoundaryProps) {\r\n super(props)\r\n this.state = { url: null }\r\n }\r\n\r\n /** Called when a child component throws during render. */\r\n static getDerivedStateFromError(error: Error) {\r\n // Only catch NotFoundError, other errors bubble\r\n return error instanceof NotFoundError ? { url: error.url } : null\r\n }\r\n\r\n /** Called after error is caught. */\r\n componentDidCatch(error: Error) {\r\n if (!(error instanceof NotFoundError)) // Only handle NotFoundError\r\n throw error // Re-throw other errors\r\n }\r\n\r\n /** Called after updates. Clears the not-found state when the URL prop changes. */\r\n componentDidUpdate(prevProps: NotFoundErrorBoundaryProps) {\r\n if (prevProps.url !== this.props.url && this.state.url) // URL changed → clear not-found state\r\n this.setState({ url: null })\r\n }\r\n\r\n render() {\r\n if (this.state.url) {\r\n const NotFoundComponent = this.props.fallback\r\n return <NotFoundComponent url={this.state.url} />\r\n }\r\n return this.props.children\r\n }\r\n}\r\n\r\nexport interface NotFoundBoundaryProps extends PropsWithChildren {\r\n fallback: ComponentType<NotFoundComponentProps>\r\n}\r\n\r\n/**\r\n * Router-aware wrapper for NotFoundErrorBoundary.\r\n * Injects the current URL so the boundary can reset on navigation.\r\n */\r\nexport function NotFoundBoundary({ fallback, children }: NotFoundBoundaryProps) {\r\n const url = usePathname()\r\n return (\r\n <NotFoundErrorBoundary fallback={fallback} url={url}>\r\n {children}\r\n </NotFoundErrorBoundary>\r\n )\r\n}\r\n","// src/core/router/components/GlobalErrorFallback.tsx\r\nimport { Box, Text, useInput } from 'ink'\r\nimport { useState } from 'react'\r\nimport { type ErrorComponentProps } from './ErrorBoundary'\r\n\r\n/** Global error fallback that always wraps the app. */\r\nexport function ErrorScreen({ error, reset }: ErrorComponentProps) {\r\n const [showStack, setShowStack] = useState(false)\r\n\r\n useInput((input) => {\r\n if (input === 'r') reset()\r\n if (input === 's') setShowStack(prev => !prev)\r\n if (input === 'q') process.exit(1)\r\n })\r\n\r\n return (\r\n <Box\r\n flexDirection=\"column\"\r\n padding={2}\r\n borderStyle=\"double\"\r\n borderColor=\"red\"\r\n >\r\n <Box marginBottom={1}>\r\n <Text bold color=\"red\">💥 Critical Application Error</Text>\r\n </Box>\r\n\r\n <Box marginBottom={1}>\r\n <Text color=\"red\">The application encountered a fatal error and cannot continue.</Text>\r\n </Box>\r\n\r\n <Box\r\n flexDirection=\"column\"\r\n padding={1}\r\n borderStyle=\"round\"\r\n borderColor=\"yellow\"\r\n marginBottom={1}\r\n >\r\n <Text bold color=\"yellow\">Error Details:</Text>\r\n <Text>{error.message}</Text>\r\n </Box>\r\n\r\n {showStack && error.stack && (\r\n <Box\r\n flexDirection=\"column\"\r\n padding={1}\r\n borderStyle=\"single\"\r\n borderColor=\"gray\"\r\n marginBottom={1}\r\n >\r\n <Text bold dimColor>Stack Trace:</Text>\r\n <Text dimColor>{error.stack}</Text>\r\n </Box>\r\n )}\r\n\r\n <Box flexDirection=\"column\" marginTop={1}>\r\n <Text bold>Options:</Text>\r\n <Text dimColor> [r] Retry [s] Toggle Stack Trace [q] Quit</Text>\r\n </Box>\r\n\r\n <Box marginTop={1}>\r\n <Text dimColor>\r\n If this error persists, please report it.\r\n </Text>\r\n </Box>\r\n </Box>\r\n )\r\n}\r\n","import { Box, Text, useInput } from 'ink'\r\nimport { useRouter } from '@/pen/api'\r\nimport { type NotFoundComponentProps } from './NotFoundBoundary'\r\n\r\n/**\r\n * Not found error screen displayed when a route is not found.\r\n * Shows the URL that was attempted.\r\n */\r\nexport function NotFoundScreen({ url }: NotFoundComponentProps) {\r\n const router = useRouter()\r\n\r\n useInput((input) => {\r\n if (input === 'b') router.back()\r\n if (input === 'q') process.exit(0)\r\n })\r\n\r\n return (\r\n <Box flexDirection=\"column\" padding={2} borderStyle=\"round\" borderColor=\"red\">\r\n <Box flexDirection=\"column\" marginBottom={1}>\r\n <Text bold color=\"red\">Route Not Found</Text>\r\n <Text dimColor>{'The router couldn\\'t match the requested URL.'}</Text>\r\n </Box>\r\n\r\n <Box flexDirection=\"column\" paddingLeft={1} marginBottom={1}>\r\n <Text>\r\n Requested:{' '}\r\n <Text color=\"yellow\" bold>\r\n {url}\r\n </Text>\r\n </Text>\r\n </Box>\r\n\r\n <Box\r\n flexDirection=\"column\"\r\n padding={1}\r\n borderStyle=\"single\"\r\n borderColor=\"gray\"\r\n marginBottom={1}\r\n >\r\n <Text bold>Actions</Text>\r\n <Text dimColor> [b] Go back</Text>\r\n <Text dimColor> [q] Quit</Text>\r\n </Box>\r\n\r\n <Text dimColor>\r\n Tip: Check the route segment name or add a screen.tsx for this path.\r\n </Text>\r\n </Box>\r\n )\r\n}\r\n","import type { PropsWithChildren } from 'react'\r\nimport { createContext } from 'react'\r\n\r\nexport type DynamicParams = Record<string, string>\r\nexport const DynamicParamsContext = createContext<DynamicParams>({})\r\nexport type DynamicParamsProviderProps = PropsWithChildren<{\r\n params: DynamicParams\r\n}>\r\n\r\nexport function DynamicParamsProvider({ params, children }: DynamicParamsProviderProps) {\r\n return (\r\n <DynamicParamsContext.Provider value={params}>\r\n {children}\r\n </DynamicParamsContext.Provider>\r\n )\r\n}\r\n","import type { ComponentType, ReactElement } from 'react'\r\nimport type { RouteTreeNode, SegmentLayer } from '@/pen/compiler'\r\nimport type { ErrorComponentProps } from '../components/ErrorBoundary'\r\nimport type { NotFoundComponentProps } from '../components/NotFoundBoundary'\r\nimport { createElement } from 'react'\r\nimport { ErrorBoundary } from '../components/ErrorBoundary'\r\nimport { NotFoundBoundary } from '../components/NotFoundBoundary'\r\nimport { NotFoundError } from '../errors'\r\n\r\nexport type PathComponentMap = Record<string, ComponentType>\r\nexport type RoutingTable = {\r\n routeTree: RouteTreeNode\r\n componentMap: PathComponentMap\r\n}\r\n\r\n/** Composes a React element from a pre-built segment layer chain, rendering the leaf screen. */\r\nexport function composeSegmentLayerChain(chain: SegmentLayer[], componentMap: PathComponentMap, url: string): ReactElement {\r\n const screenPath = chain[0]?.['screen']\r\n const leaf = screenPath\r\n ? createElement(componentMap[screenPath]!, { key: screenPath })\r\n : createElement(() => { throw new NotFoundError(url) })\r\n return compose(chain, componentMap, leaf)\r\n}\r\n\r\n/** Composes a React element from a not-found chain, throwing NotFoundError as the leaf element. */\r\nexport function composeNotFoundChain(chain: SegmentLayer[], componentMap: PathComponentMap, url: string): ReactElement {\r\n const leaf = createElement(() => { throw new NotFoundError(url) })\r\n return compose(chain, componentMap, leaf)\r\n}\r\n\r\nfunction compose(chain: SegmentLayer[], componentMap: PathComponentMap, leaf: ReactElement): ReactElement {\r\n let element = leaf\r\n\r\n for (const segment of chain) {\r\n if (segment['not-found']) {\r\n const fallback = componentMap[segment['not-found']] as ComponentType<NotFoundComponentProps>\r\n const props = { key: segment['not-found'], fallback }\r\n element = createElement(NotFoundBoundary, props, element)\r\n }\r\n if (segment['error']) {\r\n const fallback = componentMap[segment['error']] as ComponentType<ErrorComponentProps>\r\n const props = { key: segment['error'], fallback }\r\n element = createElement(ErrorBoundary, props, element)\r\n }\r\n if (segment['layout']) {\r\n const Layout = componentMap[segment['layout']]!\r\n const props = { key: segment['layout'] }\r\n element = createElement(Layout, props, element)\r\n }\r\n }\r\n\r\n return element\r\n}\r\n","import type { RouteTreeNode, SegmentLayer } from '@/pen/compiler'\r\n\r\n/**\r\n * Converts a root-to-leaf node sequence into a leaf-to-root chain of SegmentLayer.\r\n * Screens are stripped from all non-leaf nodes (only the leaf's screen renders).\r\n * Nodes with no remaining roles after stripping are omitted from the chain.\r\n */\r\nexport function buildSegmentLayerChain(routePath: RouteTreeNode[]): SegmentLayer[] {\r\n const chain: SegmentLayer[] = []\r\n const leaf = routePath[routePath.length-1]!\r\n if (leaf.roles && Object.keys(leaf.roles).length)\r\n chain.push({ ...leaf.roles })\r\n\r\n for (let i = routePath.length-2; i >= 0; i--) {\r\n const { screen: _, ...roles } = routePath[i]!.roles ?? {}\r\n if (Object.keys(roles).length)\r\n chain.push(roles)\r\n }\r\n return chain\r\n}\r\n","import type { RouteTreeNode } from '@/pen/compiler'\r\nimport { traverse } from '@/lib/tree'\r\n\r\n/**\r\n * Matches URL segments against the route tree using DFS.\r\n * Groups are transparent — entered without consuming a URL segment.\r\n *\r\n * On full match: returns the root-to-leaf path with partial=false.\r\n * On no match: returns the deepest partial path with partial=true,\r\n * appending the first group child at the dead-end so not-found boundaries stay reachable.\r\n *\r\n * @param routeTree - Root node of the route tree\r\n * @param segments - URL segments to match (e.g. ['users', '42'] from '/users/42/')\r\n * @returns routePath - Root-to-leaf node sequence,\r\n * partial - whether the match is incomplete\r\n */\r\nexport function matchRoutePath(routeTree: RouteTreeNode, segments: string[]) {\r\n let routePath: RouteTreeNode[] = [routeTree]\r\n let bestDepth = -1 // -1 assumes exact match is found\r\n\r\n traverse({ idx: 0, path: routePath }, {\r\n visit: ({ idx, path }) =>\r\n idx === segments.length && (routePath=path, bestDepth=-1, true),\r\n\r\n expand: ({ idx, path }) => {\r\n const routeNode = path[path.length-1]!\r\n const segment = segments[idx]!\r\n const childFrames = (routeNode.children ?? []).flatMap(child => (\r\n child.group ? [{ idx, path: path.concat(child) }] :\r\n child.param ? [{ idx: idx+1, path: path.concat(child) }] :\r\n child.name === segment ? [{ idx: idx+1, path: path.concat(child) }] : []\r\n ))\r\n\r\n if (!childFrames.length && idx > bestDepth) { // no children matched and deepest dead end so far\r\n routePath = path\r\n bestDepth = idx\r\n }\r\n return childFrames\r\n },\r\n })\r\n\r\n return { routePath, hasMatch: bestDepth === -1 }\r\n}\r\n","import type { ReactElement } from 'react'\r\nimport type { RouteTreeNode } from '@/pen/compiler'\r\nimport type { DynamicParams } from '../providers/DynamicParamsProvider'\r\nimport type { RoutingTable } from './composer'\r\nimport { composeSegmentLayerChain, composeNotFoundChain } from './composer'\r\nimport { buildSegmentLayerChain } from './chainer'\r\nimport { matchRoutePath } from './matcher'\r\nimport { NotFoundError } from '../errors'\r\n\r\nexport type RouteResolver = (url: string) => RouteMatch\r\nexport type RouteMatch = {\r\n element: ReactElement\r\n params?: DynamicParams\r\n}\r\n\r\nexport function createRouteResolver({ routeTree, componentMap }: RoutingTable): RouteResolver {\r\n const routeMatchCache: Record<string, RouteMatch> = {}\r\n return (url) => {\r\n // 1. Return cached element\r\n if (routeMatchCache[url])\r\n return routeMatchCache[url]\r\n\r\n const segments = toSegments(url)\r\n const { routePath, hasMatch } = matchRoutePath(routeTree, segments)\r\n const chain = buildSegmentLayerChain(routePath)\r\n const params = extractParams(routePath, segments)\r\n\r\n // 2. Create element if not cached\r\n if (hasMatch) {\r\n const element = composeSegmentLayerChain(chain, componentMap, url)\r\n const hasParams = Object.keys(params).length\r\n return (routeMatchCache[url] = hasParams ? { element, params } : { element })\r\n }\r\n\r\n // 3. No full match - find nearest ancestor with a not-found boundary and render it.\r\n const notFoundIdx = chain.findIndex(layer => layer['not-found'])\r\n if (notFoundIdx !== -1) {\r\n const element = composeNotFoundChain(chain.slice(notFoundIdx), componentMap, url)\r\n const hasParams = Object.keys(params).length\r\n return (routeMatchCache[url] = hasParams ? { element, params } : { element })\r\n }\r\n\r\n // 4. Let root not-found boundary catch\r\n throw new NotFoundError(url)\r\n }\r\n}\r\n\r\n/** Splits a URL into its path segments, expecting leading and trailing slashes (e.g. `/users/42/`). */\r\nfunction toSegments(url: string): string[] {\r\n const inner = url.slice(1, -1) // strip leading and trailing slashes\r\n return inner ? inner.split('/') : []\r\n}\r\n\r\n/** Derives dynamic params by walking the matched path and segments together. */\r\nfunction extractParams(routePath: RouteTreeNode[], segments: string[]): DynamicParams {\r\n const params: DynamicParams = {}\r\n let idx = 0\r\n for (let i=1; i < routePath.length; i++) { // skip root\r\n const routeNode = routePath[i]!\r\n if (routeNode.group) continue\r\n if (routeNode.param) params[routeNode.param] = segments[idx]!\r\n idx++\r\n }\r\n return params\r\n}\r\n","import type { ReactElement } from 'react'\r\nimport type { RoutingTable } from '../routing/composer'\r\nimport { useMemo } from 'react'\r\nimport { usePathname } from '@/pen/api'\r\nimport { DynamicParamsProvider } from '../providers/DynamicParamsProvider'\r\nimport { createRouteResolver } from '../routing/resolver'\r\n\r\nexport type FileRouterProps = {\r\n routingTable: RoutingTable\r\n}\r\n\r\n/**\r\n * Router component that matches the current URL and renders the corresponding route.\r\n * Lazily composes route elements on first visit and caches them for subsequent renders.\r\n */\r\nexport function FileRouter({ routingTable }: FileRouterProps): ReactElement {\r\n // routingTable is static — created once from generated files, never changes\r\n const routeResolver = useMemo(() => createRouteResolver(routingTable), [routingTable])\r\n const url = usePathname()\r\n const { element, params } = routeResolver(url)\r\n\r\n return (\r\n <DynamicParamsProvider params={params ?? {}}>\r\n {element}\r\n </DynamicParamsProvider>\r\n )\r\n}\r\n","import type { RoutingTable } from './routing/composer'\r\nimport { NavigationProvider } from '@/pen/api'\r\nimport { ErrorBoundary } from './components/ErrorBoundary'\r\nimport { NotFoundBoundary } from './components/NotFoundBoundary'\r\nimport { ErrorScreen } from './components/ErrorScreen'\r\nimport { NotFoundScreen } from './components/NotFoundScreen'\r\nimport { FileRouter } from './components/FileRouter'\r\n\r\nexport type AppProps = {\r\n initialUrl: string\r\n routingTable: RoutingTable\r\n}\r\n\r\n/**\r\n * Root application component.\r\n * Routes are compiled at build time via codegen - no runtime composition needed!\r\n */\r\nexport function App({ initialUrl, routingTable }: AppProps) {\r\n return (\r\n <ErrorBoundary fallback={ErrorScreen}>\r\n <NavigationProvider initialUrl={initialUrl}>\r\n <NotFoundBoundary fallback={NotFoundScreen}>\r\n <FileRouter routingTable={routingTable} />\r\n </NotFoundBoundary>\r\n </NavigationProvider>\r\n </ErrorBoundary>\r\n )\r\n}\r\n","import type { DynamicParams } from '../providers/DynamicParamsProvider'\r\nimport { useContext } from 'react'\r\nimport { DynamicParamsContext } from '../providers/DynamicParamsProvider'\r\n\r\n/**\r\n * Returns the dynamic route params for the current URL.\r\n *\r\n * @example\r\n * // With route file: app/users/[id]/screen.tsx\r\n * // and URL: /users/42/\r\n * const { id } = useParams() // { id: \"42\" }\r\n */\r\nexport function useParams(): DynamicParams {\r\n const context = useContext(DynamicParamsContext)\r\n return context\r\n}\r\n","import { useEffect, useState } from 'react'\r\n\r\nexport interface MemoryUsage {\r\n /** Heap memory currently used (in MB) */\r\n heapUsed: number\r\n /** Total heap memory allocated (in MB) */\r\n heapTotal: number\r\n /** Resident Set Size - total memory allocated for the process (in MB) */\r\n rss: number\r\n /** Memory used by C++ objects bound to JavaScript (in MB) */\r\n external: number\r\n}\r\n\r\n/**\r\n * Hook to monitor Node.js memory usage in real-time\r\n *\r\n * @param intervalMs - Polling interval in milliseconds (default: 1000ms)\r\n * @returns Current memory usage statistics in megabytes\r\n *\r\n * @example\r\n * ```tsx\r\n * function MemoryDisplay() {\r\n * const { heapUsed, heapTotal, rss } = useMemoryMonitor()\r\n * return <Text>RAM: {heapUsed}/{heapTotal} MB (RSS: {rss} MB)</Text>\r\n * }\r\n * ```\r\n */\r\nexport function useMemoryMonitor(intervalMs = 1000): MemoryUsage {\r\n const [memory, setMemory] = useState<MemoryUsage>({\r\n heapUsed: 0,\r\n heapTotal: 0,\r\n rss: 0,\r\n external: 0,\r\n })\r\n\r\n useEffect(() => {\r\n const updateMemory = () => {\r\n const usage = process.memoryUsage()\r\n setMemory({\r\n heapUsed: Math.round(usage.heapUsed / 1024 / 1024),\r\n heapTotal: Math.round(usage.heapTotal / 1024 / 1024),\r\n rss: Math.round(usage.rss / 1024 / 1024),\r\n external: Math.round(usage.external / 1024 / 1024),\r\n })\r\n }\r\n\r\n // Get initial reading immediately\r\n updateMemory()\r\n\r\n const interval = setInterval(updateMemory, intervalMs)\r\n\r\n return () => clearInterval(interval)\r\n }, [intervalMs])\r\n\r\n return memory\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import{join as e,parse as t,posix as n,relative as r,resolve as i}from"path";import{pathToFileURL as a}from"url";import{readdirSync as o,statSync as s}from"fs";function c(e){return e}const l={appDir:`./src/app`,outDir:`./.pen`,emitMetadata:!1};async function u(){try{let e=(await import(a(i(process.cwd(),`pen.config.ts`)).href)).default||{};return{...l,...e}}catch{return l}}function d(e,t){let{visit:n,expand:r,attach:i}=t,a=[e];for(;a.length;){let e=a.pop();if(n?.(e)===!0)return;let t=r?.(e)??[];for(let n=t.length-1;n>=0;n--){let r=t[n];i?.(r,e),a.push(r)}}}var f=class extends Error{constructor(e){super(e),this.name=`FileRouterError`}},p=class extends f{constructor(e){super(`Directory not found: "${e}"`),this.path=e,this.name=`DirectoryNotFoundError`}},m=class extends f{constructor(e){super(`Path is not a directory: "${e}"`),this.path=e,this.name=`NotADirectoryError`}},h=class extends f{constructor(e){super(`Root path is a file, not a directory: "${e}"\n\nThe app directory must be a directory, not a file.`),this.path=e,this.name=`RootIsFileError`}},g=class extends f{constructor(e,t){super(`Conflicting screen routes found at "${e}":\n`+t.map(e=>` - ${e}`).join(`
2
+ `)+`
3
+
4
+ Each URL can only have one screen file.
5
+ Move one screen to a different directory or rename the route segment.`),this.url=e,this.files=t,this.name=`DuplicateScreenError`}};function _(e){let t=i(e);v(t);let n={name:`app`,relPath:`/app`,absPath:t,children:[]};return d(n,{expand:e=>e.children?o(e.absPath,{withFileTypes:!0}).filter(e=>e.isFile()||e.isDirectory()).map(t=>y(t,e)).sort((e,t)=>e.name.localeCompare(t.name)):[],attach:(e,t)=>t.children.push(e)}),n}function v(e){let t=s(e,{throwIfNoEntry:!1});if(!t)throw new p(e);if(!t.isDirectory())throw new m(e)}function y(t,r){let i=t.name,a=n.join(r.relPath,i),o=e(r.absPath,i);return t.isDirectory()?{name:i,relPath:a,absPath:o,children:[]}:{name:i,relPath:a,absPath:o}}const b=[`layout`,`screen`,`error`,`not-found`];function x(e){if(e.children===void 0)throw new h(e.absPath);let t={name:``,route:`/`,type:`page`},n={};return d({fileNode:e,segmentNode:t},{visit:({fileNode:e,segmentNode:t})=>{S(t,e),C(t,e,n)},expand:({fileNode:e,segmentNode:t})=>(e.children??[]).filter(e=>e.children&&!e.name.startsWith(`_`)).map(e=>({fileNode:e,segmentNode:w(e,t.route)})).sort((e,t)=>e.segmentNode.name.localeCompare(t.segmentNode.name)),attach:(e,t)=>t.segmentNode.children.push(e.segmentNode)}),t}function S(e,n){for(let r of n.children??[]){let{name:n,ext:i}=t(r.name);i===`.tsx`&&b.includes(n)&&((e.roles??={})[n]=r.absPath)}e.children=[]}function C(e,t,n){if(e.roles?.screen){if(n[e.route])throw new g(e.route,[n[e.route],t.absPath]);n[e.route]=t.absPath}}function w(e,t){let r=e.name.startsWith(`(`)&&e.name.endsWith(`)`),i=e.name.startsWith(`[`)&&e.name.endsWith(`]`),a=i?e.name.slice(1,-1):void 0,o=r?t:i?`${t}:${a}/`:`${n.join(t,e.name)}/`;return{name:e.name,route:o,type:r?`group`:i?`dynamic`:`page`,param:a}}function T(n){let r=t(n);return e(r.dir,r.name)}function E(t,n){let r=e(n,`generated`),i=D(t,r);return d({segmentNode:t,routeNode:i},{expand:({segmentNode:e})=>(e.children??[]).map(e=>({segmentNode:e,routeNode:D(e,r)})),attach:(e,t)=>(t.routeNode.children??=[]).push(e.routeNode)}),i}function D(e,t){let{name:n,param:r,roles:i}=e,a=i&&O(i,t),o={name:n};return r!==void 0&&(o.param=r),a&&Object.keys(a).length&&(o.roles=a),n.startsWith(`(`)&&n.endsWith(`)`)&&(o.group=!0),o}function O(e,t){let n={};for(let[i,a]of Object.entries(e))n[i]=`${r(t,T(a)).replace(/\\/g,`/`)}.js`;return n}export{p as a,m as c,c as d,u as f,_ as i,h as l,b as n,g as o,x as r,f as s,E as t,d as u};
6
+ //# sourceMappingURL=route-tree-BbrKaika.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-tree-BbrKaika.mjs","names":[],"sources":["../src/pen/config.ts","../src/lib/tree.ts","../src/pen/compiler/errors.ts","../src/pen/compiler/builders/file-tree.ts","../src/pen/compiler/builders/segment-tree.ts","../src/lib/path-utils.ts","../src/pen/compiler/builders/route-tree.ts"],"sourcesContent":["import { resolve } from 'path'\r\nimport { pathToFileURL } from 'url'\r\n\r\n/**\r\n * Resolved configuration with all fields guaranteed to be present.\r\n * This is what loadConfig() returns after merging user config with defaults.\r\n */\r\nexport type ResolvedPenConfig = {\r\n /**\r\n * Directory containing your app routes.\r\n * @default './src/app'\r\n */\r\n appDir: string\r\n\r\n /**\r\n * Output directory for generated files and build artifacts.\r\n * @default './.pen'\r\n */\r\n outDir: string\r\n\r\n /**\r\n * Emit metadata files (manifest, components, element-tree) to aid debugging and tooling.\r\n * These files provide introspection into your app's structure.\r\n * @default true\r\n */\r\n emitMetadata: boolean\r\n}\r\n\r\n/**\r\n * User-facing configuration (what users write in pen.config.ts).\r\n * All fields are optional and will be merged with defaults.\r\n */\r\nexport type PenConfig = Partial<ResolvedPenConfig>\r\n\r\nexport function defineConfig(config: PenConfig): PenConfig {\r\n return config\r\n}\r\n\r\nconst defaultConfig: ResolvedPenConfig = {\r\n appDir: './src/app',\r\n outDir: './.pen',\r\n emitMetadata: false,\r\n}\r\n\r\n/**\r\n * Loads pen.config.ts from the current directory.\r\n * Falls back to defaults if config file doesn't exist.\r\n * Returns a fully resolved config with all fields guaranteed to be present.\r\n */\r\nexport async function loadConfig(): Promise<ResolvedPenConfig> {\r\n try {\r\n const configPath = resolve(process.cwd(), 'pen.config.ts')\r\n const configUrl = pathToFileURL(configPath).href\r\n\r\n const module = await import(configUrl) as { default?: PenConfig }\r\n const userConfig = module.default || {}\r\n\r\n // Merge with defaults\r\n return { ...defaultConfig, ...userConfig }\r\n }\r\n catch {\r\n // Config file doesn't exist or failed to load - use defaults\r\n return defaultConfig\r\n }\r\n}\r\n","/** Callbacks for tree traversal. */\r\nexport type TraverseCallbacks<TNode> = {\r\n /** Called when visiting each node (pre-order). Return true to stop traversal. */\r\n visit?: (node: TNode) => unknown\r\n /** Returns children for a node (or creates them) */\r\n expand?: (node: TNode) => TNode[]\r\n /** Attaches a child to its parent */\r\n attach?: (child: TNode, parent: TNode) => void\r\n}\r\n\r\n/**\r\n * Depth-first tree traversal with pluggable callbacks.\r\n *\r\n * @template TNode - The type of tree nodes\r\n * @param root - The root node to start traversal from\r\n * @param callbacks - Callbacks for visit, expand, and attach operations\r\n *\r\n * @example\r\n * // Visit existing tree\r\n * traverse(tree, {\r\n * visit: (node) => console.log(node.name),\r\n * expand: (node) => node.children ?? []\r\n * })\r\n *\r\n * @example\r\n * // Build new tree\r\n * traverse(root, {\r\n * expand: (node) => createChildren(node),\r\n * attach: (child, parent) => parent.children.push(child)\r\n * })\r\n *\r\n * @example\r\n * // Early termination\r\n * traverse(tree, {\r\n * visit: (node) => (node.name === 'target') // stops traversal\r\n * expand: (node) => node.children ?? []\r\n * })\r\n */\r\nexport function traverse<TNode>(root: TNode, callbacks: TraverseCallbacks<TNode>) {\r\n const { visit, expand, attach } = callbacks\r\n const stack = [root]\r\n\r\n while (stack.length) {\r\n const node = stack.pop()!\r\n if (visit?.(node) === true) return\r\n\r\n // Process children in reverse so\r\n // they're popped in correct order\r\n const children = expand?.(node) ?? []\r\n\r\n for (let i = children.length-1; i >= 0; i--) {\r\n const child = children[i]!\r\n attach?.(child, node)\r\n stack.push(child)\r\n }\r\n }\r\n}\r\n\r\n/** Callbacks for ancestor traversal. */\r\nexport type AncestorCallbacks<TNode> = {\r\n /** Called when visiting each ancestor node (from leaf to root) */\r\n visit: (node: TNode) => void\r\n /** Returns the parent node for a given node */\r\n parent: (node: TNode) => TNode | undefined\r\n}\r\n\r\n/**\r\n * Traverses the ancestor chain of a given node from leaf to root.\r\n *\r\n * @template TNode - The type of tree nodes\r\n * @param node - The node to start the ancestor traversal from\r\n * @param callbacks - Callbacks for visit and parent operations\r\n *\r\n * @example\r\n * // Visit ancestors in a tree structure\r\n * ancestors(node, {\r\n * visit: (node) => console.log(`Visiting: ${node.name}`),\r\n * parent: (node) => node.parent // Return the parent of the node\r\n * })\r\n *\r\n * @example\r\n * // Collect all ancestors' roles in a tree structure\r\n * const roles = []\r\n * ancestors(node, {\r\n * visit: (node) => roles.push(node.role),\r\n * parent: (node) => node.parent\r\n * })\r\n * console.log(roles) // Logs all ancestor roles from leaf to root\r\n */\r\nexport function ancestors<TNode>(node: TNode, callbacks: AncestorCallbacks<TNode>) {\r\n const { visit, parent } = callbacks\r\n let currentNode: TNode | undefined = node\r\n\r\n while (currentNode) {\r\n visit(currentNode)\r\n currentNode = parent(currentNode)\r\n }\r\n}\r\n","/**\r\n * Base error for all route-builder build errors\r\n */\r\nexport class FileRouterError extends Error {\r\n constructor(message: string) {\r\n super(message)\r\n this.name = 'FileRouterError'\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// File Tree Errors\r\n// ============================================================================\r\n\r\nexport class DirectoryNotFoundError extends FileRouterError {\r\n constructor(public path: string) {\r\n super(`Directory not found: \"${path}\"`)\r\n this.name = 'DirectoryNotFoundError'\r\n }\r\n}\r\n\r\nexport class NotADirectoryError extends FileRouterError {\r\n constructor(public path: string) {\r\n super(`Path is not a directory: \"${path}\"`)\r\n this.name = 'NotADirectoryError'\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Route Tree Errors\r\n// ============================================================================\r\n\r\nexport class RootIsFileError extends FileRouterError {\r\n constructor(public path: string) {\r\n super(\r\n `Root path is a file, not a directory: \"${path}\"\\n\\n` +\r\n 'The app directory must be a directory, not a file.',\r\n )\r\n this.name = 'RootIsFileError'\r\n }\r\n}\r\n\r\nexport class DuplicateScreenError extends FileRouterError {\r\n constructor(\r\n public url: string,\r\n public files: string[],\r\n ) {\r\n super(\r\n `Conflicting screen routes found at \"${url}\":\\n` +\r\n files.map(f => ` - ${f}`).join('\\n') + '\\n\\n' +\r\n 'Each URL can only have one screen file.\\n' +\r\n 'Move one screen to a different directory or rename the route segment.',\r\n )\r\n this.name = 'DuplicateScreenError'\r\n }\r\n}\r\n","import { readdirSync, statSync, type Dirent } from 'fs'\r\nimport { resolve, join, posix } from 'path'\r\nimport { traverse } from '@/lib/tree'\r\nimport { DirectoryNotFoundError, NotADirectoryError } from '../errors'\r\n\r\nexport type FileNode = {\r\n name: string // dirent name\r\n relPath: string\r\n absPath: string\r\n children?: FileNode[] // present only for directories\r\n}\r\n\r\n/**\r\n * Builds a file tree from a directory path.\r\n *\r\n * @param appPath - Path to the app directory\r\n * @returns File tree structure\r\n * @throws {DirectoryNotFoundError} If the directory doesn't exist\r\n * @throws {NotADirectoryError} If the path is not a directory\r\n */\r\nexport function createFileTree(appPath: string): FileNode {\r\n const absPath = resolve(appPath)\r\n validateDirectory(absPath)\r\n\r\n // Root node\r\n const name = 'app'\r\n const relPath = '/app'\r\n const root: FileNode = { name, relPath, absPath, children: [] }\r\n\r\n traverse(root, {\r\n expand: (file) => {\r\n return !file.children ? [] :\r\n readdirSync(file.absPath, { withFileTypes: true })\r\n .filter(dirent => dirent.isFile() || dirent.isDirectory())\r\n .map(dirent => createFileNode(dirent, file))\r\n .sort((a, b) => a.name.localeCompare(b.name))\r\n },\r\n attach: (child, parent) =>\r\n (parent.children!.push(child)),\r\n })\r\n return root\r\n}\r\n\r\n/** Validates that the path exists and is a directory. */\r\nfunction validateDirectory(path: string) {\r\n const stat = statSync(path, { throwIfNoEntry: false })\r\n if (!stat) throw new DirectoryNotFoundError(path)\r\n if (!stat.isDirectory()) throw new NotADirectoryError(path)\r\n}\r\n\r\nfunction createFileNode(dirent: Dirent, parent: FileNode) {\r\n const name = dirent.name\r\n const relPath = posix.join(parent.relPath, name)\r\n const absPath = join(parent.absPath, name)\r\n\r\n return dirent.isDirectory()\r\n ? { name, relPath, absPath, children: [] }\r\n : { name, relPath, absPath }\r\n}\r\n","import type { FileNode } from './file-tree'\r\nimport { parse, posix } from 'path'\r\nimport { traverse } from '@/lib/tree'\r\nimport { RootIsFileError, DuplicateScreenError } from '../errors'\r\n\r\nexport const SEGMENT_ROLES = ['layout', 'screen', 'error', 'not-found'] as const\r\nexport type SegmentRole = typeof SEGMENT_ROLES[number]\r\nexport type SegmentLayer = Partial<Record<SegmentRole, string>>\r\nexport type SegmentNode = {\r\n route: `${string}/`\r\n name: string\r\n param?: string // e.g. \"id\" from [id]\r\n type: 'page' | 'group' | 'dynamic'\r\n roles?: SegmentLayer\r\n children?: SegmentNode[]\r\n}\r\n\r\n/**\r\n * Creates a segment tree from a file system tree.\r\n *\r\n * @param fileTree - File system tree\r\n * @returns Segment tree with assigned roles and validated structure\r\n * @throws {RootIsFileError} If the root is a file instead of a directory\r\n * @throws {DuplicateScreenError} If multiple screens map to the same URL\r\n */\r\nexport function createSegmentTree(fileTree: FileNode): SegmentNode {\r\n if (fileTree.children === undefined)\r\n throw new RootIsFileError(fileTree.absPath)\r\n\r\n const segmentTree: SegmentNode = { name: '', route: '/', type: 'page' } // special case root\r\n const screens: Record<SegmentNode['route'], string> = {}\r\n const nodePair = { fileNode: fileTree, segmentNode: segmentTree }\r\n\r\n traverse(nodePair, {\r\n visit: ({ fileNode, segmentNode }) => {\r\n bindFileToSegmentRoles(segmentNode, fileNode)\r\n validateUniqueScreen(segmentNode, fileNode, screens)\r\n },\r\n expand: ({ fileNode, segmentNode }) =>\r\n (fileNode.children ?? [])\r\n .filter(file => file.children && !file.name.startsWith('_'))\r\n .map(file => ({ fileNode: file, segmentNode: createSegmentNode(file, segmentNode.route) }))\r\n .sort((a, b) => a.segmentNode.name.localeCompare(b.segmentNode.name)),\r\n\r\n attach: (child, parent) =>\r\n (parent.segmentNode.children!.push(child.segmentNode)),\r\n })\r\n\r\n return segmentTree\r\n}\r\n\r\nfunction bindFileToSegmentRoles(segment: SegmentNode, fileNode: FileNode) {\r\n for (const child of fileNode.children ?? []) {\r\n const { name, ext } = parse(child.name) as { name: SegmentRole, ext: string }\r\n if (ext === '.tsx' && SEGMENT_ROLES.includes(name))\r\n (segment.roles ??= {})[name] = child.absPath\r\n }\r\n segment.children = [] // ensures children field appear last in the object\r\n}\r\n\r\nfunction validateUniqueScreen(segment: SegmentNode, fileNode: FileNode, screens: Record<SegmentNode['route'], string>) {\r\n if (!segment.roles?.screen) return\r\n if (screens[segment.route])\r\n throw new DuplicateScreenError(segment.route, [screens[segment.route]!, fileNode.absPath])\r\n screens[segment.route] = fileNode.absPath\r\n}\r\n\r\nfunction createSegmentNode(file: FileNode, parentRoute: SegmentNode['route']): SegmentNode {\r\n const isGroup = file.name.startsWith('(') && file.name.endsWith(')')\r\n const isDynamic = file.name.startsWith('[') && file.name.endsWith(']')\r\n const param = isDynamic ? file.name.slice(1, -1) : undefined\r\n const route: SegmentNode['route']\r\n = isGroup ? parentRoute\r\n : isDynamic ? `${parentRoute}:${param}/`\r\n : `${posix.join(parentRoute, file.name)}/`\r\n\r\n const name = file.name\r\n const type = isGroup ? 'group' : isDynamic ? 'dynamic' : 'page'\r\n return { name, route, type, param }\r\n}\r\n","import { parse, join } from 'path'\r\n\r\nexport function removeExtension(filePath: string) {\r\n const parsed = parse(filePath)\r\n return join(parsed.dir, parsed.name) // Use posix for forward slashes\r\n}\r\n","import type { SegmentNode, SegmentRole, SegmentLayer } from './segment-tree'\r\nimport { join, relative } from 'path'\r\nimport { removeExtension } from '@/lib/path-utils'\r\nimport { traverse } from '@/lib/tree'\r\n\r\nexport type RouteTreeNode = {\r\n name: string // raw directory name: \"users\", \"[id]\", \"(auth)\", \"\"\r\n param?: string // dynamic param name, e.g. \"id\" from \"[id]\"\r\n group?: true // true for route groups, e.g. \"(auth)\" — don't consume URL segments\r\n roles?: SegmentLayer // relativized import paths for layout/screen/error/not-found\r\n children?: RouteTreeNode[]\r\n}\r\n\r\n/**\r\n * Builds a route tree from a segment tree.\r\n *\r\n * Strips all build-time metadata (FileNode refs, parent pointers, route strings)\r\n * and rewrites absolute paths to relative `.js` import paths from the generated dir.\r\n * The resulting tree is JSON-serializable and safe for runtime use.\r\n *\r\n * @param segmentTree - Segment tree with parent pointers\r\n * @param outDir - Output directory (to calculate relative import paths)\r\n */\r\nexport function createRouteTree(segmentTree: SegmentNode, outDir: string): RouteTreeNode {\r\n const genDir = join(outDir, 'generated')\r\n const routeTree = createRouteNode(segmentTree, genDir)\r\n const nodePair = { segmentNode: segmentTree, routeNode: routeTree }\r\n\r\n traverse(nodePair, {\r\n expand: ({ segmentNode }) =>\r\n (segmentNode.children ?? []).map(child => ({\r\n segmentNode: child,\r\n routeNode: createRouteNode(child, genDir),\r\n })),\r\n attach: (child, parent) =>\r\n (parent.routeNode.children ??= []).push(child.routeNode),\r\n })\r\n\r\n return routeTree\r\n}\r\n\r\nfunction createRouteNode(segmentNode: SegmentNode, genDir: string): RouteTreeNode {\r\n const { name, param, roles: segmentRoles } = segmentNode\r\n const roles = segmentRoles && resolveRoleImports(segmentRoles, genDir)\r\n const routeNode: RouteTreeNode = { name }\r\n\r\n if (param !== undefined) routeNode.param = param\r\n if (roles && Object.keys(roles).length) routeNode.roles = roles\r\n if (name.startsWith('(') && name.endsWith(')')) routeNode.group = true\r\n return routeNode\r\n}\r\n\r\nfunction resolveRoleImports (roles: SegmentLayer, genDir: string): SegmentLayer {\r\n const segmentRoles: SegmentLayer = {}\r\n for (const [name, path] of Object.entries(roles) as [SegmentRole, string][]) {\r\n const importPath = removeExtension(path)\r\n const relPath = relative(genDir, importPath).replace(/\\\\/g, '/')\r\n segmentRoles[name] = `${relPath}.js`\r\n }\r\n return segmentRoles\r\n}\r\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idlesummer/pen",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "File-based routing for React Ink apps (experimental)",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,6 +0,0 @@
1
- import{join as e,parse as t,posix as n,relative as r,resolve as i}from"path";import{pathToFileURL as a}from"url";import{readdirSync as o,statSync as s}from"fs";function c(e){return e}const l={appDir:`./src/app`,outDir:`./.pen`,emitMetadata:!1};async function u(){try{let e=(await import(a(i(process.cwd(),`pen.config.ts`)).href)).default||{};return{...l,...e}}catch{return l}}function d(e,t){let{visit:n,expand:r,attach:i}=t,a=[e];for(;a.length;){let e=a.pop();n?.(e);let t=r?.(e)??[];for(let n=t.length-1;n>=0;n--){let r=t[n];i?.(r,e),a.push(r)}}}function f(e,t){let{visit:n,parent:r}=t,i=e;for(;i;)n(i),i=r(i)}var p=class extends Error{constructor(e){super(e),this.name=`FileRouterError`}},m=class extends p{constructor(e){super(`Directory not found: "${e}"`),this.path=e,this.name=`DirectoryNotFoundError`}},h=class extends p{constructor(e){super(`Path is not a directory: "${e}"`),this.path=e,this.name=`NotADirectoryError`}},g=class extends p{constructor(e){super(`Root path is a file, not a directory: "${e}"\n\nThe app directory must be a directory, not a file.`),this.path=e,this.name=`RootIsFileError`}},_=class extends p{constructor(e,t){super(`Conflicting screen routes found at "${e}":\n`+t.map(e=>` - ${e}`).join(`
2
- `)+`
3
-
4
- Each URL can only have one screen file.
5
- Move one screen to a different directory or rename the route segment.`),this.url=e,this.files=t,this.name=`DuplicateScreenError`}};function v(e){let t=i(e);y(t);let n={name:`app`,relPath:`/app`,absPath:t,children:[]};return d(n,{attach:(e,t)=>t.children.push(e),expand:e=>e.children?o(e.absPath,{withFileTypes:!0}).filter(e=>e.isFile()||e.isDirectory()).map(t=>b(t,e)).sort((e,t)=>e.name.localeCompare(t.name)):[]}),n}function y(e){let t=s(e,{throwIfNoEntry:!1});if(!t)throw new m(e);if(!t.isDirectory())throw new h(e)}function b(t,r){let i=n.join(r.relPath,t.name),a=e(r.absPath,t.name);return t.isDirectory()?{name:t.name,relPath:i,absPath:a,children:[]}:{name:t.name,relPath:i,absPath:a}}const x=[`layout`,`screen`,`error`,`not-found`];function S(e){let t=C(e);return w(t),t}function C(e){if(e.children===void 0)throw new g(e.absPath);let t={segment:``,url:`/`,type:`page`,roles:{},children:[],file:e};return d(t,{attach:(e,t)=>t.children.push(e),expand:e=>(e.file.children??[]).filter(e=>e.children&&!e.name.startsWith(`_`)).map(t=>T(t,e)).sort((e,t)=>e.segment.localeCompare(t.segment))}),t}function w(e){let t={};d(e,{expand:e=>e.children??[],visit:e=>{E(e),D(e,t)}})}function T(e,t){let r=e.name.startsWith(`(`)&&e.name.endsWith(`)`);return{segment:e.name,url:r?t.url:`${n.join(t.url,e.name)}/`,type:r?`group`:`page`,roles:{},parent:t,children:[],file:e}}function E(e){for(let n of e.file.children??[]){let{name:r,ext:i}=t(n.name);i===`.tsx`&&x.includes(r)&&(e.roles[r]=n.absPath)}}function D(e,t){if(!e.roles.screen)return;let n=t[e.url];if(n)throw new _(e.url,[n,e.file.absPath]);t[e.url]=e.file.absPath}function O(n){let r=t(n);return e(r.dir,r.name)}function k(e,t){let n={},r=`${t}/generated`;return d(e,{expand:e=>e.children??[],visit:e=>{if(!e.roles.screen)return;let t=e.url;n[t]={url:t,chain:A(e,r)}}}),n}function A(e,t){let n=[];return f(e,{parent:e=>e.parent,visit:i=>{let a={},o=Object.entries(i.roles);for(let[n,s]of o)(n!==`screen`||i===e)&&(a[n]=`${r(t,O(s)).replace(/\\/g,`/`)}.js`);n.push(a)}}),n}function j(e){let t=new Set;for(let n of Object.values(e))for(let e of n.chain)for(let n of x)e[n]&&t.add(e[n]);let n=Array.from(t).sort(),r={};for(let e=0;e<n.length;e++)r[n[e]]=e;return r}function M(e,t){let n={};for(let[r,i]of Object.entries(e))n[r]=N(i,t);return n}function N(e,t){let n=Object.keys(t),r=t[e.chain[0].screen],i=JSON.stringify(n[r]),a={component:`Component${r}`,props:{key:i}};for(let r of e.chain){if(r[`not-found`]){let e=t[r[`not-found`]];a={component:`NotFoundBoundary`,props:{key:JSON.stringify(n[e]),fallback:`Component${e}`},children:a}}if(r.error){let e=t[r.error];a={component:`ErrorBoundary`,props:{key:JSON.stringify(n[e]),fallback:`Component${e}`},children:a}}if(r.layout){let e=t[r.layout],i=JSON.stringify(n[e]);a={component:`Component${e}`,props:{key:i},children:a}}}return a}export{v as a,p as c,c as d,u as f,S as i,h as l,j as n,m as o,k as r,_ as s,M as t,g as u};
6
- //# sourceMappingURL=serialized-routes-DFDKf4sB.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serialized-routes-DFDKf4sB.mjs","names":[],"sources":["../src/pen/config.ts","../src/lib/tree.ts","../src/pen/compiler/errors.ts","../src/pen/compiler/builders/file-tree.ts","../src/pen/compiler/builders/segment-tree.ts","../src/lib/path-utils.ts","../src/pen/compiler/builders/route-chain-map.ts","../src/pen/compiler/builders/component-id-map.ts","../src/pen/compiler/builders/serialized-routes.ts"],"sourcesContent":["import { resolve } from 'path'\r\nimport { pathToFileURL } from 'url'\r\n\r\n/**\r\n * Resolved configuration with all fields guaranteed to be present.\r\n * This is what loadConfig() returns after merging user config with defaults.\r\n */\r\nexport type ResolvedPenConfig = {\r\n /**\r\n * Directory containing your app routes.\r\n * @default './src/app'\r\n */\r\n appDir: string\r\n\r\n /**\r\n * Output directory for generated files and build artifacts.\r\n * @default './.pen'\r\n */\r\n outDir: string\r\n\r\n /**\r\n * Emit metadata files (manifest, components, element-tree) to aid debugging and tooling.\r\n * These files provide introspection into your app's structure.\r\n * @default true\r\n */\r\n emitMetadata: boolean\r\n}\r\n\r\n/**\r\n * User-facing configuration (what users write in pen.config.ts).\r\n * All fields are optional and will be merged with defaults.\r\n */\r\nexport type PenConfig = Partial<ResolvedPenConfig>\r\n\r\nexport function defineConfig(config: PenConfig): PenConfig {\r\n return config\r\n}\r\n\r\nconst defaultConfig: ResolvedPenConfig = {\r\n appDir: './src/app',\r\n outDir: './.pen',\r\n emitMetadata: false,\r\n}\r\n\r\n/**\r\n * Loads pen.config.ts from the current directory.\r\n * Falls back to defaults if config file doesn't exist.\r\n * Returns a fully resolved config with all fields guaranteed to be present.\r\n */\r\nexport async function loadConfig(): Promise<ResolvedPenConfig> {\r\n try {\r\n const configPath = resolve(process.cwd(), 'pen.config.ts')\r\n const configUrl = pathToFileURL(configPath).href\r\n\r\n const module = await import(configUrl) as { default?: PenConfig }\r\n const userConfig = module.default || {}\r\n\r\n // Merge with defaults\r\n return { ...defaultConfig, ...userConfig }\r\n }\r\n catch {\r\n // Config file doesn't exist or failed to load - use defaults\r\n return defaultConfig\r\n }\r\n}\r\n","/** Callbacks for tree traversal. */\r\nexport type TraverseCallbacks<TNode> = {\r\n /** Called when visiting each node (pre-order) */\r\n visit?: (node: TNode) => void\r\n /** Returns children for a node (or creates them) */\r\n expand?: (node: TNode) => TNode[]\r\n /** Attaches a child to its parent */\r\n attach?: (child: TNode, parent: TNode) => void\r\n}\r\n\r\n/**\r\n * Depth-first tree traversal with pluggable callbacks.\r\n *\r\n * @template TNode - The type of tree nodes\r\n * @param root - The root node to start traversal from\r\n * @param callbacks - Callbacks for visit, expand, and attach operations\r\n *\r\n * @example\r\n * // Visit existing tree\r\n * traverse(tree, {\r\n * visit: (node) => console.log(node.name),\r\n * expand: (node) => node.children ?? []\r\n * })\r\n *\r\n * @example\r\n * // Build new tree\r\n * traverse(root, {\r\n * expand: (node) => createChildren(node),\r\n * attach: (child, parent) => parent.children.push(child)\r\n * })\r\n */\r\nexport function traverse<TNode>(root: TNode, callbacks: TraverseCallbacks<TNode>) {\r\n const { visit, expand, attach } = callbacks\r\n const stack = [root]\r\n\r\n while (stack.length) {\r\n const node = stack.pop()!\r\n visit?.(node)\r\n\r\n // Process children in reverse so\r\n // they're popped in correct order\r\n const children = expand?.(node) ?? []\r\n\r\n for (let i = children.length-1; i >= 0; i--) {\r\n const child = children[i]!\r\n attach?.(child, node)\r\n stack.push(child)\r\n }\r\n }\r\n}\r\n\r\n/** Callbacks for ancestor traversal. */\r\nexport type AncestorCallbacks<TNode> = {\r\n /** Called when visiting each ancestor node (from leaf to root) */\r\n visit: (node: TNode) => void\r\n /** Returns the parent node for a given node */\r\n parent: (node: TNode) => TNode | undefined\r\n}\r\n\r\n/**\r\n * Traverses the ancestor chain of a given node from leaf to root.\r\n *\r\n * @template TNode - The type of tree nodes\r\n * @param node - The node to start the ancestor traversal from\r\n * @param callbacks - Callbacks for visit and parent operations\r\n *\r\n * @example\r\n * // Visit ancestors in a tree structure\r\n * ancestors(node, {\r\n * visit: (node) => console.log(`Visiting: ${node.name}`),\r\n * parent: (node) => node.parent // Return the parent of the node\r\n * })\r\n *\r\n * @example\r\n * // Collect all ancestors' roles in a tree structure\r\n * const roles = []\r\n * ancestors(node, {\r\n * visit: (node) => roles.push(node.role),\r\n * parent: (node) => node.parent\r\n * })\r\n * console.log(roles) // Logs all ancestor roles from leaf to root\r\n */\r\nexport function ancestors<TNode>(node: TNode, callbacks: AncestorCallbacks<TNode>) {\r\n const { visit, parent } = callbacks\r\n let currentNode: TNode | undefined = node\r\n\r\n while (currentNode) {\r\n visit(currentNode)\r\n currentNode = parent(currentNode)\r\n }\r\n}\r\n","/**\r\n * Base error for all route-builder build errors\r\n */\r\nexport class FileRouterError extends Error {\r\n constructor(message: string) {\r\n super(message)\r\n this.name = 'FileRouterError'\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// File Tree Errors\r\n// ============================================================================\r\n\r\nexport class DirectoryNotFoundError extends FileRouterError {\r\n constructor(public path: string) {\r\n super(`Directory not found: \"${path}\"`)\r\n this.name = 'DirectoryNotFoundError'\r\n }\r\n}\r\n\r\nexport class NotADirectoryError extends FileRouterError {\r\n constructor(public path: string) {\r\n super(`Path is not a directory: \"${path}\"`)\r\n this.name = 'NotADirectoryError'\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Route Tree Errors\r\n// ============================================================================\r\n\r\nexport class RootIsFileError extends FileRouterError {\r\n constructor(public path: string) {\r\n super(\r\n `Root path is a file, not a directory: \"${path}\"\\n\\n` +\r\n 'The app directory must be a directory, not a file.',\r\n )\r\n this.name = 'RootIsFileError'\r\n }\r\n}\r\n\r\nexport class DuplicateScreenError extends FileRouterError {\r\n constructor(\r\n public url: string,\r\n public files: string[],\r\n ) {\r\n super(\r\n `Conflicting screen routes found at \"${url}\":\\n` +\r\n files.map(f => ` - ${f}`).join('\\n') + '\\n\\n' +\r\n 'Each URL can only have one screen file.\\n' +\r\n 'Move one screen to a different directory or rename the route segment.',\r\n )\r\n this.name = 'DuplicateScreenError'\r\n }\r\n}\r\n","import { readdirSync, statSync, type Dirent } from 'fs'\r\nimport { resolve, join, posix } from 'path'\r\nimport { traverse } from '@/lib/tree'\r\nimport { DirectoryNotFoundError, NotADirectoryError } from '../errors'\r\n\r\nexport type FileNode = {\r\n name: string // dirent name\r\n relPath: string\r\n absPath: string\r\n children?: FileNode[] // present only for directories\r\n}\r\n\r\n/**\r\n * Builds a file tree from a directory path.\r\n *\r\n * @param appPath - Path to the app directory\r\n * @returns File tree structure\r\n * @throws {DirectoryNotFoundError} If the directory doesn't exist\r\n * @throws {NotADirectoryError} If the path is not a directory\r\n */\r\nexport function createFileTree(appPath: string): FileNode {\r\n const absPath = resolve(appPath)\r\n validateDirectory(absPath)\r\n\r\n // Root node\r\n const name = 'app'\r\n const relPath = '/app'\r\n const root: FileNode = { name, relPath, absPath, children: [] }\r\n\r\n traverse(root, {\r\n attach: (child, parent) => parent.children!.push(child),\r\n expand: (file) => {\r\n if (!file.children) return []\r\n\r\n return readdirSync(file.absPath, { withFileTypes: true })\r\n .filter(d => d.isFile() || d.isDirectory())\r\n .map(d => createFileNode(d, file))\r\n .sort((a, b) => a.name.localeCompare(b.name))\r\n },\r\n })\r\n return root\r\n}\r\n\r\n/** Validates that the path exists and is a directory. */\r\nfunction validateDirectory(path: string) {\r\n const stat = statSync(path, { throwIfNoEntry: false })\r\n if (!stat) throw new DirectoryNotFoundError(path)\r\n if (!stat.isDirectory()) throw new NotADirectoryError(path)\r\n}\r\n\r\nfunction createFileNode(dirent: Dirent, parent: FileNode) {\r\n const relPath = posix.join(parent.relPath, dirent.name)\r\n const absPath = join(parent.absPath, dirent.name)\r\n\r\n return dirent.isDirectory()\r\n ? { name: dirent.name, relPath, absPath, children: [] }\r\n : { name: dirent.name, relPath, absPath }\r\n}\r\n","import type { FileNode } from './file-tree'\r\nimport { parse, posix } from 'path'\r\nimport { traverse } from '@/lib/tree'\r\nimport { RootIsFileError, DuplicateScreenError } from '../errors'\r\n\r\nexport const SEGMENT_ROLES = ['layout', 'screen', 'error', 'not-found'] as const\r\nexport type SegmentRole = typeof SEGMENT_ROLES[number]\r\n\r\nexport type SegmentRoles = Partial<Record<SegmentRole, string>>\r\nexport type SegmentNode = {\r\n segment: string\r\n url: `${string}/`\r\n type: 'page' | 'group'\r\n roles: SegmentRoles\r\n parent?: SegmentNode\r\n children?: SegmentNode[]\r\n file: FileNode\r\n}\r\n\r\n/**\r\n * Creates a segment tree from a file system tree.\r\n *\r\n * @param fileTree - File system tree\r\n * @returns Segment tree with assigned roles and validated structure\r\n * @throws {RootIsFileError} If the root is a file instead of a directory\r\n * @throws {DuplicateScreenError} If multiple screens map to the same URL\r\n */\r\nexport function createSegmentTree(fileTree: FileNode): SegmentNode {\r\n const tree = buildSegmentTree(fileTree) // Builds segment tree structure from file tree\r\n bindSegmentTree(tree) // Binds roles to segments and validates tree structure\r\n return tree\r\n}\r\n\r\nfunction buildSegmentTree(fileTree: FileNode) {\r\n if (fileTree.children === undefined)\r\n throw new RootIsFileError(fileTree.absPath)\r\n\r\n const root: SegmentNode = {\r\n segment: '',\r\n url: '/',\r\n type: 'page',\r\n roles: {},\r\n children: [],\r\n file: fileTree,\r\n }\r\n\r\n traverse(root, {\r\n attach: (child, parent) => parent.children!.push(child),\r\n expand: segment =>\r\n (segment.file.children ?? [])\r\n .filter(file => file.children && !file.name.startsWith('_'))\r\n .map(file => createSegmentNode(file, segment))\r\n .sort((a, b) => a.segment.localeCompare(b.segment)),\r\n })\r\n return root\r\n}\r\n\r\nfunction bindSegmentTree(segmentTree: SegmentNode) {\r\n const screens: Record<SegmentNode['url'], string> = {}\r\n\r\n traverse(segmentTree, {\r\n expand: segment => segment.children ?? [],\r\n visit: segment => {\r\n assignRoles(segment)\r\n validateScreenUniqueness(segment, screens)\r\n },\r\n })\r\n}\r\n\r\nfunction createSegmentNode(file: FileNode, parent: SegmentNode) {\r\n const isGroup = file.name.startsWith('(') && file.name.endsWith(')')\r\n const segmentNode: SegmentNode = {\r\n segment: file.name,\r\n url: isGroup ? parent.url : `${posix.join(parent.url, file.name)}/`,\r\n type: isGroup ? 'group' : 'page',\r\n roles: {},\r\n parent,\r\n children: [],\r\n file,\r\n }\r\n return segmentNode\r\n}\r\n\r\nfunction assignRoles(segment: SegmentNode) {\r\n for (const child of segment.file.children ?? []) {\r\n const { name, ext } = parse(child.name)\r\n if (ext === '.tsx' && SEGMENT_ROLES.includes(name as SegmentRole))\r\n segment.roles[name as SegmentRole] = child.absPath\r\n }\r\n}\r\n\r\nfunction validateScreenUniqueness(segment: SegmentNode, screens: Record<SegmentNode['url'], string>) {\r\n if (!segment.roles.screen) return\r\n\r\n const existing = screens[segment.url]\r\n if (existing)\r\n throw new DuplicateScreenError(segment.url, [existing, segment.file.absPath])\r\n screens[segment.url] = segment.file.absPath\r\n}\r\n","import { parse, join } from 'path'\r\n\r\nexport function removeExtension(filePath: string) {\r\n const parsed = parse(filePath)\r\n return join(parsed.dir, parsed.name) // Use posix for forward slashes\r\n}\r\n","import type { SegmentNode, SegmentRoles } from './segment-tree'\r\nimport { ancestors, traverse } from '@/lib/tree'\r\nimport { relative } from 'path'\r\nimport { removeExtension } from '@/lib/path-utils'\r\n\r\nexport type RouteChainMap = Record<string, Route>\r\nexport type Route = {\r\n url: string\r\n chain: SegmentRoles[]\r\n}\r\n\r\n/**\r\n * Builds a route manifest from a segment tree.\r\n *\r\n * Flattens the tree into a dictionary mapping URLs to route metadata.\r\n * Only includes routes that have screens.\r\n * Paths are stored as relative import paths from the generated directory.\r\n *\r\n * @param segmentTree - Segment tree with parent pointers\r\n * @param outDir - Output directory (to calculate relative import paths)\r\n * @returns Flat manifest ready for runtime composition\r\n */\r\nexport function createRouteChainMap(segmentTree: SegmentNode, outDir: string): RouteChainMap {\r\n const routes: RouteChainMap = {}\r\n const genDir = `${outDir}/generated`\r\n\r\n traverse(segmentTree, {\r\n expand: parentSegment => parentSegment.children ?? [],\r\n visit: segment => {\r\n if (!segment.roles.screen) return\r\n const url = segment.url\r\n const chain = createSegmentChain(segment, genDir)\r\n routes[url] = { url, chain }\r\n },\r\n })\r\n return routes\r\n}\r\n\r\n/** Builds ancestor chain from leaf → root order. */\r\nfunction createSegmentChain(segment: SegmentNode, genDir: string) {\r\n const chain: SegmentRoles[] = []\r\n\r\n ancestors(segment, {\r\n parent: ancestorSegment => ancestorSegment.parent,\r\n visit: ancestorSegment => {\r\n const roles: SegmentRoles = {}\r\n const entries = Object.entries(ancestorSegment.roles) as [keyof SegmentRoles, string][]\r\n\r\n for (const [name, path] of entries) {\r\n if (name !== 'screen' || ancestorSegment === segment) { // Skip ancestor screens\r\n const importPath = removeExtension(path) // Remove extension\r\n const relPath = relative(genDir, importPath).replace(/\\\\/g, '/') // Convert to relative path\r\n roles[name] = `${relPath}.js` // Add .js for ES modules\r\n }\r\n }\r\n chain.push(roles)\r\n },\r\n })\r\n return chain\r\n}\r\n","import type { RouteChainMap } from './route-chain-map'\r\nimport { SEGMENT_ROLES } from './segment-tree'\r\n\r\n/** Map from import path to component index (keys are in sorted order) */\r\nexport type ComponentIdMap = Record<string, number>\r\n\r\n/**\r\n * Builds a mapping of import paths to component IDs from the manifest.\r\n * Collects all unique import paths and assigns them indices.\r\n * Keys are stored in sorted order for deterministic output.\r\n */\r\nexport function createComponentIdMap(routes: RouteChainMap): ComponentIdMap {\r\n const importPaths = new Set<string>()\r\n\r\n // Collect all unique import paths from manifest\r\n for (const route of Object.values(routes)) {\r\n for (const segment of route.chain) {\r\n for (const role of SEGMENT_ROLES)\r\n if (segment[role]) importPaths.add(segment[role])\r\n }\r\n }\r\n\r\n // Sort for deterministic output\r\n const imports = Array.from(importPaths).sort()\r\n const mapping: ComponentIdMap = {}\r\n for (let i = 0; i < imports.length; i++)\r\n mapping[imports[i]!] = i\r\n\r\n return mapping\r\n}\r\n","import type { Route, RouteChainMap } from './route-chain-map'\r\nimport type { ComponentIdMap } from './component-id-map'\r\n\r\nexport interface SerializedTree {\r\n component: string\r\n props: Record<string, unknown>\r\n children?: SerializedTree\r\n}\r\n\r\nexport type SerializedRouteTreeMap = Record<string, SerializedTree>\r\n\r\n/**\r\n * Creates element trees for all routeChain in the manifest.\r\n * Each tree represents the nested React component structure for a route.\r\n */\r\nexport function createSerializedRoutes(routeChain: RouteChainMap, componentIdMap: ComponentIdMap): SerializedRouteTreeMap {\r\n const serializedRoutes: SerializedRouteTreeMap = {}\r\n for (const [url, route] of Object.entries(routeChain))\r\n serializedRoutes[url] = createSerializedTree(route, componentIdMap)\r\n return serializedRoutes\r\n}\r\n\r\n/**\r\n * Builds a structured serialized element tree representing nested React components.\r\n *\r\n * This function creates a structured data tree that will be serialized into\r\n * createElement calls for the generated routeChain.ts file.\r\n */\r\nfunction createSerializedTree(route: Route, mapping: ComponentIdMap): SerializedTree {\r\n const imports = Object.keys(mapping)\r\n\r\n // Start with the screen from the first segment\r\n const screenSegment = route.chain[0]!\r\n const screenPath = screenSegment['screen']!\r\n const screenIndex = mapping[screenPath]!\r\n const screenKey = JSON.stringify(imports[screenIndex]!)\r\n\r\n let tree: SerializedTree = {\r\n component: `Component${screenIndex}`,\r\n props: { key: screenKey },\r\n }\r\n\r\n // Process segments from leaf → root (same order as runtime composition)\r\n for (const segment of route.chain) {\r\n // Not-found boundary\r\n if (segment['not-found']) {\r\n const path = segment['not-found']\r\n const index = mapping[path]!\r\n const key = JSON.stringify(imports[index]!)\r\n const fallback = `Component${index}`\r\n tree = {\r\n component: 'NotFoundBoundary',\r\n props: { key, fallback },\r\n children: tree,\r\n }\r\n }\r\n // Error boundary\r\n if (segment['error']) {\r\n const path = segment['error']\r\n const index = mapping[path]!\r\n const key = JSON.stringify(imports[index]!)\r\n const fallback = `Component${index}`\r\n tree = {\r\n component: 'ErrorBoundary',\r\n props: { key, fallback },\r\n children: tree,\r\n }\r\n }\r\n // Layout\r\n if (segment['layout']) {\r\n const path = segment['layout']\r\n const index = mapping[path]!\r\n const key = JSON.stringify(imports[index]!)\r\n tree = {\r\n component: `Component${index}`,\r\n props: { key },\r\n children: tree,\r\n }\r\n }\r\n }\r\n return tree\r\n}\r\n"],"mappings":"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"}