@pyreon/create-zero 0.5.0 → 0.11.1

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.
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import('../lib/index.js')
2
+ import("../lib/index.js")
package/lib/index.js CHANGED
@@ -299,8 +299,8 @@ function generatePackageJson(config) {
299
299
  const devDeps = {
300
300
  "@pyreon/vite-plugin": pyreonVersion("@pyreon/vite-plugin"),
301
301
  "@pyreon/zero-cli": pyreonVersion("@pyreon/zero-cli"),
302
- "typescript": "^5.9.3",
303
- "vite": "^7.0.0"
302
+ typescript: "^5.9.3",
303
+ vite: "^7.0.0"
304
304
  };
305
305
  if (config.aiToolchain) devDeps["@pyreon/mcp"] = pyreonVersion("@pyreon/mcp");
306
306
  const pkg = {
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { cp, readFile, writeFile } from 'node:fs/promises'\nimport { basename, join, resolve } from 'node:path'\nimport * as p from '@clack/prompts'\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\ninterface ProjectConfig {\n name: string\n targetDir: string\n renderMode: 'ssr-stream' | 'ssr-string' | 'ssg' | 'spa'\n features: string[]\n packageStrategy: 'meta' | 'individual'\n aiToolchain: boolean\n}\n\n// ─── Feature definitions ────────────────────────────────────────────────────\n\nconst FEATURES = {\n store: {\n label: 'State Management (@pyreon/store)',\n deps: ['@pyreon/store'],\n },\n query: {\n label: 'Data Fetching (@pyreon/query)',\n deps: ['@pyreon/query', '@tanstack/query-core'],\n },\n forms: {\n label: 'Forms + Validation (@pyreon/form, @pyreon/validation)',\n deps: ['@pyreon/form', '@pyreon/validation', 'zod'],\n },\n feature: {\n label: 'Feature CRUD (@pyreon/feature) — includes store, query, forms',\n deps: [\n '@pyreon/feature',\n '@pyreon/store',\n '@pyreon/query',\n '@pyreon/form',\n '@pyreon/validation',\n '@tanstack/query-core',\n 'zod',\n ],\n },\n i18n: {\n label: 'Internationalization (@pyreon/i18n)',\n deps: ['@pyreon/i18n'],\n },\n table: {\n label: 'Tables (@pyreon/table)',\n deps: ['@pyreon/table', '@tanstack/table-core'],\n },\n virtual: {\n label: 'Virtual Lists (@pyreon/virtual)',\n deps: ['@pyreon/virtual', '@tanstack/virtual-core'],\n },\n styler: {\n label: 'CSS-in-JS (@pyreon/styler)',\n deps: ['@pyreon/styler', '@pyreon/ui-core'],\n },\n elements: {\n label: 'UI Elements (@pyreon/elements, @pyreon/coolgrid)',\n deps: ['@pyreon/elements', '@pyreon/coolgrid', '@pyreon/unistyle', '@pyreon/ui-core'],\n },\n animations: {\n label: 'Animations (@pyreon/kinetic + 120 presets)',\n deps: ['@pyreon/kinetic', '@pyreon/kinetic-presets'],\n },\n hooks: {\n label: 'Hooks (@pyreon/hooks — 25+ signal-based utilities)',\n deps: ['@pyreon/hooks'],\n },\n charts: {\n label: 'Charts (@pyreon/charts — reactive ECharts)',\n deps: ['@pyreon/charts'],\n },\n hotkeys: {\n label: 'Hotkeys (@pyreon/hotkeys — keyboard shortcuts)',\n deps: ['@pyreon/hotkeys'],\n },\n storage: {\n label: 'Storage (@pyreon/storage — localStorage, cookies, IndexedDB)',\n deps: ['@pyreon/storage'],\n },\n flow: {\n label: 'Flow Diagrams (@pyreon/flow — reactive node graphs)',\n deps: ['@pyreon/flow'],\n },\n code: {\n label: 'Code Editor (@pyreon/code — CodeMirror 6)',\n deps: ['@pyreon/code'],\n },\n} as const\n\ntype FeatureKey = keyof typeof FEATURES\n\n// ─── Template directory ─────────────────────────────────────────────────────\n\nconst TEMPLATE_DIR = resolve(import.meta.dirname, '../templates/default')\n\n// ─── Main ───────────────────────────────────────────────────────────────────\n\nasync function main() {\n const args = process.argv.slice(2)\n const argName = args[0]\n\n if (argName === '--help' || argName === '-h') {\n console.log('Usage: create-zero [project-name]')\n process.exit(0)\n }\n\n p.intro('Create a new Pyreon Zero project')\n\n // Project name\n const name = argName ?? (await p.text({\n message: 'Project name',\n placeholder: 'my-zero-app',\n validate: (v) => {\n if (!v?.trim()) return 'Project name is required'\n if (existsSync(resolve(process.cwd(), v))) return `Directory \"${v}\" already exists`\n },\n }))\n\n if (p.isCancel(name)) {\n p.cancel('Cancelled.')\n process.exit(0)\n }\n\n const targetDir = resolve(process.cwd(), name as string)\n if (existsSync(targetDir)) {\n p.cancel(`Directory \"${name}\" already exists.`)\n process.exit(1)\n }\n\n // Rendering mode\n const renderMode = await p.select({\n message: 'Rendering mode',\n options: [\n { value: 'ssr-stream', label: 'SSR Streaming', hint: 'recommended — progressive HTML with Suspense' },\n { value: 'ssr-string', label: 'SSR String', hint: 'buffered HTML, simpler but slower TTFB' },\n { value: 'ssg', label: 'Static (SSG)', hint: 'pre-rendered at build time' },\n { value: 'spa', label: 'SPA', hint: 'client-only, no server rendering' },\n ],\n })\n\n if (p.isCancel(renderMode)) {\n p.cancel('Cancelled.')\n process.exit(0)\n }\n\n // Features\n const features = await p.multiselect({\n message: 'Select features (space to toggle, enter to confirm)',\n options: Object.entries(FEATURES).map(([key, { label }]) => ({\n value: key,\n label,\n })),\n initialValues: ['store', 'query', 'forms'],\n required: false,\n })\n\n if (p.isCancel(features)) {\n p.cancel('Cancelled.')\n process.exit(0)\n }\n\n // Package strategy\n const packageStrategy = await p.select({\n message: 'Package imports',\n options: [\n { value: 'meta', label: '@pyreon/meta (single barrel)', hint: 'one import for everything — simpler, tree-shaken at build' },\n { value: 'individual', label: 'Individual packages', hint: 'only install what you selected — smaller node_modules' },\n ],\n })\n\n if (p.isCancel(packageStrategy)) {\n p.cancel('Cancelled.')\n process.exit(0)\n }\n\n // AI toolchain\n const aiToolchain = await p.confirm({\n message: 'Include AI toolchain? (MCP server, CLAUDE.md, doctor)',\n initialValue: true,\n })\n\n if (p.isCancel(aiToolchain)) {\n p.cancel('Cancelled.')\n process.exit(0)\n }\n\n const config: ProjectConfig = {\n name: name as string,\n targetDir,\n renderMode: renderMode as ProjectConfig['renderMode'],\n features: features as string[],\n packageStrategy: packageStrategy as ProjectConfig['packageStrategy'],\n aiToolchain: aiToolchain as boolean,\n }\n\n const s = p.spinner()\n s.start('Scaffolding project...')\n\n await scaffold(config)\n\n s.stop('Project created!')\n\n // Next steps\n p.note(\n [\n `cd ${config.name}`,\n 'bun install',\n 'bun run dev',\n ].join('\\n'),\n 'Next steps',\n )\n\n p.outro('Happy building!')\n}\n\n// ─── Scaffolding ────────────────────────────────────────────────────────────\n\nasync function scaffold(config: ProjectConfig) {\n // Copy full template as base\n await cp(TEMPLATE_DIR, config.targetDir, { recursive: true })\n\n // Generate customized files\n await writeFile(\n join(config.targetDir, 'package.json'),\n generatePackageJson(config),\n )\n\n await writeFile(\n join(config.targetDir, 'vite.config.ts'),\n generateViteConfig(config),\n )\n\n await writeFile(\n join(config.targetDir, 'src/entry-server.ts'),\n generateEntryServer(config),\n )\n\n await writeFile(\n join(config.targetDir, 'env.d.ts'),\n generateEnvDts(config),\n )\n\n // Create .gitignore (npm strips it from packages)\n await writeFile(\n join(config.targetDir, '.gitignore'),\n 'node_modules\\ndist\\n.DS_Store\\n*.local\\n.pyreon\\n',\n )\n\n // AI toolchain files\n if (config.aiToolchain) {\n await writeFile(\n join(config.targetDir, '.mcp.json'),\n JSON.stringify(\n {\n mcpServers: {\n pyreon: { command: 'bunx', args: ['@pyreon/mcp'] },\n },\n },\n null,\n 2,\n ),\n )\n } else {\n // Remove AI files from copied template\n const aiFiles = ['.mcp.json', 'CLAUDE.md']\n for (const f of aiFiles) {\n const path = join(config.targetDir, f)\n if (existsSync(path)) {\n const { unlink } = await import('node:fs/promises')\n await unlink(path)\n }\n }\n }\n\n // Remove feature-specific files if features not selected\n if (!config.features.includes('feature') && !config.features.includes('forms')) {\n await removeIfExists(join(config.targetDir, 'src/routes/posts/new.tsx'))\n await removeIfExists(join(config.targetDir, 'src/features'))\n }\n\n if (!config.features.includes('store')) {\n await removeIfExists(join(config.targetDir, 'src/stores'))\n }\n\n // Remove store import from layout if store not selected\n if (!config.features.includes('store')) {\n const layoutPath = join(config.targetDir, 'src/routes/_layout.tsx')\n if (existsSync(layoutPath)) {\n let layout = await readFile(layoutPath, 'utf-8')\n layout = layout\n .replace(/import .* from '\\.\\.\\/stores\\/app'\\n/g, '')\n .replace(/.*useAppStore.*\\n/g, '')\n .replace(/\\s*<button[\\s\\S]*?sidebar-toggle[\\s\\S]*?<\\/button>\\n/g, '')\n await writeFile(layoutPath, layout)\n }\n }\n}\n\n// ─── File generators ────────────────────────────────────────────────────────\n\n// Resolve the correct version range for a @pyreon/* package\nfunction pyreonVersion(pkg: string): string {\n // Core packages\n const core = ['core', 'reactivity', 'runtime-dom', 'runtime-server', 'server', 'head', 'router', 'vite-plugin', 'compiler', 'cli', 'mcp']\n if (core.some((c) => pkg === `@pyreon/${c}`)) return '^0.7.11'\n // Zero framework packages\n if (pkg === '@pyreon/zero' || pkg === '@pyreon/meta' || pkg === '@pyreon/zero-cli' || pkg === '@pyreon/create-zero') return '^0.4.1'\n // Fundamentals\n const fundamentals = ['store', 'form', 'validation', 'query', 'table', 'virtual', 'i18n', 'feature', 'machine', 'permissions', 'flow', 'code']\n if (fundamentals.some((f) => pkg === `@pyreon/${f}`)) return '^0.10.0'\n // UI system\n return '^0.4.1'\n}\n\nfunction generatePackageJson(config: ProjectConfig): string {\n const deps: Record<string, string> = {\n '@pyreon/core': pyreonVersion('@pyreon/core'),\n '@pyreon/head': pyreonVersion('@pyreon/head'),\n '@pyreon/reactivity': pyreonVersion('@pyreon/reactivity'),\n '@pyreon/router': pyreonVersion('@pyreon/router'),\n '@pyreon/runtime-dom': pyreonVersion('@pyreon/runtime-dom'),\n '@pyreon/runtime-server': pyreonVersion('@pyreon/runtime-server'),\n '@pyreon/server': pyreonVersion('@pyreon/server'),\n '@pyreon/zero': pyreonVersion('@pyreon/zero'),\n }\n\n if (config.packageStrategy === 'meta') {\n // Single barrel — includes all fundamentals + UI system\n deps['@pyreon/meta'] = pyreonVersion('@pyreon/meta')\n // Still need non-pyreon deps for selected features\n for (const key of config.features) {\n const feature = FEATURES[key as FeatureKey]\n if (feature) {\n for (const dep of feature.deps) {\n if (!dep.startsWith('@pyreon/')) {\n if (dep.startsWith('@tanstack/')) {\n deps[dep] = dep.includes('query') ? '^5.90.0' : dep.includes('table') ? '^8.21.0' : '^3.13.0'\n } else if (dep === 'zod') {\n deps[dep] = '^4.0.0'\n }\n }\n }\n }\n }\n } else {\n // Individual packages — only install what's selected\n const allDeps = new Set<string>()\n for (const key of config.features) {\n const feature = FEATURES[key as FeatureKey]\n if (feature) {\n for (const dep of feature.deps) allDeps.add(dep)\n }\n }\n for (const dep of allDeps) {\n if (dep.startsWith('@pyreon/')) {\n deps[dep] = pyreonVersion(dep)\n } else if (dep.startsWith('@tanstack/')) {\n deps[dep] = dep.includes('query') ? '^5.90.0' : dep.includes('table') ? '^8.21.0' : '^3.13.0'\n } else if (dep === 'zod') {\n deps[dep] = '^4.0.0'\n }\n }\n }\n\n const devDeps: Record<string, string> = {\n '@pyreon/vite-plugin': pyreonVersion('@pyreon/vite-plugin'),\n '@pyreon/zero-cli': pyreonVersion('@pyreon/zero-cli'),\n 'typescript': '^5.9.3',\n 'vite': '^7.0.0',\n }\n\n if (config.aiToolchain) {\n devDeps['@pyreon/mcp'] = pyreonVersion('@pyreon/mcp')\n }\n\n const scripts: Record<string, string> = {\n dev: 'zero dev',\n build: 'zero build',\n preview: 'zero preview',\n doctor: 'zero doctor',\n 'doctor:fix': 'zero doctor --fix',\n 'doctor:ci': 'zero doctor --ci',\n }\n\n const pkg = {\n name: basename(config.name),\n version: '0.0.1',\n private: true,\n type: 'module',\n scripts,\n dependencies: Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b))),\n devDependencies: Object.fromEntries(Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b))),\n }\n\n return `${JSON.stringify(pkg, null, 2)}\\n`\n}\n\nfunction generateViteConfig(config: ProjectConfig): string {\n const modeMap = {\n 'ssr-stream': `mode: 'ssr', ssr: { mode: 'stream' }`,\n 'ssr-string': `mode: 'ssr'`,\n ssg: `mode: 'ssg'`,\n spa: `mode: 'spa'`,\n }\n\n return `import pyreon from '@pyreon/vite-plugin'\nimport zero from '@pyreon/zero'\nimport { fontPlugin } from '@pyreon/zero/font'\nimport { seoPlugin } from '@pyreon/zero/seo'\n\nexport default {\n plugins: [\n pyreon(),\n zero({ ${modeMap[config.renderMode]} }),\n\n // Google Fonts — self-hosted at build time, CDN in dev\n fontPlugin({\n google: ['Inter:wght@400;500;600;700;800', 'JetBrains Mono:wght@400'],\n fallbacks: {\n Inter: { fallback: 'Arial', sizeAdjust: 1.07, ascentOverride: 90 },\n },\n }),\n\n // Generate sitemap.xml and robots.txt at build time\n seoPlugin({\n sitemap: { origin: 'https://example.com' },\n robots: {\n rules: [{ userAgent: '*', allow: ['/'] }],\n sitemap: 'https://example.com/sitemap.xml',\n },\n }),\n ],\n}\n`\n}\n\nfunction generateEntryServer(config: ProjectConfig): string {\n const imports = [\n `import { routes } from 'virtual:zero/routes'`,\n `import { routeMiddleware } from 'virtual:zero/route-middleware'`,\n `import { createServer } from '@pyreon/zero'`,\n `import {\\n cacheMiddleware,\\n securityHeaders,\\n varyEncoding,\\n} from '@pyreon/zero/cache'`,\n ]\n\n const modeMap = {\n 'ssr-stream': `stream`,\n 'ssr-string': `string`,\n ssg: `string`,\n spa: `string`,\n }\n\n return `${imports.join('\\n')}\n\nexport default createServer({\n routes,\n routeMiddleware,\n config: {\n ssr: { mode: '${modeMap[config.renderMode]}' },\n },\n middleware: [\n securityHeaders(),\n cacheMiddleware({ staleWhileRevalidate: 120 }),\n varyEncoding(),\n ],\n})\n`\n}\n\nfunction generateEnvDts(config: ProjectConfig): string {\n let content = `/// <reference types=\"vite/client\" />\n\ndeclare module 'virtual:zero/routes' {\n import type { RouteRecord } from '@pyreon/router'\n export const routes: RouteRecord[]\n}\n\ndeclare module 'virtual:zero/route-middleware' {\n import type { RouteMiddlewareEntry } from '@pyreon/zero'\n export const routeMiddleware: RouteMiddlewareEntry[]\n}\n`\n\n if (config.features.includes('query')) {\n content += `\ndeclare module 'virtual:zero/actions' {\n export {}\n}\n`\n }\n\n return content\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nasync function removeIfExists(path: string) {\n if (!existsSync(path)) return\n const { rm } = await import('node:fs/promises')\n await rm(path, { recursive: true })\n}\n\nmain().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\"\nimport { cp, readFile, writeFile } from \"node:fs/promises\"\nimport { basename, join, resolve } from \"node:path\"\nimport * as p from \"@clack/prompts\"\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\ninterface ProjectConfig {\n name: string\n targetDir: string\n renderMode: \"ssr-stream\" | \"ssr-string\" | \"ssg\" | \"spa\"\n features: string[]\n packageStrategy: \"meta\" | \"individual\"\n aiToolchain: boolean\n}\n\n// ─── Feature definitions ────────────────────────────────────────────────────\n\nconst FEATURES = {\n store: {\n label: \"State Management (@pyreon/store)\",\n deps: [\"@pyreon/store\"],\n },\n query: {\n label: \"Data Fetching (@pyreon/query)\",\n deps: [\"@pyreon/query\", \"@tanstack/query-core\"],\n },\n forms: {\n label: \"Forms + Validation (@pyreon/form, @pyreon/validation)\",\n deps: [\"@pyreon/form\", \"@pyreon/validation\", \"zod\"],\n },\n feature: {\n label: \"Feature CRUD (@pyreon/feature) — includes store, query, forms\",\n deps: [\n \"@pyreon/feature\",\n \"@pyreon/store\",\n \"@pyreon/query\",\n \"@pyreon/form\",\n \"@pyreon/validation\",\n \"@tanstack/query-core\",\n \"zod\",\n ],\n },\n i18n: {\n label: \"Internationalization (@pyreon/i18n)\",\n deps: [\"@pyreon/i18n\"],\n },\n table: {\n label: \"Tables (@pyreon/table)\",\n deps: [\"@pyreon/table\", \"@tanstack/table-core\"],\n },\n virtual: {\n label: \"Virtual Lists (@pyreon/virtual)\",\n deps: [\"@pyreon/virtual\", \"@tanstack/virtual-core\"],\n },\n styler: {\n label: \"CSS-in-JS (@pyreon/styler)\",\n deps: [\"@pyreon/styler\", \"@pyreon/ui-core\"],\n },\n elements: {\n label: \"UI Elements (@pyreon/elements, @pyreon/coolgrid)\",\n deps: [\"@pyreon/elements\", \"@pyreon/coolgrid\", \"@pyreon/unistyle\", \"@pyreon/ui-core\"],\n },\n animations: {\n label: \"Animations (@pyreon/kinetic + 120 presets)\",\n deps: [\"@pyreon/kinetic\", \"@pyreon/kinetic-presets\"],\n },\n hooks: {\n label: \"Hooks (@pyreon/hooks — 25+ signal-based utilities)\",\n deps: [\"@pyreon/hooks\"],\n },\n charts: {\n label: \"Charts (@pyreon/charts — reactive ECharts)\",\n deps: [\"@pyreon/charts\"],\n },\n hotkeys: {\n label: \"Hotkeys (@pyreon/hotkeys — keyboard shortcuts)\",\n deps: [\"@pyreon/hotkeys\"],\n },\n storage: {\n label: \"Storage (@pyreon/storage — localStorage, cookies, IndexedDB)\",\n deps: [\"@pyreon/storage\"],\n },\n flow: {\n label: \"Flow Diagrams (@pyreon/flow — reactive node graphs)\",\n deps: [\"@pyreon/flow\"],\n },\n code: {\n label: \"Code Editor (@pyreon/code — CodeMirror 6)\",\n deps: [\"@pyreon/code\"],\n },\n} as const\n\ntype FeatureKey = keyof typeof FEATURES\n\n// ─── Template directory ─────────────────────────────────────────────────────\n\nconst TEMPLATE_DIR = resolve(import.meta.dirname, \"../templates/default\")\n\n// ─── Main ───────────────────────────────────────────────────────────────────\n\nasync function main() {\n const args = process.argv.slice(2)\n const argName = args[0]\n\n if (argName === \"--help\" || argName === \"-h\") {\n console.log(\"Usage: create-zero [project-name]\")\n process.exit(0)\n }\n\n p.intro(\"Create a new Pyreon Zero project\")\n\n // Project name\n const name =\n argName ??\n (await p.text({\n message: \"Project name\",\n placeholder: \"my-zero-app\",\n validate: (v) => {\n if (!v?.trim()) return \"Project name is required\"\n if (existsSync(resolve(process.cwd(), v))) return `Directory \"${v}\" already exists`\n },\n }))\n\n if (p.isCancel(name)) {\n p.cancel(\"Cancelled.\")\n process.exit(0)\n }\n\n const targetDir = resolve(process.cwd(), name as string)\n if (existsSync(targetDir)) {\n p.cancel(`Directory \"${name}\" already exists.`)\n process.exit(1)\n }\n\n // Rendering mode\n const renderMode = await p.select({\n message: \"Rendering mode\",\n options: [\n {\n value: \"ssr-stream\",\n label: \"SSR Streaming\",\n hint: \"recommended — progressive HTML with Suspense\",\n },\n { value: \"ssr-string\", label: \"SSR String\", hint: \"buffered HTML, simpler but slower TTFB\" },\n { value: \"ssg\", label: \"Static (SSG)\", hint: \"pre-rendered at build time\" },\n { value: \"spa\", label: \"SPA\", hint: \"client-only, no server rendering\" },\n ],\n })\n\n if (p.isCancel(renderMode)) {\n p.cancel(\"Cancelled.\")\n process.exit(0)\n }\n\n // Features\n const features = await p.multiselect({\n message: \"Select features (space to toggle, enter to confirm)\",\n options: Object.entries(FEATURES).map(([key, { label }]) => ({\n value: key,\n label,\n })),\n initialValues: [\"store\", \"query\", \"forms\"],\n required: false,\n })\n\n if (p.isCancel(features)) {\n p.cancel(\"Cancelled.\")\n process.exit(0)\n }\n\n // Package strategy\n const packageStrategy = await p.select({\n message: \"Package imports\",\n options: [\n {\n value: \"meta\",\n label: \"@pyreon/meta (single barrel)\",\n hint: \"one import for everything — simpler, tree-shaken at build\",\n },\n {\n value: \"individual\",\n label: \"Individual packages\",\n hint: \"only install what you selected — smaller node_modules\",\n },\n ],\n })\n\n if (p.isCancel(packageStrategy)) {\n p.cancel(\"Cancelled.\")\n process.exit(0)\n }\n\n // AI toolchain\n const aiToolchain = await p.confirm({\n message: \"Include AI toolchain? (MCP server, CLAUDE.md, doctor)\",\n initialValue: true,\n })\n\n if (p.isCancel(aiToolchain)) {\n p.cancel(\"Cancelled.\")\n process.exit(0)\n }\n\n const config: ProjectConfig = {\n name: name as string,\n targetDir,\n renderMode: renderMode as ProjectConfig[\"renderMode\"],\n features: features as string[],\n packageStrategy: packageStrategy as ProjectConfig[\"packageStrategy\"],\n aiToolchain: aiToolchain as boolean,\n }\n\n const s = p.spinner()\n s.start(\"Scaffolding project...\")\n\n await scaffold(config)\n\n s.stop(\"Project created!\")\n\n // Next steps\n p.note([`cd ${config.name}`, \"bun install\", \"bun run dev\"].join(\"\\n\"), \"Next steps\")\n\n p.outro(\"Happy building!\")\n}\n\n// ─── Scaffolding ────────────────────────────────────────────────────────────\n\nasync function scaffold(config: ProjectConfig) {\n // Copy full template as base\n await cp(TEMPLATE_DIR, config.targetDir, { recursive: true })\n\n // Generate customized files\n await writeFile(join(config.targetDir, \"package.json\"), generatePackageJson(config))\n\n await writeFile(join(config.targetDir, \"vite.config.ts\"), generateViteConfig(config))\n\n await writeFile(join(config.targetDir, \"src/entry-server.ts\"), generateEntryServer(config))\n\n await writeFile(join(config.targetDir, \"env.d.ts\"), generateEnvDts(config))\n\n // Create .gitignore (npm strips it from packages)\n await writeFile(\n join(config.targetDir, \".gitignore\"),\n \"node_modules\\ndist\\n.DS_Store\\n*.local\\n.pyreon\\n\",\n )\n\n // AI toolchain files\n if (config.aiToolchain) {\n await writeFile(\n join(config.targetDir, \".mcp.json\"),\n JSON.stringify(\n {\n mcpServers: {\n pyreon: { command: \"bunx\", args: [\"@pyreon/mcp\"] },\n },\n },\n null,\n 2,\n ),\n )\n } else {\n // Remove AI files from copied template\n const aiFiles = [\".mcp.json\", \"CLAUDE.md\"]\n for (const f of aiFiles) {\n const path = join(config.targetDir, f)\n if (existsSync(path)) {\n const { unlink } = await import(\"node:fs/promises\")\n await unlink(path)\n }\n }\n }\n\n // Remove feature-specific files if features not selected\n if (!config.features.includes(\"feature\") && !config.features.includes(\"forms\")) {\n await removeIfExists(join(config.targetDir, \"src/routes/posts/new.tsx\"))\n await removeIfExists(join(config.targetDir, \"src/features\"))\n }\n\n if (!config.features.includes(\"store\")) {\n await removeIfExists(join(config.targetDir, \"src/stores\"))\n }\n\n // Remove store import from layout if store not selected\n if (!config.features.includes(\"store\")) {\n const layoutPath = join(config.targetDir, \"src/routes/_layout.tsx\")\n if (existsSync(layoutPath)) {\n let layout = await readFile(layoutPath, \"utf-8\")\n layout = layout\n .replace(/import .* from '\\.\\.\\/stores\\/app'\\n/g, \"\")\n .replace(/.*useAppStore.*\\n/g, \"\")\n .replace(/\\s*<button[\\s\\S]*?sidebar-toggle[\\s\\S]*?<\\/button>\\n/g, \"\")\n await writeFile(layoutPath, layout)\n }\n }\n}\n\n// ─── File generators ────────────────────────────────────────────────────────\n\n// Resolve the correct version range for a @pyreon/* package\nfunction pyreonVersion(pkg: string): string {\n // Core packages\n const core = [\n \"core\",\n \"reactivity\",\n \"runtime-dom\",\n \"runtime-server\",\n \"server\",\n \"head\",\n \"router\",\n \"vite-plugin\",\n \"compiler\",\n \"cli\",\n \"mcp\",\n ]\n if (core.some((c) => pkg === `@pyreon/${c}`)) return \"^0.7.11\"\n // Zero framework packages\n if (\n pkg === \"@pyreon/zero\" ||\n pkg === \"@pyreon/meta\" ||\n pkg === \"@pyreon/zero-cli\" ||\n pkg === \"@pyreon/create-zero\"\n )\n return \"^0.4.1\"\n // Fundamentals\n const fundamentals = [\n \"store\",\n \"form\",\n \"validation\",\n \"query\",\n \"table\",\n \"virtual\",\n \"i18n\",\n \"feature\",\n \"machine\",\n \"permissions\",\n \"flow\",\n \"code\",\n ]\n if (fundamentals.some((f) => pkg === `@pyreon/${f}`)) return \"^0.10.0\"\n // UI system\n return \"^0.4.1\"\n}\n\nfunction generatePackageJson(config: ProjectConfig): string {\n const deps: Record<string, string> = {\n \"@pyreon/core\": pyreonVersion(\"@pyreon/core\"),\n \"@pyreon/head\": pyreonVersion(\"@pyreon/head\"),\n \"@pyreon/reactivity\": pyreonVersion(\"@pyreon/reactivity\"),\n \"@pyreon/router\": pyreonVersion(\"@pyreon/router\"),\n \"@pyreon/runtime-dom\": pyreonVersion(\"@pyreon/runtime-dom\"),\n \"@pyreon/runtime-server\": pyreonVersion(\"@pyreon/runtime-server\"),\n \"@pyreon/server\": pyreonVersion(\"@pyreon/server\"),\n \"@pyreon/zero\": pyreonVersion(\"@pyreon/zero\"),\n }\n\n if (config.packageStrategy === \"meta\") {\n // Single barrel — includes all fundamentals + UI system\n deps[\"@pyreon/meta\"] = pyreonVersion(\"@pyreon/meta\")\n // Still need non-pyreon deps for selected features\n for (const key of config.features) {\n const feature = FEATURES[key as FeatureKey]\n if (feature) {\n for (const dep of feature.deps) {\n if (!dep.startsWith(\"@pyreon/\")) {\n if (dep.startsWith(\"@tanstack/\")) {\n deps[dep] = dep.includes(\"query\")\n ? \"^5.90.0\"\n : dep.includes(\"table\")\n ? \"^8.21.0\"\n : \"^3.13.0\"\n } else if (dep === \"zod\") {\n deps[dep] = \"^4.0.0\"\n }\n }\n }\n }\n }\n } else {\n // Individual packages — only install what's selected\n const allDeps = new Set<string>()\n for (const key of config.features) {\n const feature = FEATURES[key as FeatureKey]\n if (feature) {\n for (const dep of feature.deps) allDeps.add(dep)\n }\n }\n for (const dep of allDeps) {\n if (dep.startsWith(\"@pyreon/\")) {\n deps[dep] = pyreonVersion(dep)\n } else if (dep.startsWith(\"@tanstack/\")) {\n deps[dep] = dep.includes(\"query\")\n ? \"^5.90.0\"\n : dep.includes(\"table\")\n ? \"^8.21.0\"\n : \"^3.13.0\"\n } else if (dep === \"zod\") {\n deps[dep] = \"^4.0.0\"\n }\n }\n }\n\n const devDeps: Record<string, string> = {\n \"@pyreon/vite-plugin\": pyreonVersion(\"@pyreon/vite-plugin\"),\n \"@pyreon/zero-cli\": pyreonVersion(\"@pyreon/zero-cli\"),\n typescript: \"^5.9.3\",\n vite: \"^7.0.0\",\n }\n\n if (config.aiToolchain) {\n devDeps[\"@pyreon/mcp\"] = pyreonVersion(\"@pyreon/mcp\")\n }\n\n const scripts: Record<string, string> = {\n dev: \"zero dev\",\n build: \"zero build\",\n preview: \"zero preview\",\n doctor: \"zero doctor\",\n \"doctor:fix\": \"zero doctor --fix\",\n \"doctor:ci\": \"zero doctor --ci\",\n }\n\n const pkg = {\n name: basename(config.name),\n version: \"0.0.1\",\n private: true,\n type: \"module\",\n scripts,\n dependencies: Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b))),\n devDependencies: Object.fromEntries(\n Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b)),\n ),\n }\n\n return `${JSON.stringify(pkg, null, 2)}\\n`\n}\n\nfunction generateViteConfig(config: ProjectConfig): string {\n const modeMap = {\n \"ssr-stream\": `mode: 'ssr', ssr: { mode: 'stream' }`,\n \"ssr-string\": `mode: 'ssr'`,\n ssg: `mode: 'ssg'`,\n spa: `mode: 'spa'`,\n }\n\n return `import pyreon from '@pyreon/vite-plugin'\nimport zero from '@pyreon/zero'\nimport { fontPlugin } from '@pyreon/zero/font'\nimport { seoPlugin } from '@pyreon/zero/seo'\n\nexport default {\n plugins: [\n pyreon(),\n zero({ ${modeMap[config.renderMode]} }),\n\n // Google Fonts — self-hosted at build time, CDN in dev\n fontPlugin({\n google: ['Inter:wght@400;500;600;700;800', 'JetBrains Mono:wght@400'],\n fallbacks: {\n Inter: { fallback: 'Arial', sizeAdjust: 1.07, ascentOverride: 90 },\n },\n }),\n\n // Generate sitemap.xml and robots.txt at build time\n seoPlugin({\n sitemap: { origin: 'https://example.com' },\n robots: {\n rules: [{ userAgent: '*', allow: ['/'] }],\n sitemap: 'https://example.com/sitemap.xml',\n },\n }),\n ],\n}\n`\n}\n\nfunction generateEntryServer(config: ProjectConfig): string {\n const imports = [\n `import { routes } from 'virtual:zero/routes'`,\n `import { routeMiddleware } from 'virtual:zero/route-middleware'`,\n `import { createServer } from '@pyreon/zero'`,\n `import {\\n cacheMiddleware,\\n securityHeaders,\\n varyEncoding,\\n} from '@pyreon/zero/cache'`,\n ]\n\n const modeMap = {\n \"ssr-stream\": `stream`,\n \"ssr-string\": `string`,\n ssg: `string`,\n spa: `string`,\n }\n\n return `${imports.join(\"\\n\")}\n\nexport default createServer({\n routes,\n routeMiddleware,\n config: {\n ssr: { mode: '${modeMap[config.renderMode]}' },\n },\n middleware: [\n securityHeaders(),\n cacheMiddleware({ staleWhileRevalidate: 120 }),\n varyEncoding(),\n ],\n})\n`\n}\n\nfunction generateEnvDts(config: ProjectConfig): string {\n let content = `/// <reference types=\"vite/client\" />\n\ndeclare module 'virtual:zero/routes' {\n import type { RouteRecord } from '@pyreon/router'\n export const routes: RouteRecord[]\n}\n\ndeclare module 'virtual:zero/route-middleware' {\n import type { RouteMiddlewareEntry } from '@pyreon/zero'\n export const routeMiddleware: RouteMiddlewareEntry[]\n}\n`\n\n if (config.features.includes(\"query\")) {\n content += `\ndeclare module 'virtual:zero/actions' {\n export {}\n}\n`\n }\n\n return content\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nasync function removeIfExists(path: string) {\n if (!existsSync(path)) return\n const { rm } = await import(\"node:fs/promises\")\n await rm(path, { recursive: true })\n}\n\nmain().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@pyreon/create-zero",
3
- "version": "0.5.0",
3
+ "version": "0.11.1",
4
4
  "description": "Create a new Pyreon Zero project",
5
5
  "license": "MIT",
6
6
  "author": "Vit Bokisch",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/pyreon/zero",
10
- "directory": "packages/create-zero"
9
+ "url": "https://github.com/pyreon/pyreon.git",
10
+ "directory": "packages/zero/create-zero"
11
11
  },
12
12
  "type": "module",
13
13
  "main": "./lib/index.js",
@@ -24,7 +24,8 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "build": "vl_rolldown_build",
27
- "typecheck": "tsc --noEmit"
27
+ "typecheck": "tsc --noEmit",
28
+ "lint": "biome check ."
28
29
  },
29
30
  "dependencies": {
30
31
  "@clack/prompts": "^1.0.0"
@@ -1,16 +1,16 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
- declare module 'virtual:zero/routes' {
4
- import type { RouteRecord } from '@pyreon/router'
3
+ declare module "virtual:zero/routes" {
4
+ import type { RouteRecord } from "@pyreon/router"
5
5
  export const routes: RouteRecord[]
6
6
  }
7
7
 
8
- declare module 'virtual:zero/route-middleware' {
9
- import type { RouteMiddlewareEntry } from '@pyreon/zero'
8
+ declare module "virtual:zero/route-middleware" {
9
+ import type { RouteMiddlewareEntry } from "@pyreon/zero"
10
10
  export const routeMiddleware: RouteMiddlewareEntry[]
11
11
  }
12
12
 
13
- declare module 'virtual:zero/api-routes' {
14
- import type { ApiRouteEntry } from '@pyreon/zero'
13
+ declare module "virtual:zero/api-routes" {
14
+ import type { ApiRouteEntry } from "@pyreon/zero"
15
15
  export const apiRoutes: ApiRouteEntry[]
16
16
  }
@@ -6,7 +6,7 @@
6
6
  <meta name="theme-color" content="#0a0a0b" />
7
7
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
8
  <!-- Prevent flash of wrong theme -->
9
- <script>(function(){try{var t=localStorage.getItem("zero-theme");var r=t==="light"?"light":t==="dark"?"dark":window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.dataset.theme=r}catch(e){}})()</script>
9
+ <script>(()=> {try{var t=localStorage.getItem("zero-theme");var r=t==="light"?"light":t==="dark"?"dark":window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light";document.documentElement.dataset.theme=r}catch(e){}})()</script>
10
10
  <!--pyreon-head-->
11
11
  </head>
12
12
  <body>
@@ -1,5 +1,5 @@
1
- import './global.css'
2
- import { routes } from 'virtual:zero/routes'
3
- import { startClient } from '@pyreon/zero/client'
1
+ import "./global.css"
2
+ import { routes } from "virtual:zero/routes"
3
+ import { startClient } from "@pyreon/zero/client"
4
4
 
5
5
  startClient({ routes })
@@ -1,25 +1,21 @@
1
- import { apiRoutes } from 'virtual:zero/api-routes'
2
- import { routeMiddleware } from 'virtual:zero/route-middleware'
3
- import { routes } from 'virtual:zero/routes'
4
- import { createServer } from '@pyreon/zero'
5
- import {
6
- cacheMiddleware,
7
- securityHeaders,
8
- varyEncoding,
9
- } from '@pyreon/zero/cache'
10
- import { corsMiddleware } from '@pyreon/zero/cors'
11
- import { rateLimitMiddleware } from '@pyreon/zero/rate-limit'
1
+ import { apiRoutes } from "virtual:zero/api-routes"
2
+ import { routeMiddleware } from "virtual:zero/route-middleware"
3
+ import { routes } from "virtual:zero/routes"
4
+ import { createServer } from "@pyreon/zero"
5
+ import { cacheMiddleware, securityHeaders, varyEncoding } from "@pyreon/zero/cache"
6
+ import { corsMiddleware } from "@pyreon/zero/cors"
7
+ import { rateLimitMiddleware } from "@pyreon/zero/rate-limit"
12
8
 
13
9
  export default createServer({
14
10
  routes,
15
11
  routeMiddleware,
16
12
  apiRoutes,
17
13
  config: {
18
- ssr: { mode: 'stream' },
14
+ ssr: { mode: "stream" },
19
15
  },
20
16
  middleware: [
21
17
  corsMiddleware(),
22
- rateLimitMiddleware({ max: 100, window: 60, include: ['/api/*'] }),
18
+ rateLimitMiddleware({ max: 100, window: 60, include: ["/api/*"] }),
23
19
  securityHeaders(),
24
20
  cacheMiddleware({ staleWhileRevalidate: 120 }),
25
21
  varyEncoding(),
@@ -1,12 +1,12 @@
1
- import { defineFeature } from '@pyreon/feature'
2
- import { z } from 'zod'
1
+ import { defineFeature } from "@pyreon/feature"
2
+ import { z } from "zod"
3
3
 
4
4
  export const posts = defineFeature({
5
- name: 'posts',
5
+ name: "posts",
6
6
  schema: z.object({
7
- title: z.string().min(3, 'Title must be at least 3 characters'),
8
- body: z.string().min(10, 'Body must be at least 10 characters'),
7
+ title: z.string().min(3, "Title must be at least 3 characters"),
8
+ body: z.string().min(10, "Body must be at least 10 characters"),
9
9
  published: z.boolean(),
10
10
  }),
11
- api: '/api/posts',
11
+ api: "/api/posts",
12
12
  })
@@ -1,9 +1,9 @@
1
- import { useHead } from '@pyreon/head'
2
- import type { MiddlewareContext } from '@pyreon/server'
3
- import type { LoaderContext } from '@pyreon/zero'
1
+ import { useHead } from "@pyreon/head"
2
+ import type { MiddlewareContext } from "@pyreon/server"
3
+ import type { LoaderContext } from "@pyreon/zero"
4
4
 
5
5
  export const meta = {
6
- title: 'Dashboard — Pyreon Zero',
6
+ title: "Dashboard — Pyreon Zero",
7
7
  }
8
8
 
9
9
  /**
@@ -16,11 +16,10 @@ export const meta = {
16
16
  export function guard() {
17
17
  // Simulate auth check — in a real app, check session/token
18
18
  const isAuthenticated =
19
- typeof window !== 'undefined' &&
20
- localStorage.getItem('zero-demo-auth') === 'true'
19
+ typeof window !== "undefined" && localStorage.getItem("zero-demo-auth") === "true"
21
20
 
22
21
  if (!isAuthenticated) {
23
- return '/about' // Redirect unauthenticated users
22
+ return "/about" // Redirect unauthenticated users
24
23
  }
25
24
  return true // Allow navigation
26
25
  }
@@ -34,16 +33,16 @@ export function guard() {
34
33
  */
35
34
  export const middleware = (ctx: MiddlewareContext) => {
36
35
  // Add server timing header to track route performance
37
- ctx.headers.set('Server-Timing', `route;desc="Dashboard"`)
36
+ ctx.headers.set("Server-Timing", `route;desc="Dashboard"`)
38
37
  }
39
38
 
40
39
  export async function loader(_ctx: LoaderContext) {
41
40
  return {
42
- user: 'Demo User',
41
+ user: "Demo User",
43
42
  stats: {
44
43
  views: 12_847,
45
44
  routes: 6,
46
- buildTime: '1.2s',
45
+ buildTime: "1.2s",
47
46
  },
48
47
  }
49
48
  }
@@ -57,8 +56,8 @@ export default function Dashboard() {
57
56
  <span class="badge">Protected Route</span>
58
57
  <h1 style="margin-top: var(--space-md);">Dashboard</h1>
59
58
  <p>
60
- This route is protected by a <code>guard</code> function and uses
61
- per-route <code>middleware</code> for server-side logging.
59
+ This route is protected by a <code>guard</code> function and uses per-route{" "}
60
+ <code>middleware</code> for server-side logging.
62
61
  </p>
63
62
  </div>
64
63
 
@@ -77,31 +76,27 @@ export default function Dashboard() {
77
76
  </div>
78
77
  </div>
79
78
 
80
- <div
81
- class="code-block"
82
- style="max-width: 520px; margin: var(--space-2xl) auto 0;"
83
- >
79
+ <div class="code-block" style="max-width: 520px; margin: var(--space-2xl) auto 0;">
84
80
  <div class="code-block-header">
85
81
  <span>dashboard.tsx</span>
86
82
  </div>
87
83
  <pre>
88
84
  <code>
89
- <span class="cm">{'// Navigation guard — protect routes'}</span>
90
- <span class="kw">export function</span>{' '}
91
- <span class="fn">guard</span>() {'{'}
92
- <span class="kw">if</span> (!isAuthenticated) {'{'}
93
- <span class="kw">return</span> <span class="str">"/login"</span>{' '}
94
- <span class="cm">{'// redirect'}</span>
95
- {'}'}
96
- <span class="kw">return</span> <span class="str">true</span>{' '}
97
- <span class="cm">{'// allow'}</span>
98
- {'}'}
99
- <span class="cm">{'// Per-route server middleware'}</span>
100
- <span class="kw">export const</span>{' '}
101
- <span class="fn">middleware</span> = (req, next) =&gt; {'{'}
102
- <span class="cm">{'// logging, auth, rate limiting...'}</span>
85
+ <span class="cm">{"// Navigation guard — protect routes"}</span>
86
+ <span class="kw">export function</span> <span class="fn">guard</span>() {"{"}
87
+ <span class="kw">if</span> (!isAuthenticated) {"{"}
88
+ <span class="kw">return</span> <span class="str">"/login"</span>{" "}
89
+ <span class="cm">{"// redirect"}</span>
90
+ {"}"}
91
+ <span class="kw">return</span> <span class="str">true</span>{" "}
92
+ <span class="cm">{"// allow"}</span>
93
+ {"}"}
94
+ <span class="cm">{"// Per-route server middleware"}</span>
95
+ <span class="kw">export const</span> <span class="fn">middleware</span> = (req, next)
96
+ =&gt; {"{"}
97
+ <span class="cm">{"// logging, auth, rate limiting..."}</span>
103
98
  <span class="kw">return</span> <span class="fn">next</span>(req)
104
- {'}'}
99
+ {"}"}
105
100
  </code>
106
101
  </pre>
107
102
  </div>
@@ -111,8 +106,8 @@ export default function Dashboard() {
111
106
  type="button"
112
107
  class="btn btn-secondary"
113
108
  onClick={() => {
114
- localStorage.removeItem('zero-demo-auth')
115
- window.location.href = '/about'
109
+ localStorage.removeItem("zero-demo-auth")
110
+ window.location.href = "/about"
116
111
  }}
117
112
  >
118
113
  Log out (clear demo auth)
@@ -1,22 +1,17 @@
1
- import { useHead } from '@pyreon/head'
2
- import { Link } from '@pyreon/zero/link'
1
+ import { useHead } from "@pyreon/head"
2
+ import { Link } from "@pyreon/zero/link"
3
3
 
4
4
  export default function ErrorPage() {
5
- useHead({ title: 'Something went wrong — Zero' })
5
+ useHead({ title: "Something went wrong — Zero" })
6
6
 
7
7
  return (
8
8
  <div class="error-page">
9
9
  <div class="error-code">500</div>
10
10
  <h1>Something went wrong</h1>
11
11
  <p style="color: var(--c-text-secondary); max-width: 400px;">
12
- An unexpected error occurred. Try refreshing the page or navigating back
13
- home.
12
+ An unexpected error occurred. Try refreshing the page or navigating back home.
14
13
  </p>
15
- <Link
16
- href="/"
17
- class="btn btn-primary"
18
- style="margin-top: var(--space-md);"
19
- >
14
+ <Link href="/" class="btn btn-primary" style="margin-top: var(--space-md);">
20
15
  Back to Home
21
16
  </Link>
22
17
  </div>
@@ -1,7 +1,7 @@
1
- import { QueryClient, QueryClientProvider } from '@pyreon/query'
2
- import { Link } from '@pyreon/zero/link'
3
- import { ThemeToggle } from '@pyreon/zero/theme'
4
- import { useAppStore } from '../stores/app'
1
+ import { QueryClient, QueryClientProvider } from "@pyreon/query"
2
+ import { Link } from "@pyreon/zero/link"
3
+ import { ThemeToggle } from "@pyreon/zero/theme"
4
+ import { useAppStore } from "../stores/app"
5
5
 
6
6
  const queryClient = new QueryClient({
7
7
  defaultOptions: { queries: { staleTime: 30000 } },
@@ -23,11 +23,7 @@ export function layout(props: { children: any }) {
23
23
  <Link href="/" prefetch="viewport" exactActiveClass="nav-active">
24
24
  Home
25
25
  </Link>
26
- <Link
27
- href="/counter"
28
- prefetch="hover"
29
- exactActiveClass="nav-active"
30
- >
26
+ <Link href="/counter" prefetch="hover" exactActiveClass="nav-active">
31
27
  Counter
32
28
  </Link>
33
29
  <Link href="/posts" prefetch="hover" activeClass="nav-active">
@@ -36,11 +32,7 @@ export function layout(props: { children: any }) {
36
32
  <Link href="/about" prefetch="hover" exactActiveClass="nav-active">
37
33
  About
38
34
  </Link>
39
- <Link
40
- href="/dashboard"
41
- prefetch="hover"
42
- exactActiveClass="nav-active"
43
- >
35
+ <Link href="/dashboard" prefetch="hover" exactActiveClass="nav-active">
44
36
  Dashboard
45
37
  </Link>
46
38
  <button
@@ -49,7 +41,7 @@ export function layout(props: { children: any }) {
49
41
  onClick={toggleSidebar}
50
42
  title="Toggle sidebar"
51
43
  >
52
- {() => (sidebarOpen() ? '' : '')}
44
+ {() => (sidebarOpen() ? "" : "")}
53
45
  </button>
54
46
  <ThemeToggle class="theme-toggle" />
55
47
  </nav>
@@ -58,9 +50,7 @@ export function layout(props: { children: any }) {
58
50
 
59
51
  <main class="app-main">{props.children}</main>
60
52
 
61
- <footer class="app-footer">
62
- Built with Pyreon Zero — signal-based, blazing fast.
63
- </footer>
53
+ <footer class="app-footer">Built with Pyreon Zero — signal-based, blazing fast.</footer>
64
54
  </QueryClientProvider>
65
55
  )
66
56
  }
@@ -1,15 +1,15 @@
1
- import { useHead } from '@pyreon/head'
2
- import { Link } from '@pyreon/zero/link'
1
+ import { useHead } from "@pyreon/head"
2
+ import { Link } from "@pyreon/zero/link"
3
3
 
4
4
  export const meta = {
5
- title: 'About — Pyreon Zero',
6
- description: 'Learn about the Pyreon Zero meta-framework.',
5
+ title: "About — Pyreon Zero",
6
+ description: "Learn about the Pyreon Zero meta-framework.",
7
7
  }
8
8
 
9
9
  export default function About() {
10
10
  useHead({
11
11
  title: meta.title,
12
- meta: [{ name: 'description', content: meta.description }],
12
+ meta: [{ name: "description", content: meta.description }],
13
13
  })
14
14
 
15
15
  return (
@@ -17,8 +17,8 @@ export default function About() {
17
17
  <div class="page-header">
18
18
  <h1>About Zero</h1>
19
19
  <p>
20
- A signal-based meta-framework built on Vite — designed for developers
21
- who care about performance and simplicity.
20
+ A signal-based meta-framework built on Vite — designed for developers who care about
21
+ performance and simplicity.
22
22
  </p>
23
23
  </div>
24
24
 
@@ -43,20 +43,18 @@ export default function About() {
43
43
  </h2>
44
44
  <div style="color: var(--c-text-secondary); line-height: 1.8; display: flex; flex-direction: column; gap: var(--space-lg);">
45
45
  <p>
46
- Most frameworks make you choose between developer experience and
47
- runtime performance. Zero doesn't. Pyreon's signal-based reactivity
48
- means your components compile to surgical DOM updates — no diffing,
49
- no reconciliation, no wasted work.
46
+ Most frameworks make you choose between developer experience and runtime performance.
47
+ Zero doesn't. Pyreon's signal-based reactivity means your components compile to surgical
48
+ DOM updates — no diffing, no reconciliation, no wasted work.
50
49
  </p>
51
50
  <p>
52
- On top of that, Zero gives you file-based routing, server-side
53
- rendering, static generation, incremental regeneration, font
54
- optimization, image optimization, smart caching, link prefetching,
55
- and SEO utilities — all built in, all zero-config.
51
+ On top of that, Zero gives you file-based routing, server-side rendering, static
52
+ generation, incremental regeneration, font optimization, image optimization, smart
53
+ caching, link prefetching, and SEO utilities — all built in, all zero-config.
56
54
  </p>
57
55
  <p>
58
- Deploy to Node, Bun, Vercel, Cloudflare, Netlify, or export as a
59
- static site. One codebase, any target.
56
+ Deploy to Node, Bun, Vercel, Cloudflare, Netlify, or export as a static site. One
57
+ codebase, any target.
60
58
  </p>
61
59
  </div>
62
60
  </section>
@@ -67,30 +65,15 @@ export default function About() {
67
65
  </h2>
68
66
  <div style="display: grid; gap: var(--space-sm);">
69
67
  {[
70
- ['Pyreon', 'Signal-based UI framework with JSX'],
71
- ['Vite', 'Lightning-fast dev server and optimized builds'],
72
- [
73
- 'File Router',
74
- 'Drop a file, get a route — layouts, guards, loaders',
75
- ],
76
- [
77
- 'SSR / SSG / ISR',
78
- 'Every rendering strategy, per-route overrides',
79
- ],
80
- [
81
- '<Image>',
82
- 'Lazy loading, responsive srcset, blur-up placeholders',
83
- ],
84
- ['<Link>', 'Prefetch on hover or viewport entry for instant nav'],
85
- [
86
- 'Font Plugin',
87
- 'Google Fonts optimization, preconnect, font-display:swap',
88
- ],
89
- [
90
- 'Cache MW',
91
- 'Immutable hashed assets, stale-while-revalidate pages',
92
- ],
93
- ['SEO Tools', 'Sitemap, robots.txt, JSON-LD structured data'],
68
+ ["Pyreon", "Signal-based UI framework with JSX"],
69
+ ["Vite", "Lightning-fast dev server and optimized builds"],
70
+ ["File Router", "Drop a file, get a route — layouts, guards, loaders"],
71
+ ["SSR / SSG / ISR", "Every rendering strategy, per-route overrides"],
72
+ ["<Image>", "Lazy loading, responsive srcset, blur-up placeholders"],
73
+ ["<Link>", "Prefetch on hover or viewport entry for instant nav"],
74
+ ["Font Plugin", "Google Fonts optimization, preconnect, font-display:swap"],
75
+ ["Cache MW", "Immutable hashed assets, stale-while-revalidate pages"],
76
+ ["SEO Tools", "Sitemap, robots.txt, JSON-LD structured data"],
94
77
  ].map(([name, desc]) => (
95
78
  <div
96
79
  class="card"
@@ -99,9 +82,7 @@ export default function About() {
99
82
  <code style="min-width: 130px; font-weight: 600; color: var(--c-accent);">
100
83
  {name}
101
84
  </code>
102
- <span style="color: var(--c-text-secondary); font-size: 0.9rem;">
103
- {desc}
104
- </span>
85
+ <span style="color: var(--c-text-secondary); font-size: 0.9rem;">{desc}</span>
105
86
  </div>
106
87
  ))}
107
88
  </div>
@@ -3,5 +3,5 @@
3
3
  * Useful for load balancers and uptime monitoring.
4
4
  */
5
5
  export function GET() {
6
- return Response.json({ status: 'ok', timestamp: new Date().toISOString() })
6
+ return Response.json({ status: "ok", timestamp: new Date().toISOString() })
7
7
  }
@@ -1,4 +1,4 @@
1
- import type { ApiContext } from '@pyreon/zero'
1
+ import type { ApiContext } from "@pyreon/zero"
2
2
 
3
3
  /**
4
4
  * API route example — /api/posts
@@ -10,9 +10,9 @@ import type { ApiContext } from '@pyreon/zero'
10
10
  */
11
11
 
12
12
  const POSTS = [
13
- { id: 1, title: 'Getting Started with Pyreon Zero', published: true },
14
- { id: 2, title: 'Understanding Signals', published: true },
15
- { id: 3, title: 'Server-Side Rendering Made Simple', published: false },
13
+ { id: 1, title: "Getting Started with Pyreon Zero", published: true },
14
+ { id: 2, title: "Understanding Signals", published: true },
15
+ { id: 3, title: "Server-Side Rendering Made Simple", published: false },
16
16
  ]
17
17
 
18
18
  export function GET(_ctx: ApiContext) {
@@ -26,7 +26,7 @@ export async function POST(ctx: ApiContext) {
26
26
  }
27
27
 
28
28
  if (!body.title) {
29
- return Response.json({ error: 'Title is required' }, { status: 400 })
29
+ return Response.json({ error: "Title is required" }, { status: 400 })
30
30
  }
31
31
 
32
32
  const post = {
@@ -1,8 +1,8 @@
1
- import { useHead } from '@pyreon/head'
2
- import { computed, signal } from '@pyreon/reactivity'
1
+ import { useHead } from "@pyreon/head"
2
+ import { computed, signal } from "@pyreon/reactivity"
3
3
 
4
4
  export const meta = {
5
- title: 'Counter — Pyreon Zero',
5
+ title: "Counter — Pyreon Zero",
6
6
  description: "See Pyreon's signal-based reactivity in action.",
7
7
  }
8
8
 
@@ -19,13 +19,13 @@ export default function Counter() {
19
19
  <span class="badge">Interactive Demo</span>
20
20
  <h1 style="margin-top: var(--space-md);">Signal Reactivity</h1>
21
21
  <p>
22
- Fine-grained reactivity with zero virtual DOM. Only the exact text
23
- nodes that display these values are updated — nothing else re-renders.
22
+ Fine-grained reactivity with zero virtual DOM. Only the exact text nodes that display
23
+ these values are updated — nothing else re-renders.
24
24
  </p>
25
25
  </div>
26
26
 
27
27
  <div class="counter-demo">
28
- <div class="counter-display">{count}</div>
28
+ <div class="counter-display">{() => count()}</div>
29
29
 
30
30
  <div class="counter-controls">
31
31
  <button
@@ -35,11 +35,7 @@ export default function Counter() {
35
35
  >
36
36
  -
37
37
  </button>
38
- <button
39
- type="button"
40
- class="btn btn-primary"
41
- onClick={() => count.set(0)}
42
- >
38
+ <button type="button" class="btn btn-primary" onClick={() => count.set(0)}>
43
39
  Reset
44
40
  </button>
45
41
  <button
@@ -53,38 +49,33 @@ export default function Counter() {
53
49
 
54
50
  <div class="counter-meta">
55
51
  <div>
56
- count() → <strong>{count}</strong>
52
+ count() → <strong>{() => count()}</strong>
57
53
  </div>
58
54
  <div>
59
- doubled() → <strong>{doubled}</strong>
55
+ doubled() → <strong>{() => doubled()}</strong>
60
56
  </div>
61
57
  <div>
62
- isEven() → <strong>{() => (isEven() ? 'true' : 'false')}</strong>
58
+ isEven() → <strong>{() => (isEven() ? "true" : "false")}</strong>
63
59
  </div>
64
60
  </div>
65
61
  </div>
66
62
 
67
- <div
68
- class="code-block"
69
- style="max-width: 520px; margin: var(--space-2xl) auto 0;"
70
- >
63
+ <div class="code-block" style="max-width: 520px; margin: var(--space-2xl) auto 0;">
71
64
  <div class="code-block-header">
72
65
  <span>counter.tsx</span>
73
66
  </div>
74
67
  <pre>
75
68
  <code>
76
- <span class="kw">import</span> {'{'} signal, computed {'}'}{' '}
77
- <span class="kw">from</span>{' '}
69
+ <span class="kw">import</span> {"{"} signal, computed {"}"} <span class="kw">from</span>{" "}
78
70
  <span class="str">"@pyreon/reactivity"</span>
79
- <span class="kw">const</span> <span class="fn">count</span> ={' '}
80
- <span class="fn">signal</span>(<span class="str">0</span>)
81
- <span class="kw">const</span> <span class="fn">doubled</span> ={' '}
82
- <span class="fn">computed</span>(() =&gt;{' '}
71
+ <span class="kw">const</span> <span class="fn">count</span> ={" "}
72
+ <span class="fn">signal</span>(<span class="str">0</span>)<span class="kw">const</span>{" "}
73
+ <span class="fn">doubled</span> = <span class="fn">computed</span>(() =&gt;{" "}
83
74
  <span class="fn">count</span>() * <span class="str">2</span>)
84
- <span class="cm">{'// Just reference the signal in JSX —'}</span>
85
- <span class="cm">{'// only this text node updates'}</span>
75
+ <span class="cm">{"// Just reference the signal in JSX —"}</span>
76
+ <span class="cm">{"// only this text node updates"}</span>
86
77
  <span class="tag">&lt;span&gt;</span>
87
- {'{'}count{'}'}
78
+ {"{"}count{"}"}
88
79
  <span class="tag">&lt;/span&gt;</span>
89
80
  </code>
90
81
  </pre>
@@ -1,15 +1,15 @@
1
- import { useHead } from '@pyreon/head'
2
- import { Link } from '@pyreon/zero/link'
1
+ import { useHead } from "@pyreon/head"
2
+ import { Link } from "@pyreon/zero/link"
3
3
 
4
4
  export const meta = {
5
- title: 'Pyreon Zero',
6
- description: 'The signal-based meta-framework. Build fast, stay fast.',
5
+ title: "Pyreon Zero",
6
+ description: "The signal-based meta-framework. Build fast, stay fast.",
7
7
  }
8
8
 
9
9
  export default function Home() {
10
10
  useHead({
11
- title: 'Pyreon Zero — The Signal-Based Meta-Framework',
12
- meta: [{ name: 'description', content: meta.description }],
11
+ title: "Pyreon Zero — The Signal-Based Meta-Framework",
12
+ meta: [{ name: "description", content: meta.description }],
13
13
  })
14
14
 
15
15
  return (
@@ -22,9 +22,8 @@ export default function Home() {
22
22
  <span class="gradient">Stay fast.</span>
23
23
  </h1>
24
24
  <p>
25
- Pyreon Zero is a signal-based full-stack framework powered by Vite.
26
- File-based routing, SSR, SSG, ISR — everything you need, nothing you
27
- don't.
25
+ Pyreon Zero is a signal-based full-stack framework powered by Vite. File-based routing,
26
+ SSR, SSG, ISR — everything you need, nothing you don't.
28
27
  </p>
29
28
  <div class="hero-actions">
30
29
  <Link href="/counter" class="btn btn-primary">
@@ -75,8 +74,8 @@ export default function Home() {
75
74
  </div>
76
75
  <h3>Signal-Based Reactivity</h3>
77
76
  <p>
78
- Fine-grained reactivity with zero virtual DOM overhead. Only the
79
- exact DOM nodes that need updating are touched.
77
+ Fine-grained reactivity with zero virtual DOM overhead. Only the exact DOM nodes that
78
+ need updating are touched.
80
79
  </p>
81
80
  </div>
82
81
 
@@ -98,8 +97,8 @@ export default function Home() {
98
97
  </div>
99
98
  <h3>File-Based Routing</h3>
100
99
  <p>
101
- Drop a file in <code>src/routes/</code> and it's a route. Layouts,
102
- dynamic params, catch-alls, and route groups built in.
100
+ Drop a file in <code>src/routes/</code> and it's a route. Layouts, dynamic params,
101
+ catch-alls, and route groups built in.
103
102
  </p>
104
103
  </div>
105
104
 
@@ -123,8 +122,8 @@ export default function Home() {
123
122
  </div>
124
123
  <h3>SSR / SSG / ISR / SPA</h3>
125
124
  <p>
126
- Every rendering strategy out of the box. Per-route overrides let you
127
- mix SSR pages with static marketing pages.
125
+ Every rendering strategy out of the box. Per-route overrides let you mix SSR pages with
126
+ static marketing pages.
128
127
  </p>
129
128
  </div>
130
129
 
@@ -146,9 +145,8 @@ export default function Home() {
146
145
  </div>
147
146
  <h3>Font & Image Optimization</h3>
148
147
  <p>
149
- Automatic Google Fonts inlining, font-display swap, and an{' '}
150
- <code>{'<Image>'}</code> component with lazy loading and blur-up
151
- placeholders.
148
+ Automatic Google Fonts inlining, font-display swap, and an <code>{"<Image>"}</code>{" "}
149
+ component with lazy loading and blur-up placeholders.
152
150
  </p>
153
151
  </div>
154
152
 
@@ -170,8 +168,8 @@ export default function Home() {
170
168
  </div>
171
169
  <h3>Smart Caching & Security</h3>
172
170
  <p>
173
- Built-in middleware for immutable asset caching,
174
- stale-while-revalidate, security headers, and compression hints.
171
+ Built-in middleware for immutable asset caching, stale-while-revalidate, security
172
+ headers, and compression hints.
175
173
  </p>
176
174
  </div>
177
175
 
@@ -195,8 +193,8 @@ export default function Home() {
195
193
  </div>
196
194
  <h3>Deploy Anywhere</h3>
197
195
  <p>
198
- Adapters for Node, Bun, Vercel, Cloudflare, and Netlify. Or export
199
- as a fully static site.
196
+ Adapters for Node, Bun, Vercel, Cloudflare, and Netlify. Or export as a fully static
197
+ site.
200
198
  </p>
201
199
  </div>
202
200
  </section>
@@ -1,7 +1,7 @@
1
- import { useHead } from '@pyreon/head'
2
- import { useLoaderData } from '@pyreon/router'
3
- import type { LoaderContext } from '@pyreon/zero'
4
- import { Link } from '@pyreon/zero/link'
1
+ import { useHead } from "@pyreon/head"
2
+ import { useLoaderData } from "@pyreon/router"
3
+ import type { LoaderContext } from "@pyreon/zero"
4
+ import { Link } from "@pyreon/zero/link"
5
5
 
6
6
  interface Post {
7
7
  id: number
@@ -13,52 +13,52 @@ interface Post {
13
13
  }
14
14
 
15
15
  const POSTS: Record<string, Post> = {
16
- '1': {
16
+ "1": {
17
17
  id: 1,
18
- title: 'Getting Started with Pyreon Zero',
19
- excerpt: 'Learn how to build your first app.',
18
+ title: "Getting Started with Pyreon Zero",
19
+ excerpt: "Learn how to build your first app.",
20
20
  body: "Pyreon Zero makes it incredibly easy to build modern web applications. Start by creating a new project with `bun create zero my-app`, then drop your first route file into `src/routes/`. The file-based router automatically picks up new files and generates the route tree for you.\n\nEvery route can export a `loader` for server-side data fetching, a `guard` for navigation protection, and `meta` for SEO metadata. The component itself uses JSX with Pyreon's signal-based reactivity — no virtual DOM overhead, just surgical DOM updates.",
21
- author: 'Zero Team',
22
- date: '2026-03-01',
21
+ author: "Zero Team",
22
+ date: "2026-03-01",
23
23
  },
24
- '2': {
24
+ "2": {
25
25
  id: 2,
26
- title: 'Understanding Signals',
27
- excerpt: 'Deep dive into fine-grained reactivity.',
26
+ title: "Understanding Signals",
27
+ excerpt: "Deep dive into fine-grained reactivity.",
28
28
  body: "Signals are the foundation of Pyreon's reactivity system. Unlike the virtual DOM approach used by React, signals track exactly which DOM nodes depend on which pieces of state. When a signal changes, only the precise text nodes or attributes that reference it are updated.\n\nThis means your app does zero diffing, zero reconciliation, and zero unnecessary re-renders. A counter component that updates a number on screen? Only that one text node is touched. Everything else stays completely untouched.",
29
- author: 'Zero Team',
30
- date: '2026-03-05',
29
+ author: "Zero Team",
30
+ date: "2026-03-05",
31
31
  },
32
- '3': {
32
+ "3": {
33
33
  id: 3,
34
- title: 'Server-Side Rendering Made Simple',
35
- excerpt: 'Pick the right strategy for every page.',
36
- body: 'Zero supports four rendering strategies out of the box: SSR (server-side rendering), SSG (static site generation), ISR (incremental static regeneration), and SPA (single-page application). You can set a default mode in your config and override it per-route.\n\nSSR renders fresh HTML on every request. SSG pre-renders pages at build time. ISR combines both — serving cached static pages while revalidating in the background. SPA skips server rendering entirely for fully client-side pages.',
37
- author: 'Zero Team',
38
- date: '2026-03-08',
34
+ title: "Server-Side Rendering Made Simple",
35
+ excerpt: "Pick the right strategy for every page.",
36
+ body: "Zero supports four rendering strategies out of the box: SSR (server-side rendering), SSG (static site generation), ISR (incremental static regeneration), and SPA (single-page application). You can set a default mode in your config and override it per-route.\n\nSSR renders fresh HTML on every request. SSG pre-renders pages at build time. ISR combines both — serving cached static pages while revalidating in the background. SPA skips server rendering entirely for fully client-side pages.",
37
+ author: "Zero Team",
38
+ date: "2026-03-08",
39
39
  },
40
- '4': {
40
+ "4": {
41
41
  id: 4,
42
- title: 'Deploying to Production',
43
- excerpt: 'From build to production with any platform.',
42
+ title: "Deploying to Production",
43
+ excerpt: "From build to production with any platform.",
44
44
  body: "Zero's adapter system makes deployment straightforward. Choose your target platform — Node.js, Bun, Vercel, Cloudflare Workers, or Netlify — and Zero generates the right output for that platform.\n\nThe Node adapter creates a standalone HTTP server. The Bun adapter leverages Bun.serve() for maximum performance. The static adapter exports a fully pre-rendered site ready for any CDN or static hosting platform.",
45
- author: 'Zero Team',
46
- date: '2026-03-10',
45
+ author: "Zero Team",
46
+ date: "2026-03-10",
47
47
  },
48
- '5': {
48
+ "5": {
49
49
  id: 5,
50
- title: 'Optimizing Performance',
51
- excerpt: 'Built-in performance features.',
52
- body: 'Zero includes a comprehensive performance toolkit. The font plugin automatically optimizes Google Fonts with preconnect hints and font-display:swap. The <Image> component provides lazy loading with IntersectionObserver, responsive srcset, and blur-up placeholders.\n\nThe <Link> component prefetches routes on hover or viewport entry, making navigation feel instant. Built-in cache middleware sets optimal Cache-Control headers — immutable caching for hashed assets, stale-while-revalidate for pages.',
53
- author: 'Zero Team',
54
- date: '2026-03-12',
50
+ title: "Optimizing Performance",
51
+ excerpt: "Built-in performance features.",
52
+ body: "Zero includes a comprehensive performance toolkit. The font plugin automatically optimizes Google Fonts with preconnect hints and font-display:swap. The <Image> component provides lazy loading with IntersectionObserver, responsive srcset, and blur-up placeholders.\n\nThe <Link> component prefetches routes on hover or viewport entry, making navigation feel instant. Built-in cache middleware sets optimal Cache-Control headers — immutable caching for hashed assets, stale-while-revalidate for pages.",
53
+ author: "Zero Team",
54
+ date: "2026-03-12",
55
55
  },
56
56
  }
57
57
 
58
58
  export async function loader(ctx: LoaderContext) {
59
59
  await new Promise((r) => setTimeout(r, 50))
60
60
  const post = POSTS[ctx.params.id]
61
- if (!post) throw new Error('Post not found')
61
+ if (!post) throw new Error("Post not found")
62
62
  return { post }
63
63
  }
64
64
 
@@ -68,7 +68,7 @@ export default function PostDetail() {
68
68
 
69
69
  useHead({
70
70
  title: `${post.title} — Pyreon Zero`,
71
- meta: [{ name: 'description', content: post.excerpt }],
71
+ meta: [{ name: "description", content: post.excerpt }],
72
72
  })
73
73
 
74
74
  return (
@@ -88,7 +88,7 @@ export default function PostDetail() {
88
88
  </div>
89
89
 
90
90
  <div class="post-body">
91
- {post.body.split('\n\n').map((paragraph) => (
91
+ {post.body.split("\n\n").map((paragraph) => (
92
92
  <p style="margin-bottom: var(--space-lg);">{paragraph}</p>
93
93
  ))}
94
94
  </div>
@@ -1,7 +1,7 @@
1
- import { useHead } from '@pyreon/head'
2
- import { useLoaderData } from '@pyreon/router'
3
- import type { LoaderContext } from '@pyreon/zero'
4
- import { Link } from '@pyreon/zero/link'
1
+ import { useHead } from "@pyreon/head"
2
+ import { useLoaderData } from "@pyreon/router"
3
+ import type { LoaderContext } from "@pyreon/zero"
4
+ import { Link } from "@pyreon/zero/link"
5
5
 
6
6
  interface Post {
7
7
  id: number
@@ -12,33 +12,29 @@ interface Post {
12
12
  const POSTS: Post[] = [
13
13
  {
14
14
  id: 1,
15
- title: 'Getting Started with Pyreon Zero',
16
- excerpt:
17
- 'Learn how to build your first app with file-based routing, SSR, and signals.',
15
+ title: "Getting Started with Pyreon Zero",
16
+ excerpt: "Learn how to build your first app with file-based routing, SSR, and signals.",
18
17
  },
19
18
  {
20
19
  id: 2,
21
- title: 'Understanding Signals',
22
- excerpt:
23
- 'Deep dive into fine-grained reactivity — how signals replace the virtual DOM.',
20
+ title: "Understanding Signals",
21
+ excerpt: "Deep dive into fine-grained reactivity — how signals replace the virtual DOM.",
24
22
  },
25
23
  {
26
24
  id: 3,
27
- title: 'Server-Side Rendering Made Simple',
28
- excerpt:
29
- 'SSR, SSG, ISR — pick the right strategy for every page in your app.',
25
+ title: "Server-Side Rendering Made Simple",
26
+ excerpt: "SSR, SSG, ISR — pick the right strategy for every page in your app.",
30
27
  },
31
28
  {
32
29
  id: 4,
33
- title: 'Deploying to Production',
34
- excerpt:
35
- 'From bun build to production with Node, Bun, Vercel, or Cloudflare adapters.',
30
+ title: "Deploying to Production",
31
+ excerpt: "From bun build to production with Node, Bun, Vercel, or Cloudflare adapters.",
36
32
  },
37
33
  {
38
34
  id: 5,
39
- title: 'Optimizing Performance',
35
+ title: "Optimizing Performance",
40
36
  excerpt:
41
- 'Font loading, image optimization, smart caching, and link prefetching out of the box.',
37
+ "Font loading, image optimization, smart caching, and link prefetching out of the box.",
42
38
  },
43
39
  ]
44
40
 
@@ -52,10 +48,10 @@ export default function PostsIndex() {
52
48
  const data = useLoaderData<{ posts: Post[] }>()
53
49
 
54
50
  useHead({
55
- title: 'Posts — Pyreon Zero',
51
+ title: "Posts — Pyreon Zero",
56
52
  meta: [
57
53
  {
58
- name: 'description',
54
+ name: "description",
59
55
  content: "Example posts showcasing Zero's data loading.",
60
56
  },
61
57
  ],
@@ -67,25 +63,17 @@ export default function PostsIndex() {
67
63
  <span class="badge">Data Loading</span>
68
64
  <h1 style="margin-top: var(--space-md);">Posts</h1>
69
65
  <p>
70
- Each post is loaded via a <code>loader</code> function — server-side
71
- data fetching that runs before the route renders.
66
+ Each post is loaded via a <code>loader</code> function — server-side data fetching that
67
+ runs before the route renders.
72
68
  </p>
73
- <Link
74
- href="/posts/new"
75
- class="btn"
76
- style="margin-top: var(--space-md);"
77
- >
69
+ <Link href="/posts/new" class="btn" style="margin-top: var(--space-md);">
78
70
  + New Post
79
71
  </Link>
80
72
  </div>
81
73
 
82
74
  <div class="posts-grid">
83
75
  {data.posts.map((post) => (
84
- <Link
85
- href={`/posts/${post.id}`}
86
- class="card post-card"
87
- prefetch="hover"
88
- >
76
+ <Link href={`/posts/${post.id}`} class="card post-card" prefetch="hover">
89
77
  <span class="post-id">#{post.id}</span>
90
78
  <div>
91
79
  <h3>{post.title}</h3>
@@ -95,25 +83,20 @@ export default function PostsIndex() {
95
83
  ))}
96
84
  </div>
97
85
 
98
- <div
99
- class="code-block"
100
- style="max-width: 520px; margin: var(--space-2xl) auto 0;"
101
- >
86
+ <div class="code-block" style="max-width: 520px; margin: var(--space-2xl) auto 0;">
102
87
  <div class="code-block-header">
103
88
  <span>posts/index.tsx</span>
104
89
  </div>
105
90
  <pre>
106
91
  <code>
107
- <span class="cm">{'// Export a loader — runs on the server'}</span>
108
- <span class="kw">export async function</span>{' '}
109
- <span class="fn">loader</span>(ctx) {'{'}
110
- <span class="kw">const</span> posts = <span class="kw">await</span>{' '}
111
- db.posts.<span class="fn">findMany</span>()
112
- <span class="kw">return</span> {'{'} posts {'}'}
113
- {'}'}
114
- <span class="cm">{'// Access data in the component'}</span>
115
- <span class="kw">const</span> data ={' '}
116
- <span class="fn">useLoaderData</span>()
92
+ <span class="cm">{"// Export a loader — runs on the server"}</span>
93
+ <span class="kw">export async function</span> <span class="fn">loader</span>(ctx) {"{"}
94
+ <span class="kw">const</span> posts = <span class="kw">await</span> db.posts.
95
+ <span class="fn">findMany</span>()
96
+ <span class="kw">return</span> {"{"} posts {"}"}
97
+ {"}"}
98
+ <span class="cm">{"// Access data in the component"}</span>
99
+ <span class="kw">const</span> data = <span class="fn">useLoaderData</span>()
117
100
  </code>
118
101
  </pre>
119
102
  </div>
@@ -1,16 +1,16 @@
1
- import { useHead } from '@pyreon/head'
2
- import { Link } from '@pyreon/zero/link'
3
- import { posts } from '../../features/posts'
1
+ import { useHead } from "@pyreon/head"
2
+ import { Link } from "@pyreon/zero/link"
3
+ import { posts } from "../../features/posts"
4
4
 
5
5
  export default function NewPostPage() {
6
6
  useHead({
7
- title: 'New Post — Pyreon Zero',
8
- meta: [{ name: 'description', content: 'Create a new post.' }],
7
+ title: "New Post — Pyreon Zero",
8
+ meta: [{ name: "description", content: "Create a new post." }],
9
9
  })
10
10
 
11
11
  const form = posts.useForm({
12
12
  onSuccess: () => {
13
- window.location.href = '/posts'
13
+ window.location.href = "/posts"
14
14
  },
15
15
  })
16
16
 
@@ -25,31 +25,27 @@ export default function NewPostPage() {
25
25
 
26
26
  <h1>New Post</h1>
27
27
  <p style="color: var(--c-text-muted); margin-bottom: var(--space-xl);">
28
- This form is powered by <code>@pyreon/feature</code> +{' '}
29
- <code>@pyreon/form</code> + <code>@pyreon/validation</code> with
30
- automatic Zod schema validation.
28
+ This form is powered by <code>@pyreon/feature</code> + <code>@pyreon/form</code> +{" "}
29
+ <code>@pyreon/validation</code> with automatic Zod schema validation.
31
30
  </p>
32
31
 
33
32
  <form onSubmit={(e: Event) => form.handleSubmit(e)}>
34
33
  <div class="form-field">
35
34
  <label for="title">Title</label>
36
- <input id="title" {...(form.register('title') as any)} />
35
+ <input id="title" {...(form.register("title") as any)} />
37
36
  </div>
38
37
  <div class="form-field">
39
38
  <label for="body">Body</label>
40
- <textarea id="body" rows={6} {...(form.register('body') as any)} />
39
+ <textarea id="body" rows={6} {...(form.register("body") as any)} />
41
40
  </div>
42
41
  <div class="form-field">
43
42
  <label class="checkbox-label">
44
- <input
45
- type="checkbox"
46
- {...(form.register('published', { type: 'checkbox' }) as any)}
47
- />
43
+ <input type="checkbox" {...(form.register("published", { type: "checkbox" }) as any)} />
48
44
  Published
49
45
  </label>
50
46
  </div>
51
47
  <button type="submit" class="btn" disabled={form.isSubmitting()}>
52
- {() => (form.isSubmitting() ? 'Creating...' : 'Create Post')}
48
+ {() => (form.isSubmitting() ? "Creating..." : "Create Post")}
53
49
  </button>
54
50
  </form>
55
51
  </div>
@@ -1,6 +1,6 @@
1
- import { defineStore, signal } from '@pyreon/store'
1
+ import { defineStore, signal } from "@pyreon/store"
2
2
 
3
- export const useAppStore = defineStore('app', () => {
3
+ export const useAppStore = defineStore("app", () => {
4
4
  const sidebarOpen = signal(true)
5
5
  const toggleSidebar = () => sidebarOpen.update((v) => !v)
6
6
  return { sidebarOpen, toggleSidebar }
@@ -1,28 +1,28 @@
1
- import pyreon from '@pyreon/vite-plugin'
2
- import zero from '@pyreon/zero'
3
- import { fontPlugin } from '@pyreon/zero/font'
4
- import { seoPlugin } from '@pyreon/zero/seo'
1
+ import pyreon from "@pyreon/vite-plugin"
2
+ import zero from "@pyreon/zero"
3
+ import { fontPlugin } from "@pyreon/zero/font"
4
+ import { seoPlugin } from "@pyreon/zero/seo"
5
5
 
6
6
  export default {
7
7
  plugins: [
8
8
  pyreon(),
9
- zero({ mode: 'ssr', ssr: { mode: 'stream' } }),
9
+ zero({ mode: "ssr", ssr: { mode: "stream" } }),
10
10
 
11
11
  // Google Fonts — self-hosted at build time, CDN in dev
12
12
  fontPlugin({
13
- google: ['Inter:wght@400;500;600;700;800', 'JetBrains Mono:wght@400'],
13
+ google: ["Inter:wght@400;500;600;700;800", "JetBrains Mono:wght@400"],
14
14
  // Size-adjusted fallbacks to eliminate CLS while fonts load
15
15
  fallbacks: {
16
- Inter: { fallback: 'Arial', sizeAdjust: 1.07, ascentOverride: 90 },
16
+ Inter: { fallback: "Arial", sizeAdjust: 1.07, ascentOverride: 90 },
17
17
  },
18
18
  }),
19
19
 
20
20
  // Generate sitemap.xml and robots.txt at build time
21
21
  seoPlugin({
22
- sitemap: { origin: 'https://example.com' },
22
+ sitemap: { origin: "https://example.com" },
23
23
  robots: {
24
- rules: [{ userAgent: '*', allow: ['/'] }],
25
- sitemap: 'https://example.com/sitemap.xml',
24
+ rules: [{ userAgent: "*", allow: ["/"] }],
25
+ sitemap: "https://example.com/sitemap.xml",
26
26
  },
27
27
  }),
28
28
  ],