@pyreon/create-zero 0.13.0 → 0.14.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/lib/index.js CHANGED
@@ -85,6 +85,22 @@ const FEATURES = {
85
85
  code: {
86
86
  label: "Code Editor (@pyreon/code — CodeMirror 6)",
87
87
  deps: ["@pyreon/code"]
88
+ },
89
+ toast: {
90
+ label: "Toast Notifications (@pyreon/toast)",
91
+ deps: ["@pyreon/toast"]
92
+ },
93
+ permissions: {
94
+ label: "Permissions (@pyreon/permissions — RBAC, feature flags)",
95
+ deps: ["@pyreon/permissions"]
96
+ },
97
+ "url-state": {
98
+ label: "URL State (@pyreon/url-state — URL-synced params)",
99
+ deps: ["@pyreon/url-state"]
100
+ },
101
+ rx: {
102
+ label: "Reactive Transforms (@pyreon/rx — filter, map, sortBy, groupBy)",
103
+ deps: ["@pyreon/rx"]
88
104
  }
89
105
  };
90
106
  const TEMPLATE_DIR = resolve(import.meta.dirname, "../templates/default");
@@ -182,13 +198,57 @@ async function main() {
182
198
  p.cancel("Cancelled.");
183
199
  process.exit(0);
184
200
  }
201
+ const compat = await p.select({
202
+ message: "Migrating from another framework?",
203
+ options: [
204
+ {
205
+ value: "none",
206
+ label: "No — native Pyreon",
207
+ hint: "recommended"
208
+ },
209
+ {
210
+ value: "react",
211
+ label: "React",
212
+ hint: "use useState, useEffect, etc."
213
+ },
214
+ {
215
+ value: "vue",
216
+ label: "Vue",
217
+ hint: "use ref, computed, watch, etc."
218
+ },
219
+ {
220
+ value: "solid",
221
+ label: "Solid",
222
+ hint: "use createSignal, createEffect, etc."
223
+ },
224
+ {
225
+ value: "preact",
226
+ label: "Preact",
227
+ hint: "use useState, signals, etc."
228
+ }
229
+ ]
230
+ });
231
+ if (p.isCancel(compat)) {
232
+ p.cancel("Cancelled.");
233
+ process.exit(0);
234
+ }
235
+ const lint = await p.confirm({
236
+ message: "Include @pyreon/lint? (59 Pyreon-specific rules)",
237
+ initialValue: true
238
+ });
239
+ if (p.isCancel(lint)) {
240
+ p.cancel("Cancelled.");
241
+ process.exit(0);
242
+ }
185
243
  const config = {
186
244
  name,
187
245
  targetDir,
188
246
  renderMode,
189
247
  features,
190
248
  packageStrategy,
191
- aiToolchain
249
+ aiToolchain,
250
+ compat,
251
+ lint
192
252
  };
193
253
  const s = p.spinner();
194
254
  s.start("Scaffolding project...");
@@ -208,6 +268,10 @@ async function scaffold(config) {
208
268
  await writeFile(join(config.targetDir, "src/entry-server.ts"), generateEntryServer(config));
209
269
  await writeFile(join(config.targetDir, "env.d.ts"), generateEnvDts(config));
210
270
  await writeFile(join(config.targetDir, ".gitignore"), "node_modules\ndist\n.DS_Store\n*.local\n.pyreon\n");
271
+ if (config.lint) await writeFile(join(config.targetDir, ".pyreonlintrc.json"), JSON.stringify({
272
+ $schema: "node_modules/@pyreon/lint/schema/pyreonlintrc.schema.json",
273
+ preset: "recommended"
274
+ }, null, 2) + "\n");
211
275
  if (config.aiToolchain) await writeFile(join(config.targetDir, ".mcp.json"), JSON.stringify({ mcpServers: { pyreon: {
212
276
  command: "bunx",
213
277
  args: ["@pyreon/mcp"]
@@ -276,19 +340,29 @@ function generatePackageJson(config) {
276
340
  vite: "^8.0.3"
277
341
  };
278
342
  if (config.aiToolchain) devDeps["@pyreon/mcp"] = pyreonVersion("@pyreon/mcp");
343
+ const compatPkgMap = {
344
+ react: "@pyreon/react-compat",
345
+ vue: "@pyreon/vue-compat",
346
+ solid: "@pyreon/solid-compat",
347
+ preact: "@pyreon/preact-compat"
348
+ };
349
+ if (config.compat !== "none" && compatPkgMap[config.compat]) deps[compatPkgMap[config.compat]] = pyreonVersion(compatPkgMap[config.compat]);
350
+ if (config.lint) devDeps["@pyreon/lint"] = pyreonVersion("@pyreon/lint");
351
+ const scripts = {
352
+ dev: "zero dev",
353
+ build: "zero build",
354
+ preview: "zero preview",
355
+ doctor: "zero doctor",
356
+ "doctor:fix": "zero doctor --fix",
357
+ "doctor:ci": "zero doctor --ci"
358
+ };
359
+ if (config.lint) scripts.lint = "pyreon-lint .";
279
360
  const pkg = {
280
361
  name: basename(config.name),
281
362
  version: "0.0.1",
282
363
  private: true,
283
364
  type: "module",
284
- scripts: {
285
- dev: "zero dev",
286
- build: "zero build",
287
- preview: "zero preview",
288
- doctor: "zero doctor",
289
- "doctor:fix": "zero doctor --fix",
290
- "doctor:ci": "zero doctor --ci"
291
- },
365
+ scripts,
292
366
  dependencies: Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b))),
293
367
  devDependencies: Object.fromEntries(Object.entries(devDeps).sort(([a], [b]) => a.localeCompare(b)))
294
368
  };
@@ -302,7 +376,7 @@ import { seoPlugin } from '@pyreon/zero/seo'
302
376
 
303
377
  export default {
304
378
  plugins: [
305
- pyreon(),
379
+ pyreon(${config.compat !== "none" ? `{ compat: '${config.compat}' }` : ""}),
306
380
  zero({ ${{
307
381
  "ssr-stream": `mode: 'ssr', ssr: { mode: 'stream' }`,
308
382
  "ssr-string": `mode: 'ssr'`,
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { existsSync, readFileSync } 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/**\n * All @pyreon/* packages share the same version in the monorepo.\n * Read from this package's own version — no manual updates needed.\n * When we bump versions for release, create-zero automatically uses the new version.\n */\nconst _ownPkgJson = JSON.parse(\n readFileSync(resolve(import.meta.dirname, '..', 'package.json'), 'utf-8'),\n)\nconst PYREON_VERSION = `^${_ownPkgJson.version}`\n\nfunction pyreonVersion(_pkg: string): string {\n return PYREON_VERSION\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: '^6.0.2',\n vite: '^8.0.3',\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/server'\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/server'`,\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\ndeclare module 'virtual:zero/api-routes' {\n import type { ApiRouteEntry } from '@pyreon/zero/api-routes'\n export const apiRoutes: ApiRouteEntry[]\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, readFileSync } 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 /** Framework compat mode — configures vite plugin for React/Vue/Solid/Preact migration */\n compat: 'none' | 'react' | 'vue' | 'solid' | 'preact'\n /** Include @pyreon/lint with recommended preset */\n lint: 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 toast: {\n label: 'Toast Notifications (@pyreon/toast)',\n deps: ['@pyreon/toast'],\n },\n permissions: {\n label: 'Permissions (@pyreon/permissions — RBAC, feature flags)',\n deps: ['@pyreon/permissions'],\n },\n 'url-state': {\n label: 'URL State (@pyreon/url-state — URL-synced params)',\n deps: ['@pyreon/url-state'],\n },\n rx: {\n label: 'Reactive Transforms (@pyreon/rx — filter, map, sortBy, groupBy)',\n deps: ['@pyreon/rx'],\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 // Compat mode (migration from another framework)\n const compat = await p.select({\n message: 'Migrating from another framework?',\n options: [\n { value: 'none', label: 'No — native Pyreon', hint: 'recommended' },\n { value: 'react', label: 'React', hint: 'use useState, useEffect, etc.' },\n { value: 'vue', label: 'Vue', hint: 'use ref, computed, watch, etc.' },\n { value: 'solid', label: 'Solid', hint: 'use createSignal, createEffect, etc.' },\n { value: 'preact', label: 'Preact', hint: 'use useState, signals, etc.' },\n ],\n })\n\n if (p.isCancel(compat)) {\n p.cancel('Cancelled.')\n process.exit(0)\n }\n\n // Lint\n const lint = await p.confirm({\n message: 'Include @pyreon/lint? (59 Pyreon-specific rules)',\n initialValue: true,\n })\n\n if (p.isCancel(lint)) {\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 compat: compat as ProjectConfig['compat'],\n lint: lint 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 // Lint config\n if (config.lint) {\n await writeFile(\n join(config.targetDir, '.pyreonlintrc.json'),\n JSON.stringify(\n {\n $schema: 'node_modules/@pyreon/lint/schema/pyreonlintrc.schema.json',\n preset: 'recommended',\n },\n null,\n 2,\n ) + '\\n',\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/**\n * All @pyreon/* packages share the same version in the monorepo.\n * Read from this package's own version — no manual updates needed.\n * When we bump versions for release, create-zero automatically uses the new version.\n */\nconst _ownPkgJson = JSON.parse(\n readFileSync(resolve(import.meta.dirname, '..', 'package.json'), 'utf-8'),\n)\nconst PYREON_VERSION = `^${_ownPkgJson.version}`\n\nfunction pyreonVersion(_pkg: string): string {\n return PYREON_VERSION\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: '^6.0.2',\n vite: '^8.0.3',\n }\n\n if (config.aiToolchain) {\n devDeps['@pyreon/mcp'] = pyreonVersion('@pyreon/mcp')\n }\n\n // Compat mode deps\n const compatPkgMap: Record<string, string> = {\n react: '@pyreon/react-compat',\n vue: '@pyreon/vue-compat',\n solid: '@pyreon/solid-compat',\n preact: '@pyreon/preact-compat',\n }\n if (config.compat !== 'none' && compatPkgMap[config.compat]) {\n deps[compatPkgMap[config.compat]!] = pyreonVersion(compatPkgMap[config.compat]!)\n }\n\n // Lint\n if (config.lint) {\n devDeps['@pyreon/lint'] = pyreonVersion('@pyreon/lint')\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 if (config.lint) {\n scripts.lint = 'pyreon-lint .'\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 const pyreonOpts = config.compat !== 'none' ? `{ compat: '${config.compat}' }` : ''\n\n return `import pyreon from '@pyreon/vite-plugin'\nimport zero from '@pyreon/zero/server'\nimport { fontPlugin } from '@pyreon/zero/font'\nimport { seoPlugin } from '@pyreon/zero/seo'\n\nexport default {\n plugins: [\n pyreon(${pyreonOpts}),\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/server'`,\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\ndeclare module 'virtual:zero/api-routes' {\n import type { ApiRouteEntry } from '@pyreon/zero/api-routes'\n export const apiRoutes: ApiRouteEntry[]\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,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/create-zero",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Create a new Pyreon Zero project",
5
5
  "license": "MIT",
6
6
  "author": "Vit Bokisch",
@@ -9,17 +9,17 @@ This project uses Pyreon Zero, a signal-based full-stack meta-framework. Do NOT
9
9
  - Use `effect()` not `useEffect`
10
10
  - Use `onMount` / `onUnmount` not `useEffect` with deps
11
11
  - Use `signal.set(value)` or `signal.update(fn)` to write signals
12
- - Read signals by calling them: `count()` not `count`
12
+ - In JSX, signals and computeds are **auto-called** — just write `{count}` not `{count()}`
13
+ - Outside JSX, call signals explicitly: `count()` to read, `count.set(5)` to write
13
14
 
14
15
  ## JSX
15
16
 
16
17
  - Use `class=` not `className`
17
18
  - Use `for=` not `htmlFor`
18
19
  - Use camelCase events: `onClick`, `onMouseEnter`, `onLoad` (not `onclick`, `onmouseenter`)
19
- - Use `srcSet` not `srcset`, `fetchPriority` not `fetchpriority`
20
- - Reactive text: `{() => count()}`
21
- - Conditional: `{() => show() ? <A /> : null}`
22
- - Lists: `{() => items().map(item => <Item />)}`
20
+ - Signal auto-call in JSX: `{count}` compiler inserts `()` for you
21
+ - Conditional: `{show ? <A /> : null}` — signals auto-called inside expressions too
22
+ - Lists: `{items().map(item => <Item />)}` — `.map()` still needs explicit `()`
23
23
  - Events: `onClick={() => ...}`
24
24
  - JSX import source is `@pyreon/core` (auto-configured, no manual import needed)
25
25
 
@@ -25,7 +25,8 @@ export default function Counter() {
25
25
  </div>
26
26
 
27
27
  <div class="counter-demo">
28
- <div class="counter-display">{() => count()}</div>
28
+ {/* Signal auto-call: just write {count} — the compiler adds () for you */}
29
+ <div class="counter-display">{count}</div>
29
30
 
30
31
  <div class="counter-controls">
31
32
  <button
@@ -48,35 +49,36 @@ export default function Counter() {
48
49
  </div>
49
50
 
50
51
  <div class="counter-meta">
52
+ {/* No () needed — signals and computeds are auto-called in JSX */}
51
53
  <div>
52
- count() → <strong>{() => count()}</strong>
54
+ count → <strong>{count}</strong>
53
55
  </div>
54
56
  <div>
55
- doubled() → <strong>{() => doubled()}</strong>
57
+ doubled → <strong>{doubled}</strong>
56
58
  </div>
57
59
  <div>
58
- isEven() → <strong>{() => (isEven() ? "true" : "false")}</strong>
60
+ isEven → <strong>{isEven ? "true" : "false"}</strong>
59
61
  </div>
60
62
  </div>
61
63
  </div>
62
64
 
63
65
  <div class="code-block" style="max-width: 520px; margin: var(--space-2xl) auto 0;">
64
66
  <div class="code-block-header">
65
- <span>counter.tsx</span>
67
+ <span>counter.tsx — signal auto-call</span>
66
68
  </div>
67
69
  <pre>
68
70
  <code>
69
- <span class="kw">import</span> {"{"} signal, computed {"}"} <span class="kw">from</span>{" "}
70
- <span class="str">"@pyreon/reactivity"</span>
71
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;{" "}
74
- <span class="fn">count</span>() * <span class="str">2</span>)
75
- <span class="cm">{"// Just reference the signal in JSX —"}</span>
76
- <span class="cm">{"// only this text node updates"}</span>
77
- <span class="tag">&lt;span&gt;</span>
78
- {"{"}count{"}"}
79
- <span class="tag">&lt;/span&gt;</span>
72
+ <span class="fn">signal</span>(<span class="str">0</span>)
73
+ <span class="kw">const</span> <span class="fn">doubled</span> ={" "}
74
+ <span class="fn">computed</span>(() =&gt; <span class="fn">count</span>() * <span class="str">2</span>)
75
+ {"\n"}
76
+ <span class="cm">{"// Plain JS no () needed in JSX:"}</span>
77
+ <span class="tag">&lt;div&gt;</span>
78
+ {"{"}count{"}"} × 2 = {"{"}doubled{"}"}
79
+ <span class="tag">&lt;/div&gt;</span>
80
+ {"\n"}
81
+ <span class="cm">{"// Compiler auto-calls signals for you ✓"}</span>
80
82
  </code>
81
83
  </pre>
82
84
  </div>