kavoru 0.8.9 → 0.8.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/README.md CHANGED
@@ -55,6 +55,7 @@ During setup you can pick which integrations to scaffold. Core is always include
55
55
  | `resend` | Resend email |
56
56
  | `cron` | Cron jobs |
57
57
  | `docker` | Dockerfile + Compose |
58
+ | `cli` | Project CLI (`kavoru module`, bin, root shims) |
58
59
 
59
60
  Interactive mode (TTY) shows a checkbox menu (↑↓ move, Space toggle, Enter confirm). Non-interactive runs use the full stack unless you pass flags.
60
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kavoru",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
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
@@ -15,9 +15,13 @@ export type CliOptions = {
15
15
 
16
16
  const HELP = `\
17
17
  Usage: kavoru [options] [directory]
18
+ kavoru module <module-name> [options]
18
19
 
19
20
  Create a new project from the Kavoru Elysia + Bun template.
20
21
 
22
+ Commands:
23
+ module <name> Generate src/modules/<name> (routes, service, types)
24
+
21
25
  Arguments:
22
26
  directory Project folder (use "." for current directory)
23
27
 
@@ -33,14 +37,15 @@ Options:
33
37
  --no-features <list> Comma-separated features to exclude
34
38
 
35
39
  Features:
36
- auth, postgres, otel, sentry, kafka, websocket, resend, cron, docker
37
- (prisma is accepted as an alias for postgres)
40
+ auth, postgres, otel, sentry, kafka, websocket, resend, cron, docker, cli
41
+ (prisma is accepted as an alias for postgres; kavoru-cli for cli)
38
42
 
39
43
  Examples:
40
44
  bunx kavoru@latest my-api
41
45
  bunx kavoru@latest my-api --minimal
42
46
  bunx kavoru@latest my-api --features auth,postgres,otel
43
47
  bunx kavoru@latest my-api --no-features kafka,docker,resend
48
+ bunx kavoru@latest module users
44
49
  `;
45
50
 
46
51
  export function parseArgs(argv: string[]): CliOptions {
package/src/cli.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  customizeProject,
22
22
  fetchTemplate,
23
23
  installDependencies,
24
+ linkProjectCli,
24
25
  removeGitMetadata,
25
26
  resolveTemplateSource,
26
27
  } from "./template";
@@ -136,6 +137,9 @@ export async function runCli(options: CliOptions): Promise<void> {
136
137
 
137
138
  if (options.install) {
138
139
  await installDependencies(targetDir);
140
+ if (featureSelection.cli) {
141
+ await linkProjectCli(targetDir);
142
+ }
139
143
  }
140
144
  } finally {
141
145
  await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
@@ -150,6 +154,12 @@ export async function runCli(options: CliOptions): Promise<void> {
150
154
  if (!options.install) {
151
155
  console.log(" bun install");
152
156
  }
157
+ if (featureSelection.cli) {
158
+ if (!options.install) {
159
+ console.log(" bun run link-cli # once: put kavoru on PATH");
160
+ }
161
+ console.log(" kavoru module <name>");
162
+ }
153
163
  console.log(" bun run dev");
154
164
  console.log();
155
165
  console.log(" API: http://localhost:3131");
package/src/features.ts CHANGED
@@ -11,10 +11,12 @@ export type FeatureId =
11
11
  | "websocket"
12
12
  | "resend"
13
13
  | "cron"
14
- | "docker";
14
+ | "docker"
15
+ | "cli";
15
16
 
16
17
  const FEATURE_ALIASES: Record<string, FeatureId> = {
17
18
  prisma: "postgres",
19
+ "kavoru-cli": "cli",
18
20
  };
19
21
 
20
22
  export type FeatureSelection = Record<FeatureId, boolean>;
@@ -71,6 +73,11 @@ export const FEATURES: FeatureDef[] = [
71
73
  label: "Docker",
72
74
  description: "Dockerfile and Docker Compose stack",
73
75
  },
76
+ {
77
+ id: "cli",
78
+ label: "Project CLI",
79
+ description: "kavoru module command, bin, and module scaffolds",
80
+ },
74
81
  ];
75
82
 
76
83
  export const FEATURE_IDS = FEATURES.map((feature) => feature.id);
@@ -113,6 +120,17 @@ const FEATURE_PATHS: Record<FeatureId, string[]> = {
113
120
  resend: ["src/infra/resend"],
114
121
  cron: ["src/schedules"],
115
122
  docker: ["docker-compose.yaml", "docker"],
123
+ cli: [
124
+ "bin/kavoru.js",
125
+ "kavoru",
126
+ "kavoru.cmd",
127
+ "scripts/kavoru-cli.ts",
128
+ "scripts/generate-module.ts",
129
+ "scripts/link-cli.ts",
130
+ "__tests__/generate-module.test.ts",
131
+ "__tests__/kavoru-cli.test.ts",
132
+ "__tests__/link-cli.test.ts",
133
+ ],
116
134
  };
117
135
 
118
136
  const FEATURE_DEPENDENCIES: Partial<
@@ -141,6 +159,7 @@ const FEATURE_SCRIPTS: Partial<Record<FeatureId, string[]>> = {
141
159
  otel: ["otel:view", "otel:tui"],
142
160
  sentry: ["sentry:spotlight"],
143
161
  postgres: ["seed"],
162
+ cli: ["link-cli"],
144
163
  };
145
164
 
146
165
  function resolveFeatureId(raw: string): FeatureId | null {
@@ -446,6 +465,7 @@ async function patchPackageJson(
446
465
  if (!(await pkgFile.exists())) return;
447
466
 
448
467
  const pkg = (await pkgFile.json()) as {
468
+ bin?: Record<string, string>;
449
469
  dependencies?: Record<string, string>;
450
470
  devDependencies?: Record<string, string>;
451
471
  scripts?: Record<string, string>;
@@ -476,6 +496,12 @@ async function patchPackageJson(
476
496
  pkg.scripts.start = "bun run src/index.ts";
477
497
  }
478
498
 
499
+ if (!selection.cli) {
500
+ delete pkg.bin;
501
+ } else {
502
+ pkg.bin = { kavoru: "./bin/kavoru.js" };
503
+ }
504
+
479
505
  await Bun.write(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
480
506
  }
481
507
 
package/src/index.ts CHANGED
@@ -3,10 +3,23 @@
3
3
  import { parseArgs, printHelp, printVersion } from "./args";
4
4
  import { runCli } from "./cli";
5
5
  import { log } from "./log";
6
+ import { printModuleHelp, runModuleCommand } from "./module-cli";
6
7
 
7
8
  async function main(): Promise<void> {
8
9
  try {
9
- const options = parseArgs(process.argv.slice(2));
10
+ const argv = process.argv.slice(2);
11
+
12
+ if (argv[0] === "module") {
13
+ if (argv.includes("-h") || argv.includes("--help")) {
14
+ printModuleHelp();
15
+ return;
16
+ }
17
+
18
+ await runModuleCommand(argv.slice(1));
19
+ return;
20
+ }
21
+
22
+ const options = parseArgs(argv);
10
23
 
11
24
  if (options.help) {
12
25
  printHelp();
@@ -0,0 +1,79 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { log } from "./log";
4
+
5
+ function findProjectRoot(cwd: string): string {
6
+ let current = path.resolve(cwd);
7
+
8
+ while (true) {
9
+ const packageJson = path.join(current, "package.json");
10
+ const localCli = path.join(current, "scripts/kavoru-cli.ts");
11
+ const moduleScript = path.join(current, "scripts/generate-module.ts");
12
+ if (
13
+ existsSync(packageJson) &&
14
+ (existsSync(localCli) || existsSync(moduleScript))
15
+ ) {
16
+ return current;
17
+ }
18
+
19
+ const parent = path.dirname(current);
20
+ if (parent === current) {
21
+ break;
22
+ }
23
+ current = parent;
24
+ }
25
+
26
+ throw new Error(
27
+ "Could not find a Kavoru project with the Project CLI enabled. Scaffold with the cli feature or run from a project that includes scripts/kavoru-cli.ts.",
28
+ );
29
+ }
30
+
31
+ export async function runModuleCommand(argv: string[]): Promise<void> {
32
+ const force = argv.includes("--force") || argv.includes("-f");
33
+ const name = argv.find((arg) => !arg.startsWith("-"));
34
+
35
+ if (!name) {
36
+ throw new Error("Usage: kavoru module <module-name> [--force]");
37
+ }
38
+
39
+ const projectDir = findProjectRoot(process.cwd());
40
+ const localCli = path.join(projectDir, "scripts/kavoru-cli.ts");
41
+ const scriptPath = existsSync(localCli)
42
+ ? localCli
43
+ : path.join(projectDir, "scripts/generate-module.ts");
44
+ const cmd = existsSync(localCli)
45
+ ? ["bun", scriptPath, "module", name]
46
+ : ["bun", scriptPath, name];
47
+ if (force) cmd.push("--force");
48
+
49
+ log.info(`Generating module "${name}" in ${projectDir}`);
50
+
51
+ const proc = Bun.spawn(cmd, {
52
+ cwd: projectDir,
53
+ stdout: "inherit",
54
+ stderr: "inherit",
55
+ });
56
+
57
+ const exitCode = await proc.exited;
58
+ if (exitCode !== 0) {
59
+ process.exit(exitCode ?? 1);
60
+ }
61
+ }
62
+
63
+ export function printModuleHelp(): void {
64
+ console.log(`\
65
+ Usage: kavoru module <module-name> [options]
66
+
67
+ Generate a feature module under src/modules/<module-name>/ with:
68
+ routes.ts, service.ts, types.ts
69
+ src/models/schemas/<module-name>.ts (query, body, params schemas)
70
+
71
+ Options:
72
+ -f, --force Overwrite an existing module folder
73
+ -h, --help Show help
74
+
75
+ Examples:
76
+ kavoru module users
77
+ kavoru module user-profile --force
78
+ `);
79
+ }
package/src/template.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { cp, mkdir, rm } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { TEMPLATE_BRANCH } from "./constants";
@@ -149,6 +150,16 @@ export async function installDependencies(projectDir: string): Promise<void> {
149
150
  await runCommand(["bun", "install"], projectDir);
150
151
  }
151
152
 
153
+ export async function linkProjectCli(projectDir: string): Promise<void> {
154
+ const script = path.join(projectDir, "scripts/link-cli.ts");
155
+ if (!existsSync(script)) {
156
+ return;
157
+ }
158
+
159
+ log.step("Linking kavoru to PATH (~/.bun/bin)");
160
+ await runCommand(["bun", script], projectDir);
161
+ }
162
+
152
163
  export function resolveTemplateSource(
153
164
  repo: string,
154
165
  branch: string,