@pyreon/zero-cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,31 +1,51 @@
1
- # zero-cli
1
+ # @pyreon/zero-cli
2
2
 
3
- CLI for [Pyreon Zero](https://github.com/user/pyreon-zero) — dev server, production builds, and preview.
3
+ Unified CLI for [Pyreon Zero](https://github.com/pyreon/zero) — development, builds, code quality, and project scaffolding.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- bun add -D zero-cli
8
+ bun add -D @pyreon/zero-cli
9
9
  ```
10
10
 
11
11
  ## Commands
12
12
 
13
+ ### Development
14
+
15
+ ```bash
16
+ zero dev [root] # Start dev server (prints route table on startup)
17
+ --port <port> # Server port (default: 3000)
18
+ --host [host] # Server host (use --host for 0.0.0.0)
19
+ --open # Open browser on start
20
+
21
+ zero build [root] # Build for production
22
+ --mode <mode> # Rendering mode override (ssr, ssg, spa)
23
+
24
+ zero preview [root] # Preview production build
25
+ --port <port> # Server port (default: 3000)
26
+ --host [host] # Server host
27
+ ```
28
+
29
+ ### Code Quality
30
+
13
31
  ```bash
14
- zero dev [root] # Start development server
15
- zero build [root] # Build for production
16
- zero preview [root] # Preview production build
32
+ zero doctor [root] # Detect React patterns in codebase
33
+ --fix # Auto-fix fixable issues (className → class, etc.)
34
+ --json # Output as JSON
35
+ --ci # CI mode — exit with code 1 on errors
17
36
  ```
18
37
 
19
- ### Options
38
+ ### Project Context
20
39
 
21
40
  ```bash
22
- # dev / preview
23
- --port <port> # Server port (default: 3000)
24
- --host [host] # Server host
25
- --open # Open browser on start
41
+ zero context [root] # Generate AI project context (.pyreon/context.json)
42
+ --out <path> # Custom output path
43
+ ```
26
44
 
27
- # build
28
- --mode <mode> # Rendering mode override (ssr, ssg, spa)
45
+ ### Scaffolding
46
+
47
+ ```bash
48
+ zero create <name> # Scaffold a new Pyreon Zero project
29
49
  ```
30
50
 
31
51
  ## License
package/lib/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import cac from "cac";
2
2
  import { existsSync } from "node:fs";
3
- import { join, resolve } from "node:path";
3
+ import { basename, join, resolve } from "node:path";
4
4
  import { build, createServer, preview } from "vite";
5
+ import { cp, readFile, writeFile } from "node:fs/promises";
5
6
 
6
7
  //#region src/commands/build.ts
7
8
  /** Extract zero plugin config from a Vite config file. */
@@ -80,17 +81,73 @@ async function runBuild(root, options) {
80
81
  const zeroConfig = await loadZeroConfig(join(projectRoot, "vite.config.ts"));
81
82
  const renderMode = zeroConfig?.mode ?? options.mode ?? "ssr";
82
83
  if (renderMode === "ssg" || renderMode === "isr") await prerenderIfNeeded(projectRoot, zeroConfig);
83
- if (!((zeroConfig?.adapter ?? "node") === "static" && renderMode === "ssg")) await runAdapter(projectRoot, zeroConfig ?? {});
84
+ await runAdapter(projectRoot, zeroConfig ?? {});
84
85
  const elapsed = Math.round(performance.now() - start);
85
86
  console.log(`Build completed in ${elapsed}ms`);
86
87
  }
87
88
 
89
+ //#endregion
90
+ //#region src/commands/context.ts
91
+ async function context(root, options) {
92
+ try {
93
+ const { generateContext } = await import("@pyreon/cli");
94
+ await generateContext({
95
+ cwd: resolve(root ?? "."),
96
+ outPath: options.out
97
+ });
98
+ } catch (error) {
99
+ console.error("Context generation failed:", error.message);
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ //#endregion
105
+ //#region src/commands/create.ts
106
+ async function create(name) {
107
+ if (!name) {
108
+ console.error("Usage: zero create <project-name>");
109
+ process.exit(1);
110
+ }
111
+ const targetDir = resolve(process.cwd(), name);
112
+ if (existsSync(targetDir)) {
113
+ console.error(`Directory "${name}" already exists.`);
114
+ process.exit(1);
115
+ }
116
+ try {
117
+ let sourceDir = resolve(import.meta.dirname, "../../node_modules/@pyreon/create-zero/templates/default");
118
+ if (!existsSync(sourceDir)) {
119
+ const altDir = resolve(import.meta.dirname, "../../../create-zero/templates/default");
120
+ if (existsSync(altDir)) sourceDir = altDir;
121
+ else {
122
+ console.error("Template not found. Install @pyreon/create-zero or use: bun create @pyreon/zero");
123
+ process.exit(1);
124
+ }
125
+ }
126
+ await cp(sourceDir, targetDir, { recursive: true });
127
+ const pkgPath = join(targetDir, "package.json");
128
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
129
+ pkg.name = basename(name);
130
+ await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
131
+ await writeFile(join(targetDir, ".gitignore"), "node_modules\ndist\n.DS_Store\n*.local\n");
132
+ console.log(`\nCreated "${name}"!\n`);
133
+ console.log("Next steps:");
134
+ console.log(` cd ${name}`);
135
+ console.log(" bun install");
136
+ console.log(" bun run dev");
137
+ console.log("");
138
+ } catch (error) {
139
+ console.error("Failed to create project:", error.message);
140
+ process.exit(1);
141
+ }
142
+ }
143
+
88
144
  //#endregion
89
145
  //#region src/commands/dev.ts
90
146
  async function dev(root, options) {
91
147
  try {
148
+ const projectRoot = resolve(root ?? ".");
92
149
  const server = await createServer({
93
- root: resolve(root ?? "."),
150
+ root: projectRoot,
94
151
  server: {
95
152
  port: options.port ?? 3e3,
96
153
  host: options.host === true ? "0.0.0.0" : options.host,
@@ -99,11 +156,57 @@ async function dev(root, options) {
99
156
  });
100
157
  await server.listen();
101
158
  server.printUrls();
159
+ await printRouteTable(projectRoot);
102
160
  } catch (error) {
103
161
  console.error("Failed to start dev server:", error.message);
104
162
  process.exit(1);
105
163
  }
106
164
  }
165
+ async function printRouteTable(projectRoot) {
166
+ try {
167
+ const routesDir = join(projectRoot, "src/routes");
168
+ if (!existsSync(routesDir)) return;
169
+ const { scanRouteFiles, parseFileRoutes } = await import("@pyreon/zero");
170
+ const { isApiRoute, apiFilePathToPattern } = await import("@pyreon/zero/api-routes");
171
+ const files = await scanRouteFiles(routesDir);
172
+ const pageRoutes = parseFileRoutes(files).filter((r) => !r.isLayout && !r.isError && !r.isLoading && !isApiRoute(r.filePath));
173
+ const apiFiles = files.filter(isApiRoute);
174
+ if (pageRoutes.length === 0 && apiFiles.length === 0) return;
175
+ console.log("");
176
+ console.log(" \x1B[36m Routes\x1B[0m");
177
+ console.log("");
178
+ for (const route of pageRoutes) {
179
+ const mode = route.renderMode.toUpperCase();
180
+ console.log(` \x1b[2m${mode.padEnd(4)}\x1b[0m ${route.urlPath}`);
181
+ }
182
+ if (apiFiles.length > 0) {
183
+ console.log("");
184
+ console.log(" \x1B[33m API Routes\x1B[0m");
185
+ console.log("");
186
+ for (const file of apiFiles) console.log(` \x1b[2mAPI \x1b[0m ${apiFilePathToPattern(file)}`);
187
+ }
188
+ console.log("");
189
+ } catch {}
190
+ }
191
+
192
+ //#endregion
193
+ //#region src/commands/doctor.ts
194
+ async function doctor(root, options) {
195
+ try {
196
+ const { doctor: runDoctor } = await import("@pyreon/cli");
197
+ const cwd = resolve(root ?? ".");
198
+ const errorCount = await runDoctor({
199
+ fix: options.fix ?? false,
200
+ json: options.json ?? false,
201
+ ci: options.ci ?? false,
202
+ cwd
203
+ });
204
+ if (options.ci && errorCount > 0) process.exit(1);
205
+ } catch (error) {
206
+ console.error("Doctor failed:", error.message);
207
+ process.exit(1);
208
+ }
209
+ }
107
210
 
108
211
  //#endregion
109
212
  //#region src/commands/preview.ts
@@ -128,6 +231,9 @@ const cli = cac("zero");
128
231
  cli.command("[root]", "Start dev server").alias("dev").option("--port <port>", "Server port", { default: 3e3 }).option("--host [host]", "Server host").option("--open", "Open browser on start").action(dev);
129
232
  cli.command("build [root]", "Build for production").option("--mode <mode>", "Rendering mode override").action(build$1);
130
233
  cli.command("preview [root]", "Preview production build").option("--port <port>", "Server port", { default: 3e3 }).option("--host [host]", "Server host").action(preview$1);
234
+ cli.command("doctor [root]", "Check for React patterns and framework issues").option("--fix", "Auto-fix fixable issues").option("--json", "Output as JSON").option("--ci", "CI mode — exit with code 1 on errors").action(doctor);
235
+ cli.command("context [root]", "Generate project context for AI tools").option("--out <path>", "Output path (default: .pyreon/context.json)").action(context);
236
+ cli.command("create <name>", "Scaffold a new Pyreon Zero project").action(create);
131
237
  cli.help();
132
238
  cli.version("0.0.1");
133
239
  cli.parse();
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["build","viteBuild","preview","vitePreview","build","preview"],"sources":["../src/commands/build.ts","../src/commands/dev.ts","../src/commands/preview.ts","../src/index.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { build as viteBuild } from 'vite'\n\nexport interface BuildOptions {\n mode?: string\n}\n\n/** Extract zero plugin config from a Vite config file. */\nasync function loadZeroConfig(\n configPath: string,\n): Promise<Record<string, unknown> | undefined> {\n try {\n const { loadConfigFromFile } = await import('vite')\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'production' },\n configPath,\n )\n if (!loaded) return undefined\n\n const plugins = (loaded.config.plugins ?? []) as Array<{\n name?: string\n _zeroConfig?: Record<string, unknown>\n }>\n const zeroPlugin = plugins.find(\n (p) =>\n p && typeof p === 'object' && 'name' in p && p.name === 'pyreon-zero',\n )\n if (zeroPlugin && '_zeroConfig' in zeroPlugin) {\n return zeroPlugin._zeroConfig as Record<string, unknown>\n }\n } catch {\n // Config loading is optional — fall back to defaults\n }\n return undefined\n}\n\n/** Run SSG prerendering pass if configured. */\nasync function prerenderIfNeeded(\n projectRoot: string,\n zeroConfig: Record<string, unknown> | undefined,\n) {\n const serverEntry = join(projectRoot, 'dist/server/entry-server.js')\n if (!existsSync(serverEntry)) return\n\n try {\n const { prerender } = await import('@pyreon/server')\n const serverModule = await import(serverEntry)\n\n const paths = await resolveSsgPaths(zeroConfig)\n const result = await prerender({\n handler: serverModule.default,\n paths,\n outDir: join(projectRoot, 'dist/client'),\n })\n\n for (const err of result.errors) {\n console.warn('Prerender error:', err)\n }\n } catch {\n // Prerender is best-effort — build continues without it\n }\n}\n\n/** Resolve SSG paths from config (static array or async function). */\nasync function resolveSsgPaths(\n zeroConfig: Record<string, unknown> | undefined,\n): Promise<string[]> {\n const ssgConfig = zeroConfig?.ssg as\n | { paths?: string[] | (() => string[] | Promise<string[]>) }\n | undefined\n if (!ssgConfig?.paths) return ['/']\n return typeof ssgConfig.paths === 'function'\n ? await ssgConfig.paths()\n : ssgConfig.paths\n}\n\n/** Run the deploy adapter build step. */\nasync function runAdapter(\n projectRoot: string,\n zeroConfig: Record<string, unknown>,\n) {\n try {\n const { resolveAdapter } = await import('@pyreon/zero')\n const adapter = resolveAdapter(zeroConfig)\n await adapter.build({\n serverEntry: join(projectRoot, 'dist/server/entry-server.js'),\n clientOutDir: join(projectRoot, 'dist/client'),\n outDir: join(projectRoot, 'dist/output'),\n config: zeroConfig,\n })\n } catch {\n // Adapter build is optional — output may not need it\n }\n}\n\nexport async function build(root: string | undefined, options: BuildOptions) {\n try {\n await runBuild(root, options)\n } catch (error) {\n console.error('Build failed:', (error as Error).message)\n process.exit(1)\n }\n}\n\nasync function runBuild(root: string | undefined, options: BuildOptions) {\n const projectRoot = resolve(root ?? '.')\n const start = performance.now()\n\n // Client build\n await viteBuild({\n root: projectRoot,\n build: { outDir: 'dist/client', ssrManifest: true },\n })\n\n // Server build\n await viteBuild({\n root: projectRoot,\n build: {\n outDir: 'dist/server',\n ssr: 'src/entry-server.ts',\n rollupOptions: { input: 'src/entry-server.ts' },\n },\n })\n\n const configPath = join(projectRoot, 'vite.config.ts')\n const zeroConfig = await loadZeroConfig(configPath)\n const renderMode = (zeroConfig?.mode as string) ?? options.mode ?? 'ssr'\n\n if (renderMode === 'ssg' || renderMode === 'isr') {\n await prerenderIfNeeded(projectRoot, zeroConfig)\n }\n\n const adapterName = (zeroConfig?.adapter as string) ?? 'node'\n if (!(adapterName === 'static' && renderMode === 'ssg')) {\n await runAdapter(projectRoot, zeroConfig ?? {})\n }\n\n const elapsed = Math.round(performance.now() - start)\n console.log(`Build completed in ${elapsed}ms`)\n}\n","import { resolve } from 'node:path'\nimport { createServer } from 'vite'\n\nexport interface DevOptions {\n port?: number\n host?: string | boolean\n open?: boolean\n}\n\nexport async function dev(root: string | undefined, options: DevOptions) {\n try {\n const projectRoot = resolve(root ?? '.')\n\n const server = await createServer({\n root: projectRoot,\n server: {\n port: options.port ?? 3000,\n host: options.host === true ? '0.0.0.0' : options.host,\n open: options.open,\n },\n })\n\n await server.listen()\n server.printUrls()\n } catch (error) {\n console.error('Failed to start dev server:', (error as Error).message)\n process.exit(1)\n }\n}\n","import { resolve } from 'node:path'\nimport { preview as vitePreview } from 'vite'\n\nexport interface PreviewOptions {\n port?: number\n host?: string | boolean\n}\n\nexport async function preview(\n root: string | undefined,\n options: PreviewOptions,\n) {\n try {\n const projectRoot = resolve(root ?? '.')\n\n const server = await vitePreview({\n root: projectRoot,\n preview: {\n port: options.port ?? 3000,\n host: options.host === true ? '0.0.0.0' : options.host,\n },\n })\n\n server.printUrls()\n } catch (error) {\n console.error('Failed to start preview server:', (error as Error).message)\n process.exit(1)\n }\n}\n","import cac from 'cac'\nimport { build } from './commands/build'\nimport { dev } from './commands/dev'\nimport { preview } from './commands/preview'\n\nconst cli = cac('zero')\n\ncli\n .command('[root]', 'Start dev server')\n .alias('dev')\n .option('--port <port>', 'Server port', { default: 3000 })\n .option('--host [host]', 'Server host')\n .option('--open', 'Open browser on start')\n .action(dev)\n\ncli\n .command('build [root]', 'Build for production')\n .option('--mode <mode>', 'Rendering mode override')\n .action(build)\n\ncli\n .command('preview [root]', 'Preview production build')\n .option('--port <port>', 'Server port', { default: 3000 })\n .option('--host [host]', 'Server host')\n .action(preview)\n\ncli.help()\ncli.version('0.0.1')\ncli.parse()\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["build","viteBuild","preview","vitePreview","build","preview"],"sources":["../src/commands/build.ts","../src/commands/context.ts","../src/commands/create.ts","../src/commands/dev.ts","../src/commands/doctor.ts","../src/commands/preview.ts","../src/index.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { build as viteBuild } from 'vite'\n\nexport interface BuildOptions {\n mode?: string\n}\n\n/** Extract zero plugin config from a Vite config file. */\nasync function loadZeroConfig(\n configPath: string,\n): Promise<Record<string, unknown> | undefined> {\n try {\n const { loadConfigFromFile } = await import('vite')\n const loaded = await loadConfigFromFile(\n { command: 'build', mode: 'production' },\n configPath,\n )\n if (!loaded) return undefined\n\n const plugins = (loaded.config.plugins ?? []) as Array<{\n name?: string\n _zeroConfig?: Record<string, unknown>\n }>\n const zeroPlugin = plugins.find(\n (p) =>\n p && typeof p === 'object' && 'name' in p && p.name === 'pyreon-zero',\n )\n if (zeroPlugin && '_zeroConfig' in zeroPlugin) {\n return zeroPlugin._zeroConfig as Record<string, unknown>\n }\n } catch {\n // Config loading is optional — fall back to defaults\n }\n return undefined\n}\n\n/** Run SSG prerendering pass if configured. */\nasync function prerenderIfNeeded(\n projectRoot: string,\n zeroConfig: Record<string, unknown> | undefined,\n) {\n const serverEntry = join(projectRoot, 'dist/server/entry-server.js')\n if (!existsSync(serverEntry)) return\n\n try {\n const { prerender } = await import('@pyreon/server')\n const serverModule = await import(serverEntry)\n\n const paths = await resolveSsgPaths(zeroConfig)\n const result = await prerender({\n handler: serverModule.default,\n paths,\n outDir: join(projectRoot, 'dist/client'),\n })\n\n for (const err of result.errors) {\n console.warn('Prerender error:', err)\n }\n } catch {\n // Prerender is best-effort — build continues without it\n }\n}\n\n/** Resolve SSG paths from config (static array or async function). */\nasync function resolveSsgPaths(\n zeroConfig: Record<string, unknown> | undefined,\n): Promise<string[]> {\n const ssgConfig = zeroConfig?.ssg as\n | { paths?: string[] | (() => string[] | Promise<string[]>) }\n | undefined\n if (!ssgConfig?.paths) return ['/']\n return typeof ssgConfig.paths === 'function'\n ? await ssgConfig.paths()\n : ssgConfig.paths\n}\n\n/** Run the deploy adapter build step. */\nasync function runAdapter(\n projectRoot: string,\n zeroConfig: Record<string, unknown>,\n) {\n try {\n const { resolveAdapter } = await import('@pyreon/zero')\n const adapter = resolveAdapter(zeroConfig)\n await adapter.build({\n serverEntry: join(projectRoot, 'dist/server/entry-server.js'),\n clientOutDir: join(projectRoot, 'dist/client'),\n outDir: join(projectRoot, 'dist/output'),\n config: zeroConfig,\n })\n } catch {\n // Adapter build is optional — output may not need it\n }\n}\n\nexport async function build(root: string | undefined, options: BuildOptions) {\n try {\n await runBuild(root, options)\n } catch (error) {\n console.error('Build failed:', (error as Error).message)\n process.exit(1)\n }\n}\n\nasync function runBuild(root: string | undefined, options: BuildOptions) {\n const projectRoot = resolve(root ?? '.')\n const start = performance.now()\n\n // Client build\n await viteBuild({\n root: projectRoot,\n build: { outDir: 'dist/client', ssrManifest: true },\n })\n\n // Server build\n await viteBuild({\n root: projectRoot,\n build: {\n outDir: 'dist/server',\n ssr: 'src/entry-server.ts',\n rollupOptions: { input: 'src/entry-server.ts' },\n },\n })\n\n const configPath = join(projectRoot, 'vite.config.ts')\n const zeroConfig = await loadZeroConfig(configPath)\n const renderMode = (zeroConfig?.mode as string) ?? options.mode ?? 'ssr'\n\n if (renderMode === 'ssg' || renderMode === 'isr') {\n await prerenderIfNeeded(projectRoot, zeroConfig)\n }\n\n await runAdapter(projectRoot, zeroConfig ?? {})\n\n const elapsed = Math.round(performance.now() - start)\n console.log(`Build completed in ${elapsed}ms`)\n}\n","import { resolve } from 'node:path'\n\nexport interface ContextOptions {\n out?: string\n}\n\nexport async function context(\n root: string | undefined,\n options: ContextOptions,\n) {\n try {\n const { generateContext } = await import('@pyreon/cli')\n const cwd = resolve(root ?? '.')\n await generateContext({\n cwd,\n outPath: options.out,\n })\n } catch (error) {\n console.error('Context generation failed:', (error as Error).message)\n process.exit(1)\n }\n}\n","import { existsSync } from 'node:fs'\nimport { cp, readFile, writeFile } from 'node:fs/promises'\nimport { basename, join, resolve } from 'node:path'\n\nexport async function create(name: string | undefined) {\n if (!name) {\n console.error('Usage: zero create <project-name>')\n process.exit(1)\n }\n\n const targetDir = resolve(process.cwd(), name)\n\n if (existsSync(targetDir)) {\n console.error(`Directory \"${name}\" already exists.`)\n process.exit(1)\n }\n\n try {\n // Resolve template directory relative to this package\n const templateDir = resolve(\n import.meta.dirname,\n '../../node_modules/@pyreon/create-zero/templates/default',\n )\n\n // Fallback: try workspace resolution\n let sourceDir = templateDir\n if (!existsSync(sourceDir)) {\n const altDir = resolve(\n import.meta.dirname,\n '../../../create-zero/templates/default',\n )\n if (existsSync(altDir)) {\n sourceDir = altDir\n } else {\n console.error(\n 'Template not found. Install @pyreon/create-zero or use: bun create @pyreon/zero',\n )\n process.exit(1)\n }\n }\n\n // Copy template\n await cp(sourceDir, targetDir, { recursive: true })\n\n // Update package.json with project name\n const pkgPath = join(targetDir, 'package.json')\n const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'))\n pkg.name = basename(name)\n await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\\n`)\n\n // Create .gitignore (npm strips .gitignore from packages)\n await writeFile(\n join(targetDir, '.gitignore'),\n 'node_modules\\ndist\\n.DS_Store\\n*.local\\n',\n )\n\n console.log(`\\nCreated \"${name}\"!\\n`)\n console.log('Next steps:')\n console.log(` cd ${name}`)\n console.log(' bun install')\n console.log(' bun run dev')\n console.log('')\n } catch (error) {\n console.error('Failed to create project:', (error as Error).message)\n process.exit(1)\n }\n}\n","import { existsSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { createServer } from 'vite'\n\nexport interface DevOptions {\n port?: number\n host?: string | boolean\n open?: boolean\n}\n\nexport async function dev(root: string | undefined, options: DevOptions) {\n try {\n const projectRoot = resolve(root ?? '.')\n\n const server = await createServer({\n root: projectRoot,\n server: {\n port: options.port ?? 3000,\n host: options.host === true ? '0.0.0.0' : options.host,\n open: options.open,\n },\n })\n\n await server.listen()\n server.printUrls()\n\n // Print route table after server starts\n await printRouteTable(projectRoot)\n } catch (error) {\n console.error('Failed to start dev server:', (error as Error).message)\n process.exit(1)\n }\n}\n\nasync function printRouteTable(projectRoot: string) {\n try {\n const routesDir = join(projectRoot, 'src/routes')\n if (!existsSync(routesDir)) return\n\n const { scanRouteFiles, parseFileRoutes } = await import('@pyreon/zero')\n const { isApiRoute, apiFilePathToPattern } = await import(\n '@pyreon/zero/api-routes'\n )\n\n const files = await scanRouteFiles(routesDir)\n const pageRoutes = parseFileRoutes(files).filter(\n (r) => !r.isLayout && !r.isError && !r.isLoading && !isApiRoute(r.filePath),\n )\n const apiFiles = files.filter(isApiRoute)\n\n if (pageRoutes.length === 0 && apiFiles.length === 0) return\n\n console.log('')\n console.log(' \\x1b[36m Routes\\x1b[0m')\n console.log('')\n\n for (const route of pageRoutes) {\n const mode = route.renderMode.toUpperCase()\n console.log(\n ` \\x1b[2m${mode.padEnd(4)}\\x1b[0m ${route.urlPath}`,\n )\n }\n\n if (apiFiles.length > 0) {\n console.log('')\n console.log(' \\x1b[33m API Routes\\x1b[0m')\n console.log('')\n for (const file of apiFiles) {\n console.log(` \\x1b[2mAPI \\x1b[0m ${apiFilePathToPattern(file)}`)\n }\n }\n\n console.log('')\n } catch {\n // Route table is informational — don't fail dev server\n }\n}\n","import { resolve } from 'node:path'\n\nexport interface DoctorOptions {\n fix?: boolean\n json?: boolean\n ci?: boolean\n}\n\nexport async function doctor(\n root: string | undefined,\n options: DoctorOptions,\n) {\n try {\n const { doctor: runDoctor } = await import('@pyreon/cli')\n const cwd = resolve(root ?? '.')\n const errorCount = await runDoctor({\n fix: options.fix ?? false,\n json: options.json ?? false,\n ci: options.ci ?? false,\n cwd,\n })\n if (options.ci && errorCount > 0) {\n process.exit(1)\n }\n } catch (error) {\n console.error('Doctor failed:', (error as Error).message)\n process.exit(1)\n }\n}\n","import { resolve } from 'node:path'\nimport { preview as vitePreview } from 'vite'\n\nexport interface PreviewOptions {\n port?: number\n host?: string | boolean\n}\n\nexport async function preview(\n root: string | undefined,\n options: PreviewOptions,\n) {\n try {\n const projectRoot = resolve(root ?? '.')\n\n const server = await vitePreview({\n root: projectRoot,\n preview: {\n port: options.port ?? 3000,\n host: options.host === true ? '0.0.0.0' : options.host,\n },\n })\n\n server.printUrls()\n } catch (error) {\n console.error('Failed to start preview server:', (error as Error).message)\n process.exit(1)\n }\n}\n","import cac from 'cac'\nimport { build } from './commands/build'\nimport { context } from './commands/context'\nimport { create } from './commands/create'\nimport { dev } from './commands/dev'\nimport { doctor } from './commands/doctor'\nimport { preview } from './commands/preview'\n\nconst cli = cac('zero')\n\ncli\n .command('[root]', 'Start dev server')\n .alias('dev')\n .option('--port <port>', 'Server port', { default: 3000 })\n .option('--host [host]', 'Server host')\n .option('--open', 'Open browser on start')\n .action(dev)\n\ncli\n .command('build [root]', 'Build for production')\n .option('--mode <mode>', 'Rendering mode override')\n .action(build)\n\ncli\n .command('preview [root]', 'Preview production build')\n .option('--port <port>', 'Server port', { default: 3000 })\n .option('--host [host]', 'Server host')\n .action(preview)\n\ncli\n .command('doctor [root]', 'Check for React patterns and framework issues')\n .option('--fix', 'Auto-fix fixable issues')\n .option('--json', 'Output as JSON')\n .option('--ci', 'CI mode — exit with code 1 on errors')\n .action(doctor)\n\ncli\n .command('context [root]', 'Generate project context for AI tools')\n .option('--out <path>', 'Output path (default: .pyreon/context.json)')\n .action(context)\n\ncli\n .command('create <name>', 'Scaffold a new Pyreon Zero project')\n .action(create)\n\ncli.help()\ncli.version('0.0.1')\ncli.parse()\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/zero-cli",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for Pyreon Zero — dev, build, preview",
5
5
  "license": "MIT",
6
6
  "author": "Vit Bokisch",
@@ -27,8 +27,10 @@
27
27
  "typecheck": "tsc --noEmit"
28
28
  },
29
29
  "dependencies": {
30
- "@pyreon/server": "^0.2.1",
31
- "@pyreon/zero": "workspace:^",
30
+ "@pyreon/cli": "^0.7.0",
31
+ "@pyreon/create-zero": "^0.3.0",
32
+ "@pyreon/server": "^0.7.0",
33
+ "@pyreon/zero": "^0.3.0",
32
34
  "cac": "^7.0.0",
33
35
  "vite": "^8.0.0"
34
36
  }