@kozojs/cli 0.1.4

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 ADDED
@@ -0,0 +1,1210 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_commander = require("commander");
28
+
29
+ // src/commands/new.ts
30
+ var p = __toESM(require("@clack/prompts"));
31
+ var import_picocolors2 = __toESM(require("picocolors"));
32
+ var import_execa = require("execa");
33
+
34
+ // src/utils/scaffold.ts
35
+ var import_fs_extra = __toESM(require("fs-extra"));
36
+ var import_node_path = __toESM(require("path"));
37
+ async function scaffoldProject(options) {
38
+ const { projectName, database, packageSource, template } = options;
39
+ const projectDir = import_node_path.default.resolve(process.cwd(), projectName);
40
+ const kozoCoreDep = packageSource === "local" ? "workspace:*" : "^0.1.0";
41
+ if (template === "complete") {
42
+ await scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep);
43
+ return;
44
+ }
45
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes"));
46
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "db"));
47
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "services"));
48
+ const packageJson = {
49
+ name: projectName,
50
+ version: "0.1.0",
51
+ type: "module",
52
+ scripts: {
53
+ dev: "tsx watch src/index.ts",
54
+ build: "tsc",
55
+ start: "node dist/index.js",
56
+ "db:generate": "drizzle-kit generate",
57
+ "db:push": "drizzle-kit push",
58
+ "db:studio": "drizzle-kit studio"
59
+ },
60
+ dependencies: {
61
+ "@kozojs/core": kozoCoreDep,
62
+ hono: "^4.6.0",
63
+ zod: "^3.23.0",
64
+ "drizzle-orm": "^0.36.0",
65
+ ...database === "postgresql" && { postgres: "^3.4.0" },
66
+ ...database === "mysql" && { mysql2: "^3.11.0" },
67
+ ...database === "sqlite" && { "better-sqlite3": "^11.0.0" }
68
+ },
69
+ devDependencies: {
70
+ "@types/node": "^22.0.0",
71
+ tsx: "^4.19.0",
72
+ typescript: "^5.6.0",
73
+ "drizzle-kit": "^0.28.0",
74
+ ...database === "sqlite" && { "@types/better-sqlite3": "^7.6.0" }
75
+ }
76
+ };
77
+ await import_fs_extra.default.writeJSON(import_node_path.default.join(projectDir, "package.json"), packageJson, { spaces: 2 });
78
+ const tsconfig = {
79
+ compilerOptions: {
80
+ target: "ES2022",
81
+ module: "ESNext",
82
+ moduleResolution: "bundler",
83
+ strict: true,
84
+ esModuleInterop: true,
85
+ skipLibCheck: true,
86
+ outDir: "dist",
87
+ rootDir: "src",
88
+ declaration: true
89
+ },
90
+ include: ["src/**/*"],
91
+ exclude: ["node_modules", "dist"]
92
+ };
93
+ await import_fs_extra.default.writeJSON(import_node_path.default.join(projectDir, "tsconfig.json"), tsconfig, { spaces: 2 });
94
+ const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
95
+
96
+ export default defineConfig({
97
+ schema: './src/db/schema.ts',
98
+ out: './drizzle',
99
+ dialect: '${database === "postgresql" ? "postgresql" : database === "mysql" ? "mysql" : "sqlite"}',
100
+ dbCredentials: {
101
+ ${database === "sqlite" ? "url: './data.db'" : "url: process.env.DATABASE_URL!"}
102
+ }
103
+ });
104
+ `;
105
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "drizzle.config.ts"), drizzleConfig);
106
+ const envExample = `# Database
107
+ ${database === "sqlite" ? "# SQLite uses local file, no URL needed" : "DATABASE_URL="}
108
+
109
+ # Server
110
+ PORT=3000
111
+ `;
112
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".env.example"), envExample);
113
+ const gitignore = `node_modules/
114
+ dist/
115
+ .env
116
+ *.db
117
+ .turbo/
118
+ `;
119
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".gitignore"), gitignore);
120
+ const indexTs = `import { createKozo } from '@kozojs/core';
121
+ import { services } from './services/index.js';
122
+
123
+ const app = createKozo({
124
+ services,
125
+ port: Number(process.env.PORT) || 3000,
126
+ openapi: {
127
+ info: {
128
+ title: '${projectName} API',
129
+ version: '1.0.0',
130
+ description: 'API documentation for ${projectName}'
131
+ },
132
+ servers: [
133
+ {
134
+ url: 'http://localhost:3000',
135
+ description: 'Development server'
136
+ }
137
+ ]
138
+ }
139
+ });
140
+
141
+ app.listen();
142
+ `;
143
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "index.ts"), indexTs);
144
+ const servicesTs = `import { db } from '../db/index.js';
145
+
146
+ export const services = {
147
+ db
148
+ };
149
+
150
+ // Type augmentation for autocomplete
151
+ declare module '@kozojs/core' {
152
+ interface Services {
153
+ db: typeof db;
154
+ }
155
+ }
156
+ `;
157
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "services", "index.ts"), servicesTs);
158
+ const schemaTs = getDatabaseSchema(database);
159
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "db", "schema.ts"), schemaTs);
160
+ const dbIndexTs = getDatabaseIndex(database);
161
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "db", "index.ts"), dbIndexTs);
162
+ if (database === "sqlite") {
163
+ const seedTs = getSQLiteSeed();
164
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "db", "seed.ts"), seedTs);
165
+ }
166
+ await createExampleRoutes(projectDir);
167
+ const readme = `# ${projectName}
168
+
169
+ Built with \u{1F525} **Kozo Framework**
170
+
171
+ ## Getting Started
172
+
173
+ \`\`\`bash
174
+ # Install dependencies
175
+ pnpm install
176
+
177
+ # Start development server
178
+ pnpm dev
179
+ \`\`\`
180
+
181
+ The server will start at http://localhost:3000
182
+
183
+ ## Try the API
184
+
185
+ \`\`\`bash
186
+ # Get all users
187
+ curl http://localhost:3000/users
188
+
189
+ # Create a new user
190
+ curl -X POST http://localhost:3000/users \\
191
+ -H "Content-Type: application/json" \\
192
+ -d '{"name":"Alice","email":"alice@example.com"}'
193
+
194
+ # Health check
195
+ curl http://localhost:3000
196
+
197
+ # Open Swagger UI
198
+ open http://localhost:3000/swagger
199
+ \`\`\`
200
+
201
+ ## API Documentation
202
+
203
+ Once the server is running, visit:
204
+ - **Swagger UI**: http://localhost:3000/swagger
205
+ - **OpenAPI JSON**: http://localhost:3000/doc
206
+
207
+ ## Project Structure
208
+
209
+ \`\`\`
210
+ src/
211
+ \u251C\u2500\u2500 db/
212
+ \u2502 \u251C\u2500\u2500 schema.ts # Drizzle schema
213
+ \u2502 \u251C\u2500\u2500 seed.ts # Database initialization${database === "sqlite" ? " (SQLite)" : ""}
214
+ \u2502 \u2514\u2500\u2500 index.ts # Database client
215
+ \u251C\u2500\u2500 routes/
216
+ \u2502 \u251C\u2500\u2500 index.ts # GET /
217
+ \u2502 \u2514\u2500\u2500 users/
218
+ \u2502 \u251C\u2500\u2500 get.ts # GET /users
219
+ \u2502 \u2514\u2500\u2500 post.ts # POST /users
220
+ \u251C\u2500\u2500 services/
221
+ \u2502 \u2514\u2500\u2500 index.ts # Service definitions
222
+ \u2514\u2500\u2500 index.ts # Entry point
223
+ \`\`\`
224
+
225
+ ## Database Commands
226
+
227
+ \`\`\`bash
228
+ pnpm db:generate # Generate migrations
229
+ pnpm db:push # Push schema to database
230
+ pnpm db:studio # Open Drizzle Studio
231
+ \`\`\`
232
+
233
+ ${database === "sqlite" ? "## SQLite Notes\n\nThe database is automatically initialized with example data on first run.\nDatabase file: `./data.db`\n" : ""}
234
+ ## Documentation
235
+
236
+ - [Kozo Docs](https://kozo.dev/docs)
237
+ - [Drizzle ORM](https://orm.drizzle.team)
238
+ - [Hono](https://hono.dev)
239
+ `;
240
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "README.md"), readme);
241
+ }
242
+ function getDatabaseSchema(database) {
243
+ if (database === "postgresql") {
244
+ return `import { pgTable, uuid, varchar, timestamp } from 'drizzle-orm/pg-core';
245
+
246
+ export const users = pgTable('users', {
247
+ id: uuid('id').primaryKey().defaultRandom(),
248
+ name: varchar('name', { length: 255 }).notNull(),
249
+ email: varchar('email', { length: 255 }).unique().notNull(),
250
+ createdAt: timestamp('created_at').defaultNow().notNull()
251
+ });
252
+ `;
253
+ }
254
+ if (database === "mysql") {
255
+ return `import { mysqlTable, varchar, timestamp } from 'drizzle-orm/mysql-core';
256
+
257
+ export const users = mysqlTable('users', {
258
+ id: varchar('id', { length: 36 }).primaryKey(),
259
+ name: varchar('name', { length: 255 }).notNull(),
260
+ email: varchar('email', { length: 255 }).unique().notNull(),
261
+ createdAt: timestamp('created_at').defaultNow().notNull()
262
+ });
263
+ `;
264
+ }
265
+ return `import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
266
+
267
+ export const users = sqliteTable('users', {
268
+ id: integer('id').primaryKey({ autoIncrement: true }),
269
+ name: text('name').notNull(),
270
+ email: text('email').unique().notNull(),
271
+ createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date())
272
+ });
273
+ `;
274
+ }
275
+ function getDatabaseIndex(database) {
276
+ if (database === "postgresql") {
277
+ return `import { drizzle } from 'drizzle-orm/postgres-js';
278
+ import postgres from 'postgres';
279
+ import * as schema from './schema.js';
280
+
281
+ const client = postgres(process.env.DATABASE_URL!);
282
+ export const db = drizzle(client, { schema });
283
+ `;
284
+ }
285
+ if (database === "mysql") {
286
+ return `import { drizzle } from 'drizzle-orm/mysql2';
287
+ import mysql from 'mysql2/promise';
288
+ import * as schema from './schema.js';
289
+
290
+ const connection = await mysql.createConnection(process.env.DATABASE_URL!);
291
+ export const db = drizzle(connection, { schema, mode: 'default' });
292
+ `;
293
+ }
294
+ return `import { drizzle } from 'drizzle-orm/better-sqlite3';
295
+ import Database from 'better-sqlite3';
296
+ import * as schema from './schema.js';
297
+ import { initDatabase } from './seed.js';
298
+
299
+ const sqlite = new Database('./data.db');
300
+ export const db = drizzle(sqlite, { schema });
301
+
302
+ // Initialize database tables on first run
303
+ initDatabase(db);
304
+ `;
305
+ }
306
+ function getSQLiteSeed() {
307
+ return `import type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
308
+ import { users } from './schema.js';
309
+
310
+ let initialized = false;
311
+
312
+ export function initDatabase(db: BetterSQLite3Database<any>) {
313
+ if (initialized) return;
314
+
315
+ try {
316
+ // Create tables if they don't exist
317
+ db.run(\`
318
+ CREATE TABLE IF NOT EXISTS users (
319
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
320
+ name TEXT NOT NULL,
321
+ email TEXT UNIQUE NOT NULL,
322
+ created_at INTEGER
323
+ )
324
+ \`);
325
+
326
+ // Check if we need to seed
327
+ const count = db.get(\`SELECT COUNT(*) as count FROM users\`) as { count: number };
328
+
329
+ if (count.count === 0) {
330
+ console.log('\u{1F331} Seeding database with example data...');
331
+
332
+ // Insert example users
333
+ db.insert(users).values([
334
+ { name: 'John Doe', email: 'john@example.com', createdAt: new Date() },
335
+ { name: 'Jane Smith', email: 'jane@example.com', createdAt: new Date() }
336
+ ]).run();
337
+
338
+ console.log('\u2705 Database seeded!');
339
+ }
340
+
341
+ initialized = true;
342
+ } catch (err) {
343
+ console.error('Failed to initialize database:', err);
344
+ }
345
+ }
346
+ `;
347
+ }
348
+ async function createExampleRoutes(projectDir) {
349
+ const indexRoute = `export default async () => {
350
+ return {
351
+ status: 'ok',
352
+ framework: 'Kozo \u{1F525}',
353
+ timestamp: new Date().toISOString()
354
+ };
355
+ };
356
+ `;
357
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "index.ts"), indexRoute);
358
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes", "users"));
359
+ const getUsersRoute = `import type { HandlerContext } from '@kozojs/core';
360
+ import { users } from '../../db/schema.js';
361
+
362
+ export const meta = {
363
+ summary: 'Get all users',
364
+ description: 'Returns a list of all users in the database'
365
+ };
366
+
367
+ export default async ({ services: { db } }: HandlerContext) => {
368
+ const allUsers = db.select().from(users).all();
369
+ return { users: allUsers };
370
+ };
371
+ `;
372
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "users", "get.ts"), getUsersRoute);
373
+ const postUsersRoute = `import { z } from 'zod';
374
+ import type { HandlerContext } from '@kozojs/core';
375
+ import { users } from '../../db/schema.js';
376
+
377
+ export const schema = {
378
+ body: z.object({
379
+ name: z.string().min(2),
380
+ email: z.string().email()
381
+ })
382
+ };
383
+
384
+ export const meta = {
385
+ summary: 'Create a new user',
386
+ description: 'Creates a new user with name and email'
387
+ };
388
+
389
+ type Body = z.infer<typeof schema.body>;
390
+
391
+ export default async ({ body, services: { db } }: HandlerContext<Body>) => {
392
+ const user = db.insert(users).values({
393
+ ...body,
394
+ createdAt: new Date()
395
+ }).returning().get();
396
+
397
+ return { success: true, user };
398
+ };
399
+ `;
400
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "users", "post.ts"), postUsersRoute);
401
+ }
402
+ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep) {
403
+ await import_fs_extra.default.ensureDir(projectDir);
404
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src"));
405
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "schemas"));
406
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes"));
407
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes", "auth"));
408
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes", "users"));
409
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes", "posts"));
410
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "utils"));
411
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "data"));
412
+ const packageJson = {
413
+ name: projectName,
414
+ version: "1.0.0",
415
+ type: "module",
416
+ scripts: {
417
+ dev: "tsx watch src/index.ts",
418
+ build: "tsc",
419
+ start: "node dist/index.js",
420
+ "type-check": "tsc --noEmit"
421
+ },
422
+ dependencies: {
423
+ "@kozojs/core": kozoCoreDep,
424
+ "@hono/node-server": "^1.13.0",
425
+ hono: "^4.6.0",
426
+ zod: "^3.23.0"
427
+ },
428
+ devDependencies: {
429
+ "@types/node": "^22.0.0",
430
+ tsx: "^4.19.0",
431
+ typescript: "^5.6.0"
432
+ }
433
+ };
434
+ await import_fs_extra.default.writeJSON(import_node_path.default.join(projectDir, "package.json"), packageJson, { spaces: 2 });
435
+ const tsconfig = {
436
+ compilerOptions: {
437
+ target: "ES2022",
438
+ module: "ESNext",
439
+ moduleResolution: "bundler",
440
+ strict: true,
441
+ esModuleInterop: true,
442
+ skipLibCheck: true,
443
+ outDir: "dist",
444
+ rootDir: "src",
445
+ declaration: true,
446
+ experimentalDecorators: true,
447
+ emitDecoratorMetadata: true
448
+ },
449
+ include: ["src/**/*"],
450
+ exclude: ["node_modules", "dist"]
451
+ };
452
+ await import_fs_extra.default.writeJSON(import_node_path.default.join(projectDir, "tsconfig.json"), tsconfig, { spaces: 2 });
453
+ const gitignore = `node_modules/
454
+ dist/
455
+ .env
456
+ .turbo/
457
+ *.log
458
+ `;
459
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".gitignore"), gitignore);
460
+ const envExample = `# Server
461
+ PORT=3000
462
+ NODE_ENV=development
463
+ `;
464
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".env.example"), envExample);
465
+ const indexTs = `import { createKozo } from '@kozojs/core';
466
+ import { registerAuthRoutes } from './routes/auth/index.js';
467
+ import { registerUserRoutes } from './routes/users/index.js';
468
+ import { registerPostRoutes } from './routes/posts/index.js';
469
+ import { registerHealthRoute } from './routes/health.js';
470
+ import { registerStatsRoute } from './routes/stats.js';
471
+
472
+ const app = createKozo({
473
+ port: Number(process.env.PORT) || 3000,
474
+ });
475
+
476
+ // Register all routes
477
+ registerHealthRoute(app);
478
+ registerAuthRoutes(app);
479
+ registerUserRoutes(app);
480
+ registerPostRoutes(app);
481
+ registerStatsRoute(app);
482
+
483
+ console.log('\u{1F525} Kozo server running on http://localhost:3000');
484
+ console.log('\u{1F4CA} Features: Auth, Users CRUD, Posts, Stats');
485
+ console.log('\u26A1 Optimized with pre-compiled handlers and Zod schemas');
486
+ console.log('');
487
+ console.log('\u{1F4DA} Try these endpoints:');
488
+ console.log(' GET /health');
489
+ console.log(' GET /users');
490
+ console.log(' POST /auth/login');
491
+ console.log(' GET /posts?published=true&page=1&limit=10');
492
+ console.log(' GET /stats');
493
+
494
+ app.listen();
495
+ `;
496
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "index.ts"), indexTs);
497
+ await createCompleteSchemas(projectDir);
498
+ await createCompleteUtils(projectDir);
499
+ await createCompleteDataStore(projectDir);
500
+ await createCompleteRoutes(projectDir);
501
+ const readme = `# ${projectName}
502
+
503
+ Built with \u{1F525} **Kozo Framework** - Production-ready server template
504
+
505
+ ## Features
506
+
507
+ \u2728 **Complete API Implementation**
508
+ - \u2705 Authentication (login, me)
509
+ - \u2705 User CRUD (Create, Read, Update, Delete)
510
+ - \u2705 Posts with filtering and pagination
511
+ - \u2705 Statistics endpoint
512
+ - \u2705 Health check
513
+
514
+ \u26A1 **Performance Optimized**
515
+ - Pre-compiled Zod schemas (no runtime overhead)
516
+ - Fast-path routes for health checks
517
+ - Optimized handler closures
518
+ - Zero runtime decisions
519
+
520
+ \u{1F3AF} **Type-Safe**
521
+ - Full TypeScript inference
522
+ - Zod validation for all inputs
523
+ - Auto-generated types from schemas
524
+
525
+ ## Quick Start
526
+
527
+ \`\`\`bash
528
+ # Install dependencies
529
+ npm install
530
+
531
+ # Start development server
532
+ npm run dev
533
+ \`\`\`
534
+
535
+ The server will start at **http://localhost:3000**
536
+
537
+ ## API Endpoints
538
+
539
+ ### Authentication
540
+ | Method | Endpoint | Description |
541
+ |--------|----------|-------------|
542
+ | POST | /auth/login | Login with email/password |
543
+ | GET | /auth/me | Get current user |
544
+
545
+ ### Users
546
+ | Method | Endpoint | Description |
547
+ |--------|----------|-------------|
548
+ | GET | /users | List all users (paginated) |
549
+ | GET | /users/:id | Get user by ID |
550
+ | POST | /users | Create new user |
551
+ | PUT | /users/:id | Update user |
552
+ | DELETE | /users/:id | Delete user |
553
+
554
+ ### Posts
555
+ | Method | Endpoint | Description |
556
+ |--------|----------|-------------|
557
+ | GET | /posts | List posts (with filters) |
558
+ | GET | /posts/:id | Get post with author |
559
+ | POST | /posts | Create new post |
560
+
561
+ ### System
562
+ | Method | Endpoint | Description |
563
+ |--------|----------|-------------|
564
+ | GET | /health | Health check |
565
+ | GET | /stats | System statistics |
566
+
567
+ ## Example Requests
568
+
569
+ ### Create User
570
+ \`\`\`bash
571
+ curl -X POST http://localhost:3000/users \\
572
+ -H "Content-Type: application/json" \\
573
+ -d '{
574
+ "name": "Alice Smith",
575
+ "email": "alice@example.com",
576
+ "role": "user"
577
+ }'
578
+ \`\`\`
579
+
580
+ ### Login
581
+ \`\`\`bash
582
+ curl -X POST http://localhost:3000/auth/login \\
583
+ -H "Content-Type: application/json" \\
584
+ -d '{
585
+ "email": "admin@kozo.dev",
586
+ "password": "secret123"
587
+ }'
588
+ \`\`\`
589
+
590
+ ### List Users (Paginated)
591
+ \`\`\`bash
592
+ curl "http://localhost:3000/users?page=1&limit=10"
593
+ \`\`\`
594
+
595
+ ### Filter Posts
596
+ \`\`\`bash
597
+ curl "http://localhost:3000/posts?published=true&tag=framework"
598
+ \`\`\`
599
+
600
+ ### Get Statistics
601
+ \`\`\`bash
602
+ curl http://localhost:3000/stats
603
+ \`\`\`
604
+
605
+ ## Project Structure
606
+
607
+ \`\`\`
608
+ ${projectName}/
609
+ \u251C\u2500\u2500 src/
610
+ \u2502 \u251C\u2500\u2500 data/
611
+ \u2502 \u2502 \u2514\u2500\u2500 store.ts # In-memory data store
612
+ \u2502 \u251C\u2500\u2500 routes/
613
+ \u2502 \u2502 \u251C\u2500\u2500 auth/
614
+ \u2502 \u2502 \u2502 \u2514\u2500\u2500 index.ts # Auth routes (login, me)
615
+ \u2502 \u2502 \u251C\u2500\u2500 users/
616
+ \u2502 \u2502 \u2502 \u2514\u2500\u2500 index.ts # User CRUD routes
617
+ \u2502 \u2502 \u251C\u2500\u2500 posts/
618
+ \u2502 \u2502 \u2502 \u2514\u2500\u2500 index.ts # Post routes
619
+ \u2502 \u2502 \u251C\u2500\u2500 health.ts # Health check
620
+ \u2502 \u2502 \u2514\u2500\u2500 stats.ts # Statistics
621
+ \u2502 \u251C\u2500\u2500 schemas/
622
+ \u2502 \u2502 \u251C\u2500\u2500 user.ts # User schemas
623
+ \u2502 \u2502 \u251C\u2500\u2500 post.ts # Post schemas
624
+ \u2502 \u2502 \u2514\u2500\u2500 common.ts # Common schemas
625
+ \u2502 \u251C\u2500\u2500 utils/
626
+ \u2502 \u2502 \u2514\u2500\u2500 helpers.ts # Helper functions
627
+ \u2502 \u2514\u2500\u2500 index.ts # Entry point
628
+ \u251C\u2500\u2500 package.json
629
+ \u251C\u2500\u2500 tsconfig.json
630
+ \u2514\u2500\u2500 README.md
631
+ \`\`\`
632
+
633
+ ## Development
634
+
635
+ \`\`\`bash
636
+ npm run dev # Start with hot reload
637
+ npm run build # Build for production
638
+ npm start # Run production build
639
+ npm run type-check # TypeScript validation
640
+ \`\`\`
641
+
642
+ ## Performance Notes
643
+
644
+ This template uses Kozo's optimized patterns:
645
+ - **Pre-compiled schemas**: Zod schemas compiled to Ajv validators at boot
646
+ - **Handler closures**: Routes capture dependencies at startup
647
+ - **Fast paths**: Simple routes skip unnecessary middleware
648
+ - **Minimal allocations**: Context objects only include what's needed
649
+
650
+ These optimizations make Kozo competitive with Fastify while providing:
651
+ - Better type safety with Zod
652
+ - Simpler API than NestJS
653
+ - More features than plain Hono
654
+
655
+ ## Next Steps
656
+
657
+ 1. **Add database**: Integrate Drizzle ORM for persistent storage
658
+ 2. **Add authentication**: Implement JWT with proper password hashing
659
+ 3. **Add validation**: Enhance error handling and input validation
660
+ 4. **Add tests**: Write unit and integration tests
661
+ 5. **Deploy**: Deploy to Vercel, Railway, or any Node.js host
662
+
663
+ ## Documentation
664
+
665
+ - [Kozo Documentation](https://kozo.dev/docs)
666
+ - [Zod Schema Validation](https://zod.dev)
667
+ - [Hono Framework](https://hono.dev)
668
+
669
+ ---
670
+
671
+ Built with \u2764\uFE0F using Kozo Framework
672
+ `;
673
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "README.md"), readme);
674
+ }
675
+ async function createCompleteSchemas(projectDir) {
676
+ const userSchemas = `import { z } from 'zod';
677
+
678
+ export const UserSchema = z.object({
679
+ id: z.string().uuid(),
680
+ email: z.string().email(),
681
+ name: z.string().min(2).max(50),
682
+ role: z.enum(['user', 'admin']).default('user'),
683
+ createdAt: z.date(),
684
+ updatedAt: z.date(),
685
+ });
686
+
687
+ export const CreateUserSchema = z.object({
688
+ email: z.string().email(),
689
+ name: z.string().min(2).max(50),
690
+ role: z.enum(['user', 'admin']).optional(),
691
+ });
692
+
693
+ export const UpdateUserSchema = z.object({
694
+ name: z.string().min(2).max(50).optional(),
695
+ role: z.enum(['user', 'admin']).optional(),
696
+ });
697
+
698
+ export type User = z.infer<typeof UserSchema>;
699
+ export type CreateUser = z.infer<typeof CreateUserSchema>;
700
+ export type UpdateUser = z.infer<typeof UpdateUserSchema>;
701
+ `;
702
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "schemas", "user.ts"), userSchemas);
703
+ const postSchemas = `import { z } from 'zod';
704
+ import { UserSchema } from './user.js';
705
+
706
+ export const PostSchema = z.object({
707
+ id: z.string().uuid(),
708
+ title: z.string().min(1).max(200),
709
+ content: z.string().min(1),
710
+ authorId: z.string().uuid(),
711
+ published: z.boolean().default(false),
712
+ tags: z.array(z.string()).default([]),
713
+ createdAt: z.date(),
714
+ updatedAt: z.date(),
715
+ });
716
+
717
+ export const PostWithAuthorSchema = PostSchema.extend({
718
+ author: UserSchema,
719
+ });
720
+
721
+ export const CreatePostSchema = z.object({
722
+ title: z.string().min(1).max(200),
723
+ content: z.string().min(1),
724
+ published: z.boolean().optional(),
725
+ tags: z.array(z.string()).optional(),
726
+ });
727
+
728
+ export type Post = z.infer<typeof PostSchema>;
729
+ export type PostWithAuthor = z.infer<typeof PostWithAuthorSchema>;
730
+ export type CreatePost = z.infer<typeof CreatePostSchema>;
731
+ `;
732
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "schemas", "post.ts"), postSchemas);
733
+ const commonSchemas = `import { z } from 'zod';
734
+
735
+ export const PaginationSchema = z.object({
736
+ page: z.coerce.number().min(1).default(1),
737
+ limit: z.coerce.number().min(1).max(100).default(10),
738
+ });
739
+
740
+ export const PostFiltersSchema = z.object({
741
+ published: z.coerce.boolean().optional(),
742
+ authorId: z.string().uuid().optional(),
743
+ tag: z.string().optional(),
744
+ });
745
+
746
+ export type Pagination = z.infer<typeof PaginationSchema>;
747
+ export type PostFilters = z.infer<typeof PostFiltersSchema>;
748
+ `;
749
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "schemas", "common.ts"), commonSchemas);
750
+ }
751
+ async function createCompleteUtils(projectDir) {
752
+ const helpers = `export function generateUUID(): string {
753
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
754
+ const r = Math.random() * 16 | 0;
755
+ const v = c == 'x' ? r : (r & 0x3 | 0x8);
756
+ return v.toString(16);
757
+ });
758
+ }
759
+
760
+ export function paginate<T>(items: T[], page: number, limit: number) {
761
+ const start = (page - 1) * limit;
762
+ const end = start + limit;
763
+ return {
764
+ data: items.slice(start, end),
765
+ total: items.length,
766
+ page,
767
+ limit,
768
+ totalPages: Math.ceil(items.length / limit),
769
+ };
770
+ }
771
+ `;
772
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "utils", "helpers.ts"), helpers);
773
+ }
774
+ async function createCompleteDataStore(projectDir) {
775
+ const store = `import type { User } from '../schemas/user.js';
776
+ import type { Post } from '../schemas/post.js';
777
+
778
+ export const users: User[] = [
779
+ {
780
+ id: '550e8400-e29b-41d4-a716-446655440000',
781
+ email: 'admin@kozo.dev',
782
+ name: 'Admin User',
783
+ role: 'admin',
784
+ createdAt: new Date('2024-01-01'),
785
+ updatedAt: new Date('2024-01-01'),
786
+ },
787
+ {
788
+ id: '550e8400-e29b-41d4-a716-446655440001',
789
+ email: 'john@example.com',
790
+ name: 'John Doe',
791
+ role: 'user',
792
+ createdAt: new Date('2024-01-15'),
793
+ updatedAt: new Date('2024-01-15'),
794
+ },
795
+ ];
796
+
797
+ export const posts: Post[] = [
798
+ {
799
+ id: '550e8400-e29b-41d4-a716-446655440010',
800
+ title: 'Welcome to Kozo Framework',
801
+ content: 'This is the first post in our amazing framework...',
802
+ authorId: '550e8400-e29b-41d4-a716-446655440000',
803
+ published: true,
804
+ tags: ['framework', 'typescript', 'backend'],
805
+ createdAt: new Date('2024-01-01'),
806
+ updatedAt: new Date('2024-01-01'),
807
+ },
808
+ ];
809
+ `;
810
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "data", "store.ts"), store);
811
+ }
812
+ async function createCompleteRoutes(projectDir) {
813
+ const healthRoute = `import type { Kozo } from '@kozojs/core';
814
+
815
+ export function registerHealthRoute(app: Kozo) {
816
+ app.get('/health', {}, (c) => {
817
+ return {
818
+ status: 'ok',
819
+ timestamp: new Date().toISOString(),
820
+ version: '1.0.0',
821
+ uptime: process.uptime(),
822
+ };
823
+ });
824
+ }
825
+ `;
826
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "health.ts"), healthRoute);
827
+ const statsRoute = `import { z } from 'zod';
828
+ import type { Kozo } from '@kozojs/core';
829
+ import { users } from '../data/store.js';
830
+ import { posts } from '../data/store.js';
831
+
832
+ export function registerStatsRoute(app: Kozo) {
833
+ app.get('/stats', {
834
+ response: z.object({
835
+ users: z.object({
836
+ total: z.number(),
837
+ admins: z.number(),
838
+ regular: z.number(),
839
+ }),
840
+ posts: z.object({
841
+ total: z.number(),
842
+ published: z.number(),
843
+ drafts: z.number(),
844
+ totalTags: z.number(),
845
+ }),
846
+ performance: z.object({
847
+ uptime: z.number(),
848
+ memoryUsage: z.object({
849
+ rss: z.number(),
850
+ heapTotal: z.number(),
851
+ heapUsed: z.number(),
852
+ }),
853
+ }),
854
+ }),
855
+ }, (c) => {
856
+ const memUsage = process.memoryUsage();
857
+ return {
858
+ users: {
859
+ total: users.length,
860
+ admins: users.filter(u => u.role === 'admin').length,
861
+ regular: users.filter(u => u.role === 'user').length,
862
+ },
863
+ posts: {
864
+ total: posts.length,
865
+ published: posts.filter(p => p.published).length,
866
+ drafts: posts.filter(p => !p.published).length,
867
+ totalTags: [...new Set(posts.flatMap(p => p.tags))].length,
868
+ },
869
+ performance: {
870
+ uptime: process.uptime(),
871
+ memoryUsage: {
872
+ rss: memUsage.rss,
873
+ heapTotal: memUsage.heapTotal,
874
+ heapUsed: memUsage.heapUsed,
875
+ },
876
+ },
877
+ };
878
+ });
879
+ }
880
+ `;
881
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "stats.ts"), statsRoute);
882
+ const authRoutes = `import { z } from 'zod';
883
+ import type { Kozo } from '@kozojs/core';
884
+ import { UserSchema } from '../../schemas/user.js';
885
+ import { users } from '../../data/store.js';
886
+
887
+ export function registerAuthRoutes(app: Kozo) {
888
+ // POST /auth/login
889
+ app.post('/auth/login', {
890
+ body: z.object({
891
+ email: z.string().email(),
892
+ password: z.string().min(6),
893
+ }),
894
+ response: z.object({
895
+ success: z.boolean(),
896
+ token: z.string(),
897
+ user: UserSchema,
898
+ }),
899
+ }, (c) => {
900
+ const user = users.find(u => u.email === c.body.email);
901
+ if (!user) {
902
+ return c.json({ error: 'Invalid credentials' }, 401);
903
+ }
904
+ const token = \`mock_jwt_\${user.id}_\${Date.now()}\`;
905
+ return { success: true, token, user };
906
+ });
907
+
908
+ // GET /auth/me
909
+ app.get('/auth/me', {
910
+ response: UserSchema,
911
+ }, (c) => users[0]);
912
+ }
913
+ `;
914
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "auth", "index.ts"), authRoutes);
915
+ const userRoutes = `import { z } from 'zod';
916
+ import type { Kozo } from '@kozojs/core';
917
+ import { UserSchema, CreateUserSchema, UpdateUserSchema } from '../../schemas/user.js';
918
+ import { PaginationSchema } from '../../schemas/common.js';
919
+ import { users } from '../../data/store.js';
920
+ import { generateUUID, paginate } from '../../utils/helpers.js';
921
+
922
+ export function registerUserRoutes(app: Kozo) {
923
+ // GET /users
924
+ app.get('/users', {
925
+ query: PaginationSchema,
926
+ response: z.object({
927
+ data: z.array(UserSchema),
928
+ total: z.number(),
929
+ page: z.number(),
930
+ limit: z.number(),
931
+ totalPages: z.number(),
932
+ }),
933
+ }, (c) => {
934
+ const { page, limit } = c.query;
935
+ return paginate(users, page, limit);
936
+ });
937
+
938
+ // GET /users/:id
939
+ app.get('/users/:id', {
940
+ params: z.object({ id: z.string().uuid() }),
941
+ response: UserSchema,
942
+ }, (c) => {
943
+ const user = users.find(u => u.id === c.params.id);
944
+ if (!user) return c.json({ error: 'User not found' }, 404);
945
+ return user;
946
+ });
947
+
948
+ // POST /users
949
+ app.post('/users', {
950
+ body: CreateUserSchema,
951
+ response: UserSchema,
952
+ }, (c) => {
953
+ const existing = users.find(u => u.email === c.body.email);
954
+ if (existing) return c.json({ error: 'Email already exists' }, 409);
955
+
956
+ const newUser = {
957
+ id: generateUUID(),
958
+ email: c.body.email,
959
+ name: c.body.name,
960
+ role: c.body.role || 'user' as const,
961
+ createdAt: new Date(),
962
+ updatedAt: new Date(),
963
+ };
964
+ users.push(newUser);
965
+ return newUser;
966
+ });
967
+
968
+ // PUT /users/:id
969
+ app.put('/users/:id', {
970
+ params: z.object({ id: z.string().uuid() }),
971
+ body: UpdateUserSchema,
972
+ response: UserSchema,
973
+ }, (c) => {
974
+ const user = users.find(u => u.id === c.params.id);
975
+ if (!user) return c.json({ error: 'User not found' }, 404);
976
+
977
+ if (c.body.name) user.name = c.body.name;
978
+ if (c.body.role) user.role = c.body.role;
979
+ user.updatedAt = new Date();
980
+ return user;
981
+ });
982
+
983
+ // DELETE /users/:id
984
+ app.delete('/users/:id', {
985
+ params: z.object({ id: z.string().uuid() }),
986
+ response: z.object({
987
+ success: z.boolean(),
988
+ deletedId: z.string(),
989
+ }),
990
+ }, (c) => {
991
+ const index = users.findIndex(u => u.id === c.params.id);
992
+ if (index === -1) return c.json({ error: 'User not found' }, 404);
993
+
994
+ users.splice(index, 1);
995
+ return { success: true, deletedId: c.params.id };
996
+ });
997
+ }
998
+ `;
999
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "users", "index.ts"), userRoutes);
1000
+ const postRoutes = `import { z } from 'zod';
1001
+ import type { Kozo } from '@kozojs/core';
1002
+ import { PostSchema, PostWithAuthorSchema, CreatePostSchema } from '../../schemas/post.js';
1003
+ import { PaginationSchema, PostFiltersSchema } from '../../schemas/common.js';
1004
+ import { posts, users } from '../../data/store.js';
1005
+ import { generateUUID, paginate } from '../../utils/helpers.js';
1006
+
1007
+ export function registerPostRoutes(app: Kozo) {
1008
+ // GET /posts
1009
+ app.get('/posts', {
1010
+ query: PaginationSchema.merge(PostFiltersSchema),
1011
+ response: z.object({
1012
+ data: z.array(PostWithAuthorSchema),
1013
+ total: z.number(),
1014
+ page: z.number(),
1015
+ limit: z.number(),
1016
+ totalPages: z.number(),
1017
+ }),
1018
+ }, (c) => {
1019
+ const { page, limit, published, authorId, tag } = c.query;
1020
+
1021
+ let filteredPosts = posts;
1022
+ if (published !== undefined) {
1023
+ filteredPosts = filteredPosts.filter(p => p.published === published);
1024
+ }
1025
+ if (authorId) {
1026
+ filteredPosts = filteredPosts.filter(p => p.authorId === authorId);
1027
+ }
1028
+ if (tag) {
1029
+ filteredPosts = filteredPosts.filter(p => p.tags.includes(tag));
1030
+ }
1031
+
1032
+ const postsWithAuthors = filteredPosts.map(post => ({
1033
+ ...post,
1034
+ author: users.find(u => u.id === post.authorId)!,
1035
+ }));
1036
+
1037
+ return paginate(postsWithAuthors, page, limit);
1038
+ });
1039
+
1040
+ // GET /posts/:id
1041
+ app.get('/posts/:id', {
1042
+ params: z.object({ id: z.string().uuid() }),
1043
+ response: PostWithAuthorSchema,
1044
+ }, (c) => {
1045
+ const post = posts.find(p => p.id === c.params.id);
1046
+ if (!post) return c.json({ error: 'Post not found' }, 404);
1047
+
1048
+ const author = users.find(u => u.id === post.authorId);
1049
+ if (!author) return c.json({ error: 'Post author not found' }, 500);
1050
+
1051
+ return { ...post, author };
1052
+ });
1053
+
1054
+ // POST /posts
1055
+ app.post('/posts', {
1056
+ body: CreatePostSchema,
1057
+ response: PostSchema,
1058
+ }, (c) => {
1059
+ const authorId = users[0].id;
1060
+ const newPost = {
1061
+ id: generateUUID(),
1062
+ title: c.body.title,
1063
+ content: c.body.content,
1064
+ authorId,
1065
+ published: c.body.published || false,
1066
+ tags: c.body.tags || [],
1067
+ createdAt: new Date(),
1068
+ updatedAt: new Date(),
1069
+ };
1070
+ posts.push(newPost);
1071
+ return newPost;
1072
+ });
1073
+ }
1074
+ `;
1075
+ await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "posts", "index.ts"), postRoutes);
1076
+ }
1077
+
1078
+ // src/utils/ascii-art.ts
1079
+ var import_picocolors = __toESM(require("picocolors"));
1080
+ var KOZO_LOGO = `
1081
+ ${import_picocolors.default.red(" _ __")}${import_picocolors.default.yellow("___ ")}${import_picocolors.default.red("______")}${import_picocolors.default.yellow("___ ")}
1082
+ ${import_picocolors.default.red("| |/ /")}${import_picocolors.default.yellow(" _ \\\\")}${import_picocolors.default.red("|_ /")}${import_picocolors.default.yellow(" _ \\\\")}
1083
+ ${import_picocolors.default.red("| ' /")}${import_picocolors.default.yellow(" (_) |")}${import_picocolors.default.red("/ /")}${import_picocolors.default.yellow(" (_) |")}
1084
+ ${import_picocolors.default.red("|_|\\_\\\\")}${import_picocolors.default.yellow("___/")}${import_picocolors.default.red("___|\\\\")}${import_picocolors.default.yellow("___/")}
1085
+ `;
1086
+ var KOZO_BANNER = `
1087
+ ${import_picocolors.default.bold(import_picocolors.default.red("\u{1F525} KOZO"))} ${import_picocolors.default.dim("- The Structure for the Edge")}
1088
+ `;
1089
+ function printLogo() {
1090
+ console.log(KOZO_LOGO);
1091
+ }
1092
+
1093
+ // src/commands/new.ts
1094
+ async function newCommand(projectName) {
1095
+ printLogo();
1096
+ p.intro(import_picocolors2.default.bold(import_picocolors2.default.red("\u{1F525} Create a new Kozo project")));
1097
+ const project = await p.group(
1098
+ {
1099
+ name: () => {
1100
+ if (projectName) {
1101
+ if (!/^[a-z0-9-]+$/.test(projectName)) {
1102
+ p.log.error("Project name must use lowercase letters, numbers, and hyphens only");
1103
+ process.exit(1);
1104
+ }
1105
+ return Promise.resolve(projectName);
1106
+ }
1107
+ return p.text({
1108
+ message: "Project name",
1109
+ placeholder: "my-kozo-app",
1110
+ validate: (value) => {
1111
+ if (!value) return "Project name is required";
1112
+ if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only";
1113
+ }
1114
+ });
1115
+ },
1116
+ template: () => p.select({
1117
+ message: "Template",
1118
+ options: [
1119
+ { value: "complete", label: "Complete Server", hint: "Full production-ready app (Auth, CRUD, Stats)" },
1120
+ { value: "starter", label: "Starter", hint: "Minimal setup with database" },
1121
+ { value: "saas", label: "SaaS", hint: "Auth + Stripe + Email (coming soon)" },
1122
+ { value: "ecommerce", label: "E-commerce", hint: "Products + Orders (coming soon)" }
1123
+ ]
1124
+ }),
1125
+ database: ({ results }) => {
1126
+ if (results.template === "complete") {
1127
+ return Promise.resolve("none");
1128
+ }
1129
+ return p.select({
1130
+ message: "Database provider",
1131
+ options: [
1132
+ { value: "postgresql", label: "PostgreSQL", hint: "Neon, Supabase, Railway" },
1133
+ { value: "mysql", label: "MySQL", hint: "PlanetScale" },
1134
+ { value: "sqlite", label: "SQLite", hint: "Turso, local dev" }
1135
+ ]
1136
+ });
1137
+ },
1138
+ packageSource: () => p.select({
1139
+ message: "Package source for @kozojs/core",
1140
+ options: [
1141
+ { value: "npm", label: "npm registry", hint: "Use published version (recommended)" },
1142
+ { value: "local", label: "Local workspace", hint: "Link to local monorepo (for development)" }
1143
+ ],
1144
+ initialValue: "npm"
1145
+ }),
1146
+ install: () => {
1147
+ return Promise.resolve(true);
1148
+ }
1149
+ },
1150
+ {
1151
+ onCancel: () => {
1152
+ p.cancel("Operation cancelled");
1153
+ process.exit(0);
1154
+ }
1155
+ }
1156
+ );
1157
+ const s = p.spinner();
1158
+ s.start("Creating project structure...");
1159
+ try {
1160
+ await scaffoldProject({
1161
+ projectName: project.name,
1162
+ database: project.database,
1163
+ template: project.template,
1164
+ packageSource: project.packageSource
1165
+ });
1166
+ s.stop("Project structure created!");
1167
+ } catch (err) {
1168
+ s.stop("Failed to create project");
1169
+ p.log.error(String(err));
1170
+ process.exit(1);
1171
+ }
1172
+ if (project.install) {
1173
+ s.start("Installing dependencies...");
1174
+ try {
1175
+ await (0, import_execa.execa)("pnpm", ["install"], {
1176
+ cwd: project.name,
1177
+ stdio: "pipe"
1178
+ });
1179
+ s.stop("Dependencies installed!");
1180
+ } catch {
1181
+ try {
1182
+ await (0, import_execa.execa)("npm", ["install"], {
1183
+ cwd: project.name,
1184
+ stdio: "pipe"
1185
+ });
1186
+ s.stop("Dependencies installed!");
1187
+ } catch (err) {
1188
+ s.stop("Failed to install dependencies");
1189
+ p.log.warn("Run `pnpm install` or `npm install` manually");
1190
+ }
1191
+ }
1192
+ }
1193
+ p.outro(import_picocolors2.default.green("\u2728 Project ready!"));
1194
+ console.log(`
1195
+ ${import_picocolors2.default.bold("Next steps:")}
1196
+
1197
+ ${import_picocolors2.default.cyan(`cd ${project.name}`)}
1198
+ ${!project.install ? import_picocolors2.default.cyan("pnpm install") + "\n " : ""}${import_picocolors2.default.cyan("pnpm dev")}
1199
+
1200
+ ${import_picocolors2.default.dim("Documentation:")} ${import_picocolors2.default.underline("https://kozo.dev/docs")}
1201
+ `);
1202
+ }
1203
+
1204
+ // src/index.ts
1205
+ var program = new import_commander.Command();
1206
+ program.name("kozo").description("CLI to scaffold new Kozo Framework projects").version("0.2.6");
1207
+ program.argument("[project-name]", "Name of the project").action(async (projectName) => {
1208
+ await newCommand(projectName);
1209
+ });
1210
+ program.parse();