@forinda/kickjs-cli 1.2.1 → 1.2.3

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/dist/cli.js CHANGED
@@ -22,7 +22,13 @@ import { fileURLToPath } from "url";
22
22
  // src/utils/fs.ts
23
23
  import { writeFile, mkdir, access, readFile } from "fs/promises";
24
24
  import { dirname } from "path";
25
+ var _dryRun = false;
26
+ function setDryRun(enabled) {
27
+ _dryRun = enabled;
28
+ }
29
+ __name(setDryRun, "setDryRun");
25
30
  async function writeFileSafe(filePath, content) {
31
+ if (_dryRun) return;
26
32
  await mkdir(dirname(filePath), {
27
33
  recursive: true
28
34
  });
@@ -266,6 +272,7 @@ export default defineConfig({
266
272
  },
267
273
  })
268
274
  `);
275
+ await writeFileSafe(join(dir, "README.md"), generateReadme(name, template, packageManager));
269
276
  if (options.initGit) {
270
277
  try {
271
278
  execSync("git init", {
@@ -423,6 +430,91 @@ bootstrap({
423
430
  }
424
431
  }
425
432
  __name(getEntryFile, "getEntryFile");
433
+ function generateReadme(name, template, pm) {
434
+ const templateLabels = {
435
+ rest: "REST API",
436
+ graphql: "GraphQL API",
437
+ ddd: "Domain-Driven Design",
438
+ cqrs: "CQRS + Event-Driven",
439
+ minimal: "Minimal"
440
+ };
441
+ const packages = [
442
+ "@forinda/kickjs-core",
443
+ "@forinda/kickjs-http",
444
+ "@forinda/kickjs-config"
445
+ ];
446
+ if (template !== "minimal") {
447
+ packages.push("@forinda/kickjs-swagger", "@forinda/kickjs-devtools");
448
+ }
449
+ if (template === "graphql") packages.push("@forinda/kickjs-graphql");
450
+ if (template === "cqrs") {
451
+ packages.push("@forinda/kickjs-queue", "@forinda/kickjs-ws", "@forinda/kickjs-otel");
452
+ }
453
+ return `# ${name}
454
+
455
+ A **${templateLabels[template] ?? "REST API"}** built with [KickJS](https://forinda.github.io/kick-js/) \u2014 a decorator-driven Node.js framework on Express 5 and TypeScript.
456
+
457
+ ## Getting Started
458
+
459
+ \`\`\`bash
460
+ ${pm} install
461
+ kick dev
462
+ \`\`\`
463
+
464
+ ## Scripts
465
+
466
+ | Command | Description |
467
+ |---|---|
468
+ | \`kick dev\` | Start dev server with Vite HMR |
469
+ | \`kick build\` | Production build |
470
+ | \`kick start\` | Run production build |
471
+ | \`${pm} run test\` | Run tests with Vitest |
472
+ | \`kick g module <name>\` | Generate a DDD module |
473
+ | \`kick g scaffold <name> <fields...>\` | Generate CRUD from field definitions |
474
+ | \`kick add <package>\` | Add a KickJS package |
475
+
476
+ ## Project Structure
477
+
478
+ \`\`\`
479
+ src/
480
+ \u251C\u2500\u2500 index.ts # Application entry point
481
+ \u251C\u2500\u2500 modules/ # Feature modules (controllers, services, repos)
482
+ \u2502 \u2514\u2500\u2500 index.ts # Module registry
483
+ \u2514\u2500\u2500 ...
484
+ \`\`\`
485
+
486
+ ## Packages
487
+
488
+ ${packages.map((p) => `- \`${p}\``).join("\n")}
489
+
490
+ ## Adding Features
491
+
492
+ \`\`\`bash
493
+ kick add auth # Authentication (JWT, API key, OAuth)
494
+ kick add swagger # OpenAPI documentation
495
+ kick add ws # WebSocket support
496
+ kick add queue # Background job processing
497
+ kick add mailer # Email sending
498
+ kick add cron # Scheduled tasks
499
+ kick add --list # Show all available packages
500
+ \`\`\`
501
+
502
+ ## Environment Variables
503
+
504
+ Copy \`.env.example\` to \`.env\` and configure:
505
+
506
+ | Variable | Default | Description |
507
+ |---|---|---|
508
+ | \`PORT\` | \`3000\` | Server port |
509
+ | \`NODE_ENV\` | \`development\` | Environment |
510
+
511
+ ## Learn More
512
+
513
+ - [KickJS Documentation](https://forinda.github.io/kick-js/)
514
+ - [CLI Reference](https://forinda.github.io/kick-js/api/cli.html)
515
+ `;
516
+ }
517
+ __name(generateReadme, "generateReadme");
426
518
 
427
519
  // src/commands/init.ts
428
520
  function ask(question, defaultValue) {
@@ -1926,7 +2018,7 @@ function promptUser(question) {
1926
2018
  }
1927
2019
  __name(promptUser, "promptUser");
1928
2020
  async function generateModule(options) {
1929
- const { name, modulesDir, noEntity, noTests, repo = "inmemory", force } = options;
2021
+ const { name, modulesDir, noEntity, noTests, repo = "inmemory", force, dryRun } = options;
1930
2022
  let pattern = options.pattern ?? "ddd";
1931
2023
  if (options.minimal) pattern = "minimal";
1932
2024
  const kebab = toKebabCase(name);
@@ -1938,6 +2030,10 @@ async function generateModule(options) {
1938
2030
  let overwriteAll = force ?? false;
1939
2031
  const write = /* @__PURE__ */ __name(async (relativePath, content) => {
1940
2032
  const fullPath = join2(moduleDir, relativePath);
2033
+ if (dryRun) {
2034
+ files.push(fullPath);
2035
+ return;
2036
+ }
1941
2037
  if (!overwriteAll && await fileExists(fullPath)) {
1942
2038
  const answer = await promptUser(` File already exists: ${relativePath}
1943
2039
  Overwrite? (y/n/a = yes/no/all) `);
@@ -1979,7 +2075,9 @@ async function generateModule(options) {
1979
2075
  await generateDddFiles(ctx);
1980
2076
  break;
1981
2077
  }
1982
- await autoRegisterModule(modulesDir, pascal, plural);
2078
+ if (!dryRun) {
2079
+ await autoRegisterModule(modulesDir, pascal, plural);
2080
+ }
1983
2081
  return files;
1984
2082
  }
1985
2083
  __name(generateModule, "generateModule");
@@ -3299,13 +3397,19 @@ async function loadKickConfig(cwd) {
3299
3397
  __name(loadKickConfig, "loadKickConfig");
3300
3398
 
3301
3399
  // src/commands/generate.ts
3302
- function printGenerated(files) {
3400
+ function isDryRun(cmd) {
3401
+ return cmd.parent?.opts()?.dryRun ?? false;
3402
+ }
3403
+ __name(isDryRun, "isDryRun");
3404
+ function printGenerated(files, dryRun = false) {
3303
3405
  const cwd = process.cwd();
3406
+ const label = dryRun ? "Would generate" : "Generated";
3304
3407
  console.log(`
3305
- Generated ${files.length} file${files.length === 1 ? "" : "s"}:`);
3408
+ ${label} ${files.length} file${files.length === 1 ? "" : "s"}:`);
3306
3409
  for (const f of files) {
3307
3410
  console.log(` ${f.replace(cwd + "/", "")}`);
3308
3411
  }
3412
+ if (dryRun) console.log("\n (dry run \u2014 no files were written)");
3309
3413
  console.log();
3310
3414
  }
3311
3415
  __name(printGenerated, "printGenerated");
@@ -3369,14 +3473,16 @@ function printGeneratorList() {
3369
3473
  }
3370
3474
  __name(printGeneratorList, "printGeneratorList");
3371
3475
  function registerGenerateCommand(program) {
3372
- const gen = program.command("generate").alias("g").description("Generate code scaffolds").option("--list", "List all available generators").action((opts) => {
3476
+ const gen = program.command("generate").alias("g").description("Generate code scaffolds").option("--list", "List all available generators").option("--dry-run", "Preview files that would be generated without writing them").action((opts) => {
3373
3477
  if (opts.list) {
3374
3478
  printGeneratorList();
3375
3479
  } else {
3376
3480
  gen.help();
3377
3481
  }
3378
3482
  });
3379
- gen.command("module <name>").description("Generate a module (structure depends on project pattern)").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--repo <type>", "Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>", "Override project pattern: rest | ddd | cqrs | minimal").option("--minimal", "Shorthand for --pattern minimal").option("--modules-dir <dir>", "Modules directory").option("-f, --force", "Overwrite existing files without prompting").action(async (name, opts) => {
3483
+ gen.command("module <name>").description("Generate a module (structure depends on project pattern)").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--repo <type>", "Repository implementation: inmemory | drizzle | prisma").option("--pattern <pattern>", "Override project pattern: rest | ddd | cqrs | minimal").option("--minimal", "Shorthand for --pattern minimal").option("--modules-dir <dir>", "Modules directory").option("-f, --force", "Overwrite existing files without prompting").action(async (name, opts, cmd) => {
3484
+ const dryRun = isDryRun(cmd);
3485
+ setDryRun(dryRun);
3380
3486
  const config = await loadKickConfig(process.cwd());
3381
3487
  const modulesDir = opts.modulesDir ?? config?.modulesDir ?? "src/modules";
3382
3488
  const repo = opts.repo ?? config?.defaultRepo ?? "inmemory";
@@ -3389,18 +3495,23 @@ function registerGenerateCommand(program) {
3389
3495
  repo,
3390
3496
  minimal: opts.minimal,
3391
3497
  force: opts.force,
3392
- pattern
3498
+ pattern,
3499
+ dryRun
3393
3500
  });
3394
- printGenerated(files);
3501
+ printGenerated(files, dryRun);
3395
3502
  });
3396
- gen.command("adapter <name>").description("Generate an AppAdapter with lifecycle hooks and middleware support").option("-o, --out <dir>", "Output directory", "src/adapters").action(async (name, opts) => {
3503
+ gen.command("adapter <name>").description("Generate an AppAdapter with lifecycle hooks and middleware support").option("-o, --out <dir>", "Output directory", "src/adapters").action(async (name, opts, cmd) => {
3504
+ const dryRun = isDryRun(cmd);
3505
+ setDryRun(dryRun);
3397
3506
  const files = await generateAdapter({
3398
3507
  name,
3399
3508
  outDir: resolve4(opts.out)
3400
3509
  });
3401
- printGenerated(files);
3510
+ printGenerated(files, dryRun);
3402
3511
  });
3403
- gen.command("middleware <name>").description("Generate an Express middleware function\n Use -m to scope it to a module: kick g middleware auth -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts) => {
3512
+ gen.command("middleware <name>").description("Generate an Express middleware function\n Use -m to scope it to a module: kick g middleware auth -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
3513
+ const dryRun = isDryRun(cmd);
3514
+ setDryRun(dryRun);
3404
3515
  const config = await loadKickConfig(process.cwd());
3405
3516
  const modulesDir = config?.modulesDir ?? "src/modules";
3406
3517
  const files = await generateMiddleware({
@@ -3410,9 +3521,11 @@ function registerGenerateCommand(program) {
3410
3521
  modulesDir,
3411
3522
  pattern: config?.pattern
3412
3523
  });
3413
- printGenerated(files);
3524
+ printGenerated(files, dryRun);
3414
3525
  });
3415
- gen.command("guard <name>").description("Generate a route guard (auth, roles, etc.)\n Use -m to scope it to a module: kick g guard admin -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts) => {
3526
+ gen.command("guard <name>").description("Generate a route guard (auth, roles, etc.)\n Use -m to scope it to a module: kick g guard admin -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
3527
+ const dryRun = isDryRun(cmd);
3528
+ setDryRun(dryRun);
3416
3529
  const config = await loadKickConfig(process.cwd());
3417
3530
  const modulesDir = config?.modulesDir ?? "src/modules";
3418
3531
  const files = await generateGuard({
@@ -3422,9 +3535,11 @@ function registerGenerateCommand(program) {
3422
3535
  modulesDir,
3423
3536
  pattern: config?.pattern
3424
3537
  });
3425
- printGenerated(files);
3538
+ printGenerated(files, dryRun);
3426
3539
  });
3427
- gen.command("service <name>").description("Generate a @Service() class\n Use -m to scope it to a module: kick g service payment -m orders").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts) => {
3540
+ gen.command("service <name>").description("Generate a @Service() class\n Use -m to scope it to a module: kick g service payment -m orders").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
3541
+ const dryRun = isDryRun(cmd);
3542
+ setDryRun(dryRun);
3428
3543
  const config = await loadKickConfig(process.cwd());
3429
3544
  const modulesDir = config?.modulesDir ?? "src/modules";
3430
3545
  const files = await generateService({
@@ -3434,9 +3549,11 @@ function registerGenerateCommand(program) {
3434
3549
  modulesDir,
3435
3550
  pattern: config?.pattern
3436
3551
  });
3437
- printGenerated(files);
3552
+ printGenerated(files, dryRun);
3438
3553
  });
3439
- gen.command("controller <name>").description("Generate a @Controller() class with basic routes\n Use -m to scope it to a module: kick g controller auth -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts) => {
3554
+ gen.command("controller <name>").description("Generate a @Controller() class with basic routes\n Use -m to scope it to a module: kick g controller auth -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
3555
+ const dryRun = isDryRun(cmd);
3556
+ setDryRun(dryRun);
3440
3557
  const config = await loadKickConfig(process.cwd());
3441
3558
  const modulesDir = config?.modulesDir ?? "src/modules";
3442
3559
  const files = await generateController2({
@@ -3446,9 +3563,11 @@ function registerGenerateCommand(program) {
3446
3563
  modulesDir,
3447
3564
  pattern: config?.pattern
3448
3565
  });
3449
- printGenerated(files);
3566
+ printGenerated(files, dryRun);
3450
3567
  });
3451
- gen.command("dto <name>").description("Generate a Zod DTO schema\n Use -m to scope it to a module: kick g dto create-user -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts) => {
3568
+ gen.command("dto <name>").description("Generate a Zod DTO schema\n Use -m to scope it to a module: kick g dto create-user -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module folder").action(async (name, opts, cmd) => {
3569
+ const dryRun = isDryRun(cmd);
3570
+ setDryRun(dryRun);
3452
3571
  const config = await loadKickConfig(process.cwd());
3453
3572
  const modulesDir = config?.modulesDir ?? "src/modules";
3454
3573
  const files = await generateDto({
@@ -3458,9 +3577,11 @@ function registerGenerateCommand(program) {
3458
3577
  modulesDir,
3459
3578
  pattern: config?.pattern
3460
3579
  });
3461
- printGenerated(files);
3580
+ printGenerated(files, dryRun);
3462
3581
  });
3463
- gen.command("test <name>").description("Generate a Vitest test scaffold\n Use -m to scope it to a module: kick g test user-service -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module's __tests__/ folder").action(async (name, opts) => {
3582
+ gen.command("test <name>").description("Generate a Vitest test scaffold\n Use -m to scope it to a module: kick g test user-service -m users").option("-o, --out <dir>", "Output directory (overrides --module)").option("-m, --module <module>", "Place inside a module's __tests__/ folder").action(async (name, opts, cmd) => {
3583
+ const dryRun = isDryRun(cmd);
3584
+ setDryRun(dryRun);
3464
3585
  const config = await loadKickConfig(process.cwd());
3465
3586
  const modulesDir = config?.modulesDir ?? "src/modules";
3466
3587
  const files = await generateTest({
@@ -3469,24 +3590,30 @@ function registerGenerateCommand(program) {
3469
3590
  moduleName: opts.module,
3470
3591
  modulesDir
3471
3592
  });
3472
- printGenerated(files);
3593
+ printGenerated(files, dryRun);
3473
3594
  });
3474
- gen.command("resolver <name>").description("Generate a GraphQL @Resolver class with @Query and @Mutation methods").option("-o, --out <dir>", "Output directory", "src/resolvers").action(async (name, opts) => {
3595
+ gen.command("resolver <name>").description("Generate a GraphQL @Resolver class with @Query and @Mutation methods").option("-o, --out <dir>", "Output directory", "src/resolvers").action(async (name, opts, cmd) => {
3596
+ const dryRun = isDryRun(cmd);
3597
+ setDryRun(dryRun);
3475
3598
  const files = await generateResolver({
3476
3599
  name,
3477
3600
  outDir: resolve4(opts.out)
3478
3601
  });
3479
- printGenerated(files);
3602
+ printGenerated(files, dryRun);
3480
3603
  });
3481
- gen.command("job <name>").description("Generate a @Job queue processor with @Process handlers").option("-o, --out <dir>", "Output directory", "src/jobs").option("-q, --queue <name>", "Queue name (default: <name>-queue)").action(async (name, opts) => {
3604
+ gen.command("job <name>").description("Generate a @Job queue processor with @Process handlers").option("-o, --out <dir>", "Output directory", "src/jobs").option("-q, --queue <name>", "Queue name (default: <name>-queue)").action(async (name, opts, cmd) => {
3605
+ const dryRun = isDryRun(cmd);
3606
+ setDryRun(dryRun);
3482
3607
  const files = await generateJob({
3483
3608
  name,
3484
3609
  outDir: resolve4(opts.out),
3485
3610
  queue: opts.queue
3486
3611
  });
3487
- printGenerated(files);
3612
+ printGenerated(files, dryRun);
3488
3613
  });
3489
- gen.command("scaffold <name> [fields...]").description("Generate a full CRUD module from field definitions\n Example: kick g scaffold Post title:string body:text published:boolean?\n Types: string, text, number, int, float, boolean, date, email, url, uuid, json, enum:a,b,c\n Append ? for optional fields: description:text?").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--modules-dir <dir>", "Modules directory").action(async (name, rawFields, opts) => {
3614
+ gen.command("scaffold <name> [fields...]").description("Generate a full CRUD module from field definitions\n Example: kick g scaffold Post title:string body:text published:boolean?\n Types: string, text, number, int, float, boolean, date, email, url, uuid, json, enum:a,b,c\n Append ? for optional fields: description:text?").option("--no-entity", "Skip entity and value object generation").option("--no-tests", "Skip test file generation").option("--modules-dir <dir>", "Modules directory").action(async (name, rawFields, opts, cmd) => {
3615
+ const dryRun = isDryRun(cmd);
3616
+ setDryRun(dryRun);
3490
3617
  if (rawFields.length === 0) {
3491
3618
  console.error("\n Error: At least one field is required.\n Usage: kick g scaffold <name> <field:type> [field:type...]\n Example: kick g scaffold Post title:string body:text published:boolean\n");
3492
3619
  process.exit(1);
@@ -3506,16 +3633,18 @@ function registerGenerateCommand(program) {
3506
3633
  for (const f of fields) {
3507
3634
  console.log(` ${f.name}: ${f.type}${f.optional ? " (optional)" : ""}`);
3508
3635
  }
3509
- printGenerated(files);
3636
+ printGenerated(files, dryRun);
3510
3637
  });
3511
- gen.command("config").description("Generate a kick.config.ts at the project root").option("--modules-dir <dir>", "Modules directory path", "src/modules").option("--repo <type>", "Default repository type: inmemory | drizzle | prisma", "inmemory").option("-f, --force", "Overwrite existing kick.config.ts without prompting").action(async (opts) => {
3638
+ gen.command("config").description("Generate a kick.config.ts at the project root").option("--modules-dir <dir>", "Modules directory path", "src/modules").option("--repo <type>", "Default repository type: inmemory | drizzle | prisma", "inmemory").option("-f, --force", "Overwrite existing kick.config.ts without prompting").action(async (opts, cmd) => {
3639
+ const dryRun = isDryRun(cmd);
3640
+ setDryRun(dryRun);
3512
3641
  const files = await generateConfig({
3513
3642
  outDir: resolve4("."),
3514
3643
  modulesDir: opts.modulesDir,
3515
3644
  defaultRepo: opts.repo,
3516
3645
  force: opts.force
3517
3646
  });
3518
- printGenerated(files);
3647
+ printGenerated(files, dryRun);
3519
3648
  });
3520
3649
  }
3521
3650
  __name(registerGenerateCommand, "registerGenerateCommand");