@kozojs/cli 0.1.20 → 0.1.22
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 → index.mjs} +604 -477
- package/package.json +1 -1
- package/lib/index.d.ts +0 -2
|
@@ -1,43 +1,18 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
-
mod
|
|
24
|
-
));
|
|
25
|
-
|
|
26
1
|
// src/index.ts
|
|
27
|
-
|
|
2
|
+
import { Command } from "commander";
|
|
28
3
|
|
|
29
4
|
// src/commands/new.ts
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
5
|
+
import * as p from "@clack/prompts";
|
|
6
|
+
import pc2 from "picocolors";
|
|
7
|
+
import { execa } from "execa";
|
|
33
8
|
|
|
34
9
|
// src/utils/scaffold.ts
|
|
35
|
-
|
|
36
|
-
|
|
10
|
+
import fs from "fs-extra";
|
|
11
|
+
import path from "path";
|
|
37
12
|
async function scaffoldProject(options) {
|
|
38
13
|
const { projectName, runtime, database, dbPort, auth, packageSource, template, frontend, extras } = options;
|
|
39
|
-
const projectDir =
|
|
40
|
-
const kozoCoreDep = packageSource === "local" ? "workspace:*" : "^0.
|
|
14
|
+
const projectDir = path.resolve(process.cwd(), projectName);
|
|
15
|
+
const kozoCoreDep = packageSource === "local" ? "workspace:*" : "^0.3.1";
|
|
41
16
|
if (frontend !== "none") {
|
|
42
17
|
await scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth, frontend, extras, template);
|
|
43
18
|
return;
|
|
@@ -55,9 +30,9 @@ async function scaffoldProject(options) {
|
|
|
55
30
|
if (extras.includes("github-actions")) await createGitHubActions(projectDir);
|
|
56
31
|
return;
|
|
57
32
|
}
|
|
58
|
-
await
|
|
59
|
-
await
|
|
60
|
-
await
|
|
33
|
+
await fs.ensureDir(path.join(projectDir, "src", "routes"));
|
|
34
|
+
await fs.ensureDir(path.join(projectDir, "src", "db"));
|
|
35
|
+
await fs.ensureDir(path.join(projectDir, "src", "services"));
|
|
61
36
|
const packageJson = {
|
|
62
37
|
name: projectName,
|
|
63
38
|
version: "0.1.0",
|
|
@@ -72,6 +47,7 @@ async function scaffoldProject(options) {
|
|
|
72
47
|
},
|
|
73
48
|
dependencies: {
|
|
74
49
|
"@kozojs/core": kozoCoreDep,
|
|
50
|
+
"uWebSockets.js": "github:uNetworking/uWebSockets.js#6609a88ffa9a16ac5158046761356ce03250a0df",
|
|
75
51
|
hono: "^4.6.0",
|
|
76
52
|
zod: "^3.23.0",
|
|
77
53
|
"drizzle-orm": "^0.36.0",
|
|
@@ -87,7 +63,7 @@ async function scaffoldProject(options) {
|
|
|
87
63
|
...database === "sqlite" && { "@types/better-sqlite3": "^7.6.0" }
|
|
88
64
|
}
|
|
89
65
|
};
|
|
90
|
-
await
|
|
66
|
+
await fs.writeJSON(path.join(projectDir, "package.json"), packageJson, { spaces: 2 });
|
|
91
67
|
const tsconfig = {
|
|
92
68
|
compilerOptions: {
|
|
93
69
|
target: "ES2022",
|
|
@@ -103,7 +79,7 @@ async function scaffoldProject(options) {
|
|
|
103
79
|
include: ["src/**/*"],
|
|
104
80
|
exclude: ["node_modules", "dist"]
|
|
105
81
|
};
|
|
106
|
-
await
|
|
82
|
+
await fs.writeJSON(path.join(projectDir, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
107
83
|
const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
|
|
108
84
|
|
|
109
85
|
export default defineConfig({
|
|
@@ -115,21 +91,21 @@ export default defineConfig({
|
|
|
115
91
|
}
|
|
116
92
|
});
|
|
117
93
|
`;
|
|
118
|
-
await
|
|
94
|
+
await fs.writeFile(path.join(projectDir, "drizzle.config.ts"), drizzleConfig);
|
|
119
95
|
const envExample = `# Database
|
|
120
96
|
${database === "sqlite" ? "# SQLite uses local file, no URL needed" : "DATABASE_URL="}
|
|
121
97
|
|
|
122
98
|
# Server
|
|
123
99
|
PORT=3000
|
|
124
100
|
`;
|
|
125
|
-
await
|
|
101
|
+
await fs.writeFile(path.join(projectDir, ".env.example"), envExample);
|
|
126
102
|
const gitignore = `node_modules/
|
|
127
103
|
dist/
|
|
128
104
|
.env
|
|
129
105
|
*.db
|
|
130
106
|
.turbo/
|
|
131
107
|
`;
|
|
132
|
-
await
|
|
108
|
+
await fs.writeFile(path.join(projectDir, ".gitignore"), gitignore);
|
|
133
109
|
const indexTs = `import { createKozo } from '@kozojs/core';
|
|
134
110
|
import { services } from './services/index.js';
|
|
135
111
|
|
|
@@ -151,9 +127,9 @@ const app = createKozo({
|
|
|
151
127
|
}
|
|
152
128
|
});
|
|
153
129
|
|
|
154
|
-
app.
|
|
130
|
+
await app.nativeListen();
|
|
155
131
|
`;
|
|
156
|
-
await
|
|
132
|
+
await fs.writeFile(path.join(projectDir, "src", "index.ts"), indexTs);
|
|
157
133
|
const servicesTs = `import { db } from '../db/index.js';
|
|
158
134
|
|
|
159
135
|
export const services = {
|
|
@@ -167,14 +143,14 @@ declare module '@kozojs/core' {
|
|
|
167
143
|
}
|
|
168
144
|
}
|
|
169
145
|
`;
|
|
170
|
-
await
|
|
146
|
+
await fs.writeFile(path.join(projectDir, "src", "services", "index.ts"), servicesTs);
|
|
171
147
|
const schemaTs = getDatabaseSchema(database);
|
|
172
|
-
await
|
|
148
|
+
await fs.writeFile(path.join(projectDir, "src", "db", "schema.ts"), schemaTs);
|
|
173
149
|
const dbIndexTs = getDatabaseIndex(database);
|
|
174
|
-
await
|
|
150
|
+
await fs.writeFile(path.join(projectDir, "src", "db", "index.ts"), dbIndexTs);
|
|
175
151
|
if (database === "sqlite") {
|
|
176
152
|
const seedTs = getSQLiteSeed();
|
|
177
|
-
await
|
|
153
|
+
await fs.writeFile(path.join(projectDir, "src", "db", "seed.ts"), seedTs);
|
|
178
154
|
}
|
|
179
155
|
await createExampleRoutes(projectDir);
|
|
180
156
|
const readme = `# ${projectName}
|
|
@@ -250,7 +226,7 @@ ${database === "sqlite" ? "## SQLite Notes\n\nThe database is automatically init
|
|
|
250
226
|
- [Drizzle ORM](https://orm.drizzle.team)
|
|
251
227
|
- [Hono](https://hono.dev)
|
|
252
228
|
`;
|
|
253
|
-
await
|
|
229
|
+
await fs.writeFile(path.join(projectDir, "README.md"), readme);
|
|
254
230
|
if (database !== "none" && database !== "sqlite") await createDockerCompose(projectDir, projectName, database, dbPort);
|
|
255
231
|
if (extras.includes("docker")) await createDockerfile(projectDir, runtime);
|
|
256
232
|
if (extras.includes("github-actions")) await createGitHubActions(projectDir);
|
|
@@ -370,8 +346,8 @@ async function createExampleRoutes(projectDir) {
|
|
|
370
346
|
};
|
|
371
347
|
};
|
|
372
348
|
`;
|
|
373
|
-
await
|
|
374
|
-
await
|
|
349
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "index.ts"), indexRoute);
|
|
350
|
+
await fs.ensureDir(path.join(projectDir, "src", "routes", "users"));
|
|
375
351
|
const getUsersRoute = `import type { HandlerContext } from '@kozojs/core';
|
|
376
352
|
import { users } from '../../db/schema.js';
|
|
377
353
|
|
|
@@ -385,7 +361,7 @@ export default async ({ services: { db } }: HandlerContext) => {
|
|
|
385
361
|
return { users: allUsers };
|
|
386
362
|
};
|
|
387
363
|
`;
|
|
388
|
-
await
|
|
364
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "users", "get.ts"), getUsersRoute);
|
|
389
365
|
const postUsersRoute = `import { z } from 'zod';
|
|
390
366
|
import type { HandlerContext } from '@kozojs/core';
|
|
391
367
|
import { users } from '../../db/schema.js';
|
|
@@ -413,26 +389,26 @@ export default async ({ body, services: { db } }: HandlerContext<Body>) => {
|
|
|
413
389
|
return { success: true, user };
|
|
414
390
|
};
|
|
415
391
|
`;
|
|
416
|
-
await
|
|
392
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "users", "post.ts"), postUsersRoute);
|
|
417
393
|
}
|
|
418
394
|
async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, runtime, database = "none", dbPort, auth = true) {
|
|
419
|
-
await
|
|
420
|
-
await
|
|
421
|
-
await
|
|
422
|
-
await
|
|
423
|
-
await
|
|
424
|
-
await
|
|
425
|
-
await
|
|
426
|
-
await
|
|
427
|
-
await
|
|
428
|
-
await
|
|
395
|
+
await fs.ensureDir(projectDir);
|
|
396
|
+
await fs.ensureDir(path.join(projectDir, "src"));
|
|
397
|
+
await fs.ensureDir(path.join(projectDir, "src", "schemas"));
|
|
398
|
+
await fs.ensureDir(path.join(projectDir, "src", "routes"));
|
|
399
|
+
await fs.ensureDir(path.join(projectDir, "src", "routes", "auth"));
|
|
400
|
+
await fs.ensureDir(path.join(projectDir, "src", "routes", "users"));
|
|
401
|
+
await fs.ensureDir(path.join(projectDir, "src", "routes", "posts"));
|
|
402
|
+
await fs.ensureDir(path.join(projectDir, "src", "middleware"));
|
|
403
|
+
await fs.ensureDir(path.join(projectDir, "src", "utils"));
|
|
404
|
+
await fs.ensureDir(path.join(projectDir, "src", "data"));
|
|
429
405
|
const hasDb = database !== "none";
|
|
430
406
|
if (hasDb) {
|
|
431
|
-
await
|
|
432
|
-
await
|
|
433
|
-
await
|
|
407
|
+
await fs.ensureDir(path.join(projectDir, "src", "db"));
|
|
408
|
+
await fs.writeFile(path.join(projectDir, "src", "db", "schema.ts"), getDatabaseSchema(database));
|
|
409
|
+
await fs.writeFile(path.join(projectDir, "src", "db", "index.ts"), getDatabaseIndex(database));
|
|
434
410
|
if (database === "sqlite") {
|
|
435
|
-
await
|
|
411
|
+
await fs.writeFile(path.join(projectDir, "src", "db", "seed.ts"), getSQLiteSeed());
|
|
436
412
|
}
|
|
437
413
|
}
|
|
438
414
|
const packageJson = {
|
|
@@ -454,6 +430,7 @@ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, ru
|
|
|
454
430
|
"@kozojs/core": kozoCoreDep,
|
|
455
431
|
...auth && { "@kozojs/auth": kozoCoreDep === "workspace:*" ? "workspace:*" : "^0.1.0" },
|
|
456
432
|
"@hono/node-server": "^1.13.0",
|
|
433
|
+
...runtime === "node" && { "uWebSockets.js": "github:uNetworking/uWebSockets.js#6609a88ffa9a16ac5158046761356ce03250a0df" },
|
|
457
434
|
hono: "^4.6.0",
|
|
458
435
|
zod: "^3.23.0",
|
|
459
436
|
dotenv: "^16.4.0",
|
|
@@ -470,7 +447,7 @@ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, ru
|
|
|
470
447
|
...database === "sqlite" && { "@types/better-sqlite3": "^7.6.0" }
|
|
471
448
|
}
|
|
472
449
|
};
|
|
473
|
-
await
|
|
450
|
+
await fs.writeJSON(path.join(projectDir, "package.json"), packageJson, { spaces: 2 });
|
|
474
451
|
const tsconfig = {
|
|
475
452
|
compilerOptions: {
|
|
476
453
|
target: "ES2022",
|
|
@@ -488,14 +465,14 @@ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, ru
|
|
|
488
465
|
include: ["src/**/*"],
|
|
489
466
|
exclude: ["node_modules", "dist"]
|
|
490
467
|
};
|
|
491
|
-
await
|
|
468
|
+
await fs.writeJSON(path.join(projectDir, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
492
469
|
const gitignore = `node_modules/
|
|
493
470
|
dist/
|
|
494
471
|
.env
|
|
495
472
|
.turbo/
|
|
496
473
|
*.log
|
|
497
474
|
`;
|
|
498
|
-
await
|
|
475
|
+
await fs.writeFile(path.join(projectDir, ".gitignore"), gitignore);
|
|
499
476
|
const pgPort = dbPort ?? 5436;
|
|
500
477
|
const dbUrl = database === "postgresql" ? `postgresql://postgres:postgres@localhost:${pgPort}/${projectName}` : database === "mysql" ? `mysql://root:root@localhost:3306/${projectName}` : void 0;
|
|
501
478
|
const envExample = `# Server
|
|
@@ -515,8 +492,8 @@ CORS_ORIGIN=http://localhost:5173
|
|
|
515
492
|
RATE_LIMIT_MAX=100
|
|
516
493
|
RATE_LIMIT_WINDOW=60000
|
|
517
494
|
`;
|
|
518
|
-
await
|
|
519
|
-
await
|
|
495
|
+
await fs.writeFile(path.join(projectDir, ".env.example"), envExample);
|
|
496
|
+
await fs.writeFile(path.join(projectDir, ".env"), envExample);
|
|
520
497
|
if (hasDb) {
|
|
521
498
|
const dialect = database === "postgresql" ? "postgresql" : database === "mysql" ? "mysql" : "sqlite";
|
|
522
499
|
const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
|
|
@@ -531,7 +508,7 @@ export default defineConfig({
|
|
|
531
508
|
},
|
|
532
509
|
});
|
|
533
510
|
`;
|
|
534
|
-
await
|
|
511
|
+
await fs.writeFile(path.join(projectDir, "drizzle.config.ts"), drizzleConfig);
|
|
535
512
|
}
|
|
536
513
|
const indexTs = `import { createKozo, cors, logger, rateLimit } from '@kozojs/core';
|
|
537
514
|
${auth ? "import { authenticateJWT } from '@kozojs/auth';" : ""}
|
|
@@ -576,7 +553,7 @@ console.log('');
|
|
|
576
553
|
console.log('\u{1F525} Kozo server starting\u2026');
|
|
577
554
|
console.log('');
|
|
578
555
|
|
|
579
|
-
await app.
|
|
556
|
+
await app.nativeListen(PORT);
|
|
580
557
|
console.log('');
|
|
581
558
|
console.log('\u{1F4DA} Endpoints:');
|
|
582
559
|
console.log(' GET /health Health check');
|
|
@@ -594,7 +571,7 @@ console.log('');
|
|
|
594
571
|
console.log('\u{1F512} Middleware: CORS \xB7 Rate limit \xB7 JWT \xB7 Logger');
|
|
595
572
|
console.log('\u{1F6E1}\uFE0F Graceful shutdown enabled (SIGTERM / SIGINT)');
|
|
596
573
|
`;
|
|
597
|
-
await
|
|
574
|
+
await fs.writeFile(path.join(projectDir, "src", "index.ts"), indexTs);
|
|
598
575
|
await createCompleteSchemas(projectDir);
|
|
599
576
|
await createCompleteUtils(projectDir);
|
|
600
577
|
await createCompleteDataStore(projectDir);
|
|
@@ -759,7 +736,7 @@ Request \u2500\u2500\u25BA \u2502 uWebSockets \u2502 C++ HTTP parser + epoll/kq
|
|
|
759
736
|
|
|
760
737
|
Built with \u2764\uFE0F using Kozo Framework
|
|
761
738
|
`;
|
|
762
|
-
await
|
|
739
|
+
await fs.writeFile(path.join(projectDir, "README.md"), readme);
|
|
763
740
|
}
|
|
764
741
|
async function createCompleteSchemas(projectDir) {
|
|
765
742
|
const userSchemas = `import { z } from 'zod';
|
|
@@ -788,7 +765,7 @@ export type User = z.infer<typeof UserSchema>;
|
|
|
788
765
|
export type CreateUser = z.infer<typeof CreateUserSchema>;
|
|
789
766
|
export type UpdateUser = z.infer<typeof UpdateUserSchema>;
|
|
790
767
|
`;
|
|
791
|
-
await
|
|
768
|
+
await fs.writeFile(path.join(projectDir, "src", "schemas", "user.ts"), userSchemas);
|
|
792
769
|
const postSchemas = `import { z } from 'zod';
|
|
793
770
|
import { UserSchema } from './user.js';
|
|
794
771
|
|
|
@@ -818,7 +795,7 @@ export type Post = z.infer<typeof PostSchema>;
|
|
|
818
795
|
export type PostWithAuthor = z.infer<typeof PostWithAuthorSchema>;
|
|
819
796
|
export type CreatePost = z.infer<typeof CreatePostSchema>;
|
|
820
797
|
`;
|
|
821
|
-
await
|
|
798
|
+
await fs.writeFile(path.join(projectDir, "src", "schemas", "post.ts"), postSchemas);
|
|
822
799
|
const commonSchemas = `import { z } from 'zod';
|
|
823
800
|
|
|
824
801
|
export const PaginationSchema = z.object({
|
|
@@ -835,7 +812,7 @@ export const PostFiltersSchema = z.object({
|
|
|
835
812
|
export type Pagination = z.infer<typeof PaginationSchema>;
|
|
836
813
|
export type PostFilters = z.infer<typeof PostFiltersSchema>;
|
|
837
814
|
`;
|
|
838
|
-
await
|
|
815
|
+
await fs.writeFile(path.join(projectDir, "src", "schemas", "common.ts"), commonSchemas);
|
|
839
816
|
}
|
|
840
817
|
async function createCompleteUtils(projectDir) {
|
|
841
818
|
const helpers = `export function generateUUID(): string {
|
|
@@ -858,7 +835,7 @@ export function paginate<T>(items: T[], page: number, limit: number) {
|
|
|
858
835
|
};
|
|
859
836
|
}
|
|
860
837
|
`;
|
|
861
|
-
await
|
|
838
|
+
await fs.writeFile(path.join(projectDir, "src", "utils", "helpers.ts"), helpers);
|
|
862
839
|
}
|
|
863
840
|
async function createCompleteDataStore(projectDir) {
|
|
864
841
|
const store = `import type { User } from '../schemas/user.js';
|
|
@@ -896,7 +873,7 @@ export const posts: Post[] = [
|
|
|
896
873
|
},
|
|
897
874
|
];
|
|
898
875
|
`;
|
|
899
|
-
await
|
|
876
|
+
await fs.writeFile(path.join(projectDir, "src", "data", "store.ts"), store);
|
|
900
877
|
}
|
|
901
878
|
async function createCompleteRoutes(projectDir) {
|
|
902
879
|
const healthRoute = `import type { Kozo } from '@kozojs/core';
|
|
@@ -912,7 +889,7 @@ export function registerHealthRoute(app: Kozo) {
|
|
|
912
889
|
});
|
|
913
890
|
}
|
|
914
891
|
`;
|
|
915
|
-
await
|
|
892
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "health.ts"), healthRoute);
|
|
916
893
|
const statsRoute = `import { z } from 'zod';
|
|
917
894
|
import type { Kozo } from '@kozojs/core';
|
|
918
895
|
import { users } from '../data/store.js';
|
|
@@ -967,7 +944,7 @@ export function registerStatsRoute(app: Kozo) {
|
|
|
967
944
|
});
|
|
968
945
|
}
|
|
969
946
|
`;
|
|
970
|
-
await
|
|
947
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "stats.ts"), statsRoute);
|
|
971
948
|
const authRoutes = `import { z } from 'zod';
|
|
972
949
|
import type { Kozo } from '@kozojs/core';
|
|
973
950
|
import { createJWT } from '@kozojs/auth';
|
|
@@ -1013,7 +990,7 @@ export function registerAuthRoutes(app: Kozo) {
|
|
|
1013
990
|
});
|
|
1014
991
|
}
|
|
1015
992
|
`;
|
|
1016
|
-
await
|
|
993
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "auth", "index.ts"), authRoutes);
|
|
1017
994
|
const userRoutes = `import { z } from 'zod';
|
|
1018
995
|
import type { Kozo } from '@kozojs/core';
|
|
1019
996
|
import { UserSchema, CreateUserSchema, UpdateUserSchema } from '../../schemas/user.js';
|
|
@@ -1098,7 +1075,7 @@ export function registerUserRoutes(app: Kozo) {
|
|
|
1098
1075
|
});
|
|
1099
1076
|
}
|
|
1100
1077
|
`;
|
|
1101
|
-
await
|
|
1078
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "users", "index.ts"), userRoutes);
|
|
1102
1079
|
const postRoutes = `import { z } from 'zod';
|
|
1103
1080
|
import type { Kozo } from '@kozojs/core';
|
|
1104
1081
|
import { PostSchema, PostWithAuthorSchema, CreatePostSchema } from '../../schemas/post.js';
|
|
@@ -1174,10 +1151,10 @@ export function registerPostRoutes(app: Kozo) {
|
|
|
1174
1151
|
});
|
|
1175
1152
|
}
|
|
1176
1153
|
`;
|
|
1177
|
-
await
|
|
1154
|
+
await fs.writeFile(path.join(projectDir, "src", "routes", "posts", "index.ts"), postRoutes);
|
|
1178
1155
|
}
|
|
1179
1156
|
async function scaffoldApiOnlyTemplate(projectDir, projectName, kozoCoreDep, runtime) {
|
|
1180
|
-
await
|
|
1157
|
+
await fs.ensureDir(path.join(projectDir, "src"));
|
|
1181
1158
|
const packageJson = {
|
|
1182
1159
|
name: projectName,
|
|
1183
1160
|
version: "1.0.0",
|
|
@@ -1191,7 +1168,8 @@ async function scaffoldApiOnlyTemplate(projectDir, projectName, kozoCoreDep, run
|
|
|
1191
1168
|
"@kozojs/core": kozoCoreDep,
|
|
1192
1169
|
hono: "^4.6.0",
|
|
1193
1170
|
zod: "^3.23.0",
|
|
1194
|
-
...runtime === "node" && { "@hono/node-server": "^1.13.0" }
|
|
1171
|
+
...runtime === "node" && { "@hono/node-server": "^1.13.0" },
|
|
1172
|
+
...runtime === "node" && { "uWebSockets.js": "github:uNetworking/uWebSockets.js#6609a88ffa9a16ac5158046761356ce03250a0df" }
|
|
1195
1173
|
},
|
|
1196
1174
|
devDependencies: {
|
|
1197
1175
|
"@types/node": "^22.0.0",
|
|
@@ -1199,7 +1177,7 @@ async function scaffoldApiOnlyTemplate(projectDir, projectName, kozoCoreDep, run
|
|
|
1199
1177
|
typescript: "^5.6.0"
|
|
1200
1178
|
}
|
|
1201
1179
|
};
|
|
1202
|
-
await
|
|
1180
|
+
await fs.writeJSON(path.join(projectDir, "package.json"), packageJson, { spaces: 2 });
|
|
1203
1181
|
const tsconfig = {
|
|
1204
1182
|
compilerOptions: {
|
|
1205
1183
|
target: "ES2022",
|
|
@@ -1214,7 +1192,7 @@ async function scaffoldApiOnlyTemplate(projectDir, projectName, kozoCoreDep, run
|
|
|
1214
1192
|
include: ["src/**/*"],
|
|
1215
1193
|
exclude: ["node_modules", "dist"]
|
|
1216
1194
|
};
|
|
1217
|
-
await
|
|
1195
|
+
await fs.writeJSON(path.join(projectDir, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
1218
1196
|
const indexTs = `import { createKozo } from '@kozojs/core';
|
|
1219
1197
|
import { z } from 'zod';
|
|
1220
1198
|
|
|
@@ -1237,8 +1215,8 @@ app.get('/hello/:name', {
|
|
|
1237
1215
|
console.log('\u{1F525} Kozo running on http://localhost:3000');
|
|
1238
1216
|
await app.nativeListen();
|
|
1239
1217
|
`;
|
|
1240
|
-
await
|
|
1241
|
-
await
|
|
1218
|
+
await fs.writeFile(path.join(projectDir, "src", "index.ts"), indexTs);
|
|
1219
|
+
await fs.writeFile(path.join(projectDir, ".gitignore"), "node_modules/\ndist/\n.env\n");
|
|
1242
1220
|
}
|
|
1243
1221
|
async function createDockerCompose(dir, projectName, database, dbPort, includeApiService = false, runtime = "node") {
|
|
1244
1222
|
if (database === "none" || database === "sqlite") return;
|
|
@@ -1319,7 +1297,7 @@ async function createDockerCompose(dir, projectName, database, dbPort, includeAp
|
|
|
1319
1297
|
const volumes = database === "postgresql" ? "\nvolumes:\n postgres_data:\n" : database === "mysql" ? "\nvolumes:\n mysql_data:\n" : "";
|
|
1320
1298
|
const compose = `services:
|
|
1321
1299
|
${services}${volumes}`;
|
|
1322
|
-
await
|
|
1300
|
+
await fs.writeFile(path.join(dir, "docker-compose.yml"), compose);
|
|
1323
1301
|
}
|
|
1324
1302
|
async function createDockerfile(projectDir, runtime) {
|
|
1325
1303
|
const dockerfile = runtime === "bun" ? `FROM oven/bun:1 AS builder
|
|
@@ -1350,11 +1328,11 @@ RUN npm ci --omit=dev
|
|
|
1350
1328
|
EXPOSE 3000
|
|
1351
1329
|
CMD ["node", "dist/index.js"]
|
|
1352
1330
|
`;
|
|
1353
|
-
await
|
|
1354
|
-
await
|
|
1331
|
+
await fs.writeFile(path.join(projectDir, "Dockerfile"), dockerfile);
|
|
1332
|
+
await fs.writeFile(path.join(projectDir, ".dockerignore"), "node_modules\ndist\n.git\n.env\n");
|
|
1355
1333
|
}
|
|
1356
1334
|
async function createGitHubActions(projectDir) {
|
|
1357
|
-
await
|
|
1335
|
+
await fs.ensureDir(path.join(projectDir, ".github", "workflows"));
|
|
1358
1336
|
const workflow = `name: CI
|
|
1359
1337
|
|
|
1360
1338
|
on:
|
|
@@ -1376,15 +1354,15 @@ jobs:
|
|
|
1376
1354
|
- run: npm run build
|
|
1377
1355
|
- run: npm test --if-present
|
|
1378
1356
|
`;
|
|
1379
|
-
await
|
|
1357
|
+
await fs.writeFile(path.join(projectDir, ".github", "workflows", "ci.yml"), workflow);
|
|
1380
1358
|
}
|
|
1381
1359
|
async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth, frontend, extras, template) {
|
|
1382
1360
|
const hasDb = database !== "none";
|
|
1383
|
-
await
|
|
1384
|
-
await
|
|
1385
|
-
if (hasDb) await
|
|
1386
|
-
await
|
|
1387
|
-
await
|
|
1361
|
+
await fs.ensureDir(path.join(projectDir, "apps", "api", "src", "routes"));
|
|
1362
|
+
await fs.ensureDir(path.join(projectDir, "apps", "api", "src", "data"));
|
|
1363
|
+
if (hasDb) await fs.ensureDir(path.join(projectDir, "apps", "api", "src", "db"));
|
|
1364
|
+
await fs.ensureDir(path.join(projectDir, "apps", "web", "src", "lib"));
|
|
1365
|
+
await fs.ensureDir(path.join(projectDir, ".vscode"));
|
|
1388
1366
|
const rootPackageJson = {
|
|
1389
1367
|
name: projectName,
|
|
1390
1368
|
private: true,
|
|
@@ -1393,32 +1371,32 @@ async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, ru
|
|
|
1393
1371
|
build: "pnpm run --recursive build"
|
|
1394
1372
|
}
|
|
1395
1373
|
};
|
|
1396
|
-
await
|
|
1397
|
-
await
|
|
1374
|
+
await fs.writeJSON(path.join(projectDir, "package.json"), rootPackageJson, { spaces: 2 });
|
|
1375
|
+
await fs.writeFile(path.join(projectDir, "pnpm-workspace.yaml"), `packages:
|
|
1398
1376
|
- 'apps/*'
|
|
1399
1377
|
`);
|
|
1400
|
-
await
|
|
1378
|
+
await fs.writeFile(path.join(projectDir, ".gitignore"), "node_modules/\ndist/\n.env\n*.log\n");
|
|
1401
1379
|
await scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth);
|
|
1402
1380
|
await scaffoldFullstackWeb(projectDir, projectName, frontend, auth);
|
|
1403
1381
|
await scaffoldFullstackReadme(projectDir, projectName);
|
|
1404
1382
|
if (database !== "none" && database !== "sqlite") await createDockerCompose(projectDir, projectName, database, dbPort);
|
|
1405
|
-
if (extras.includes("docker")) await createDockerfile(
|
|
1383
|
+
if (extras.includes("docker")) await createDockerfile(path.join(projectDir, "apps", "api"), runtime);
|
|
1406
1384
|
if (extras.includes("github-actions")) await createGitHubActions(projectDir);
|
|
1407
1385
|
}
|
|
1408
1386
|
async function scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime, database = "none", dbPort, auth = true) {
|
|
1409
|
-
const apiDir =
|
|
1387
|
+
const apiDir = path.join(projectDir, "apps", "api");
|
|
1410
1388
|
const hasDb = database !== "none";
|
|
1411
1389
|
if (hasDb) {
|
|
1412
|
-
await
|
|
1413
|
-
await
|
|
1414
|
-
await
|
|
1390
|
+
await fs.ensureDir(path.join(apiDir, "src", "db"));
|
|
1391
|
+
await fs.writeFile(path.join(apiDir, "src", "db", "schema.ts"), getDatabaseSchema(database));
|
|
1392
|
+
await fs.writeFile(path.join(apiDir, "src", "db", "index.ts"), getDatabaseIndex(database));
|
|
1415
1393
|
if (database === "sqlite") {
|
|
1416
|
-
await
|
|
1394
|
+
await fs.writeFile(path.join(apiDir, "src", "db", "seed.ts"), getSQLiteSeed());
|
|
1417
1395
|
}
|
|
1418
1396
|
const dialect = database === "postgresql" ? "postgresql" : database === "mysql" ? "mysql" : "sqlite";
|
|
1419
1397
|
const pgPort = dbPort ?? 5436;
|
|
1420
1398
|
const dbUrl = database === "postgresql" ? `postgresql://postgres:postgres@localhost:${pgPort}/${projectName}` : database === "mysql" ? `mysql://root:root@localhost:3306/${projectName}` : void 0;
|
|
1421
|
-
await
|
|
1399
|
+
await fs.writeFile(path.join(apiDir, "drizzle.config.ts"), `import { defineConfig } from 'drizzle-kit';
|
|
1422
1400
|
import 'dotenv/config';
|
|
1423
1401
|
|
|
1424
1402
|
export default defineConfig({
|
|
@@ -1434,14 +1412,14 @@ export default defineConfig({
|
|
|
1434
1412
|
NODE_ENV=development
|
|
1435
1413
|
${dbUrl ? `DATABASE_URL=${dbUrl}
|
|
1436
1414
|
` : ""}${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
|
|
1437
|
-
await
|
|
1438
|
-
await
|
|
1415
|
+
await fs.writeFile(path.join(apiDir, ".env"), envContent);
|
|
1416
|
+
await fs.writeFile(path.join(apiDir, ".env.example"), envContent);
|
|
1439
1417
|
} else {
|
|
1440
1418
|
const envContent = `PORT=3000
|
|
1441
1419
|
NODE_ENV=development
|
|
1442
1420
|
${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
|
|
1443
|
-
await
|
|
1444
|
-
await
|
|
1421
|
+
await fs.writeFile(path.join(apiDir, ".env"), envContent);
|
|
1422
|
+
await fs.writeFile(path.join(apiDir, ".env.example"), envContent);
|
|
1445
1423
|
}
|
|
1446
1424
|
const apiPackageJson = {
|
|
1447
1425
|
name: `@${projectName}/api`,
|
|
@@ -1463,6 +1441,7 @@ ${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
|
|
|
1463
1441
|
zod: "^3.23.0",
|
|
1464
1442
|
dotenv: "^16.4.0",
|
|
1465
1443
|
...runtime === "node" && { "@hono/node-server": "^1.13.0" },
|
|
1444
|
+
...runtime === "node" && { "uWebSockets.js": "github:uNetworking/uWebSockets.js#6609a88ffa9a16ac5158046761356ce03250a0df" },
|
|
1466
1445
|
...hasDb && { "drizzle-orm": "^0.36.0" },
|
|
1467
1446
|
...database === "postgresql" && { postgres: "^3.4.0" },
|
|
1468
1447
|
...database === "mysql" && { mysql2: "^3.11.0" },
|
|
@@ -1476,7 +1455,7 @@ ${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
|
|
|
1476
1455
|
...database === "sqlite" && { "@types/better-sqlite3": "^7.6.0" }
|
|
1477
1456
|
}
|
|
1478
1457
|
};
|
|
1479
|
-
await
|
|
1458
|
+
await fs.writeJSON(path.join(apiDir, "package.json"), apiPackageJson, { spaces: 2 });
|
|
1480
1459
|
const tsconfig = {
|
|
1481
1460
|
compilerOptions: {
|
|
1482
1461
|
target: "ES2022",
|
|
@@ -1491,10 +1470,9 @@ ${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
|
|
|
1491
1470
|
include: ["src/**/*"],
|
|
1492
1471
|
exclude: ["node_modules", "dist"]
|
|
1493
1472
|
};
|
|
1494
|
-
await
|
|
1473
|
+
await fs.writeJSON(path.join(apiDir, "tsconfig.json"), tsconfig, { spaces: 2 });
|
|
1495
1474
|
const authImport = auth ? `import { authenticateJWT } from '@kozojs/auth';
|
|
1496
1475
|
` : "";
|
|
1497
|
-
const servicesSetup = "\nconst services = {};\n";
|
|
1498
1476
|
const authMiddleware = auth ? `
|
|
1499
1477
|
// JWT protects all /api/* routes except public ones
|
|
1500
1478
|
const JWT_SECRET = process.env.JWT_SECRET || 'change-me';
|
|
@@ -1505,21 +1483,22 @@ app.getApp().use('/api/*', (c, next) => {
|
|
|
1505
1483
|
return _jwt(c, next);
|
|
1506
1484
|
});
|
|
1507
1485
|
` : "";
|
|
1508
|
-
await
|
|
1486
|
+
await fs.outputFile(path.join(apiDir, "src", "index.ts"), `import 'dotenv/config';
|
|
1509
1487
|
import { createKozo } from '@kozojs/core';
|
|
1510
|
-
${authImport}import {
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1488
|
+
${authImport}import { fileURLToPath } from 'node:url';
|
|
1489
|
+
import { dirname, join } from 'node:path';
|
|
1490
|
+
|
|
1491
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1492
|
+
const PORT = Number(process.env.PORT) || 3000;
|
|
1493
|
+
const app = createKozo({ port: PORT });
|
|
1494
|
+
${authMiddleware}await app.loadRoutes(join(__dirname, 'routes'));
|
|
1515
1495
|
|
|
1516
1496
|
export type AppType = typeof app;
|
|
1517
1497
|
|
|
1518
|
-
console.log(
|
|
1519
|
-
|
|
1520
|
-
await app.listen();
|
|
1498
|
+
console.log(\`\u{1F525} ${projectName} API on http://localhost:\${PORT}\`);
|
|
1499
|
+
${runtime === "node" ? "await app.nativeListen();" : "await app.listen();"}
|
|
1521
1500
|
`);
|
|
1522
|
-
await
|
|
1501
|
+
await fs.outputFile(path.join(apiDir, "src", "schemas", "index.ts"), `import { z } from 'zod';
|
|
1523
1502
|
|
|
1524
1503
|
export const UserSchema = z.object({
|
|
1525
1504
|
id: z.string(),
|
|
@@ -1529,6 +1508,18 @@ export const UserSchema = z.object({
|
|
|
1529
1508
|
createdAt: z.string().optional(),
|
|
1530
1509
|
});
|
|
1531
1510
|
|
|
1511
|
+
export const CreateUserBody = z.object({
|
|
1512
|
+
name: z.string().min(1),
|
|
1513
|
+
email: z.string().email(),
|
|
1514
|
+
role: z.enum(['admin', 'user']).optional(),
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
export const UpdateUserBody = z.object({
|
|
1518
|
+
name: z.string().optional(),
|
|
1519
|
+
email: z.string().email().optional(),
|
|
1520
|
+
role: z.enum(['admin', 'user']).optional(),
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1532
1523
|
export const PostSchema = z.object({
|
|
1533
1524
|
id: z.string(),
|
|
1534
1525
|
title: z.string(),
|
|
@@ -1538,6 +1529,19 @@ export const PostSchema = z.object({
|
|
|
1538
1529
|
createdAt: z.string().optional(),
|
|
1539
1530
|
});
|
|
1540
1531
|
|
|
1532
|
+
export const CreatePostBody = z.object({
|
|
1533
|
+
title: z.string().min(1),
|
|
1534
|
+
content: z.string().optional(),
|
|
1535
|
+
authorId: z.string().optional(),
|
|
1536
|
+
published: z.boolean().optional(),
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
export const UpdatePostBody = z.object({
|
|
1540
|
+
title: z.string().optional(),
|
|
1541
|
+
content: z.string().optional(),
|
|
1542
|
+
published: z.boolean().optional(),
|
|
1543
|
+
});
|
|
1544
|
+
|
|
1541
1545
|
export const TaskSchema = z.object({
|
|
1542
1546
|
id: z.string(),
|
|
1543
1547
|
title: z.string(),
|
|
@@ -1546,302 +1550,415 @@ export const TaskSchema = z.object({
|
|
|
1546
1550
|
createdAt: z.string(),
|
|
1547
1551
|
});
|
|
1548
1552
|
|
|
1549
|
-
export const
|
|
1550
|
-
|
|
1551
|
-
|
|
1553
|
+
export const CreateTaskBody = z.object({
|
|
1554
|
+
title: z.string().min(1),
|
|
1555
|
+
priority: z.enum(['low', 'medium', 'high']).optional(),
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
export const UpdateTaskBody = z.object({
|
|
1559
|
+
title: z.string().optional(),
|
|
1560
|
+
completed: z.boolean().optional(),
|
|
1561
|
+
priority: z.enum(['low', 'medium', 'high']).optional(),
|
|
1562
|
+
});
|
|
1563
|
+
`);
|
|
1564
|
+
await fs.outputFile(path.join(apiDir, "src", "data", "index.ts"), `export const users = [
|
|
1565
|
+
{ id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin' as const, createdAt: new Date().toISOString() },
|
|
1566
|
+
{ id: '2', name: 'Bob', email: 'bob@example.com', role: 'user' as const, createdAt: new Date().toISOString() },
|
|
1552
1567
|
];
|
|
1553
1568
|
|
|
1554
|
-
export const posts
|
|
1569
|
+
export const posts = [
|
|
1555
1570
|
{ id: '1', title: 'Hello World', content: 'First post!', authorId: '1', published: true, createdAt: new Date().toISOString() },
|
|
1556
1571
|
{ id: '2', title: 'Draft', content: 'Work in progress', authorId: '2', published: false, createdAt: new Date().toISOString() },
|
|
1557
1572
|
];
|
|
1558
1573
|
|
|
1559
|
-
export const tasks
|
|
1560
|
-
{ id: '1', title: 'Setup project', completed: true, priority: 'high', createdAt: new Date().toISOString() },
|
|
1561
|
-
{ id: '2', title: 'Write tests', completed: false, priority: 'medium', createdAt: new Date().toISOString() },
|
|
1562
|
-
{ id: '3', title: 'Deploy', completed: false, priority: 'low', createdAt: new Date().toISOString() },
|
|
1574
|
+
export const tasks = [
|
|
1575
|
+
{ id: '1', title: 'Setup project', completed: true, priority: 'high' as const, createdAt: new Date().toISOString() },
|
|
1576
|
+
{ id: '2', title: 'Write tests', completed: false, priority: 'medium' as const, createdAt: new Date().toISOString() },
|
|
1577
|
+
{ id: '3', title: 'Deploy', completed: false, priority: 'low' as const, createdAt: new Date().toISOString() },
|
|
1563
1578
|
];
|
|
1564
1579
|
`);
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
export
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
}
|
|
1580
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "health", "get.ts"), `import { z } from 'zod';
|
|
1581
|
+
|
|
1582
|
+
export const schema = {
|
|
1583
|
+
response: z.object({
|
|
1584
|
+
status: z.string(),
|
|
1585
|
+
timestamp: z.string(),
|
|
1586
|
+
version: z.string(),
|
|
1587
|
+
uptime: z.number(),
|
|
1588
|
+
}),
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
export default async () => ({
|
|
1592
|
+
status: 'ok',
|
|
1593
|
+
timestamp: new Date().toISOString(),
|
|
1594
|
+
version: '1.0.0',
|
|
1595
|
+
uptime: process.uptime(),
|
|
1596
|
+
});
|
|
1583
1597
|
`);
|
|
1584
|
-
await
|
|
1585
|
-
import { users, posts, tasks } from '
|
|
1598
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "stats", "get.ts"), `import { z } from 'zod';
|
|
1599
|
+
import { users, posts, tasks } from '../../../data/index.js';
|
|
1586
1600
|
|
|
1587
|
-
export
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1601
|
+
export const schema = {
|
|
1602
|
+
response: z.object({
|
|
1603
|
+
users: z.number(),
|
|
1604
|
+
posts: z.number(),
|
|
1605
|
+
tasks: z.number(),
|
|
1606
|
+
publishedPosts: z.number(),
|
|
1607
|
+
completedTasks: z.number(),
|
|
1608
|
+
}),
|
|
1609
|
+
};
|
|
1594
1610
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
}
|
|
1611
|
+
export default async () => ({
|
|
1612
|
+
users: users.length,
|
|
1613
|
+
posts: posts.length,
|
|
1614
|
+
tasks: tasks.length,
|
|
1615
|
+
publishedPosts: posts.filter(p => p.published).length,
|
|
1616
|
+
completedTasks: tasks.filter(t => t.completed).length,
|
|
1617
|
+
});
|
|
1603
1618
|
`);
|
|
1604
|
-
await
|
|
1605
|
-
import { z } from 'zod';
|
|
1606
|
-
import { users, UserSchema } from '../data';
|
|
1619
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "echo", "get.ts"), `import { z } from 'zod';
|
|
1607
1620
|
|
|
1608
|
-
export
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1621
|
+
export const schema = {
|
|
1622
|
+
query: z.object({ message: z.string() }),
|
|
1623
|
+
response: z.object({
|
|
1624
|
+
echo: z.string(),
|
|
1625
|
+
timestamp: z.string(),
|
|
1626
|
+
}),
|
|
1627
|
+
};
|
|
1612
1628
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
return user;
|
|
1620
|
-
});
|
|
1629
|
+
export default async ({ query }: { query: { message: string } }) => ({
|
|
1630
|
+
echo: query.message,
|
|
1631
|
+
timestamp: new Date().toISOString(),
|
|
1632
|
+
});
|
|
1633
|
+
`);
|
|
1634
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "validate", "post.ts"), `import { z } from 'zod';
|
|
1621
1635
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
name: c.body.name,
|
|
1633
|
-
email: c.body.email,
|
|
1634
|
-
role: c.body.role || 'user' as const,
|
|
1635
|
-
createdAt: new Date().toISOString(),
|
|
1636
|
-
};
|
|
1637
|
-
users.push(newUser);
|
|
1638
|
-
return newUser;
|
|
1639
|
-
});
|
|
1636
|
+
export const schema = {
|
|
1637
|
+
body: z.object({
|
|
1638
|
+
email: z.string().email(),
|
|
1639
|
+
age: z.number().min(0).max(150),
|
|
1640
|
+
}),
|
|
1641
|
+
response: z.object({
|
|
1642
|
+
valid: z.boolean(),
|
|
1643
|
+
data: z.object({ email: z.string(), age: z.number() }),
|
|
1644
|
+
}),
|
|
1645
|
+
};
|
|
1640
1646
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
}, (c) => {
|
|
1650
|
-
const idx = users.findIndex(u => u.id === c.params.id);
|
|
1651
|
-
if (idx === -1) throw new Error('User not found');
|
|
1652
|
-
users[idx] = { ...users[idx], ...c.body };
|
|
1653
|
-
return users[idx];
|
|
1654
|
-
});
|
|
1647
|
+
export default async ({ body }: { body: { email: string; age: number } }) => ({
|
|
1648
|
+
valid: true,
|
|
1649
|
+
data: body,
|
|
1650
|
+
});
|
|
1651
|
+
`);
|
|
1652
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "users", "get.ts"), `import { z } from 'zod';
|
|
1653
|
+
import { users } from '../../../data/index.js';
|
|
1654
|
+
import { UserSchema } from '../../../schemas/index.js';
|
|
1655
1655
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
users.splice(idx, 1);
|
|
1662
|
-
return { success: true, message: 'User deleted' };
|
|
1663
|
-
});
|
|
1664
|
-
}
|
|
1656
|
+
export const schema = {
|
|
1657
|
+
response: z.array(UserSchema),
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
export default async () => users;
|
|
1665
1661
|
`);
|
|
1666
|
-
await
|
|
1667
|
-
import {
|
|
1668
|
-
import { posts, PostSchema } from '../data';
|
|
1662
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "users", "post.ts"), `import { users } from '../../../data/index.js';
|
|
1663
|
+
import { UserSchema, CreateUserBody } from '../../../schemas/index.js';
|
|
1669
1664
|
|
|
1670
|
-
export
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
}, (c) => {
|
|
1675
|
-
if (c.query.published !== undefined) {
|
|
1676
|
-
return posts.filter(p => p.published === c.query.published);
|
|
1677
|
-
}
|
|
1678
|
-
return posts;
|
|
1679
|
-
});
|
|
1665
|
+
export const schema = {
|
|
1666
|
+
body: CreateUserBody,
|
|
1667
|
+
response: UserSchema,
|
|
1668
|
+
};
|
|
1680
1669
|
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
}
|
|
1670
|
+
export default async ({ body }: { body: { name: string; email: string; role?: 'admin' | 'user' } }) => {
|
|
1671
|
+
const newUser = {
|
|
1672
|
+
id: String(Date.now()),
|
|
1673
|
+
name: body.name,
|
|
1674
|
+
email: body.email,
|
|
1675
|
+
role: body.role ?? ('user' as const),
|
|
1676
|
+
createdAt: new Date().toISOString(),
|
|
1677
|
+
};
|
|
1678
|
+
users.push(newUser);
|
|
1679
|
+
return newUser;
|
|
1680
|
+
};
|
|
1681
|
+
`);
|
|
1682
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "users", "[id]", "get.ts"), `import { z } from 'zod';
|
|
1683
|
+
import { KozoError } from '@kozojs/core';
|
|
1684
|
+
import { users } from '../../../../data/index.js';
|
|
1685
|
+
import { UserSchema } from '../../../../schemas/index.js';
|
|
1689
1686
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
authorId: z.string(),
|
|
1695
|
-
published: z.boolean().optional(),
|
|
1696
|
-
}),
|
|
1697
|
-
response: PostSchema,
|
|
1698
|
-
}, (c) => {
|
|
1699
|
-
const newPost = {
|
|
1700
|
-
id: String(Date.now()),
|
|
1701
|
-
title: c.body.title,
|
|
1702
|
-
content: c.body.content,
|
|
1703
|
-
authorId: c.body.authorId,
|
|
1704
|
-
published: c.body.published ?? false,
|
|
1705
|
-
createdAt: new Date().toISOString(),
|
|
1706
|
-
};
|
|
1707
|
-
posts.push(newPost);
|
|
1708
|
-
return newPost;
|
|
1709
|
-
});
|
|
1687
|
+
export const schema = {
|
|
1688
|
+
params: z.object({ id: z.string() }),
|
|
1689
|
+
response: UserSchema,
|
|
1690
|
+
};
|
|
1710
1691
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
if (idx === -1) throw new Error('Post not found');
|
|
1722
|
-
posts[idx] = { ...posts[idx], ...c.body };
|
|
1723
|
-
return posts[idx];
|
|
1724
|
-
});
|
|
1692
|
+
export default async ({ params }: { params: { id: string } }) => {
|
|
1693
|
+
const user = users.find(u => u.id === params.id);
|
|
1694
|
+
if (!user) throw new KozoError('User not found', 404, 'NOT_FOUND');
|
|
1695
|
+
return user;
|
|
1696
|
+
};
|
|
1697
|
+
`);
|
|
1698
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "users", "[id]", "put.ts"), `import { z } from 'zod';
|
|
1699
|
+
import { KozoError } from '@kozojs/core';
|
|
1700
|
+
import { users } from '../../../../data/index.js';
|
|
1701
|
+
import { UserSchema, UpdateUserBody } from '../../../../schemas/index.js';
|
|
1725
1702
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1703
|
+
export const schema = {
|
|
1704
|
+
params: z.object({ id: z.string() }),
|
|
1705
|
+
body: UpdateUserBody,
|
|
1706
|
+
response: UserSchema,
|
|
1707
|
+
};
|
|
1708
|
+
|
|
1709
|
+
export default async ({
|
|
1710
|
+
params,
|
|
1711
|
+
body,
|
|
1712
|
+
}: {
|
|
1713
|
+
params: { id: string };
|
|
1714
|
+
body: { name?: string; email?: string; role?: 'admin' | 'user' };
|
|
1715
|
+
}) => {
|
|
1716
|
+
const idx = users.findIndex(u => u.id === params.id);
|
|
1717
|
+
if (idx === -1) throw new KozoError('User not found', 404, 'NOT_FOUND');
|
|
1718
|
+
users[idx] = { ...users[idx], ...body };
|
|
1719
|
+
return users[idx];
|
|
1720
|
+
};
|
|
1735
1721
|
`);
|
|
1736
|
-
await
|
|
1737
|
-
import {
|
|
1738
|
-
import {
|
|
1722
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "users", "[id]", "delete.ts"), `import { z } from 'zod';
|
|
1723
|
+
import { KozoError } from '@kozojs/core';
|
|
1724
|
+
import { users } from '../../../../data/index.js';
|
|
1739
1725
|
|
|
1740
|
-
export
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
priority: z.enum(['low', 'medium', 'high']).optional(),
|
|
1745
|
-
}),
|
|
1746
|
-
response: z.array(TaskSchema),
|
|
1747
|
-
}, (c) => {
|
|
1748
|
-
let result = [...tasks];
|
|
1749
|
-
if (c.query.completed !== undefined) {
|
|
1750
|
-
result = result.filter(t => t.completed === c.query.completed);
|
|
1751
|
-
}
|
|
1752
|
-
if (c.query.priority) {
|
|
1753
|
-
result = result.filter(t => t.priority === c.query.priority);
|
|
1754
|
-
}
|
|
1755
|
-
return result;
|
|
1756
|
-
});
|
|
1726
|
+
export const schema = {
|
|
1727
|
+
params: z.object({ id: z.string() }),
|
|
1728
|
+
response: z.object({ success: z.boolean(), message: z.string() }),
|
|
1729
|
+
};
|
|
1757
1730
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
}
|
|
1731
|
+
export default async ({ params }: { params: { id: string } }) => {
|
|
1732
|
+
const idx = users.findIndex(u => u.id === params.id);
|
|
1733
|
+
if (idx === -1) throw new KozoError('User not found', 404, 'NOT_FOUND');
|
|
1734
|
+
users.splice(idx, 1);
|
|
1735
|
+
return { success: true, message: 'User deleted' };
|
|
1736
|
+
};
|
|
1737
|
+
`);
|
|
1738
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "posts", "get.ts"), `import { z } from 'zod';
|
|
1739
|
+
import { posts } from '../../../data/index.js';
|
|
1740
|
+
import { PostSchema } from '../../../schemas/index.js';
|
|
1766
1741
|
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
}),
|
|
1772
|
-
response: TaskSchema,
|
|
1773
|
-
}, (c) => {
|
|
1774
|
-
const newTask = {
|
|
1775
|
-
id: String(Date.now()),
|
|
1776
|
-
title: c.body.title,
|
|
1777
|
-
completed: false,
|
|
1778
|
-
priority: c.body.priority || 'medium' as const,
|
|
1779
|
-
createdAt: new Date().toISOString(),
|
|
1780
|
-
};
|
|
1781
|
-
tasks.push(newTask);
|
|
1782
|
-
return newTask;
|
|
1783
|
-
});
|
|
1742
|
+
export const schema = {
|
|
1743
|
+
query: z.object({ published: z.coerce.boolean().optional() }),
|
|
1744
|
+
response: z.array(PostSchema),
|
|
1745
|
+
};
|
|
1784
1746
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
const idx = tasks.findIndex(t => t.id === c.params.id);
|
|
1795
|
-
if (idx === -1) throw new Error('Task not found');
|
|
1796
|
-
tasks[idx] = { ...tasks[idx], ...c.body };
|
|
1797
|
-
return tasks[idx];
|
|
1798
|
-
});
|
|
1747
|
+
export default async ({ query }: { query: { published?: boolean } }) => {
|
|
1748
|
+
if (query.published !== undefined) {
|
|
1749
|
+
return posts.filter(p => p.published === query.published);
|
|
1750
|
+
}
|
|
1751
|
+
return posts;
|
|
1752
|
+
};
|
|
1753
|
+
`);
|
|
1754
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "posts", "post.ts"), `import { posts, users } from '../../../data/index.js';
|
|
1755
|
+
import { PostSchema, CreatePostBody } from '../../../schemas/index.js';
|
|
1799
1756
|
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
const idx = tasks.findIndex(t => t.id === c.params.id);
|
|
1805
|
-
if (idx === -1) throw new Error('Task not found');
|
|
1806
|
-
tasks[idx].completed = !tasks[idx].completed;
|
|
1807
|
-
return tasks[idx];
|
|
1808
|
-
});
|
|
1757
|
+
export const schema = {
|
|
1758
|
+
body: CreatePostBody,
|
|
1759
|
+
response: PostSchema,
|
|
1760
|
+
};
|
|
1809
1761
|
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1762
|
+
export default async ({
|
|
1763
|
+
body,
|
|
1764
|
+
}: {
|
|
1765
|
+
body: { title: string; content?: string; authorId?: string; published?: boolean };
|
|
1766
|
+
}) => {
|
|
1767
|
+
const authorId = body.authorId ?? users[0]?.id ?? 'unknown';
|
|
1768
|
+
const newPost = {
|
|
1769
|
+
id: String(Date.now()),
|
|
1770
|
+
title: body.title,
|
|
1771
|
+
content: body.content ?? '',
|
|
1772
|
+
authorId,
|
|
1773
|
+
published: body.published ?? false,
|
|
1774
|
+
createdAt: new Date().toISOString(),
|
|
1775
|
+
};
|
|
1776
|
+
posts.push(newPost);
|
|
1777
|
+
return newPost;
|
|
1778
|
+
};
|
|
1819
1779
|
`);
|
|
1820
|
-
await
|
|
1821
|
-
import {
|
|
1780
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "posts", "[id]", "get.ts"), `import { z } from 'zod';
|
|
1781
|
+
import { KozoError } from '@kozojs/core';
|
|
1782
|
+
import { posts } from '../../../../data/index.js';
|
|
1783
|
+
import { PostSchema } from '../../../../schemas/index.js';
|
|
1822
1784
|
|
|
1823
|
-
export
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
echo: c.query.message,
|
|
1828
|
-
timestamp: new Date().toISOString(),
|
|
1829
|
-
}));
|
|
1785
|
+
export const schema = {
|
|
1786
|
+
params: z.object({ id: z.string() }),
|
|
1787
|
+
response: PostSchema,
|
|
1788
|
+
};
|
|
1830
1789
|
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
}
|
|
1790
|
+
export default async ({ params }: { params: { id: string } }) => {
|
|
1791
|
+
const post = posts.find(p => p.id === params.id);
|
|
1792
|
+
if (!post) throw new KozoError('Post not found', 404, 'NOT_FOUND');
|
|
1793
|
+
return post;
|
|
1794
|
+
};
|
|
1795
|
+
`);
|
|
1796
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "posts", "[id]", "put.ts"), `import { z } from 'zod';
|
|
1797
|
+
import { KozoError } from '@kozojs/core';
|
|
1798
|
+
import { posts } from '../../../../data/index.js';
|
|
1799
|
+
import { PostSchema, UpdatePostBody } from '../../../../schemas/index.js';
|
|
1800
|
+
|
|
1801
|
+
export const schema = {
|
|
1802
|
+
params: z.object({ id: z.string() }),
|
|
1803
|
+
body: UpdatePostBody,
|
|
1804
|
+
response: PostSchema,
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
export default async ({
|
|
1808
|
+
params,
|
|
1809
|
+
body,
|
|
1810
|
+
}: {
|
|
1811
|
+
params: { id: string };
|
|
1812
|
+
body: { title?: string; content?: string; published?: boolean };
|
|
1813
|
+
}) => {
|
|
1814
|
+
const idx = posts.findIndex(p => p.id === params.id);
|
|
1815
|
+
if (idx === -1) throw new KozoError('Post not found', 404, 'NOT_FOUND');
|
|
1816
|
+
posts[idx] = { ...posts[idx], ...body };
|
|
1817
|
+
return posts[idx];
|
|
1818
|
+
};
|
|
1819
|
+
`);
|
|
1820
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "posts", "[id]", "delete.ts"), `import { z } from 'zod';
|
|
1821
|
+
import { KozoError } from '@kozojs/core';
|
|
1822
|
+
import { posts } from '../../../../data/index.js';
|
|
1823
|
+
|
|
1824
|
+
export const schema = {
|
|
1825
|
+
params: z.object({ id: z.string() }),
|
|
1826
|
+
response: z.object({ success: z.boolean(), message: z.string() }),
|
|
1827
|
+
};
|
|
1828
|
+
|
|
1829
|
+
export default async ({ params }: { params: { id: string } }) => {
|
|
1830
|
+
const idx = posts.findIndex(p => p.id === params.id);
|
|
1831
|
+
if (idx === -1) throw new KozoError('Post not found', 404, 'NOT_FOUND');
|
|
1832
|
+
posts.splice(idx, 1);
|
|
1833
|
+
return { success: true, message: 'Post deleted' };
|
|
1834
|
+
};
|
|
1835
|
+
`);
|
|
1836
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "tasks", "get.ts"), `import { z } from 'zod';
|
|
1837
|
+
import { tasks } from '../../../data/index.js';
|
|
1838
|
+
import { TaskSchema } from '../../../schemas/index.js';
|
|
1839
|
+
|
|
1840
|
+
export const schema = {
|
|
1841
|
+
query: z.object({
|
|
1842
|
+
completed: z.coerce.boolean().optional(),
|
|
1843
|
+
priority: z.enum(['low', 'medium', 'high']).optional(),
|
|
1844
|
+
}),
|
|
1845
|
+
response: z.array(TaskSchema),
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
export default async ({
|
|
1849
|
+
query,
|
|
1850
|
+
}: {
|
|
1851
|
+
query: { completed?: boolean; priority?: 'low' | 'medium' | 'high' };
|
|
1852
|
+
}) => {
|
|
1853
|
+
let result = [...tasks];
|
|
1854
|
+
if (query.completed !== undefined) {
|
|
1855
|
+
result = result.filter(t => t.completed === query.completed);
|
|
1856
|
+
}
|
|
1857
|
+
if (query.priority) {
|
|
1858
|
+
result = result.filter(t => t.priority === query.priority);
|
|
1859
|
+
}
|
|
1860
|
+
return result;
|
|
1861
|
+
};
|
|
1862
|
+
`);
|
|
1863
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "tasks", "post.ts"), `import { tasks } from '../../../data/index.js';
|
|
1864
|
+
import { TaskSchema, CreateTaskBody } from '../../../schemas/index.js';
|
|
1865
|
+
|
|
1866
|
+
export const schema = {
|
|
1867
|
+
body: CreateTaskBody,
|
|
1868
|
+
response: TaskSchema,
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
export default async ({
|
|
1872
|
+
body,
|
|
1873
|
+
}: {
|
|
1874
|
+
body: { title: string; priority?: 'low' | 'medium' | 'high' };
|
|
1875
|
+
}) => {
|
|
1876
|
+
const newTask = {
|
|
1877
|
+
id: String(Date.now()),
|
|
1878
|
+
title: body.title,
|
|
1879
|
+
completed: false,
|
|
1880
|
+
priority: body.priority ?? ('medium' as const),
|
|
1881
|
+
createdAt: new Date().toISOString(),
|
|
1882
|
+
};
|
|
1883
|
+
tasks.push(newTask);
|
|
1884
|
+
return newTask;
|
|
1885
|
+
};
|
|
1886
|
+
`);
|
|
1887
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "tasks", "[id]", "get.ts"), `import { z } from 'zod';
|
|
1888
|
+
import { KozoError } from '@kozojs/core';
|
|
1889
|
+
import { tasks } from '../../../../data/index.js';
|
|
1890
|
+
import { TaskSchema } from '../../../../schemas/index.js';
|
|
1891
|
+
|
|
1892
|
+
export const schema = {
|
|
1893
|
+
params: z.object({ id: z.string() }),
|
|
1894
|
+
response: TaskSchema,
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
export default async ({ params }: { params: { id: string } }) => {
|
|
1898
|
+
const task = tasks.find(t => t.id === params.id);
|
|
1899
|
+
if (!task) throw new KozoError('Task not found', 404, 'NOT_FOUND');
|
|
1900
|
+
return task;
|
|
1901
|
+
};
|
|
1902
|
+
`);
|
|
1903
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "tasks", "[id]", "put.ts"), `import { z } from 'zod';
|
|
1904
|
+
import { KozoError } from '@kozojs/core';
|
|
1905
|
+
import { tasks } from '../../../../data/index.js';
|
|
1906
|
+
import { TaskSchema, UpdateTaskBody } from '../../../../schemas/index.js';
|
|
1907
|
+
|
|
1908
|
+
export const schema = {
|
|
1909
|
+
params: z.object({ id: z.string() }),
|
|
1910
|
+
body: UpdateTaskBody,
|
|
1911
|
+
response: TaskSchema,
|
|
1912
|
+
};
|
|
1913
|
+
|
|
1914
|
+
export default async ({
|
|
1915
|
+
params,
|
|
1916
|
+
body,
|
|
1917
|
+
}: {
|
|
1918
|
+
params: { id: string };
|
|
1919
|
+
body: { title?: string; completed?: boolean; priority?: 'low' | 'medium' | 'high' };
|
|
1920
|
+
}) => {
|
|
1921
|
+
const idx = tasks.findIndex(t => t.id === params.id);
|
|
1922
|
+
if (idx === -1) throw new KozoError('Task not found', 404, 'NOT_FOUND');
|
|
1923
|
+
tasks[idx] = { ...tasks[idx], ...body };
|
|
1924
|
+
return tasks[idx];
|
|
1925
|
+
};
|
|
1926
|
+
`);
|
|
1927
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "tasks", "[id]", "delete.ts"), `import { z } from 'zod';
|
|
1928
|
+
import { KozoError } from '@kozojs/core';
|
|
1929
|
+
import { tasks } from '../../../../data/index.js';
|
|
1930
|
+
|
|
1931
|
+
export const schema = {
|
|
1932
|
+
params: z.object({ id: z.string() }),
|
|
1933
|
+
response: z.object({ success: z.boolean(), message: z.string() }),
|
|
1934
|
+
};
|
|
1935
|
+
|
|
1936
|
+
export default async ({ params }: { params: { id: string } }) => {
|
|
1937
|
+
const idx = tasks.findIndex(t => t.id === params.id);
|
|
1938
|
+
if (idx === -1) throw new KozoError('Task not found', 404, 'NOT_FOUND');
|
|
1939
|
+
tasks.splice(idx, 1);
|
|
1940
|
+
return { success: true, message: 'Task deleted' };
|
|
1941
|
+
};
|
|
1942
|
+
`);
|
|
1943
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "tasks", "[id]", "toggle", "patch.ts"), `import { z } from 'zod';
|
|
1944
|
+
import { KozoError } from '@kozojs/core';
|
|
1945
|
+
import { tasks } from '../../../../../data/index.js';
|
|
1946
|
+
import { TaskSchema } from '../../../../../schemas/index.js';
|
|
1947
|
+
|
|
1948
|
+
export const schema = {
|
|
1949
|
+
params: z.object({ id: z.string() }),
|
|
1950
|
+
response: TaskSchema,
|
|
1951
|
+
};
|
|
1952
|
+
|
|
1953
|
+
export default async ({ params }: { params: { id: string } }) => {
|
|
1954
|
+
const idx = tasks.findIndex(t => t.id === params.id);
|
|
1955
|
+
if (idx === -1) throw new KozoError('Task not found', 404, 'NOT_FOUND');
|
|
1956
|
+
tasks[idx].completed = !tasks[idx].completed;
|
|
1957
|
+
return tasks[idx];
|
|
1958
|
+
};
|
|
1841
1959
|
`);
|
|
1842
1960
|
if (auth) {
|
|
1843
|
-
await
|
|
1844
|
-
import { z } from 'zod';
|
|
1961
|
+
await fs.outputFile(path.join(apiDir, "src", "routes", "api", "auth", "login", "post.ts"), `import { z } from 'zod';
|
|
1845
1962
|
import { createJWT, UnauthorizedError } from '@kozojs/auth';
|
|
1846
1963
|
|
|
1847
1964
|
const JWT_SECRET = process.env.JWT_SECRET || 'change-me';
|
|
@@ -1851,28 +1968,38 @@ const DEMO_USERS = [
|
|
|
1851
1968
|
{ email: 'user@demo.com', password: 'user123', role: 'user', name: 'User' },
|
|
1852
1969
|
];
|
|
1853
1970
|
|
|
1854
|
-
export
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
)
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1971
|
+
export const schema = {
|
|
1972
|
+
body: z.object({
|
|
1973
|
+
email: z.string().email(),
|
|
1974
|
+
password: z.string(),
|
|
1975
|
+
}),
|
|
1976
|
+
response: z.object({
|
|
1977
|
+
token: z.string(),
|
|
1978
|
+
user: z.object({
|
|
1979
|
+
email: z.string(),
|
|
1980
|
+
role: z.string(),
|
|
1981
|
+
name: z.string(),
|
|
1982
|
+
}),
|
|
1983
|
+
}),
|
|
1984
|
+
};
|
|
1985
|
+
|
|
1986
|
+
export default async ({ body }: { body: { email: string; password: string } }) => {
|
|
1987
|
+
const user = DEMO_USERS.find(u => u.email === body.email && u.password === body.password);
|
|
1988
|
+
if (!user) throw new UnauthorizedError('Invalid credentials');
|
|
1989
|
+
const token = await createJWT(
|
|
1990
|
+
{ email: user.email, role: user.role, name: user.name },
|
|
1991
|
+
JWT_SECRET,
|
|
1992
|
+
{ expiresIn: '24h' },
|
|
1993
|
+
);
|
|
1994
|
+
return { token, user: { email: user.email, role: user.role, name: user.name } };
|
|
1995
|
+
};
|
|
1869
1996
|
`);
|
|
1870
1997
|
}
|
|
1871
1998
|
}
|
|
1872
1999
|
async function scaffoldFullstackWeb(projectDir, projectName, frontend, auth = false) {
|
|
1873
|
-
const webDir =
|
|
1874
|
-
await
|
|
1875
|
-
await
|
|
2000
|
+
const webDir = path.join(projectDir, "apps", "web");
|
|
2001
|
+
await fs.ensureDir(path.join(webDir, "src", "lib"));
|
|
2002
|
+
await fs.ensureDir(path.join(webDir, "src", "pages"));
|
|
1876
2003
|
const packageJson = {
|
|
1877
2004
|
name: `@${projectName}/web`,
|
|
1878
2005
|
version: "1.0.0",
|
|
@@ -1894,8 +2021,8 @@ async function scaffoldFullstackWeb(projectDir, projectName, frontend, auth = fa
|
|
|
1894
2021
|
tailwindcss: "^4.0.0"
|
|
1895
2022
|
}
|
|
1896
2023
|
};
|
|
1897
|
-
await
|
|
1898
|
-
await
|
|
2024
|
+
await fs.writeJSON(path.join(webDir, "package.json"), packageJson, { spaces: 2 });
|
|
2025
|
+
await fs.writeJSON(path.join(webDir, "tsconfig.json"), {
|
|
1899
2026
|
compilerOptions: {
|
|
1900
2027
|
target: "ES2020",
|
|
1901
2028
|
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
@@ -1913,7 +2040,7 @@ async function scaffoldFullstackWeb(projectDir, projectName, frontend, auth = fa
|
|
|
1913
2040
|
},
|
|
1914
2041
|
include: ["src"]
|
|
1915
2042
|
}, { spaces: 2 });
|
|
1916
|
-
await
|
|
2043
|
+
await fs.writeFile(path.join(webDir, "vite.config.ts"), `import { defineConfig } from 'vite';
|
|
1917
2044
|
import react from '@vitejs/plugin-react';
|
|
1918
2045
|
import tailwindcss from '@tailwindcss/vite';
|
|
1919
2046
|
|
|
@@ -1927,7 +2054,7 @@ export default defineConfig({
|
|
|
1927
2054
|
},
|
|
1928
2055
|
});
|
|
1929
2056
|
`);
|
|
1930
|
-
await
|
|
2057
|
+
await fs.writeFile(path.join(webDir, "index.html"), `<!DOCTYPE html>
|
|
1931
2058
|
<html lang="en">
|
|
1932
2059
|
<head>
|
|
1933
2060
|
<meta charset="UTF-8" />
|
|
@@ -1940,12 +2067,12 @@ export default defineConfig({
|
|
|
1940
2067
|
</body>
|
|
1941
2068
|
</html>
|
|
1942
2069
|
`);
|
|
1943
|
-
await
|
|
2070
|
+
await fs.writeFile(path.join(webDir, "src", "index.css"), `@import "tailwindcss";
|
|
1944
2071
|
|
|
1945
2072
|
body { background-color: rgb(15 23 42); color: rgb(241 245 249); font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
1946
2073
|
* { box-sizing: border-box; }
|
|
1947
2074
|
`);
|
|
1948
|
-
await
|
|
2075
|
+
await fs.writeFile(path.join(webDir, "src", "lib", "api.ts"), `const API_BASE = '';
|
|
1949
2076
|
|
|
1950
2077
|
${auth ? `export function getToken(): string | null {
|
|
1951
2078
|
return localStorage.getItem('kozo_token');
|
|
@@ -1982,7 +2109,7 @@ ${auth ? ` const token = getToken();
|
|
|
1982
2109
|
return { data, status: res.status, ms: Math.round(performance.now() - start) };
|
|
1983
2110
|
}
|
|
1984
2111
|
`);
|
|
1985
|
-
await
|
|
2112
|
+
await fs.writeFile(path.join(webDir, "src", "main.tsx"), `import React from 'react';
|
|
1986
2113
|
import ReactDOM from 'react-dom/client';
|
|
1987
2114
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
1988
2115
|
import App from './App';
|
|
@@ -2000,14 +2127,14 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
2000
2127
|
</React.StrictMode>
|
|
2001
2128
|
);
|
|
2002
2129
|
`);
|
|
2003
|
-
await
|
|
2004
|
-
await
|
|
2005
|
-
await
|
|
2006
|
-
await
|
|
2130
|
+
await fs.writeFile(path.join(webDir, "src", "pages", "Dashboard.tsx"), generateDashboardPage());
|
|
2131
|
+
await fs.writeFile(path.join(webDir, "src", "pages", "Users.tsx"), generateUsersPage());
|
|
2132
|
+
await fs.writeFile(path.join(webDir, "src", "pages", "Posts.tsx"), generatePostsPage());
|
|
2133
|
+
await fs.writeFile(path.join(webDir, "src", "pages", "Tasks.tsx"), generateTasksPage());
|
|
2007
2134
|
if (auth) {
|
|
2008
|
-
await
|
|
2135
|
+
await fs.writeFile(path.join(webDir, "src", "pages", "Login.tsx"), generateLoginPage());
|
|
2009
2136
|
}
|
|
2010
|
-
await
|
|
2137
|
+
await fs.writeFile(path.join(webDir, "src", "App.tsx"), generateAppTsx(projectName, auth));
|
|
2011
2138
|
}
|
|
2012
2139
|
function generateAppTsx(projectName, auth) {
|
|
2013
2140
|
const authImports = auth ? `import { getToken, clearToken } from './lib/api';` : "";
|
|
@@ -2791,19 +2918,19 @@ const res = await client.api.users.$get();
|
|
|
2791
2918
|
const users = await res.json(); // Fully typed!
|
|
2792
2919
|
\`\`\`
|
|
2793
2920
|
`;
|
|
2794
|
-
await
|
|
2921
|
+
await fs.writeFile(path.join(projectDir, "README.md"), readme);
|
|
2795
2922
|
}
|
|
2796
2923
|
|
|
2797
2924
|
// src/utils/ascii-art.ts
|
|
2798
|
-
|
|
2925
|
+
import pc from "picocolors";
|
|
2799
2926
|
var KOZO_LOGO = `
|
|
2800
|
-
${
|
|
2801
|
-
${
|
|
2802
|
-
${
|
|
2803
|
-
${
|
|
2927
|
+
${pc.red(" _ __")}${pc.yellow("___ ")}${pc.red("______")}${pc.yellow("___ ")}
|
|
2928
|
+
${pc.red("| |/ /")}${pc.yellow(" _ \\\\")}${pc.red("|_ /")}${pc.yellow(" _ \\\\")}
|
|
2929
|
+
${pc.red("| ' /")}${pc.yellow(" (_) |")}${pc.red("/ /")}${pc.yellow(" (_) |")}
|
|
2930
|
+
${pc.red("|_|\\_\\\\")}${pc.yellow("___/")}${pc.red("___|\\\\")}${pc.yellow("___/")}
|
|
2804
2931
|
`;
|
|
2805
2932
|
var KOZO_BANNER = `
|
|
2806
|
-
${
|
|
2933
|
+
${pc.bold(pc.red("\u{1F525} KOZO"))} ${pc.dim("- The Structure for the Edge")}
|
|
2807
2934
|
`;
|
|
2808
2935
|
function printLogo() {
|
|
2809
2936
|
console.log(KOZO_LOGO);
|
|
@@ -2812,7 +2939,7 @@ function printLogo() {
|
|
|
2812
2939
|
// src/commands/new.ts
|
|
2813
2940
|
async function newCommand(projectName) {
|
|
2814
2941
|
printLogo();
|
|
2815
|
-
p.intro(
|
|
2942
|
+
p.intro(pc2.bold(pc2.red("\u{1F525} Create a new Kozo project")));
|
|
2816
2943
|
const isLocalWorkspace = process.env.KOZO_LOCAL === "true";
|
|
2817
2944
|
const project = await p.group(
|
|
2818
2945
|
{
|
|
@@ -2930,14 +3057,14 @@ async function newCommand(projectName) {
|
|
|
2930
3057
|
if (project.install) {
|
|
2931
3058
|
s.start("Installing dependencies...");
|
|
2932
3059
|
try {
|
|
2933
|
-
await
|
|
3060
|
+
await execa("pnpm", ["install"], {
|
|
2934
3061
|
cwd: project.name,
|
|
2935
3062
|
stdio: "pipe"
|
|
2936
3063
|
});
|
|
2937
3064
|
s.stop("Dependencies installed!");
|
|
2938
3065
|
} catch {
|
|
2939
3066
|
try {
|
|
2940
|
-
await
|
|
3067
|
+
await execa("npm", ["install"], {
|
|
2941
3068
|
cwd: project.name,
|
|
2942
3069
|
stdio: "pipe"
|
|
2943
3070
|
});
|
|
@@ -2948,33 +3075,33 @@ async function newCommand(projectName) {
|
|
|
2948
3075
|
}
|
|
2949
3076
|
}
|
|
2950
3077
|
}
|
|
2951
|
-
p.outro(
|
|
3078
|
+
p.outro(pc2.green("\u2728 Project ready!"));
|
|
2952
3079
|
console.log(`
|
|
2953
|
-
${
|
|
3080
|
+
${pc2.bold("Next steps:")}
|
|
2954
3081
|
|
|
2955
|
-
${
|
|
2956
|
-
${!project.install ?
|
|
3082
|
+
${pc2.cyan(`cd ${project.name}`)}
|
|
3083
|
+
${!project.install ? pc2.cyan("pnpm install") + "\n " : ""}${pc2.cyan("pnpm dev")}
|
|
2957
3084
|
|
|
2958
|
-
${
|
|
3085
|
+
${pc2.dim("Documentation:")} ${pc2.underline("https://kozo-docs.vercel.app")}
|
|
2959
3086
|
`);
|
|
2960
3087
|
}
|
|
2961
3088
|
|
|
2962
3089
|
// src/commands/build.ts
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
3090
|
+
import { execa as execa2 } from "execa";
|
|
3091
|
+
import pc3 from "picocolors";
|
|
3092
|
+
import fs2 from "fs-extra";
|
|
3093
|
+
import path2 from "path";
|
|
2967
3094
|
|
|
2968
3095
|
// src/routing/manifest.ts
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
3096
|
+
import { createHash } from "crypto";
|
|
3097
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
3098
|
+
import { join as join2, dirname } from "path";
|
|
3099
|
+
import { glob as glob2 } from "glob";
|
|
2973
3100
|
|
|
2974
3101
|
// src/routing/scan.ts
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
3102
|
+
import { glob } from "glob";
|
|
3103
|
+
import { join } from "path";
|
|
3104
|
+
import { readFileSync } from "fs";
|
|
2978
3105
|
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
2979
3106
|
function fileToRoute(filePath) {
|
|
2980
3107
|
const normalized = filePath.replace(/\\/g, "/");
|
|
@@ -3012,7 +3139,7 @@ function isRouteFile(file) {
|
|
|
3012
3139
|
function detectSchemas(absolutePath) {
|
|
3013
3140
|
let source = "";
|
|
3014
3141
|
try {
|
|
3015
|
-
source =
|
|
3142
|
+
source = readFileSync(absolutePath, "utf8");
|
|
3016
3143
|
} catch {
|
|
3017
3144
|
return { hasBodySchema: false, hasQuerySchema: false };
|
|
3018
3145
|
}
|
|
@@ -3032,7 +3159,7 @@ function routeScore(urlPath) {
|
|
|
3032
3159
|
}
|
|
3033
3160
|
async function scanRoutes(options) {
|
|
3034
3161
|
const { routesDir, verbose = false } = options;
|
|
3035
|
-
const files = await
|
|
3162
|
+
const files = await glob("**/*.{ts,js}", {
|
|
3036
3163
|
cwd: routesDir,
|
|
3037
3164
|
nodir: true,
|
|
3038
3165
|
ignore: ["**/_*.ts", "**/_*.js", "**/*.test.ts", "**/*.spec.ts", "**/*.test.js", "**/*.spec.js"]
|
|
@@ -3042,7 +3169,7 @@ async function scanRoutes(options) {
|
|
|
3042
3169
|
if (!isRouteFile(file)) continue;
|
|
3043
3170
|
const parsed = fileToRoute(file);
|
|
3044
3171
|
if (!parsed) continue;
|
|
3045
|
-
const absolutePath =
|
|
3172
|
+
const absolutePath = join(routesDir, file);
|
|
3046
3173
|
const { hasBodySchema, hasQuerySchema } = detectSchemas(absolutePath);
|
|
3047
3174
|
const params = extractParams(parsed.path);
|
|
3048
3175
|
routes.push({
|
|
@@ -3067,17 +3194,17 @@ async function scanRoutes(options) {
|
|
|
3067
3194
|
|
|
3068
3195
|
// src/routing/manifest.ts
|
|
3069
3196
|
async function hashRouteFiles(routesDir) {
|
|
3070
|
-
const files = await (
|
|
3197
|
+
const files = await glob2("**/*.{ts,js}", {
|
|
3071
3198
|
cwd: routesDir,
|
|
3072
3199
|
nodir: true,
|
|
3073
3200
|
ignore: ["**/_*.ts", "**/_*.js", "**/*.test.ts", "**/*.spec.ts", "**/*.test.js", "**/*.spec.js"]
|
|
3074
3201
|
});
|
|
3075
3202
|
files.sort();
|
|
3076
|
-
const hash =
|
|
3203
|
+
const hash = createHash("sha256");
|
|
3077
3204
|
for (const file of files) {
|
|
3078
3205
|
hash.update(file);
|
|
3079
3206
|
try {
|
|
3080
|
-
const content = (
|
|
3207
|
+
const content = readFileSync2(join2(routesDir, file));
|
|
3081
3208
|
hash.update(content);
|
|
3082
3209
|
} catch {
|
|
3083
3210
|
}
|
|
@@ -3085,9 +3212,9 @@ async function hashRouteFiles(routesDir) {
|
|
|
3085
3212
|
return hash.digest("hex");
|
|
3086
3213
|
}
|
|
3087
3214
|
function readExistingManifest(manifestPath) {
|
|
3088
|
-
if (!
|
|
3215
|
+
if (!existsSync(manifestPath)) return null;
|
|
3089
3216
|
try {
|
|
3090
|
-
const raw = (
|
|
3217
|
+
const raw = readFileSync2(manifestPath, "utf8");
|
|
3091
3218
|
return JSON.parse(raw);
|
|
3092
3219
|
} catch {
|
|
3093
3220
|
return null;
|
|
@@ -3097,7 +3224,7 @@ async function generateManifest(options) {
|
|
|
3097
3224
|
const {
|
|
3098
3225
|
routesDir,
|
|
3099
3226
|
projectRoot,
|
|
3100
|
-
outputPath = (
|
|
3227
|
+
outputPath = join2(projectRoot, "routes-manifest.json"),
|
|
3101
3228
|
cache = true,
|
|
3102
3229
|
verbose = false
|
|
3103
3230
|
} = options;
|
|
@@ -3130,11 +3257,11 @@ async function generateManifest(options) {
|
|
|
3130
3257
|
contentHash,
|
|
3131
3258
|
routes: entries
|
|
3132
3259
|
};
|
|
3133
|
-
const dir =
|
|
3134
|
-
if (!
|
|
3135
|
-
|
|
3260
|
+
const dir = dirname(outputPath);
|
|
3261
|
+
if (!existsSync(dir)) {
|
|
3262
|
+
mkdirSync(dir, { recursive: true });
|
|
3136
3263
|
}
|
|
3137
|
-
|
|
3264
|
+
writeFileSync(outputPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
3138
3265
|
if (verbose) {
|
|
3139
3266
|
console.log(` \u2713 Generated routes-manifest.json (${entries.length} routes, hash: ${contentHash.slice(0, 8)}\u2026)`);
|
|
3140
3267
|
}
|
|
@@ -3147,22 +3274,22 @@ function printBox(title) {
|
|
|
3147
3274
|
const width = 50;
|
|
3148
3275
|
const pad = Math.max(0, Math.floor((width - title.length) / 2));
|
|
3149
3276
|
const line = "\u2500".repeat(width);
|
|
3150
|
-
console.log(
|
|
3151
|
-
console.log(
|
|
3152
|
-
console.log(
|
|
3277
|
+
console.log(pc3.cyan(`\u250C${line}\u2510`));
|
|
3278
|
+
console.log(pc3.cyan("\u2502") + " ".repeat(pad) + pc3.bold(title) + " ".repeat(width - pad - title.length) + pc3.cyan("\u2502"));
|
|
3279
|
+
console.log(pc3.cyan(`\u2514${line}\u2518`));
|
|
3153
3280
|
console.log();
|
|
3154
3281
|
}
|
|
3155
3282
|
function step(n, total, label) {
|
|
3156
|
-
console.log(
|
|
3283
|
+
console.log(pc3.dim(`[${n}/${total}]`) + " " + pc3.cyan("\u2192") + " " + label);
|
|
3157
3284
|
}
|
|
3158
3285
|
function ok(label) {
|
|
3159
|
-
console.log(
|
|
3286
|
+
console.log(pc3.green(" \u2713") + " " + label);
|
|
3160
3287
|
}
|
|
3161
3288
|
function fail(label, err) {
|
|
3162
|
-
console.log(
|
|
3289
|
+
console.log(pc3.red(" \u2717") + " " + label);
|
|
3163
3290
|
if (err) {
|
|
3164
3291
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3165
|
-
console.log(
|
|
3292
|
+
console.log(pc3.dim(" " + msg));
|
|
3166
3293
|
}
|
|
3167
3294
|
}
|
|
3168
3295
|
async function buildCommand(options = {}) {
|
|
@@ -3173,11 +3300,11 @@ async function buildCommand(options = {}) {
|
|
|
3173
3300
|
let currentStep = 0;
|
|
3174
3301
|
currentStep++;
|
|
3175
3302
|
step(currentStep, TOTAL_STEPS, "Checking project structure\u2026");
|
|
3176
|
-
if (!
|
|
3303
|
+
if (!fs2.existsSync(path2.join(cwd, "package.json"))) {
|
|
3177
3304
|
fail("No package.json found. Run this command inside a Kozo project.");
|
|
3178
3305
|
process.exit(1);
|
|
3179
3306
|
}
|
|
3180
|
-
if (!
|
|
3307
|
+
if (!fs2.existsSync(path2.join(cwd, "node_modules"))) {
|
|
3181
3308
|
fail("Dependencies not installed. Run `npm install` first.");
|
|
3182
3309
|
process.exit(1);
|
|
3183
3310
|
}
|
|
@@ -3185,7 +3312,7 @@ async function buildCommand(options = {}) {
|
|
|
3185
3312
|
currentStep++;
|
|
3186
3313
|
step(currentStep, TOTAL_STEPS, "Cleaning previous build\u2026");
|
|
3187
3314
|
try {
|
|
3188
|
-
await
|
|
3315
|
+
await fs2.remove(path2.join(cwd, "dist"));
|
|
3189
3316
|
ok("dist/ cleaned");
|
|
3190
3317
|
} catch (err) {
|
|
3191
3318
|
fail("Failed to clean dist/", err);
|
|
@@ -3195,12 +3322,12 @@ async function buildCommand(options = {}) {
|
|
|
3195
3322
|
currentStep++;
|
|
3196
3323
|
step(currentStep, TOTAL_STEPS, "Generating routes manifest\u2026");
|
|
3197
3324
|
const routesDirRel = options.routesDir ?? "src/routes";
|
|
3198
|
-
const routesDirAbs =
|
|
3199
|
-
if (!
|
|
3200
|
-
console.log(
|
|
3325
|
+
const routesDirAbs = path2.join(cwd, routesDirRel);
|
|
3326
|
+
if (!fs2.existsSync(routesDirAbs)) {
|
|
3327
|
+
console.log(pc3.dim(` \u26A0 Routes directory not found (${routesDirRel}), skipping manifest.`));
|
|
3201
3328
|
} else {
|
|
3202
3329
|
try {
|
|
3203
|
-
const manifestOutAbs = options.manifestOut ?
|
|
3330
|
+
const manifestOutAbs = options.manifestOut ? path2.join(cwd, options.manifestOut) : path2.join(cwd, "routes-manifest.json");
|
|
3204
3331
|
const manifest = await generateManifest({
|
|
3205
3332
|
routesDir: routesDirAbs,
|
|
3206
3333
|
projectRoot: cwd,
|
|
@@ -3211,7 +3338,7 @@ async function buildCommand(options = {}) {
|
|
|
3211
3338
|
ok(`Manifest ready \u2014 ${manifest.routes.length} route(s)`);
|
|
3212
3339
|
} catch (err) {
|
|
3213
3340
|
fail("Manifest generation failed", err);
|
|
3214
|
-
console.log(
|
|
3341
|
+
console.log(pc3.dim(" Continuing build without manifest\u2026"));
|
|
3215
3342
|
}
|
|
3216
3343
|
}
|
|
3217
3344
|
}
|
|
@@ -3219,7 +3346,7 @@ async function buildCommand(options = {}) {
|
|
|
3219
3346
|
step(currentStep, TOTAL_STEPS, "Compiling with tsup\u2026");
|
|
3220
3347
|
try {
|
|
3221
3348
|
const tsupArgs = ["tsup", ...options.tsupArgs ?? []];
|
|
3222
|
-
await (
|
|
3349
|
+
await execa2("npx", tsupArgs, {
|
|
3223
3350
|
cwd,
|
|
3224
3351
|
stdio: "inherit",
|
|
3225
3352
|
env: { ...process.env, NODE_ENV: "production" }
|
|
@@ -3230,12 +3357,12 @@ async function buildCommand(options = {}) {
|
|
|
3230
3357
|
process.exit(1);
|
|
3231
3358
|
}
|
|
3232
3359
|
console.log();
|
|
3233
|
-
console.log(
|
|
3360
|
+
console.log(pc3.green("\u2705 Build successful"));
|
|
3234
3361
|
console.log();
|
|
3235
3362
|
}
|
|
3236
3363
|
|
|
3237
3364
|
// src/index.ts
|
|
3238
|
-
var program = new
|
|
3365
|
+
var program = new Command();
|
|
3239
3366
|
program.name("kozo").description("CLI to scaffold new Kozo Framework projects").version("0.2.6");
|
|
3240
3367
|
program.argument("[project-name]", "Name of the project").action(async (projectName) => {
|
|
3241
3368
|
await newCommand(projectName);
|