@kozojs/cli 0.1.9 → 0.1.11
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 +86 -35
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -35,7 +35,7 @@ var import_execa = require("execa");
|
|
|
35
35
|
var import_fs_extra = __toESM(require("fs-extra"));
|
|
36
36
|
var import_node_path = __toESM(require("path"));
|
|
37
37
|
async function scaffoldProject(options) {
|
|
38
|
-
const { projectName, runtime, database, packageSource, template, frontend, extras } = options;
|
|
38
|
+
const { projectName, runtime, database, dbPort, auth, packageSource, template, frontend, extras } = options;
|
|
39
39
|
const projectDir = import_node_path.default.resolve(process.cwd(), projectName);
|
|
40
40
|
const kozoCoreDep = packageSource === "local" ? "workspace:*" : "^0.2.0";
|
|
41
41
|
if (frontend !== "none") {
|
|
@@ -43,7 +43,7 @@ async function scaffoldProject(options) {
|
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
if (template === "complete") {
|
|
46
|
-
await scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, runtime);
|
|
46
|
+
await scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth);
|
|
47
47
|
if (extras.includes("docker")) await createDockerfile(projectDir, runtime);
|
|
48
48
|
if (extras.includes("github-actions")) await createGitHubActions(projectDir);
|
|
49
49
|
return;
|
|
@@ -245,7 +245,7 @@ pnpm db:studio # Open Drizzle Studio
|
|
|
245
245
|
${database === "sqlite" ? "## SQLite Notes\n\nThe database is automatically initialized with example data on first run.\nDatabase file: `./data.db`\n" : ""}
|
|
246
246
|
## Documentation
|
|
247
247
|
|
|
248
|
-
- [Kozo Docs](https://kozo.
|
|
248
|
+
- [Kozo Docs](https://kozo-docs.vercel.app)
|
|
249
249
|
- [Drizzle ORM](https://orm.drizzle.team)
|
|
250
250
|
- [Hono](https://hono.dev)
|
|
251
251
|
`;
|
|
@@ -411,7 +411,7 @@ export default async ({ body, services: { db } }: HandlerContext<Body>) => {
|
|
|
411
411
|
`;
|
|
412
412
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "routes", "users", "post.ts"), postUsersRoute);
|
|
413
413
|
}
|
|
414
|
-
async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, runtime) {
|
|
414
|
+
async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, runtime, database = "none", dbPort, auth = true) {
|
|
415
415
|
await import_fs_extra.default.ensureDir(projectDir);
|
|
416
416
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src"));
|
|
417
417
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "schemas"));
|
|
@@ -422,6 +422,15 @@ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, ru
|
|
|
422
422
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "middleware"));
|
|
423
423
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "utils"));
|
|
424
424
|
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "data"));
|
|
425
|
+
const hasDb = database !== "none";
|
|
426
|
+
if (hasDb) {
|
|
427
|
+
await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "src", "db"));
|
|
428
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "db", "schema.ts"), getDatabaseSchema(database));
|
|
429
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "db", "index.ts"), getDatabaseIndex(database));
|
|
430
|
+
if (database === "sqlite") {
|
|
431
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "src", "db", "seed.ts"), getSQLiteSeed());
|
|
432
|
+
}
|
|
433
|
+
}
|
|
425
434
|
const packageJson = {
|
|
426
435
|
name: projectName,
|
|
427
436
|
version: "1.0.0",
|
|
@@ -430,19 +439,30 @@ async function scaffoldCompleteTemplate(projectDir, projectName, kozoCoreDep, ru
|
|
|
430
439
|
dev: "tsx watch src/index.ts",
|
|
431
440
|
build: "tsc",
|
|
432
441
|
start: "node dist/index.js",
|
|
433
|
-
"type-check": "tsc --noEmit"
|
|
442
|
+
"type-check": "tsc --noEmit",
|
|
443
|
+
...hasDb && {
|
|
444
|
+
"db:generate": "drizzle-kit generate",
|
|
445
|
+
"db:push": "drizzle-kit push",
|
|
446
|
+
"db:studio": "drizzle-kit studio"
|
|
447
|
+
}
|
|
434
448
|
},
|
|
435
449
|
dependencies: {
|
|
436
450
|
"@kozojs/core": kozoCoreDep,
|
|
437
|
-
"@kozojs/auth": kozoCoreDep,
|
|
451
|
+
...auth && { "@kozojs/auth": kozoCoreDep },
|
|
438
452
|
"@hono/node-server": "^1.13.0",
|
|
439
453
|
hono: "^4.6.0",
|
|
440
|
-
zod: "^3.23.0"
|
|
454
|
+
zod: "^3.23.0",
|
|
455
|
+
...hasDb && { "drizzle-orm": "^0.36.0" },
|
|
456
|
+
...database === "postgresql" && { postgres: "^3.4.0" },
|
|
457
|
+
...database === "mysql" && { mysql2: "^3.11.0" },
|
|
458
|
+
...database === "sqlite" && { "better-sqlite3": "^11.0.0" }
|
|
441
459
|
},
|
|
442
460
|
devDependencies: {
|
|
443
461
|
"@types/node": "^22.0.0",
|
|
444
462
|
tsx: "^4.19.0",
|
|
445
|
-
typescript: "^5.6.0"
|
|
463
|
+
typescript: "^5.6.0",
|
|
464
|
+
...hasDb && { "drizzle-kit": "^0.28.0" },
|
|
465
|
+
...database === "sqlite" && { "@types/better-sqlite3": "^7.6.0" }
|
|
446
466
|
}
|
|
447
467
|
};
|
|
448
468
|
await import_fs_extra.default.writeJSON(import_node_path.default.join(projectDir, "package.json"), packageJson, { spaces: 2 });
|
|
@@ -471,13 +491,18 @@ dist/
|
|
|
471
491
|
*.log
|
|
472
492
|
`;
|
|
473
493
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".gitignore"), gitignore);
|
|
494
|
+
const pgPort = dbPort ?? 5436;
|
|
495
|
+
const dbUrl = database === "postgresql" ? `postgresql://postgres:postgres@localhost:${pgPort}/${projectName}` : database === "mysql" ? `mysql://root:root@localhost:3306/${projectName}` : void 0;
|
|
474
496
|
const envExample = `# Server
|
|
475
497
|
PORT=3000
|
|
476
498
|
NODE_ENV=development
|
|
477
|
-
|
|
478
|
-
#
|
|
499
|
+
${database !== "none" && dbUrl ? `
|
|
500
|
+
# Database
|
|
501
|
+
DATABASE_URL=${dbUrl}
|
|
502
|
+
` : ""}
|
|
503
|
+
${auth ? `# JWT Authentication
|
|
479
504
|
JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars
|
|
480
|
-
|
|
505
|
+
` : ""}
|
|
481
506
|
# CORS
|
|
482
507
|
CORS_ORIGIN=http://localhost:5173
|
|
483
508
|
|
|
@@ -487,17 +512,34 @@ RATE_LIMIT_WINDOW=60000
|
|
|
487
512
|
`;
|
|
488
513
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".env.example"), envExample);
|
|
489
514
|
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".env"), envExample);
|
|
515
|
+
if (hasDb) {
|
|
516
|
+
const dialect = database === "postgresql" ? "postgresql" : database === "mysql" ? "mysql" : "sqlite";
|
|
517
|
+
const drizzleConfig = `import { defineConfig } from 'drizzle-kit';
|
|
518
|
+
import 'dotenv/config';
|
|
519
|
+
|
|
520
|
+
export default defineConfig({
|
|
521
|
+
schema: './src/db/schema.ts',
|
|
522
|
+
out: './drizzle',
|
|
523
|
+
dialect: '${dialect}',
|
|
524
|
+
dbCredentials: {
|
|
525
|
+
${database === "sqlite" ? "url: './data.db'" : "url: process.env.DATABASE_URL!"}
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
`;
|
|
529
|
+
await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, "drizzle.config.ts"), drizzleConfig);
|
|
530
|
+
}
|
|
490
531
|
const indexTs = `import { createKozo, cors, logger, rateLimit } from '@kozojs/core';
|
|
491
|
-
import { authenticateJWT } from '@kozojs/auth';
|
|
492
|
-
import { registerAuthRoutes } from './routes/auth/index.js';
|
|
532
|
+
${auth ? "import { authenticateJWT } from '@kozojs/auth';" : ""}
|
|
533
|
+
${auth ? "import { registerAuthRoutes } from './routes/auth/index.js';" : ""}
|
|
493
534
|
import { registerUserRoutes } from './routes/users/index.js';
|
|
494
535
|
import { registerPostRoutes } from './routes/posts/index.js';
|
|
495
536
|
import { registerHealthRoute } from './routes/health.js';
|
|
496
537
|
import { registerStatsRoute } from './routes/stats.js';
|
|
538
|
+
${hasDb ? "import { db } from './db/index.js';" : ""}
|
|
497
539
|
|
|
498
540
|
// \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
499
541
|
const PORT = Number(process.env.PORT) || 3000;
|
|
500
|
-
const JWT_SECRET = process.env.JWT_SECRET || 'change-me-to-a-random-secret';
|
|
542
|
+
${auth ? "const JWT_SECRET = process.env.JWT_SECRET || 'change-me-to-a-random-secret';" : ""}
|
|
501
543
|
const CORS_ORIGIN = process.env.CORS_ORIGIN || '*';
|
|
502
544
|
const RATE_LIMIT_MAX = Number(process.env.RATE_LIMIT_MAX) || 100;
|
|
503
545
|
const RATE_LIMIT_WINDOW = Number(process.env.RATE_LIMIT_WINDOW) || 60_000;
|
|
@@ -509,13 +551,11 @@ const app = createKozo({ port: PORT });
|
|
|
509
551
|
app.getApp().use('*', logger());
|
|
510
552
|
app.getApp().use('*', cors({ origin: CORS_ORIGIN }));
|
|
511
553
|
app.getApp().use('/api/*', rateLimit({ max: RATE_LIMIT_MAX, windowMs: RATE_LIMIT_WINDOW }));
|
|
512
|
-
app.getApp().use('/api/*', authenticateJWT(JWT_SECRET, {
|
|
513
|
-
prefix: '/api',
|
|
514
|
-
}));
|
|
554
|
+
${auth ? `app.getApp().use('/api/*', authenticateJWT(JWT_SECRET, { prefix: '/api' }));` : ""}
|
|
515
555
|
|
|
516
556
|
// \u2500\u2500\u2500 Routes (native compiled \u2014 zero overhead) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
517
557
|
registerHealthRoute(app);
|
|
518
|
-
registerAuthRoutes(app);
|
|
558
|
+
${auth ? "registerAuthRoutes(app);" : ""}
|
|
519
559
|
registerUserRoutes(app);
|
|
520
560
|
registerPostRoutes(app);
|
|
521
561
|
registerStatsRoute(app);
|
|
@@ -2275,6 +2315,7 @@ function printLogo() {
|
|
|
2275
2315
|
async function newCommand(projectName) {
|
|
2276
2316
|
printLogo();
|
|
2277
2317
|
p.intro(import_picocolors2.default.bold(import_picocolors2.default.red("\u{1F525} Create a new Kozo project")));
|
|
2318
|
+
const isLocalWorkspace = process.env.KOZO_LOCAL === "true";
|
|
2278
2319
|
const project = await p.group(
|
|
2279
2320
|
{
|
|
2280
2321
|
name: () => {
|
|
@@ -2312,18 +2353,35 @@ async function newCommand(projectName) {
|
|
|
2312
2353
|
]
|
|
2313
2354
|
}),
|
|
2314
2355
|
database: ({ results }) => {
|
|
2315
|
-
if (results.template === "
|
|
2356
|
+
if (results.template === "api-only") {
|
|
2316
2357
|
return Promise.resolve("none");
|
|
2317
2358
|
}
|
|
2318
2359
|
return p.select({
|
|
2319
2360
|
message: "Database",
|
|
2320
2361
|
options: [
|
|
2321
|
-
{ value: "
|
|
2322
|
-
{ value: "
|
|
2323
|
-
{ value: "
|
|
2362
|
+
{ value: "postgresql", label: "PostgreSQL + Drizzle", hint: "Standard \u2014 recommended for production" },
|
|
2363
|
+
{ value: "mysql", label: "MySQL + Drizzle", hint: "PlanetScale compatible" },
|
|
2364
|
+
{ value: "sqlite", label: "SQLite + Drizzle", hint: "Zero setup, great for local dev" },
|
|
2365
|
+
{ value: "none", label: "None", hint: "In-memory store (demo only)" }
|
|
2324
2366
|
]
|
|
2325
2367
|
});
|
|
2326
2368
|
},
|
|
2369
|
+
dbPort: ({ results }) => {
|
|
2370
|
+
if (results.database !== "postgresql") return Promise.resolve(void 0);
|
|
2371
|
+
return p.text({
|
|
2372
|
+
message: "PostgreSQL port",
|
|
2373
|
+
placeholder: "5436",
|
|
2374
|
+
initialValue: "5436",
|
|
2375
|
+
validate: (v) => v && isNaN(Number(v)) ? "Must be a valid port number" : void 0
|
|
2376
|
+
});
|
|
2377
|
+
},
|
|
2378
|
+
auth: ({ results }) => {
|
|
2379
|
+
if (results.template === "api-only") return Promise.resolve(false);
|
|
2380
|
+
return p.confirm({
|
|
2381
|
+
message: "Include JWT authentication?",
|
|
2382
|
+
initialValue: true
|
|
2383
|
+
});
|
|
2384
|
+
},
|
|
2327
2385
|
frontend: () => p.select({
|
|
2328
2386
|
message: "Frontend",
|
|
2329
2387
|
options: [
|
|
@@ -2338,19 +2396,10 @@ async function newCommand(projectName) {
|
|
|
2338
2396
|
message: "Extras",
|
|
2339
2397
|
options: [
|
|
2340
2398
|
{ value: "docker", label: "Docker", hint: "Multi-stage Dockerfile" },
|
|
2341
|
-
{ value: "github-actions", label: "GitHub Actions", hint: "CI/CD pipeline" }
|
|
2342
|
-
{ value: "auth", label: "Authentication", hint: "JWT middleware + login routes" }
|
|
2399
|
+
{ value: "github-actions", label: "GitHub Actions", hint: "CI/CD pipeline" }
|
|
2343
2400
|
],
|
|
2344
2401
|
required: false
|
|
2345
2402
|
}),
|
|
2346
|
-
packageSource: () => p.select({
|
|
2347
|
-
message: "Package source",
|
|
2348
|
-
options: [
|
|
2349
|
-
{ value: "npm", label: "npm registry", hint: "Published version (recommended)" },
|
|
2350
|
-
{ value: "local", label: "Local workspace", hint: "Link to monorepo (dev only)" }
|
|
2351
|
-
],
|
|
2352
|
-
initialValue: "npm"
|
|
2353
|
-
}),
|
|
2354
2403
|
install: () => Promise.resolve(true)
|
|
2355
2404
|
},
|
|
2356
2405
|
{
|
|
@@ -2367,10 +2416,12 @@ async function newCommand(projectName) {
|
|
|
2367
2416
|
projectName: project.name,
|
|
2368
2417
|
runtime: project.runtime,
|
|
2369
2418
|
template: project.template,
|
|
2370
|
-
database: project.database,
|
|
2419
|
+
database: project.database ?? "none",
|
|
2420
|
+
dbPort: project.dbPort ? Number(project.dbPort) : void 0,
|
|
2421
|
+
auth: project.auth,
|
|
2371
2422
|
frontend: project.frontend,
|
|
2372
2423
|
extras: project.extras,
|
|
2373
|
-
packageSource:
|
|
2424
|
+
packageSource: isLocalWorkspace ? "local" : "npm"
|
|
2374
2425
|
});
|
|
2375
2426
|
s.stop("Project structure created!");
|
|
2376
2427
|
} catch (err) {
|
|
@@ -2406,7 +2457,7 @@ ${import_picocolors2.default.bold("Next steps:")}
|
|
|
2406
2457
|
${import_picocolors2.default.cyan(`cd ${project.name}`)}
|
|
2407
2458
|
${!project.install ? import_picocolors2.default.cyan("pnpm install") + "\n " : ""}${import_picocolors2.default.cyan("pnpm dev")}
|
|
2408
2459
|
|
|
2409
|
-
${import_picocolors2.default.dim("Documentation:")} ${import_picocolors2.default.underline("https://
|
|
2460
|
+
${import_picocolors2.default.dim("Documentation:")} ${import_picocolors2.default.underline("https://kozo-docs.vercel.app")}
|
|
2410
2461
|
`);
|
|
2411
2462
|
}
|
|
2412
2463
|
|