kavoru 0.9.7 → 0.9.9

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/README.md CHANGED
@@ -38,7 +38,7 @@ bunx kavoru@latest my-api
38
38
  | `--no-install` | Skip `bun install` |
39
39
  | `--repo owner/name` | Override template repo (default: `mertthesamael/Kavoru`) |
40
40
  | `--branch name` | Template branch (default: `master`) |
41
- | `--minimal` | Core only health, OpenAPI, response envelope |
41
+ | `--minimal` | Optional integrations off (Docker Compose + Project CLI always included) |
42
42
  | `--features list` | Comma-separated features to include |
43
43
  | `--no-features list`| Comma-separated features to exclude (default: all on) |
44
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kavoru",
3
- "version": "0.9.7",
3
+ "version": "0.9.9",
4
4
  "description": "Scaffold a new Kavoru (Elysia + Bun) backend from the official template",
5
5
  "type": "module",
6
6
  "bin": {
package/src/args.ts CHANGED
@@ -32,7 +32,7 @@ Options:
32
32
  --no-install Skip "bun install" after scaffolding
33
33
  --repo <owner/name> GitHub template repo (default: mertthesamael/Kavoru)
34
34
  --branch <name> Template branch (default: master)
35
- --minimal Core only (health, OpenAPI, response envelope)
35
+ --minimal Core optional integrations only (Docker + Project CLI always included)
36
36
  --features <list> Comma-separated features to include (default: all)
37
37
  --no-features <list> Comma-separated features to exclude
38
38
 
package/src/features.ts CHANGED
@@ -17,6 +17,19 @@ export type FeatureId =
17
17
  /** Always scaffolded — not a CLI toggle. */
18
18
  export const ALWAYS_INCLUDED = ["docker", "cli"] as const;
19
19
 
20
+ export const MANDATORY_FEATURE_DEFS = [
21
+ {
22
+ id: "docker",
23
+ label: "Docker Compose",
24
+ description: "docker-compose.yaml + app image",
25
+ },
26
+ {
27
+ id: "cli",
28
+ label: "Project CLI",
29
+ description: "kavoru module command, bin, shims",
30
+ },
31
+ ] as const;
32
+
20
33
  const FEATURE_ALIASES: Record<string, FeatureId> = {
21
34
  prisma: "postgres",
22
35
  };
@@ -288,9 +301,12 @@ export function parseFeatureExcludeList(
288
301
  }
289
302
 
290
303
  export function formatFeatureSelection(selection: FeatureSelection): string {
304
+ const mandatory = ALWAYS_INCLUDED.join(", ");
291
305
  const enabled = enabledFeatures(selection);
292
- if (enabled.length === 0) return "core only";
293
- return enabled.join(", ");
306
+ if (enabled.length === 0) {
307
+ return `${mandatory} · optional: none`;
308
+ }
309
+ return `${mandatory} · optional: ${enabled.join(", ")}`;
294
310
  }
295
311
 
296
312
  async function removePaths(projectDir: string, relativePaths: string[]) {
@@ -479,41 +495,73 @@ async function patchEntryIndex(
479
495
  await writeText(projectDir, "src/index.ts", buildEntryIndex(selection));
480
496
  }
481
497
 
482
- async function patchServerIndex(
483
- projectDir: string,
484
- selection: FeatureSelection,
485
- ) {
486
- const relativePath = "src/server/index.ts";
487
- const current = await readText(projectDir, relativePath);
488
- if (!current) return;
498
+ export function buildServerIndex(selection: FeatureSelection): string {
499
+ const imports = [
500
+ 'import { Elysia } from "elysia";',
501
+ 'import { config } from "../config/index";',
502
+ 'import { logger } from "../common/logger";',
503
+ 'import { registerModules } from "../modules";',
504
+ selection.cron ? 'import { schedules } from "../schedules";' : null,
505
+ ]
506
+ .filter(Boolean)
507
+ .join("\n");
489
508
 
490
- let content = current;
509
+ const elysiaCtor = selection.websocket
510
+ ? `new Elysia({
511
+ websocket: {
512
+ idleTimeout: 120,
513
+ },
514
+ })`
515
+ : "new Elysia()";
516
+
517
+ const uses = [
518
+ " .use(registerModules)",
519
+ selection.cron ? " .use(schedules)" : null,
520
+ ]
521
+ .filter(Boolean)
522
+ .join("\n");
491
523
 
492
- if (selection.cron) {
493
- content = content.replace(
494
- /\/\/import \{ schedules \} from "\.\.\/schedules";.*\n/,
495
- 'import { schedules } from "../schedules";\n',
496
- );
497
- content = content.replace(
498
- /\/\/\.use\(schedules\);.*\n/,
499
- " .use(schedules);\n",
500
- );
501
- } else {
502
- content = content.replace(
503
- /^import \{ schedules \} from "\.\.\/schedules";\n/m,
504
- "",
505
- );
506
- content = content.replace(/^\s*\.use\(schedules\);\n/m, "");
524
+ return `${imports}
525
+
526
+ export class HttpServer {
527
+ private app: any;
528
+ private server?: ReturnType<Elysia["listen"]>;
529
+
530
+ constructor() {
531
+ this.app = ${elysiaCtor}
532
+ ${uses};
507
533
  }
508
534
 
509
- if (!selection.websocket) {
510
- content = content.replace(
511
- /new Elysia\(\{\s*websocket:\s*\{\s*idleTimeout:\s*120,\s*\},\s*\}\)/,
512
- "new Elysia()",
513
- );
535
+ async start() {
536
+ if (this.server) return;
537
+
538
+ await this.app.modules;
539
+
540
+ this.server = this.app.listen(config.env.server.port, () => {
541
+ logger.info(
542
+ \`API ready on port \${config.env.server.port}. Version: \${config.version}\`,
543
+ );
544
+ });
514
545
  }
515
546
 
516
- await writeText(projectDir, relativePath, content);
547
+ async stop() {
548
+ if (!this.server) return;
549
+ this.server.stop();
550
+ this.server = undefined;
551
+ }
552
+ }
553
+ `;
554
+ }
555
+
556
+ async function patchServerIndex(
557
+ projectDir: string,
558
+ selection: FeatureSelection,
559
+ ) {
560
+ await writeText(
561
+ projectDir,
562
+ "src/server/index.ts",
563
+ buildServerIndex(selection),
564
+ );
517
565
  }
518
566
 
519
567
  async function patchPackageJson(
package/src/prompts.ts CHANGED
@@ -2,6 +2,7 @@ import { stdin, stdout } from "node:process";
2
2
  import {
3
3
  ALL_FEATURES,
4
4
  FEATURES,
5
+ MANDATORY_FEATURE_DEFS,
5
6
  MINIMAL_FEATURES,
6
7
  normalizeFeatureSelection,
7
8
  type FeatureId,
@@ -84,10 +85,19 @@ function renderCheckboxMenu(
84
85
  ): number {
85
86
  const lines: string[] = [
86
87
  `${cyan}◆${reset} Select optional features ${dim}(↑↓ move · Space toggle · Enter confirm)${reset}`,
87
- `${dim} a = all · m = minimal${reset}`,
88
+ `${dim} a = all · m = minimal optional only${reset}`,
88
89
  "",
90
+ `${dim}Always included:${reset}`,
89
91
  ];
90
92
 
93
+ for (const feature of MANDATORY_FEATURE_DEFS) {
94
+ lines.push(
95
+ ` [x] ${feature.label.padEnd(22)} ${dim}${feature.description}${reset}`,
96
+ );
97
+ }
98
+
99
+ lines.push("", `${dim}Optional:${reset}`);
100
+
91
101
  FEATURES.forEach((feature, index) => {
92
102
  const isActive = index === activeIndex;
93
103
  const pointer = isActive ? `${cyan}❯${reset}` : " ";