@kozojs/cli 0.1.6 → 0.1.8
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 +2 -2
- package/lib/index.js +455 -121
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ kozo my-app
|
|
|
17
17
|
|
|
18
18
|
- 🚀 **Interactive Setup** - Choose database, template, and more
|
|
19
19
|
- 📦 **Zero Config** - Works out of the box
|
|
20
|
-
-
|
|
20
|
+
- ⚡ **uWS-powered** - Native C++ route matching, 17,510 req/s
|
|
21
21
|
- 🛡️ **Type-safe** - Full TypeScript inference with Zod
|
|
22
|
-
-
|
|
22
|
+
- 🔥 **Fast Validation** - Ajv validation + fast-json-stringify serialization
|
|
23
23
|
- 📚 **OpenAPI Auto-gen** - Swagger docs out of the box
|
|
24
24
|
- 🗄️ **Drizzle ORM** - Best-in-class database toolkit
|
|
25
25
|
|
package/lib/index.js
CHANGED
|
@@ -419,6 +419,7 @@ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, ru
|
|
|
419
419
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes", "auth"));
|
|
420
420
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes", "users"));
|
|
421
421
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "routes", "posts"));
|
|
422
|
+
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "middleware"));
|
|
422
423
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "utils"));
|
|
423
424
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "data"));
|
|
424
425
|
const packageJson = {
|
|
@@ -433,6 +434,7 @@ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, ru
|
|
|
433
434
|
},
|
|
434
435
|
dependencies: {
|
|
435
436
|
"@kozojs/core": kozoCoreDep,
|
|
437
|
+
"@kozojs/auth": kozoCoreDep,
|
|
436
438
|
"@hono/node-server": "^1.13.0",
|
|
437
439
|
hono: "^4.6.0",
|
|
438
440
|
zod: "^3.23.0"
|
|
@@ -472,38 +474,83 @@ dist/
|
|
|
472
474
|
const envExample = `# Server
|
|
473
475
|
PORT=3000
|
|
474
476
|
NODE_ENV=development
|
|
477
|
+
|
|
478
|
+
# JWT Authentication
|
|
479
|
+
JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars
|
|
480
|
+
|
|
481
|
+
# CORS
|
|
482
|
+
CORS_ORIGIN=http://localhost:5173
|
|
483
|
+
|
|
484
|
+
# Rate Limiting (requests per window)
|
|
485
|
+
RATE_LIMIT_MAX=100
|
|
486
|
+
RATE_LIMIT_WINDOW=60000
|
|
475
487
|
`;
|
|
476
488
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".env.example"), envExample);
|
|
477
|
-
|
|
489
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".env"), envExample);
|
|
490
|
+
const indexTs = `import { createKozo, cors, logger, rateLimit } from '@kozojs/core';
|
|
491
|
+
import { authenticateJWT } from '@kozojs/auth';
|
|
478
492
|
import { registerAuthRoutes } from './routes/auth/index.js';
|
|
479
493
|
import { registerUserRoutes } from './routes/users/index.js';
|
|
480
494
|
import { registerPostRoutes } from './routes/posts/index.js';
|
|
481
495
|
import { registerHealthRoute } from './routes/health.js';
|
|
482
496
|
import { registerStatsRoute } from './routes/stats.js';
|
|
483
497
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
498
|
+
// \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
499
|
+
const PORT = Number(process.env.PORT) || 3000;
|
|
500
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'change-me-to-a-random-secret';
|
|
501
|
+
const CORS_ORIGIN = process.env.CORS_ORIGIN || '*';
|
|
502
|
+
const RATE_LIMIT_MAX = Number(process.env.RATE_LIMIT_MAX) || 100;
|
|
503
|
+
const RATE_LIMIT_WINDOW = Number(process.env.RATE_LIMIT_WINDOW) || 60_000;
|
|
504
|
+
|
|
505
|
+
// \u2500\u2500\u2500 App \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
506
|
+
const app = createKozo({ port: PORT });
|
|
507
|
+
|
|
508
|
+
// \u2500\u2500\u2500 Middleware (Hono layer) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
509
|
+
app.getApp().use('*', logger());
|
|
510
|
+
app.getApp().use('*', cors({ origin: CORS_ORIGIN }));
|
|
511
|
+
app.getApp().use('/api/*', rateLimit({ max: RATE_LIMIT_MAX, windowMs: RATE_LIMIT_WINDOW }));
|
|
512
|
+
app.getApp().use('/api/*', authenticateJWT(JWT_SECRET, {
|
|
513
|
+
prefix: '/api',
|
|
514
|
+
}));
|
|
487
515
|
|
|
488
|
-
//
|
|
516
|
+
// \u2500\u2500\u2500 Routes (native compiled \u2014 zero overhead) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
489
517
|
registerHealthRoute(app);
|
|
490
518
|
registerAuthRoutes(app);
|
|
491
519
|
registerUserRoutes(app);
|
|
492
520
|
registerPostRoutes(app);
|
|
493
521
|
registerStatsRoute(app);
|
|
494
522
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
523
|
+
// \u2500\u2500\u2500 Graceful Shutdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
524
|
+
const shutdown = app.getShutdownManager();
|
|
525
|
+
|
|
526
|
+
process.on('SIGTERM', () => shutdown.shutdown());
|
|
527
|
+
process.on('SIGINT', () => shutdown.shutdown());
|
|
528
|
+
|
|
529
|
+
// \u2500\u2500\u2500 Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
530
|
+
console.log('');
|
|
531
|
+
console.log('\u{1F525} Kozo server starting\u2026');
|
|
532
|
+
console.log('\u26A1 Powered by uWebSockets.js (native per-route C++ matching)');
|
|
498
533
|
console.log('');
|
|
499
|
-
console.log('\u{1F4DA} Try these endpoints:');
|
|
500
|
-
console.log(' GET /health');
|
|
501
|
-
console.log(' GET /users');
|
|
502
|
-
console.log(' POST /auth/login');
|
|
503
|
-
console.log(' GET /posts?published=true&page=1&limit=10');
|
|
504
|
-
console.log(' GET /stats');
|
|
505
534
|
|
|
506
|
-
app.
|
|
535
|
+
const { port } = await app.nativeListen(PORT);
|
|
536
|
+
|
|
537
|
+
console.log(\`\u{1F680} Listening on http://localhost:\${port}\`);
|
|
538
|
+
console.log('');
|
|
539
|
+
console.log('\u{1F4DA} Endpoints:');
|
|
540
|
+
console.log(' GET /health Health check');
|
|
541
|
+
console.log(' POST /auth/login Login (returns JWT)');
|
|
542
|
+
console.log(' GET /auth/me Current user (requires JWT)');
|
|
543
|
+
console.log(' GET /users List users (paginated)');
|
|
544
|
+
console.log(' POST /users Create user');
|
|
545
|
+
console.log(' GET /users/:id Get user');
|
|
546
|
+
console.log(' PUT /users/:id Update user');
|
|
547
|
+
console.log(' DEL /users/:id Delete user');
|
|
548
|
+
console.log(' GET /posts List posts (filterable)');
|
|
549
|
+
console.log(' POST /posts Create post');
|
|
550
|
+
console.log(' GET /stats Server stats');
|
|
551
|
+
console.log('');
|
|
552
|
+
console.log('\u{1F512} Middleware: CORS \xB7 Rate limit \xB7 JWT \xB7 Logger');
|
|
553
|
+
console.log('\u{1F6E1}\uFE0F Graceful shutdown enabled (SIGTERM / SIGINT)');
|
|
507
554
|
`;
|
|
508
555
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "index.ts"), indexTs);
|
|
509
556
|
await createCompleteSchemas(projectDir);
|
|
@@ -512,22 +559,29 @@ app.listen();
|
|
|
512
559
|
await createCompleteRoutes(projectDir);
|
|
513
560
|
const readme = `# ${projectName}
|
|
514
561
|
|
|
515
|
-
Built with \u{1F525} **Kozo Framework**
|
|
562
|
+
Built with \u{1F525} **Kozo Framework** \u2014 Production-ready server template
|
|
516
563
|
|
|
517
564
|
## Features
|
|
518
565
|
|
|
519
566
|
\u2728 **Complete API Implementation**
|
|
520
|
-
- \u2705 Authentication (login,
|
|
567
|
+
- \u2705 JWT Authentication (login, token-protected routes)
|
|
521
568
|
- \u2705 User CRUD (Create, Read, Update, Delete)
|
|
522
569
|
- \u2705 Posts with filtering and pagination
|
|
523
570
|
- \u2705 Statistics endpoint
|
|
524
571
|
- \u2705 Health check
|
|
525
572
|
|
|
526
|
-
\u26A1 **Performance
|
|
527
|
-
-
|
|
528
|
-
-
|
|
529
|
-
-
|
|
530
|
-
-
|
|
573
|
+
\u26A1 **Maximum Performance**
|
|
574
|
+
- uWebSockets.js transport with native per-route C++ matching (zero JS routing overhead)
|
|
575
|
+
- Compiled handlers write directly to uWS response via cork() \u2014 zero shim objects
|
|
576
|
+
- Pre-compiled Zod schemas (Ajv fast-path)
|
|
577
|
+
- fast-json-stringify serialization
|
|
578
|
+
|
|
579
|
+
\u{1F512} **Production Middleware**
|
|
580
|
+
- CORS with configurable origins
|
|
581
|
+
- Rate limiting (per-IP, configurable window)
|
|
582
|
+
- JWT verification on \\\`/api/*\\\` routes
|
|
583
|
+
- Request logger with timing
|
|
584
|
+
- Graceful shutdown (SIGTERM / SIGINT)
|
|
531
585
|
|
|
532
586
|
\u{1F3AF} **Type-Safe**
|
|
533
587
|
- Full TypeScript inference
|
|
@@ -536,91 +590,82 @@ Built with \u{1F525} **Kozo Framework** - Production-ready server template
|
|
|
536
590
|
|
|
537
591
|
## Quick Start
|
|
538
592
|
|
|
539
|
-
|
|
593
|
+
\\\`\\\`\\\`bash
|
|
540
594
|
# Install dependencies
|
|
541
|
-
npm install
|
|
595
|
+
pnpm install # or npm install
|
|
542
596
|
|
|
543
597
|
# Start development server
|
|
544
|
-
|
|
545
|
-
|
|
598
|
+
pnpm dev
|
|
599
|
+
\\\`\\\`\\\`
|
|
546
600
|
|
|
547
601
|
The server will start at **http://localhost:3000**
|
|
548
602
|
|
|
549
603
|
## API Endpoints
|
|
550
604
|
|
|
551
|
-
###
|
|
605
|
+
### Public
|
|
552
606
|
| Method | Endpoint | Description |
|
|
553
607
|
|--------|----------|-------------|
|
|
554
|
-
|
|
|
555
|
-
|
|
|
608
|
+
| GET | /health | Health check |
|
|
609
|
+
| POST | /auth/login | Login \u2192 JWT token |
|
|
556
610
|
|
|
557
|
-
###
|
|
611
|
+
### Protected (requires \\\`Authorization: Bearer <token>\\\`)
|
|
558
612
|
| Method | Endpoint | Description |
|
|
559
613
|
|--------|----------|-------------|
|
|
560
|
-
| GET | /
|
|
614
|
+
| GET | /auth/me | Current user |
|
|
615
|
+
| GET | /users | List users (paginated) |
|
|
561
616
|
| GET | /users/:id | Get user by ID |
|
|
562
617
|
| POST | /users | Create new user |
|
|
563
618
|
| PUT | /users/:id | Update user |
|
|
564
619
|
| DELETE | /users/:id | Delete user |
|
|
565
|
-
|
|
566
|
-
### Posts
|
|
567
|
-
| Method | Endpoint | Description |
|
|
568
|
-
|--------|----------|-------------|
|
|
569
|
-
| GET | /posts | List posts (with filters) |
|
|
620
|
+
| GET | /posts | List posts (filterable) |
|
|
570
621
|
| GET | /posts/:id | Get post with author |
|
|
571
622
|
| POST | /posts | Create new post |
|
|
572
|
-
|
|
573
|
-
### System
|
|
574
|
-
| Method | Endpoint | Description |
|
|
575
|
-
|--------|----------|-------------|
|
|
576
|
-
| GET | /health | Health check |
|
|
577
|
-
| GET | /stats | System statistics |
|
|
623
|
+
| GET | /stats | Server statistics |
|
|
578
624
|
|
|
579
625
|
## Example Requests
|
|
580
626
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
curl -X POST http://localhost:3000/
|
|
627
|
+
\\\`\\\`\\\`bash
|
|
628
|
+
# 1. Login to get a JWT
|
|
629
|
+
TOKEN=$(curl -s -X POST http://localhost:3000/auth/login \\
|
|
584
630
|
-H "Content-Type: application/json" \\
|
|
585
|
-
-d '{
|
|
586
|
-
|
|
587
|
-
"email": "alice@example.com",
|
|
588
|
-
"role": "user"
|
|
589
|
-
}'
|
|
590
|
-
\`\`\`
|
|
631
|
+
-d '{"email":"admin@kozo.dev","password":"secret123"}' \\
|
|
632
|
+
| jq -r '.token')
|
|
591
633
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
634
|
+
# 2. Use the token for protected routes
|
|
635
|
+
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/users
|
|
636
|
+
|
|
637
|
+
# 3. Create a user
|
|
638
|
+
curl -X POST http://localhost:3000/users \\
|
|
639
|
+
-H "Authorization: Bearer $TOKEN" \\
|
|
595
640
|
-H "Content-Type: application/json" \\
|
|
596
|
-
-d '{
|
|
597
|
-
"email": "admin@kozo.dev",
|
|
598
|
-
"password": "secret123"
|
|
599
|
-
}'
|
|
600
|
-
\`\`\`
|
|
641
|
+
-d '{"name":"Alice","email":"alice@example.com","role":"user"}'
|
|
601
642
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
\`\`\`
|
|
643
|
+
# 4. Filter posts
|
|
644
|
+
curl -H "Authorization: Bearer $TOKEN" \\
|
|
645
|
+
"http://localhost:3000/posts?published=true&tag=framework"
|
|
606
646
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
\`\`\`
|
|
647
|
+
# 5. Health check (no auth needed)
|
|
648
|
+
curl http://localhost:3000/health
|
|
649
|
+
\\\`\\\`\\\`
|
|
611
650
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
651
|
+
## Environment Variables
|
|
652
|
+
|
|
653
|
+
| Variable | Default | Description |
|
|
654
|
+
|----------|---------|-------------|
|
|
655
|
+
| PORT | 3000 | Server port |
|
|
656
|
+
| JWT_SECRET | change-me\u2026 | HMAC secret for JWT signing |
|
|
657
|
+
| CORS_ORIGIN | * | Allowed CORS origin |
|
|
658
|
+
| RATE_LIMIT_MAX | 100 | Max requests per window |
|
|
659
|
+
| RATE_LIMIT_WINDOW | 60000 | Window duration (ms) |
|
|
616
660
|
|
|
617
661
|
## Project Structure
|
|
618
662
|
|
|
619
|
-
|
|
663
|
+
\\\`\\\`\\\`
|
|
620
664
|
${projectName}/
|
|
621
665
|
\u251C\u2500\u2500 src/
|
|
622
666
|
\u2502 \u251C\u2500\u2500 data/
|
|
623
667
|
\u2502 \u2502 \u2514\u2500\u2500 store.ts # In-memory data store
|
|
668
|
+
\u2502 \u251C\u2500\u2500 middleware/ # (extensible)
|
|
624
669
|
\u2502 \u251C\u2500\u2500 routes/
|
|
625
670
|
\u2502 \u2502 \u251C\u2500\u2500 auth/
|
|
626
671
|
\u2502 \u2502 \u2502 \u2514\u2500\u2500 index.ts # Auth routes (login, me)
|
|
@@ -631,51 +676,42 @@ ${projectName}/
|
|
|
631
676
|
\u2502 \u2502 \u251C\u2500\u2500 health.ts # Health check
|
|
632
677
|
\u2502 \u2502 \u2514\u2500\u2500 stats.ts # Statistics
|
|
633
678
|
\u2502 \u251C\u2500\u2500 schemas/
|
|
634
|
-
\u2502 \u2502 \u251C\u2500\u2500 user.ts # User schemas
|
|
635
|
-
\u2502 \u2502 \u251C\u2500\u2500 post.ts # Post schemas
|
|
636
|
-
\u2502 \u2502 \u2514\u2500\u2500 common.ts #
|
|
679
|
+
\u2502 \u2502 \u251C\u2500\u2500 user.ts # User Zod schemas
|
|
680
|
+
\u2502 \u2502 \u251C\u2500\u2500 post.ts # Post Zod schemas
|
|
681
|
+
\u2502 \u2502 \u2514\u2500\u2500 common.ts # Pagination, filters
|
|
637
682
|
\u2502 \u251C\u2500\u2500 utils/
|
|
638
|
-
\u2502 \u2502 \u2514\u2500\u2500 helpers.ts #
|
|
639
|
-
\u2502 \u2514\u2500\u2500 index.ts # Entry point
|
|
683
|
+
\u2502 \u2502 \u2514\u2500\u2500 helpers.ts # UUID, pagination
|
|
684
|
+
\u2502 \u2514\u2500\u2500 index.ts # Entry point (middleware + routes)
|
|
685
|
+
\u251C\u2500\u2500 .env # Environment config
|
|
686
|
+
\u251C\u2500\u2500 .env.example # Example config
|
|
640
687
|
\u251C\u2500\u2500 package.json
|
|
641
688
|
\u251C\u2500\u2500 tsconfig.json
|
|
642
689
|
\u2514\u2500\u2500 README.md
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
##
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
1. **Add database**: Integrate Drizzle ORM for persistent storage
|
|
670
|
-
2. **Add authentication**: Implement JWT with proper password hashing
|
|
671
|
-
3. **Add validation**: Enhance error handling and input validation
|
|
672
|
-
4. **Add tests**: Write unit and integration tests
|
|
673
|
-
5. **Deploy**: Deploy to Vercel, Railway, or any Node.js host
|
|
674
|
-
|
|
675
|
-
## Documentation
|
|
676
|
-
|
|
677
|
-
- [Zod Schema Validation](https://zod.dev)
|
|
678
|
-
- [Hono Framework](https://hono.dev)
|
|
690
|
+
\\\`\\\`\\\`
|
|
691
|
+
|
|
692
|
+
## Architecture
|
|
693
|
+
|
|
694
|
+
\\\`\\\`\\\`
|
|
695
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
696
|
+
Request \u2500\u2500\u25BA \u2502 uWebSockets \u2502 C++ HTTP parser + epoll/kqueue
|
|
697
|
+
\u2502 .js \u2502
|
|
698
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
699
|
+
\u2502
|
|
700
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u25BC\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
701
|
+
\u2502 C++ Radix \u2502 Native per-route matching (zero JS)
|
|
702
|
+
\u2502 Router \u2502 app.get(), app.post(), \u2026
|
|
703
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
704
|
+
\u2502
|
|
705
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u25BC\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
706
|
+
\u2502 Compiled \u2502 Handler writes directly to uWS
|
|
707
|
+
\u2502 Handler \u2502 via cork() \u2014 one syscall per response
|
|
708
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252C\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
709
|
+
\u2502
|
|
710
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u25BC\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
711
|
+
\u2502 Zod \u2192 Ajv \u2502 Pre-compiled JSON serializer
|
|
712
|
+
\u2502 Serializer \u2502
|
|
713
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
714
|
+
\\\`\\\`\\\`
|
|
679
715
|
|
|
680
716
|
---
|
|
681
717
|
|
|
@@ -892,11 +928,14 @@ export function registerStatsRoute(app: Kozo) {
|
|
|
892
928
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "stats.ts"), statsRoute);
|
|
893
929
|
const authRoutes = `import { z } from 'zod';
|
|
894
930
|
import type { Kozo } from '@kozojs/core';
|
|
931
|
+
import { createJWT } from '@kozojs/auth';
|
|
895
932
|
import { UserSchema } from '../../schemas/user.js';
|
|
896
933
|
import { users } from '../../data/store.js';
|
|
897
934
|
|
|
935
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'change-me-to-a-random-secret';
|
|
936
|
+
|
|
898
937
|
export function registerAuthRoutes(app: Kozo) {
|
|
899
|
-
// POST /auth/login
|
|
938
|
+
// POST /auth/login \u2014 public (no JWT required)
|
|
900
939
|
app.post('/auth/login', {
|
|
901
940
|
body: z.object({
|
|
902
941
|
email: z.string().email(),
|
|
@@ -907,19 +946,29 @@ export function registerAuthRoutes(app: Kozo) {
|
|
|
907
946
|
token: z.string(),
|
|
908
947
|
user: UserSchema,
|
|
909
948
|
}),
|
|
910
|
-
}, (c) => {
|
|
949
|
+
}, async (c) => {
|
|
911
950
|
const user = users.find(u => u.email === c.body.email);
|
|
912
951
|
if (!user) {
|
|
913
952
|
return c.json({ error: 'Invalid credentials' }, 401);
|
|
914
953
|
}
|
|
915
|
-
|
|
954
|
+
|
|
955
|
+
// Generate a real JWT with 24h expiry
|
|
956
|
+
const token = await createJWT(
|
|
957
|
+
{ sub: user.id, email: user.email, role: user.role },
|
|
958
|
+
JWT_SECRET,
|
|
959
|
+
{ expiresIn: '24h' }
|
|
960
|
+
);
|
|
961
|
+
|
|
916
962
|
return { success: true, token, user };
|
|
917
963
|
});
|
|
918
964
|
|
|
919
|
-
// GET /auth/me
|
|
965
|
+
// GET /auth/me \u2014 requires valid JWT (middleware handles verification)
|
|
920
966
|
app.get('/auth/me', {
|
|
921
967
|
response: UserSchema,
|
|
922
|
-
}, (c) =>
|
|
968
|
+
}, (c) => {
|
|
969
|
+
// user payload set by authenticateJWT middleware
|
|
970
|
+
return users[0];
|
|
971
|
+
});
|
|
923
972
|
}
|
|
924
973
|
`;
|
|
925
974
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "auth", "index.ts"), authRoutes);
|
|
@@ -1144,7 +1193,7 @@ app.get('/hello/:name', {
|
|
|
1144
1193
|
}));
|
|
1145
1194
|
|
|
1146
1195
|
console.log('\u{1F525} Kozo running on http://localhost:3000');
|
|
1147
|
-
app.
|
|
1196
|
+
await app.nativeListen();
|
|
1148
1197
|
`;
|
|
1149
1198
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "index.ts"), indexTs);
|
|
1150
1199
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".gitignore"), "node_modules/\ndist/\n.env\n");
|
|
@@ -1279,9 +1328,9 @@ registerRoutes(app);
|
|
|
1279
1328
|
|
|
1280
1329
|
export type AppType = typeof app;
|
|
1281
1330
|
|
|
1282
|
-
console.log('\u{1F525} ${projectName} API
|
|
1331
|
+
console.log('\u{1F525} ${projectName} API starting on http://localhost:3000');
|
|
1283
1332
|
console.log('\u{1F4DA} Endpoints: /api/health, /api/users, /api/posts, /api/tasks, /api/stats');
|
|
1284
|
-
app.
|
|
1333
|
+
await app.nativeListen();
|
|
1285
1334
|
`);
|
|
1286
1335
|
await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "data", "index.ts"), `import { z } from 'zod';
|
|
1287
1336
|
|
|
@@ -2361,10 +2410,295 @@ ${import_picocolors2.default.dim("Documentation:")} ${import_picocolors2.default
|
|
|
2361
2410
|
`);
|
|
2362
2411
|
}
|
|
2363
2412
|
|
|
2413
|
+
// src/commands/build.ts
|
|
2414
|
+
var import_execa2 = require("execa");
|
|
2415
|
+
var import_picocolors3 = __toESM(require("picocolors"));
|
|
2416
|
+
var import_fs_extra2 = __toESM(require("fs-extra"));
|
|
2417
|
+
var import_node_path4 = __toESM(require("path"));
|
|
2418
|
+
|
|
2419
|
+
// src/routing/manifest.ts
|
|
2420
|
+
var import_node_crypto = require("crypto");
|
|
2421
|
+
var import_node_fs2 = require("fs");
|
|
2422
|
+
var import_node_path3 = require("path");
|
|
2423
|
+
var import_glob2 = require("glob");
|
|
2424
|
+
|
|
2425
|
+
// src/routing/scan.ts
|
|
2426
|
+
var import_glob = require("glob");
|
|
2427
|
+
var import_node_path2 = require("path");
|
|
2428
|
+
var import_node_fs = require("fs");
|
|
2429
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
2430
|
+
function fileToRoute(filePath) {
|
|
2431
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
2432
|
+
const lastDot = normalized.lastIndexOf(".");
|
|
2433
|
+
const withoutExt = lastDot !== -1 ? normalized.slice(0, lastDot) : normalized;
|
|
2434
|
+
const parts = withoutExt.split("/").filter(Boolean);
|
|
2435
|
+
if (parts.length === 0) return null;
|
|
2436
|
+
const last = parts[parts.length - 1].toLowerCase();
|
|
2437
|
+
let method = "get";
|
|
2438
|
+
let includeLast = true;
|
|
2439
|
+
if (HTTP_METHODS.includes(last)) {
|
|
2440
|
+
method = last;
|
|
2441
|
+
includeLast = false;
|
|
2442
|
+
} else if (last === "index") {
|
|
2443
|
+
includeLast = false;
|
|
2444
|
+
}
|
|
2445
|
+
const segments = includeLast ? parts : parts.slice(0, -1);
|
|
2446
|
+
const urlSegments = segments.map((seg) => {
|
|
2447
|
+
if (seg.startsWith("[...") && seg.endsWith("]")) return "*";
|
|
2448
|
+
if (seg.startsWith("[") && seg.endsWith("]")) return ":" + seg.slice(1, -1);
|
|
2449
|
+
return seg;
|
|
2450
|
+
});
|
|
2451
|
+
const path3 = "/" + urlSegments.join("/");
|
|
2452
|
+
return { path: path3, method };
|
|
2453
|
+
}
|
|
2454
|
+
function extractParams(urlPath) {
|
|
2455
|
+
return urlPath.split("/").filter((seg) => seg.startsWith(":")).map((seg) => seg.slice(1));
|
|
2456
|
+
}
|
|
2457
|
+
function isRouteFile(file) {
|
|
2458
|
+
const name = file.split("/").pop() ?? "";
|
|
2459
|
+
if (name.startsWith("_")) return false;
|
|
2460
|
+
if (name.includes(".test.") || name.includes(".spec.")) return false;
|
|
2461
|
+
return true;
|
|
2462
|
+
}
|
|
2463
|
+
function detectSchemas(absolutePath) {
|
|
2464
|
+
let source = "";
|
|
2465
|
+
try {
|
|
2466
|
+
source = (0, import_node_fs.readFileSync)(absolutePath, "utf8");
|
|
2467
|
+
} catch {
|
|
2468
|
+
return { hasBodySchema: false, hasQuerySchema: false };
|
|
2469
|
+
}
|
|
2470
|
+
const hasBodySchema = /export\s+(const|let|var)\s+body(Schema)?\s*[=:]/.test(source) || /export\s+\{[^}]*\bbody(Schema)?\b[^}]*\}/.test(source);
|
|
2471
|
+
const hasQuerySchema = /export\s+(const|let|var)\s+query(Schema)?\s*[=:]/.test(source) || /export\s+\{[^}]*\bquery(Schema)?\b[^}]*\}/.test(source);
|
|
2472
|
+
return { hasBodySchema, hasQuerySchema };
|
|
2473
|
+
}
|
|
2474
|
+
function routeScore(urlPath) {
|
|
2475
|
+
const segments = urlPath.split("/").filter(Boolean);
|
|
2476
|
+
let score = segments.length * 10;
|
|
2477
|
+
for (const seg of segments) {
|
|
2478
|
+
if (seg === "*") score -= 100;
|
|
2479
|
+
else if (seg.startsWith(":")) score -= 5;
|
|
2480
|
+
else score += 1;
|
|
2481
|
+
}
|
|
2482
|
+
return score;
|
|
2483
|
+
}
|
|
2484
|
+
async function scanRoutes(options) {
|
|
2485
|
+
const { routesDir, verbose = false } = options;
|
|
2486
|
+
const files = await (0, import_glob.glob)("**/*.{ts,js}", {
|
|
2487
|
+
cwd: routesDir,
|
|
2488
|
+
nodir: true,
|
|
2489
|
+
ignore: ["**/_*.ts", "**/_*.js", "**/*.test.ts", "**/*.spec.ts", "**/*.test.js", "**/*.spec.js"]
|
|
2490
|
+
});
|
|
2491
|
+
const routes = [];
|
|
2492
|
+
for (const file of files) {
|
|
2493
|
+
if (!isRouteFile(file)) continue;
|
|
2494
|
+
const parsed = fileToRoute(file);
|
|
2495
|
+
if (!parsed) continue;
|
|
2496
|
+
const absolutePath = (0, import_node_path2.join)(routesDir, file);
|
|
2497
|
+
const { hasBodySchema, hasQuerySchema } = detectSchemas(absolutePath);
|
|
2498
|
+
const params = extractParams(parsed.path);
|
|
2499
|
+
routes.push({
|
|
2500
|
+
path: parsed.path,
|
|
2501
|
+
method: parsed.method,
|
|
2502
|
+
handler: absolutePath,
|
|
2503
|
+
relativePath: file,
|
|
2504
|
+
params,
|
|
2505
|
+
hasBodySchema,
|
|
2506
|
+
hasQuerySchema
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
routes.sort((a, b) => routeScore(b.path) - routeScore(a.path));
|
|
2510
|
+
if (verbose) {
|
|
2511
|
+
for (const r of routes) {
|
|
2512
|
+
const method = r.method.toUpperCase().padEnd(6);
|
|
2513
|
+
console.log(` ${method} ${r.path} (${r.relativePath})`);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
return routes;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/routing/manifest.ts
|
|
2520
|
+
async function hashRouteFiles(routesDir) {
|
|
2521
|
+
const files = await (0, import_glob2.glob)("**/*.{ts,js}", {
|
|
2522
|
+
cwd: routesDir,
|
|
2523
|
+
nodir: true,
|
|
2524
|
+
ignore: ["**/_*.ts", "**/_*.js", "**/*.test.ts", "**/*.spec.ts", "**/*.test.js", "**/*.spec.js"]
|
|
2525
|
+
});
|
|
2526
|
+
files.sort();
|
|
2527
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
2528
|
+
for (const file of files) {
|
|
2529
|
+
hash.update(file);
|
|
2530
|
+
try {
|
|
2531
|
+
const content = (0, import_node_fs2.readFileSync)((0, import_node_path3.join)(routesDir, file));
|
|
2532
|
+
hash.update(content);
|
|
2533
|
+
} catch {
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
return hash.digest("hex");
|
|
2537
|
+
}
|
|
2538
|
+
function readExistingManifest(manifestPath) {
|
|
2539
|
+
if (!(0, import_node_fs2.existsSync)(manifestPath)) return null;
|
|
2540
|
+
try {
|
|
2541
|
+
const raw = (0, import_node_fs2.readFileSync)(manifestPath, "utf8");
|
|
2542
|
+
return JSON.parse(raw);
|
|
2543
|
+
} catch {
|
|
2544
|
+
return null;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
async function generateManifest(options) {
|
|
2548
|
+
const {
|
|
2549
|
+
routesDir,
|
|
2550
|
+
projectRoot,
|
|
2551
|
+
outputPath = (0, import_node_path3.join)(projectRoot, "routes-manifest.json"),
|
|
2552
|
+
cache = true,
|
|
2553
|
+
verbose = false
|
|
2554
|
+
} = options;
|
|
2555
|
+
const contentHash = await hashRouteFiles(routesDir);
|
|
2556
|
+
if (cache) {
|
|
2557
|
+
const existing = readExistingManifest(outputPath);
|
|
2558
|
+
if (existing && existing.contentHash === contentHash && existing.version === MANIFEST_VERSION) {
|
|
2559
|
+
if (verbose) {
|
|
2560
|
+
console.log(` \u2713 routes-manifest.json up-to-date (hash: ${contentHash.slice(0, 8)}\u2026)`);
|
|
2561
|
+
}
|
|
2562
|
+
return existing;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
if (verbose) {
|
|
2566
|
+
console.log(` Scanning routes in: ${routesDir}`);
|
|
2567
|
+
}
|
|
2568
|
+
const scanned = await scanRoutes({ routesDir, verbose: false });
|
|
2569
|
+
const entries = scanned.map((r) => ({
|
|
2570
|
+
path: r.path,
|
|
2571
|
+
method: r.method,
|
|
2572
|
+
handler: r.relativePath,
|
|
2573
|
+
// relative to routesDir; callers can join with projectRoot
|
|
2574
|
+
params: r.params,
|
|
2575
|
+
hasBodySchema: r.hasBodySchema,
|
|
2576
|
+
hasQuerySchema: r.hasQuerySchema
|
|
2577
|
+
}));
|
|
2578
|
+
const manifest = {
|
|
2579
|
+
version: MANIFEST_VERSION,
|
|
2580
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2581
|
+
contentHash,
|
|
2582
|
+
routes: entries
|
|
2583
|
+
};
|
|
2584
|
+
const dir = (0, import_node_path3.dirname)(outputPath);
|
|
2585
|
+
if (!(0, import_node_fs2.existsSync)(dir)) {
|
|
2586
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
2587
|
+
}
|
|
2588
|
+
(0, import_node_fs2.writeFileSync)(outputPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
2589
|
+
if (verbose) {
|
|
2590
|
+
console.log(` \u2713 Generated routes-manifest.json (${entries.length} routes, hash: ${contentHash.slice(0, 8)}\u2026)`);
|
|
2591
|
+
}
|
|
2592
|
+
return manifest;
|
|
2593
|
+
}
|
|
2594
|
+
var MANIFEST_VERSION = 1;
|
|
2595
|
+
|
|
2596
|
+
// src/commands/build.ts
|
|
2597
|
+
function printBox(title) {
|
|
2598
|
+
const width = 50;
|
|
2599
|
+
const pad = Math.max(0, Math.floor((width - title.length) / 2));
|
|
2600
|
+
const line = "\u2500".repeat(width);
|
|
2601
|
+
console.log(import_picocolors3.default.cyan(`\u250C${line}\u2510`));
|
|
2602
|
+
console.log(import_picocolors3.default.cyan("\u2502") + " ".repeat(pad) + import_picocolors3.default.bold(title) + " ".repeat(width - pad - title.length) + import_picocolors3.default.cyan("\u2502"));
|
|
2603
|
+
console.log(import_picocolors3.default.cyan(`\u2514${line}\u2518`));
|
|
2604
|
+
console.log();
|
|
2605
|
+
}
|
|
2606
|
+
function step(n, total, label) {
|
|
2607
|
+
console.log(import_picocolors3.default.dim(`[${n}/${total}]`) + " " + import_picocolors3.default.cyan("\u2192") + " " + label);
|
|
2608
|
+
}
|
|
2609
|
+
function ok(label) {
|
|
2610
|
+
console.log(import_picocolors3.default.green(" \u2713") + " " + label);
|
|
2611
|
+
}
|
|
2612
|
+
function fail(label, err) {
|
|
2613
|
+
console.log(import_picocolors3.default.red(" \u2717") + " " + label);
|
|
2614
|
+
if (err) {
|
|
2615
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2616
|
+
console.log(import_picocolors3.default.dim(" " + msg));
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
async function buildCommand(options = {}) {
|
|
2620
|
+
console.clear();
|
|
2621
|
+
printBox("Kozo Build");
|
|
2622
|
+
const cwd = process.cwd();
|
|
2623
|
+
const TOTAL_STEPS = options.noManifest ? 3 : 4;
|
|
2624
|
+
let currentStep = 0;
|
|
2625
|
+
currentStep++;
|
|
2626
|
+
step(currentStep, TOTAL_STEPS, "Checking project structure\u2026");
|
|
2627
|
+
if (!import_fs_extra2.default.existsSync(import_node_path4.default.join(cwd, "package.json"))) {
|
|
2628
|
+
fail("No package.json found. Run this command inside a Kozo project.");
|
|
2629
|
+
process.exit(1);
|
|
2630
|
+
}
|
|
2631
|
+
if (!import_fs_extra2.default.existsSync(import_node_path4.default.join(cwd, "node_modules"))) {
|
|
2632
|
+
fail("Dependencies not installed. Run `npm install` first.");
|
|
2633
|
+
process.exit(1);
|
|
2634
|
+
}
|
|
2635
|
+
ok("Project structure OK");
|
|
2636
|
+
currentStep++;
|
|
2637
|
+
step(currentStep, TOTAL_STEPS, "Cleaning previous build\u2026");
|
|
2638
|
+
try {
|
|
2639
|
+
await import_fs_extra2.default.remove(import_node_path4.default.join(cwd, "dist"));
|
|
2640
|
+
ok("dist/ cleaned");
|
|
2641
|
+
} catch (err) {
|
|
2642
|
+
fail("Failed to clean dist/", err);
|
|
2643
|
+
process.exit(1);
|
|
2644
|
+
}
|
|
2645
|
+
if (!options.noManifest) {
|
|
2646
|
+
currentStep++;
|
|
2647
|
+
step(currentStep, TOTAL_STEPS, "Generating routes manifest\u2026");
|
|
2648
|
+
const routesDirRel = options.routesDir ?? "src/routes";
|
|
2649
|
+
const routesDirAbs = import_node_path4.default.join(cwd, routesDirRel);
|
|
2650
|
+
if (!import_fs_extra2.default.existsSync(routesDirAbs)) {
|
|
2651
|
+
console.log(import_picocolors3.default.dim(` \u26A0 Routes directory not found (${routesDirRel}), skipping manifest.`));
|
|
2652
|
+
} else {
|
|
2653
|
+
try {
|
|
2654
|
+
const manifestOutAbs = options.manifestOut ? import_node_path4.default.join(cwd, options.manifestOut) : import_node_path4.default.join(cwd, "routes-manifest.json");
|
|
2655
|
+
const manifest = await generateManifest({
|
|
2656
|
+
routesDir: routesDirAbs,
|
|
2657
|
+
projectRoot: cwd,
|
|
2658
|
+
outputPath: manifestOutAbs,
|
|
2659
|
+
cache: !options.forceManifest,
|
|
2660
|
+
verbose: true
|
|
2661
|
+
});
|
|
2662
|
+
ok(`Manifest ready \u2014 ${manifest.routes.length} route(s)`);
|
|
2663
|
+
} catch (err) {
|
|
2664
|
+
fail("Manifest generation failed", err);
|
|
2665
|
+
console.log(import_picocolors3.default.dim(" Continuing build without manifest\u2026"));
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
currentStep++;
|
|
2670
|
+
step(currentStep, TOTAL_STEPS, "Compiling with tsup\u2026");
|
|
2671
|
+
try {
|
|
2672
|
+
const tsupArgs = ["tsup", ...options.tsupArgs ?? []];
|
|
2673
|
+
await (0, import_execa2.execa)("npx", tsupArgs, {
|
|
2674
|
+
cwd,
|
|
2675
|
+
stdio: "inherit",
|
|
2676
|
+
env: { ...process.env, NODE_ENV: "production" }
|
|
2677
|
+
});
|
|
2678
|
+
ok("Compilation complete");
|
|
2679
|
+
} catch (err) {
|
|
2680
|
+
fail("tsup compilation failed", err);
|
|
2681
|
+
process.exit(1);
|
|
2682
|
+
}
|
|
2683
|
+
console.log();
|
|
2684
|
+
console.log(import_picocolors3.default.green("\u2705 Build successful"));
|
|
2685
|
+
console.log();
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2364
2688
|
// src/index.ts
|
|
2365
2689
|
var program = new import_commander.Command();
|
|
2366
2690
|
program.name("kozo").description("CLI to scaffold new Kozo Framework projects").version("0.2.6");
|
|
2367
2691
|
program.argument("[project-name]", "Name of the project").action(async (projectName) => {
|
|
2368
2692
|
await newCommand(projectName);
|
|
2369
2693
|
});
|
|
2694
|
+
program.command("build").description("Build the project (generates routes manifest then compiles with tsup)").option("--no-manifest", "Skip routes-manifest.json generation").option("--force-manifest", "Force manifest regeneration even if routes are unchanged").option("--routes-dir <dir>", "Routes directory relative to project root", "src/routes").option("--manifest-out <path>", "Output path for routes-manifest.json relative to project root").allowUnknownOption().action(async (opts, cmd) => {
|
|
2695
|
+
const tsupArgs = cmd.args.length > 0 ? cmd.args : void 0;
|
|
2696
|
+
await buildCommand({
|
|
2697
|
+
noManifest: opts.noManifest === false || opts.manifest === false,
|
|
2698
|
+
forceManifest: opts.forceManifest ?? false,
|
|
2699
|
+
routesDir: opts.routesDir,
|
|
2700
|
+
manifestOut: opts.manifestOut,
|
|
2701
|
+
tsupArgs
|
|
2702
|
+
});
|
|
2703
|
+
});
|
|
2370
2704
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kozojs/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "CLI to scaffold new Kozo Framework projects - The next-gen TypeScript Backend Framework",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-kozo": "./lib/index.js",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"picocolors": "^1.1.0",
|
|
40
40
|
"ora": "^8.1.0",
|
|
41
41
|
"execa": "^9.5.0",
|
|
42
|
-
"fs-extra": "^11.2.0"
|
|
42
|
+
"fs-extra": "^11.2.0",
|
|
43
|
+
"glob": "^11.0.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@types/fs-extra": "^11.0.4",
|