@kozojs/cli 0.1.19 → 0.1.21

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.
Files changed (2) hide show
  1. package/lib/index.js +437 -288
  2. package/package.json +51 -51
package/lib/index.js CHANGED
@@ -1494,31 +1494,32 @@ ${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
1494
1494
  await import_fs_extra.default.writeJSON(import_node_path.default.join(apiDir, "tsconfig.json"), tsconfig, { spaces: 2 });
1495
1495
  const authImport = auth ? `import { authenticateJWT } from '@kozojs/auth';
1496
1496
  ` : "";
1497
- const servicesSetup = "\nconst services = {};\n";
1498
1497
  const authMiddleware = auth ? `
1499
- // JWT protects all /api/* routes except /api/auth/*
1498
+ // JWT protects all /api/* routes except public ones
1500
1499
  const JWT_SECRET = process.env.JWT_SECRET || 'change-me';
1501
1500
  const _jwt = authenticateJWT(JWT_SECRET, { prefix: '' });
1501
+ const publicPaths = ['/api/auth/', '/api/health', '/api/stats'];
1502
1502
  app.getApp().use('/api/*', (c, next) => {
1503
- if (c.req.path.startsWith('/api/auth/')) return next();
1503
+ if (publicPaths.some(p => c.req.path.startsWith(p))) return next();
1504
1504
  return _jwt(c, next);
1505
1505
  });
1506
1506
  ` : "";
1507
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "index.ts"), `import 'dotenv/config';
1507
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "index.ts"), `import 'dotenv/config';
1508
1508
  import { createKozo } from '@kozojs/core';
1509
- ${authImport}import { registerRoutes } from './routes/index.js';
1510
- ${servicesSetup}
1511
- const app = createKozo({ port: 3000, services });
1512
- ${authMiddleware}
1513
- registerRoutes(app);
1509
+ ${authImport}import { fileURLToPath } from 'node:url';
1510
+ import { dirname, join } from 'node:path';
1511
+
1512
+ const __dirname = dirname(fileURLToPath(import.meta.url));
1513
+ const PORT = Number(process.env.PORT) || 3000;
1514
+ const app = createKozo({ port: PORT });
1515
+ ${authMiddleware}await app.loadRoutes(join(__dirname, 'routes'));
1514
1516
 
1515
1517
  export type AppType = typeof app;
1516
1518
 
1517
- console.log('\u{1F525} ${projectName} API starting on http://localhost:3000');
1518
- console.log('\u{1F4DA} Endpoints: /api/health, /api/users, /api/posts, /api/tasks, /api/stats');
1519
+ console.log(\`\u{1F525} ${projectName} API on http://localhost:\${PORT}\`);
1519
1520
  await app.listen();
1520
1521
  `);
1521
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "data", "index.ts"), `import { z } from 'zod';
1522
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "schemas", "index.ts"), `import { z } from 'zod';
1522
1523
 
1523
1524
  export const UserSchema = z.object({
1524
1525
  id: z.string(),
@@ -1528,6 +1529,18 @@ export const UserSchema = z.object({
1528
1529
  createdAt: z.string().optional(),
1529
1530
  });
1530
1531
 
1532
+ export const CreateUserBody = z.object({
1533
+ name: z.string().min(1),
1534
+ email: z.string().email(),
1535
+ role: z.enum(['admin', 'user']).optional(),
1536
+ });
1537
+
1538
+ export const UpdateUserBody = z.object({
1539
+ name: z.string().optional(),
1540
+ email: z.string().email().optional(),
1541
+ role: z.enum(['admin', 'user']).optional(),
1542
+ });
1543
+
1531
1544
  export const PostSchema = z.object({
1532
1545
  id: z.string(),
1533
1546
  title: z.string(),
@@ -1537,6 +1550,19 @@ export const PostSchema = z.object({
1537
1550
  createdAt: z.string().optional(),
1538
1551
  });
1539
1552
 
1553
+ export const CreatePostBody = z.object({
1554
+ title: z.string().min(1),
1555
+ content: z.string().optional(),
1556
+ authorId: z.string().optional(),
1557
+ published: z.boolean().optional(),
1558
+ });
1559
+
1560
+ export const UpdatePostBody = z.object({
1561
+ title: z.string().optional(),
1562
+ content: z.string().optional(),
1563
+ published: z.boolean().optional(),
1564
+ });
1565
+
1540
1566
  export const TaskSchema = z.object({
1541
1567
  id: z.string(),
1542
1568
  title: z.string(),
@@ -1545,302 +1571,415 @@ export const TaskSchema = z.object({
1545
1571
  createdAt: z.string(),
1546
1572
  });
1547
1573
 
1548
- export const users: z.infer<typeof UserSchema>[] = [
1549
- { id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin', createdAt: new Date().toISOString() },
1550
- { id: '2', name: 'Bob', email: 'bob@example.com', role: 'user', createdAt: new Date().toISOString() },
1574
+ export const CreateTaskBody = z.object({
1575
+ title: z.string().min(1),
1576
+ priority: z.enum(['low', 'medium', 'high']).optional(),
1577
+ });
1578
+
1579
+ export const UpdateTaskBody = z.object({
1580
+ title: z.string().optional(),
1581
+ completed: z.boolean().optional(),
1582
+ priority: z.enum(['low', 'medium', 'high']).optional(),
1583
+ });
1584
+ `);
1585
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "data", "index.ts"), `export const users = [
1586
+ { id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin' as const, createdAt: new Date().toISOString() },
1587
+ { id: '2', name: 'Bob', email: 'bob@example.com', role: 'user' as const, createdAt: new Date().toISOString() },
1551
1588
  ];
1552
1589
 
1553
- export const posts: z.infer<typeof PostSchema>[] = [
1590
+ export const posts = [
1554
1591
  { id: '1', title: 'Hello World', content: 'First post!', authorId: '1', published: true, createdAt: new Date().toISOString() },
1555
1592
  { id: '2', title: 'Draft', content: 'Work in progress', authorId: '2', published: false, createdAt: new Date().toISOString() },
1556
1593
  ];
1557
1594
 
1558
- export const tasks: z.infer<typeof TaskSchema>[] = [
1559
- { id: '1', title: 'Setup project', completed: true, priority: 'high', createdAt: new Date().toISOString() },
1560
- { id: '2', title: 'Write tests', completed: false, priority: 'medium', createdAt: new Date().toISOString() },
1561
- { id: '3', title: 'Deploy', completed: false, priority: 'low', createdAt: new Date().toISOString() },
1595
+ export const tasks = [
1596
+ { id: '1', title: 'Setup project', completed: true, priority: 'high' as const, createdAt: new Date().toISOString() },
1597
+ { id: '2', title: 'Write tests', completed: false, priority: 'medium' as const, createdAt: new Date().toISOString() },
1598
+ { id: '3', title: 'Deploy', completed: false, priority: 'low' as const, createdAt: new Date().toISOString() },
1562
1599
  ];
1563
1600
  `);
1564
- const authRoutesImport = auth ? `import { registerAuthRoutes } from './auth';
1565
- ` : "";
1566
- const authRoutesCall = auth ? ` registerAuthRoutes(app); // Public auth endpoint must register before JWT middleware
1567
- ` : "";
1568
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "routes", "index.ts"), `import type { Kozo } from '@kozojs/core';
1569
- import { registerHealthRoutes } from './health';
1570
- import { registerUserRoutes } from './users';
1571
- import { registerPostRoutes } from './posts';
1572
- import { registerTaskRoutes } from './tasks';
1573
- import { registerToolRoutes } from './tools';
1574
- ${authRoutesImport}
1575
- export function registerRoutes(app: Kozo) {
1576
- ${authRoutesCall} registerHealthRoutes(app);
1577
- registerUserRoutes(app);
1578
- registerPostRoutes(app);
1579
- registerTaskRoutes(app);
1580
- registerToolRoutes(app);
1581
- }
1601
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "health", "get.ts"), `import { z } from 'zod';
1602
+
1603
+ export const schema = {
1604
+ response: z.object({
1605
+ status: z.string(),
1606
+ timestamp: z.string(),
1607
+ version: z.string(),
1608
+ uptime: z.number(),
1609
+ }),
1610
+ };
1611
+
1612
+ export default async () => ({
1613
+ status: 'ok',
1614
+ timestamp: new Date().toISOString(),
1615
+ version: '1.0.0',
1616
+ uptime: process.uptime(),
1617
+ });
1582
1618
  `);
1583
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "routes", "health.ts"), `import type { Kozo } from '@kozojs/core';
1584
- import { users, posts, tasks } from '../data';
1619
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "stats", "get.ts"), `import { z } from 'zod';
1620
+ import { users, posts, tasks } from '../../../data/index.js';
1585
1621
 
1586
- export function registerHealthRoutes(app: Kozo) {
1587
- app.get('/api/health', {}, () => ({
1588
- status: 'ok',
1589
- timestamp: new Date().toISOString(),
1590
- version: '1.0.0',
1591
- uptime: process.uptime(),
1592
- }));
1622
+ export const schema = {
1623
+ response: z.object({
1624
+ users: z.number(),
1625
+ posts: z.number(),
1626
+ tasks: z.number(),
1627
+ publishedPosts: z.number(),
1628
+ completedTasks: z.number(),
1629
+ }),
1630
+ };
1593
1631
 
1594
- app.get('/api/stats', {}, () => ({
1595
- users: users.length,
1596
- posts: posts.length,
1597
- tasks: tasks.length,
1598
- publishedPosts: posts.filter(p => p.published).length,
1599
- completedTasks: tasks.filter(t => t.completed).length,
1600
- }));
1601
- }
1632
+ export default async () => ({
1633
+ users: users.length,
1634
+ posts: posts.length,
1635
+ tasks: tasks.length,
1636
+ publishedPosts: posts.filter(p => p.published).length,
1637
+ completedTasks: tasks.filter(t => t.completed).length,
1638
+ });
1602
1639
  `);
1603
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "routes", "users.ts"), `import type { Kozo } from '@kozojs/core';
1604
- import { z } from 'zod';
1605
- import { users, UserSchema } from '../data';
1640
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "echo", "get.ts"), `import { z } from 'zod';
1606
1641
 
1607
- export function registerUserRoutes(app: Kozo) {
1608
- app.get('/api/users', {
1609
- response: z.array(UserSchema),
1610
- }, () => users);
1642
+ export const schema = {
1643
+ query: z.object({ message: z.string() }),
1644
+ response: z.object({
1645
+ echo: z.string(),
1646
+ timestamp: z.string(),
1647
+ }),
1648
+ };
1611
1649
 
1612
- app.get('/api/users/:id', {
1613
- params: z.object({ id: z.string() }),
1614
- response: UserSchema,
1615
- }, (c) => {
1616
- const user = users.find(u => u.id === c.params.id);
1617
- if (!user) throw new Error('User not found');
1618
- return user;
1619
- });
1650
+ export default async ({ query }: { query: { message: string } }) => ({
1651
+ echo: query.message,
1652
+ timestamp: new Date().toISOString(),
1653
+ });
1654
+ `);
1655
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "validate", "post.ts"), `import { z } from 'zod';
1620
1656
 
1621
- app.post('/api/users', {
1622
- body: z.object({
1623
- name: z.string().min(1),
1624
- email: z.string().email(),
1625
- role: z.enum(['admin', 'user']).optional(),
1626
- }),
1627
- response: UserSchema,
1628
- }, (c) => {
1629
- const newUser = {
1630
- id: String(Date.now()),
1631
- name: c.body.name,
1632
- email: c.body.email,
1633
- role: c.body.role || 'user' as const,
1634
- createdAt: new Date().toISOString(),
1635
- };
1636
- users.push(newUser);
1637
- return newUser;
1638
- });
1657
+ export const schema = {
1658
+ body: z.object({
1659
+ email: z.string().email(),
1660
+ age: z.number().min(0).max(150),
1661
+ }),
1662
+ response: z.object({
1663
+ valid: z.boolean(),
1664
+ data: z.object({ email: z.string(), age: z.number() }),
1665
+ }),
1666
+ };
1639
1667
 
1640
- app.put('/api/users/:id', {
1641
- params: z.object({ id: z.string() }),
1642
- body: z.object({
1643
- name: z.string().min(1).optional(),
1644
- email: z.string().email().optional(),
1645
- role: z.enum(['admin', 'user']).optional(),
1646
- }),
1647
- response: UserSchema,
1648
- }, (c) => {
1649
- const idx = users.findIndex(u => u.id === c.params.id);
1650
- if (idx === -1) throw new Error('User not found');
1651
- users[idx] = { ...users[idx], ...c.body };
1652
- return users[idx];
1653
- });
1668
+ export default async ({ body }: { body: { email: string; age: number } }) => ({
1669
+ valid: true,
1670
+ data: body,
1671
+ });
1672
+ `);
1673
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "users", "get.ts"), `import { z } from 'zod';
1674
+ import { users } from '../../../data/index.js';
1675
+ import { UserSchema } from '../../../schemas/index.js';
1654
1676
 
1655
- app.delete('/api/users/:id', {
1656
- params: z.object({ id: z.string() }),
1657
- }, (c) => {
1658
- const idx = users.findIndex(u => u.id === c.params.id);
1659
- if (idx === -1) throw new Error('User not found');
1660
- users.splice(idx, 1);
1661
- return { success: true, message: 'User deleted' };
1662
- });
1663
- }
1677
+ export const schema = {
1678
+ response: z.array(UserSchema),
1679
+ };
1680
+
1681
+ export default async () => users;
1664
1682
  `);
1665
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "routes", "posts.ts"), `import type { Kozo } from '@kozojs/core';
1666
- import { z } from 'zod';
1667
- import { posts, PostSchema } from '../data';
1683
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "users", "post.ts"), `import { users } from '../../../data/index.js';
1684
+ import { UserSchema, CreateUserBody } from '../../../schemas/index.js';
1668
1685
 
1669
- export function registerPostRoutes(app: Kozo) {
1670
- app.get('/api/posts', {
1671
- query: z.object({ published: z.coerce.boolean().optional() }),
1672
- response: z.array(PostSchema),
1673
- }, (c) => {
1674
- if (c.query.published !== undefined) {
1675
- return posts.filter(p => p.published === c.query.published);
1676
- }
1677
- return posts;
1678
- });
1686
+ export const schema = {
1687
+ body: CreateUserBody,
1688
+ response: UserSchema,
1689
+ };
1679
1690
 
1680
- app.get('/api/posts/:id', {
1681
- params: z.object({ id: z.string() }),
1682
- response: PostSchema,
1683
- }, (c) => {
1684
- const post = posts.find(p => p.id === c.params.id);
1685
- if (!post) throw new Error('Post not found');
1686
- return post;
1687
- });
1691
+ export default async ({ body }: { body: { name: string; email: string; role?: 'admin' | 'user' } }) => {
1692
+ const newUser = {
1693
+ id: String(Date.now()),
1694
+ name: body.name,
1695
+ email: body.email,
1696
+ role: body.role ?? ('user' as const),
1697
+ createdAt: new Date().toISOString(),
1698
+ };
1699
+ users.push(newUser);
1700
+ return newUser;
1701
+ };
1702
+ `);
1703
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "users", "[id]", "get.ts"), `import { z } from 'zod';
1704
+ import { KozoError } from '@kozojs/core';
1705
+ import { users } from '../../../../data/index.js';
1706
+ import { UserSchema } from '../../../../schemas/index.js';
1688
1707
 
1689
- app.post('/api/posts', {
1690
- body: z.object({
1691
- title: z.string().min(1),
1692
- content: z.string(),
1693
- authorId: z.string(),
1694
- published: z.boolean().optional(),
1695
- }),
1696
- response: PostSchema,
1697
- }, (c) => {
1698
- const newPost = {
1699
- id: String(Date.now()),
1700
- title: c.body.title,
1701
- content: c.body.content,
1702
- authorId: c.body.authorId,
1703
- published: c.body.published ?? false,
1704
- createdAt: new Date().toISOString(),
1705
- };
1706
- posts.push(newPost);
1707
- return newPost;
1708
- });
1708
+ export const schema = {
1709
+ params: z.object({ id: z.string() }),
1710
+ response: UserSchema,
1711
+ };
1709
1712
 
1710
- app.put('/api/posts/:id', {
1711
- params: z.object({ id: z.string() }),
1712
- body: z.object({
1713
- title: z.string().min(1).optional(),
1714
- content: z.string().optional(),
1715
- published: z.boolean().optional(),
1716
- }),
1717
- response: PostSchema,
1718
- }, (c) => {
1719
- const idx = posts.findIndex(p => p.id === c.params.id);
1720
- if (idx === -1) throw new Error('Post not found');
1721
- posts[idx] = { ...posts[idx], ...c.body };
1722
- return posts[idx];
1723
- });
1713
+ export default async ({ params }: { params: { id: string } }) => {
1714
+ const user = users.find(u => u.id === params.id);
1715
+ if (!user) throw new KozoError('User not found', 404, 'NOT_FOUND');
1716
+ return user;
1717
+ };
1718
+ `);
1719
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "users", "[id]", "put.ts"), `import { z } from 'zod';
1720
+ import { KozoError } from '@kozojs/core';
1721
+ import { users } from '../../../../data/index.js';
1722
+ import { UserSchema, UpdateUserBody } from '../../../../schemas/index.js';
1724
1723
 
1725
- app.delete('/api/posts/:id', {
1726
- params: z.object({ id: z.string() }),
1727
- }, (c) => {
1728
- const idx = posts.findIndex(p => p.id === c.params.id);
1729
- if (idx === -1) throw new Error('Post not found');
1730
- posts.splice(idx, 1);
1731
- return { success: true, message: 'Post deleted' };
1732
- });
1733
- }
1724
+ export const schema = {
1725
+ params: z.object({ id: z.string() }),
1726
+ body: UpdateUserBody,
1727
+ response: UserSchema,
1728
+ };
1729
+
1730
+ export default async ({
1731
+ params,
1732
+ body,
1733
+ }: {
1734
+ params: { id: string };
1735
+ body: { name?: string; email?: string; role?: 'admin' | 'user' };
1736
+ }) => {
1737
+ const idx = users.findIndex(u => u.id === params.id);
1738
+ if (idx === -1) throw new KozoError('User not found', 404, 'NOT_FOUND');
1739
+ users[idx] = { ...users[idx], ...body };
1740
+ return users[idx];
1741
+ };
1734
1742
  `);
1735
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "routes", "tasks.ts"), `import type { Kozo } from '@kozojs/core';
1736
- import { z } from 'zod';
1737
- import { tasks, TaskSchema } from '../data';
1743
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "users", "[id]", "delete.ts"), `import { z } from 'zod';
1744
+ import { KozoError } from '@kozojs/core';
1745
+ import { users } from '../../../../data/index.js';
1738
1746
 
1739
- export function registerTaskRoutes(app: Kozo) {
1740
- app.get('/api/tasks', {
1741
- query: z.object({
1742
- completed: z.coerce.boolean().optional(),
1743
- priority: z.enum(['low', 'medium', 'high']).optional(),
1744
- }),
1745
- response: z.array(TaskSchema),
1746
- }, (c) => {
1747
- let result = [...tasks];
1748
- if (c.query.completed !== undefined) {
1749
- result = result.filter(t => t.completed === c.query.completed);
1750
- }
1751
- if (c.query.priority) {
1752
- result = result.filter(t => t.priority === c.query.priority);
1753
- }
1754
- return result;
1755
- });
1747
+ export const schema = {
1748
+ params: z.object({ id: z.string() }),
1749
+ response: z.object({ success: z.boolean(), message: z.string() }),
1750
+ };
1756
1751
 
1757
- app.get('/api/tasks/:id', {
1758
- params: z.object({ id: z.string() }),
1759
- response: TaskSchema,
1760
- }, (c) => {
1761
- const task = tasks.find(t => t.id === c.params.id);
1762
- if (!task) throw new Error('Task not found');
1763
- return task;
1764
- });
1752
+ export default async ({ params }: { params: { id: string } }) => {
1753
+ const idx = users.findIndex(u => u.id === params.id);
1754
+ if (idx === -1) throw new KozoError('User not found', 404, 'NOT_FOUND');
1755
+ users.splice(idx, 1);
1756
+ return { success: true, message: 'User deleted' };
1757
+ };
1758
+ `);
1759
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "posts", "get.ts"), `import { z } from 'zod';
1760
+ import { posts } from '../../../data/index.js';
1761
+ import { PostSchema } from '../../../schemas/index.js';
1765
1762
 
1766
- app.post('/api/tasks', {
1767
- body: z.object({
1768
- title: z.string().min(1),
1769
- priority: z.enum(['low', 'medium', 'high']).optional(),
1770
- }),
1771
- response: TaskSchema,
1772
- }, (c) => {
1773
- const newTask = {
1774
- id: String(Date.now()),
1775
- title: c.body.title,
1776
- completed: false,
1777
- priority: c.body.priority || 'medium' as const,
1778
- createdAt: new Date().toISOString(),
1779
- };
1780
- tasks.push(newTask);
1781
- return newTask;
1782
- });
1763
+ export const schema = {
1764
+ query: z.object({ published: z.coerce.boolean().optional() }),
1765
+ response: z.array(PostSchema),
1766
+ };
1783
1767
 
1784
- app.put('/api/tasks/:id', {
1785
- params: z.object({ id: z.string() }),
1786
- body: z.object({
1787
- title: z.string().min(1).optional(),
1788
- completed: z.boolean().optional(),
1789
- priority: z.enum(['low', 'medium', 'high']).optional(),
1790
- }),
1791
- response: TaskSchema,
1792
- }, (c) => {
1793
- const idx = tasks.findIndex(t => t.id === c.params.id);
1794
- if (idx === -1) throw new Error('Task not found');
1795
- tasks[idx] = { ...tasks[idx], ...c.body };
1796
- return tasks[idx];
1797
- });
1768
+ export default async ({ query }: { query: { published?: boolean } }) => {
1769
+ if (query.published !== undefined) {
1770
+ return posts.filter(p => p.published === query.published);
1771
+ }
1772
+ return posts;
1773
+ };
1774
+ `);
1775
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "posts", "post.ts"), `import { posts, users } from '../../../data/index.js';
1776
+ import { PostSchema, CreatePostBody } from '../../../schemas/index.js';
1798
1777
 
1799
- app.patch('/api/tasks/:id/toggle', {
1800
- params: z.object({ id: z.string() }),
1801
- response: TaskSchema,
1802
- }, (c) => {
1803
- const idx = tasks.findIndex(t => t.id === c.params.id);
1804
- if (idx === -1) throw new Error('Task not found');
1805
- tasks[idx].completed = !tasks[idx].completed;
1806
- return tasks[idx];
1807
- });
1778
+ export const schema = {
1779
+ body: CreatePostBody,
1780
+ response: PostSchema,
1781
+ };
1808
1782
 
1809
- app.delete('/api/tasks/:id', {
1810
- params: z.object({ id: z.string() }),
1811
- }, (c) => {
1812
- const idx = tasks.findIndex(t => t.id === c.params.id);
1813
- if (idx === -1) throw new Error('Task not found');
1814
- tasks.splice(idx, 1);
1815
- return { success: true, message: 'Task deleted' };
1816
- });
1817
- }
1783
+ export default async ({
1784
+ body,
1785
+ }: {
1786
+ body: { title: string; content?: string; authorId?: string; published?: boolean };
1787
+ }) => {
1788
+ const authorId = body.authorId ?? users[0]?.id ?? 'unknown';
1789
+ const newPost = {
1790
+ id: String(Date.now()),
1791
+ title: body.title,
1792
+ content: body.content ?? '',
1793
+ authorId,
1794
+ published: body.published ?? false,
1795
+ createdAt: new Date().toISOString(),
1796
+ };
1797
+ posts.push(newPost);
1798
+ return newPost;
1799
+ };
1818
1800
  `);
1819
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "routes", "tools.ts"), `import type { Kozo } from '@kozojs/core';
1820
- import { z } from 'zod';
1801
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "posts", "[id]", "get.ts"), `import { z } from 'zod';
1802
+ import { KozoError } from '@kozojs/core';
1803
+ import { posts } from '../../../../data/index.js';
1804
+ import { PostSchema } from '../../../../schemas/index.js';
1821
1805
 
1822
- export function registerToolRoutes(app: Kozo) {
1823
- app.get('/api/echo', {
1824
- query: z.object({ message: z.string() }),
1825
- }, (c) => ({
1826
- echo: c.query.message,
1827
- timestamp: new Date().toISOString(),
1828
- }));
1806
+ export const schema = {
1807
+ params: z.object({ id: z.string() }),
1808
+ response: PostSchema,
1809
+ };
1829
1810
 
1830
- app.post('/api/validate', {
1831
- body: z.object({
1832
- email: z.string().email(),
1833
- age: z.number().min(0).max(150),
1834
- }),
1835
- }, (c) => ({
1836
- valid: true,
1837
- data: c.body,
1838
- }));
1839
- }
1811
+ export default async ({ params }: { params: { id: string } }) => {
1812
+ const post = posts.find(p => p.id === params.id);
1813
+ if (!post) throw new KozoError('Post not found', 404, 'NOT_FOUND');
1814
+ return post;
1815
+ };
1816
+ `);
1817
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "posts", "[id]", "put.ts"), `import { z } from 'zod';
1818
+ import { KozoError } from '@kozojs/core';
1819
+ import { posts } from '../../../../data/index.js';
1820
+ import { PostSchema, UpdatePostBody } from '../../../../schemas/index.js';
1821
+
1822
+ export const schema = {
1823
+ params: z.object({ id: z.string() }),
1824
+ body: UpdatePostBody,
1825
+ response: PostSchema,
1826
+ };
1827
+
1828
+ export default async ({
1829
+ params,
1830
+ body,
1831
+ }: {
1832
+ params: { id: string };
1833
+ body: { title?: string; content?: string; published?: boolean };
1834
+ }) => {
1835
+ const idx = posts.findIndex(p => p.id === params.id);
1836
+ if (idx === -1) throw new KozoError('Post not found', 404, 'NOT_FOUND');
1837
+ posts[idx] = { ...posts[idx], ...body };
1838
+ return posts[idx];
1839
+ };
1840
+ `);
1841
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "posts", "[id]", "delete.ts"), `import { z } from 'zod';
1842
+ import { KozoError } from '@kozojs/core';
1843
+ import { posts } from '../../../../data/index.js';
1844
+
1845
+ export const schema = {
1846
+ params: z.object({ id: z.string() }),
1847
+ response: z.object({ success: z.boolean(), message: z.string() }),
1848
+ };
1849
+
1850
+ export default async ({ params }: { params: { id: string } }) => {
1851
+ const idx = posts.findIndex(p => p.id === params.id);
1852
+ if (idx === -1) throw new KozoError('Post not found', 404, 'NOT_FOUND');
1853
+ posts.splice(idx, 1);
1854
+ return { success: true, message: 'Post deleted' };
1855
+ };
1856
+ `);
1857
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "tasks", "get.ts"), `import { z } from 'zod';
1858
+ import { tasks } from '../../../data/index.js';
1859
+ import { TaskSchema } from '../../../schemas/index.js';
1860
+
1861
+ export const schema = {
1862
+ query: z.object({
1863
+ completed: z.coerce.boolean().optional(),
1864
+ priority: z.enum(['low', 'medium', 'high']).optional(),
1865
+ }),
1866
+ response: z.array(TaskSchema),
1867
+ };
1868
+
1869
+ export default async ({
1870
+ query,
1871
+ }: {
1872
+ query: { completed?: boolean; priority?: 'low' | 'medium' | 'high' };
1873
+ }) => {
1874
+ let result = [...tasks];
1875
+ if (query.completed !== undefined) {
1876
+ result = result.filter(t => t.completed === query.completed);
1877
+ }
1878
+ if (query.priority) {
1879
+ result = result.filter(t => t.priority === query.priority);
1880
+ }
1881
+ return result;
1882
+ };
1883
+ `);
1884
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "tasks", "post.ts"), `import { tasks } from '../../../data/index.js';
1885
+ import { TaskSchema, CreateTaskBody } from '../../../schemas/index.js';
1886
+
1887
+ export const schema = {
1888
+ body: CreateTaskBody,
1889
+ response: TaskSchema,
1890
+ };
1891
+
1892
+ export default async ({
1893
+ body,
1894
+ }: {
1895
+ body: { title: string; priority?: 'low' | 'medium' | 'high' };
1896
+ }) => {
1897
+ const newTask = {
1898
+ id: String(Date.now()),
1899
+ title: body.title,
1900
+ completed: false,
1901
+ priority: body.priority ?? ('medium' as const),
1902
+ createdAt: new Date().toISOString(),
1903
+ };
1904
+ tasks.push(newTask);
1905
+ return newTask;
1906
+ };
1907
+ `);
1908
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "tasks", "[id]", "get.ts"), `import { z } from 'zod';
1909
+ import { KozoError } from '@kozojs/core';
1910
+ import { tasks } from '../../../../data/index.js';
1911
+ import { TaskSchema } from '../../../../schemas/index.js';
1912
+
1913
+ export const schema = {
1914
+ params: z.object({ id: z.string() }),
1915
+ response: TaskSchema,
1916
+ };
1917
+
1918
+ export default async ({ params }: { params: { id: string } }) => {
1919
+ const task = tasks.find(t => t.id === params.id);
1920
+ if (!task) throw new KozoError('Task not found', 404, 'NOT_FOUND');
1921
+ return task;
1922
+ };
1923
+ `);
1924
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "tasks", "[id]", "put.ts"), `import { z } from 'zod';
1925
+ import { KozoError } from '@kozojs/core';
1926
+ import { tasks } from '../../../../data/index.js';
1927
+ import { TaskSchema, UpdateTaskBody } from '../../../../schemas/index.js';
1928
+
1929
+ export const schema = {
1930
+ params: z.object({ id: z.string() }),
1931
+ body: UpdateTaskBody,
1932
+ response: TaskSchema,
1933
+ };
1934
+
1935
+ export default async ({
1936
+ params,
1937
+ body,
1938
+ }: {
1939
+ params: { id: string };
1940
+ body: { title?: string; completed?: boolean; priority?: 'low' | 'medium' | 'high' };
1941
+ }) => {
1942
+ const idx = tasks.findIndex(t => t.id === params.id);
1943
+ if (idx === -1) throw new KozoError('Task not found', 404, 'NOT_FOUND');
1944
+ tasks[idx] = { ...tasks[idx], ...body };
1945
+ return tasks[idx];
1946
+ };
1947
+ `);
1948
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "tasks", "[id]", "delete.ts"), `import { z } from 'zod';
1949
+ import { KozoError } from '@kozojs/core';
1950
+ import { tasks } from '../../../../data/index.js';
1951
+
1952
+ export const schema = {
1953
+ params: z.object({ id: z.string() }),
1954
+ response: z.object({ success: z.boolean(), message: z.string() }),
1955
+ };
1956
+
1957
+ export default async ({ params }: { params: { id: string } }) => {
1958
+ const idx = tasks.findIndex(t => t.id === params.id);
1959
+ if (idx === -1) throw new KozoError('Task not found', 404, 'NOT_FOUND');
1960
+ tasks.splice(idx, 1);
1961
+ return { success: true, message: 'Task deleted' };
1962
+ };
1963
+ `);
1964
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "tasks", "[id]", "toggle", "patch.ts"), `import { z } from 'zod';
1965
+ import { KozoError } from '@kozojs/core';
1966
+ import { tasks } from '../../../../../data/index.js';
1967
+ import { TaskSchema } from '../../../../../schemas/index.js';
1968
+
1969
+ export const schema = {
1970
+ params: z.object({ id: z.string() }),
1971
+ response: TaskSchema,
1972
+ };
1973
+
1974
+ export default async ({ params }: { params: { id: string } }) => {
1975
+ const idx = tasks.findIndex(t => t.id === params.id);
1976
+ if (idx === -1) throw new KozoError('Task not found', 404, 'NOT_FOUND');
1977
+ tasks[idx].completed = !tasks[idx].completed;
1978
+ return tasks[idx];
1979
+ };
1840
1980
  `);
1841
1981
  if (auth) {
1842
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "routes", "auth.ts"), `import type { Kozo } from '@kozojs/core';
1843
- import { z } from 'zod';
1982
+ await import_fs_extra.default.outputFile(import_node_path.default.join(apiDir, "src", "routes", "api", "auth", "login", "post.ts"), `import { z } from 'zod';
1844
1983
  import { createJWT, UnauthorizedError } from '@kozojs/auth';
1845
1984
 
1846
1985
  const JWT_SECRET = process.env.JWT_SECRET || 'change-me';
@@ -1850,21 +1989,31 @@ const DEMO_USERS = [
1850
1989
  { email: 'user@demo.com', password: 'user123', role: 'user', name: 'User' },
1851
1990
  ];
1852
1991
 
1853
- export function registerAuthRoutes(app: Kozo) {
1854
- app.post('/api/auth/login', {
1855
- body: z.object({ email: z.string().email(), password: z.string() }),
1856
- }, async (c) => {
1857
- const { email, password } = c.body;
1858
- const user = DEMO_USERS.find(u => u.email === email && u.password === password);
1859
- if (!user) throw new UnauthorizedError('Invalid credentials');
1860
- const token = await createJWT(
1861
- { email: user.email, role: user.role, name: user.name },
1862
- JWT_SECRET,
1863
- { expiresIn: '24h' },
1864
- );
1865
- return { token, user: { email: user.email, role: user.role, name: user.name } };
1866
- });
1867
- }
1992
+ export const schema = {
1993
+ body: z.object({
1994
+ email: z.string().email(),
1995
+ password: z.string(),
1996
+ }),
1997
+ response: z.object({
1998
+ token: z.string(),
1999
+ user: z.object({
2000
+ email: z.string(),
2001
+ role: z.string(),
2002
+ name: z.string(),
2003
+ }),
2004
+ }),
2005
+ };
2006
+
2007
+ export default async ({ body }: { body: { email: string; password: string } }) => {
2008
+ const user = DEMO_USERS.find(u => u.email === body.email && u.password === body.password);
2009
+ if (!user) throw new UnauthorizedError('Invalid credentials');
2010
+ const token = await createJWT(
2011
+ { email: user.email, role: user.role, name: user.name },
2012
+ JWT_SECRET,
2013
+ { expiresIn: '24h' },
2014
+ );
2015
+ return { token, user: { email: user.email, role: user.role, name: user.name } };
2016
+ };
1868
2017
  `);
1869
2018
  }
1870
2019
  }
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "@kozojs/cli",
3
- "version": "0.1.19",
4
- "description": "CLI to scaffold new Kozo Framework projects - The next-gen TypeScript Backend Framework",
5
- "bin": {
6
- "create-kozo": "./lib/index.js",
7
- "kozo": "./lib/index.js"
8
- },
9
- "main": "./lib/index.js",
10
- "types": "./lib/index.d.ts",
11
- "files": [
12
- "lib",
13
- "README.md"
14
- ],
15
- "scripts": {
16
- "build": "tsup",
17
- "dev": "tsup --watch"
18
- },
19
- "keywords": [
20
- "kozo",
21
- "framework",
22
- "backend",
23
- "typescript",
24
- "hono",
25
- "api",
26
- "rest",
27
- "cli",
28
- "scaffold",
29
- "generator"
30
- ],
31
- "author": "Kozo Team",
32
- "license": "MIT",
33
- "engines": {
34
- "node": ">=18.0.0"
35
- },
36
- "dependencies": {
37
- "@clack/prompts": "^0.8.0",
38
- "commander": "^12.0.0",
39
- "picocolors": "^1.1.0",
40
- "ora": "^8.1.0",
41
- "execa": "^9.5.0",
42
- "fs-extra": "^11.2.0",
43
- "glob": "^11.0.0"
44
- },
45
- "devDependencies": {
46
- "@types/fs-extra": "^11.0.4",
47
- "@types/node": "^22.0.0",
48
- "tsup": "^8.3.0",
49
- "typescript": "^5.6.0"
50
- }
51
- }
1
+ {
2
+ "name": "@kozojs/cli",
3
+ "version": "0.1.21",
4
+ "description": "CLI to scaffold new Kozo Framework projects - The next-gen TypeScript Backend Framework",
5
+ "bin": {
6
+ "create-kozo": "./lib/index.js",
7
+ "kozo": "./lib/index.js"
8
+ },
9
+ "main": "./lib/index.js",
10
+ "types": "./lib/index.d.ts",
11
+ "files": [
12
+ "lib",
13
+ "README.md"
14
+ ],
15
+ "keywords": [
16
+ "kozo",
17
+ "framework",
18
+ "backend",
19
+ "typescript",
20
+ "hono",
21
+ "api",
22
+ "rest",
23
+ "cli",
24
+ "scaffold",
25
+ "generator"
26
+ ],
27
+ "author": "Kozo Team",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "dependencies": {
33
+ "@clack/prompts": "^0.8.0",
34
+ "commander": "^12.0.0",
35
+ "picocolors": "^1.1.0",
36
+ "ora": "^8.1.0",
37
+ "execa": "^9.5.0",
38
+ "fs-extra": "^11.2.0",
39
+ "glob": "^11.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/fs-extra": "^11.0.4",
43
+ "@types/node": "^22.0.0",
44
+ "tsup": "^8.3.0",
45
+ "typescript": "^5.6.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "dev": "tsup --watch"
50
+ }
51
+ }