@kozojs/cli 0.1.10 → 0.1.12

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 +160 -46
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -35,15 +35,15 @@ 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") {
42
- await scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, frontend, extras, template);
42
+ await scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth, frontend, extras, template);
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;
@@ -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);
@@ -1257,9 +1297,11 @@ jobs:
1257
1297
  `;
1258
1298
  await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".github", "workflows", "ci.yml"), workflow);
1259
1299
  }
1260
- async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, frontend, extras, template) {
1300
+ async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth, frontend, extras, template) {
1301
+ const hasDb = database !== "none";
1261
1302
  await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "routes"));
1262
- await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "data"));
1303
+ if (!hasDb) await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "data"));
1304
+ if (hasDb) await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "api", "src", "db"));
1263
1305
  await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, "apps", "web", "src", "lib"));
1264
1306
  await import_fs_extra.default.ensureDir(import_node_path.default.join(projectDir, ".vscode"));
1265
1307
  const rootPackageJson = {
@@ -1275,32 +1317,81 @@ async function scaffoldFullstackProject(projectDir, projectName, kozoCoreDep, ru
1275
1317
  - 'apps/*'
1276
1318
  `);
1277
1319
  await import_fs_extra.default.writeFile(import_node_path.default.join(projectDir, ".gitignore"), "node_modules/\ndist/\n.env\n*.log\n");
1278
- await scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime);
1320
+ await scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime, database, dbPort, auth);
1279
1321
  await scaffoldFullstackWeb(projectDir, projectName, frontend);
1280
1322
  await scaffoldFullstackReadme(projectDir, projectName);
1281
1323
  if (extras.includes("docker")) await createDockerfile(import_node_path.default.join(projectDir, "apps", "api"), runtime);
1282
1324
  if (extras.includes("github-actions")) await createGitHubActions(projectDir);
1283
1325
  }
1284
- async function scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime) {
1326
+ async function scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtime, database = "none", dbPort, auth = true) {
1285
1327
  const apiDir = import_node_path.default.join(projectDir, "apps", "api");
1328
+ const hasDb = database !== "none";
1329
+ if (hasDb) {
1330
+ await import_fs_extra.default.ensureDir(import_node_path.default.join(apiDir, "src", "db"));
1331
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "db", "schema.ts"), getDatabaseSchema(database));
1332
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "db", "index.ts"), getDatabaseIndex(database));
1333
+ if (database === "sqlite") {
1334
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "db", "seed.ts"), getSQLiteSeed());
1335
+ }
1336
+ const dialect = database === "postgresql" ? "postgresql" : database === "mysql" ? "mysql" : "sqlite";
1337
+ const pgPort = dbPort ?? 5436;
1338
+ const dbUrl = database === "postgresql" ? `postgresql://postgres:postgres@localhost:${pgPort}/${projectName}` : database === "mysql" ? `mysql://root:root@localhost:3306/${projectName}` : void 0;
1339
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "drizzle.config.ts"), `import { defineConfig } from 'drizzle-kit';
1340
+ import 'dotenv/config';
1341
+
1342
+ export default defineConfig({
1343
+ schema: './src/db/schema.ts',
1344
+ out: './drizzle',
1345
+ dialect: '${dialect}',
1346
+ dbCredentials: {
1347
+ ${database === "sqlite" ? "url: './data.db'" : "url: process.env.DATABASE_URL!"}
1348
+ },
1349
+ });
1350
+ `);
1351
+ const envContent = `PORT=3000
1352
+ NODE_ENV=development
1353
+ ${dbUrl ? `DATABASE_URL=${dbUrl}
1354
+ ` : ""}${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
1355
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env"), envContent);
1356
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env.example"), envContent);
1357
+ } else {
1358
+ const envContent = `PORT=3000
1359
+ NODE_ENV=development
1360
+ ${auth ? "JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars\n" : ""}`;
1361
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env"), envContent);
1362
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, ".env.example"), envContent);
1363
+ }
1286
1364
  const apiPackageJson = {
1287
1365
  name: `@${projectName}/api`,
1288
1366
  version: "1.0.0",
1289
1367
  type: "module",
1290
1368
  scripts: {
1291
1369
  dev: runtime === "bun" ? "bun --watch src/index.ts" : "tsx watch src/index.ts",
1292
- build: "tsc"
1370
+ build: "tsc",
1371
+ ...hasDb && {
1372
+ "db:generate": "drizzle-kit generate",
1373
+ "db:push": "drizzle-kit push",
1374
+ "db:studio": "drizzle-kit studio"
1375
+ }
1293
1376
  },
1294
1377
  dependencies: {
1295
1378
  "@kozojs/core": kozoCoreDep,
1379
+ ...auth && { "@kozojs/auth": kozoCoreDep },
1296
1380
  hono: "^4.6.0",
1297
1381
  zod: "^3.23.0",
1298
- ...runtime === "node" && { "@hono/node-server": "^1.13.0" }
1382
+ dotenv: "^16.4.0",
1383
+ ...runtime === "node" && { "@hono/node-server": "^1.13.0" },
1384
+ ...hasDb && { "drizzle-orm": "^0.36.0" },
1385
+ ...database === "postgresql" && { postgres: "^3.4.0" },
1386
+ ...database === "mysql" && { mysql2: "^3.11.0" },
1387
+ ...database === "sqlite" && { "better-sqlite3": "^11.0.0" }
1299
1388
  },
1300
1389
  devDependencies: {
1301
1390
  "@types/node": "^22.0.0",
1302
1391
  ...runtime !== "bun" && { tsx: "^4.19.0" },
1303
- typescript: "^5.6.0"
1392
+ typescript: "^5.6.0",
1393
+ ...hasDb && { "drizzle-kit": "^0.28.0" },
1394
+ ...database === "sqlite" && { "@types/better-sqlite3": "^7.6.0" }
1304
1395
  }
1305
1396
  };
1306
1397
  await import_fs_extra.default.writeJSON(import_node_path.default.join(apiDir, "package.json"), apiPackageJson, { spaces: 2 });
@@ -1319,11 +1410,23 @@ async function scaffoldFullstackApi(projectDir, projectName, kozoCoreDep, runtim
1319
1410
  exclude: ["node_modules", "dist"]
1320
1411
  };
1321
1412
  await import_fs_extra.default.writeJSON(import_node_path.default.join(apiDir, "tsconfig.json"), tsconfig, { spaces: 2 });
1322
- await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "index.ts"), `import { createKozo } from '@kozojs/core';
1323
- import { registerRoutes } from './routes';
1324
-
1325
- const app = createKozo({ port: 3000 });
1326
-
1413
+ const dbImport = hasDb ? `import { createDb } from './db/index.js';
1414
+ ` : "";
1415
+ const authImport = auth ? `import { authenticateJWT } from '@kozojs/auth';
1416
+ ` : "";
1417
+ const servicesSetup = hasDb ? `
1418
+ const db = await createDb();
1419
+ const services = { db };
1420
+ ` : "\nconst services = {};\n";
1421
+ const authMiddleware = auth ? `
1422
+ app.use('*', authenticateJWT({ secret: process.env.JWT_SECRET || 'change-me' }));
1423
+ ` : "";
1424
+ await import_fs_extra.default.writeFile(import_node_path.default.join(apiDir, "src", "index.ts"), `import 'dotenv/config';
1425
+ import { createKozo } from '@kozojs/core';
1426
+ ${dbImport}${authImport}import { registerRoutes } from './routes/index.js';
1427
+ ${servicesSetup}
1428
+ const app = createKozo({ port: 3000, services });
1429
+ ${authMiddleware}
1327
1430
  registerRoutes(app);
1328
1431
 
1329
1432
  export type AppType = typeof app;
@@ -2275,6 +2378,7 @@ function printLogo() {
2275
2378
  async function newCommand(projectName) {
2276
2379
  printLogo();
2277
2380
  p.intro(import_picocolors2.default.bold(import_picocolors2.default.red("\u{1F525} Create a new Kozo project")));
2381
+ const isLocalWorkspace = process.env.KOZO_LOCAL === "true";
2278
2382
  const project = await p.group(
2279
2383
  {
2280
2384
  name: () => {
@@ -2312,18 +2416,35 @@ async function newCommand(projectName) {
2312
2416
  ]
2313
2417
  }),
2314
2418
  database: ({ results }) => {
2315
- if (results.template === "complete" || results.template === "api-only") {
2419
+ if (results.template === "api-only") {
2316
2420
  return Promise.resolve("none");
2317
2421
  }
2318
2422
  return p.select({
2319
2423
  message: "Database",
2320
2424
  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" }
2425
+ { value: "postgresql", label: "PostgreSQL + Drizzle", hint: "Standard \u2014 recommended for production" },
2426
+ { value: "mysql", label: "MySQL + Drizzle", hint: "PlanetScale compatible" },
2427
+ { value: "sqlite", label: "SQLite + Drizzle", hint: "Zero setup, great for local dev" },
2428
+ { value: "none", label: "None", hint: "In-memory store (demo only)" }
2324
2429
  ]
2325
2430
  });
2326
2431
  },
2432
+ dbPort: ({ results }) => {
2433
+ if (results.database !== "postgresql") return Promise.resolve(void 0);
2434
+ return p.text({
2435
+ message: "PostgreSQL port",
2436
+ placeholder: "5436",
2437
+ initialValue: "5436",
2438
+ validate: (v) => v && isNaN(Number(v)) ? "Must be a valid port number" : void 0
2439
+ });
2440
+ },
2441
+ auth: ({ results }) => {
2442
+ if (results.template === "api-only") return Promise.resolve(false);
2443
+ return p.confirm({
2444
+ message: "Include JWT authentication?",
2445
+ initialValue: true
2446
+ });
2447
+ },
2327
2448
  frontend: () => p.select({
2328
2449
  message: "Frontend",
2329
2450
  options: [
@@ -2338,19 +2459,10 @@ async function newCommand(projectName) {
2338
2459
  message: "Extras",
2339
2460
  options: [
2340
2461
  { 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" }
2462
+ { value: "github-actions", label: "GitHub Actions", hint: "CI/CD pipeline" }
2343
2463
  ],
2344
2464
  required: false
2345
2465
  }),
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
2466
  install: () => Promise.resolve(true)
2355
2467
  },
2356
2468
  {
@@ -2367,10 +2479,12 @@ async function newCommand(projectName) {
2367
2479
  projectName: project.name,
2368
2480
  runtime: project.runtime,
2369
2481
  template: project.template,
2370
- database: project.database,
2482
+ database: project.database ?? "none",
2483
+ dbPort: project.dbPort ? Number(project.dbPort) : void 0,
2484
+ auth: project.auth,
2371
2485
  frontend: project.frontend,
2372
2486
  extras: project.extras,
2373
- packageSource: project.packageSource
2487
+ packageSource: isLocalWorkspace ? "local" : "npm"
2374
2488
  });
2375
2489
  s.stop("Project structure created!");
2376
2490
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kozojs/cli",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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",