@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.
Files changed (2) hide show
  1. package/lib/index.js +86 -35
  2. 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.dev/docs)
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
- # JWT Authentication
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 === "complete" || results.template === "api-only") {
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: "sqlite", label: "SQLite / Turso", hint: "Perfect for Edge and local dev" },
2322
- { value: "postgresql", label: "PostgreSQL + Drizzle", hint: "Standard Enterprise" },
2323
- { value: "mysql", label: "MySQL + Drizzle", hint: "PlanetScale compatible" }
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: project.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://kozojs.dev/docs")}
2460
+ ${import_picocolors2.default.dim("Documentation:")} ${import_picocolors2.default.underline("https://kozo-docs.vercel.app")}
2410
2461
  `);
2411
2462
  }
2412
2463
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kozojs/cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "CLI to scaffold new Kozo Framework projects - The next-gen TypeScript Backend Framework",
5
5
  "bin": {
6
6
  "create-kozo": "./lib/index.js",