@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.
- package/lib/index.js +437 -288
- 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
|
|
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(
|
|
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.
|
|
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 {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
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(
|
|
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.
|
|
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
|
|
1549
|
-
|
|
1550
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
export
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
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.
|
|
1584
|
-
import { users, posts, tasks } from '
|
|
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
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
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
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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.
|
|
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
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
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
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
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
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
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
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
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.
|
|
1666
|
-
import {
|
|
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
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
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
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
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
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
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.
|
|
1736
|
-
import {
|
|
1737
|
-
import {
|
|
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
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
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
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
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
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
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
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
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.
|
|
1820
|
-
import {
|
|
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
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
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
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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.
|
|
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
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
)
|
|
1865
|
-
|
|
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.
|
|
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
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"fs-extra": "^11.
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
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
|
+
}
|