@forinda/kickjs-cli 3.1.3 → 4.0.0

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/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @forinda/kickjs-cli v3.1.3
2
+ * @forinda/kickjs-cli v4.0.0
3
3
  *
4
4
  * Copyright (c) Felix Orinda
5
5
  *
@@ -8,13 +8,13 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { dirname, join, resolve } from "node:path";
11
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
12
12
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
13
13
  import * as clack from "@clack/prompts";
14
14
  import pc from "picocolors";
15
15
  import pkg from "pluralize";
16
16
  import { execSync } from "node:child_process";
17
- import { readFileSync } from "node:fs";
17
+ import { existsSync, readFileSync } from "node:fs";
18
18
  import { fileURLToPath } from "node:url";
19
19
  /** Write a file, creating parent directories if needed. Skips writing in dry run mode. */
20
20
  async function writeFileSafe(filePath, content) {
@@ -1543,15 +1543,15 @@ function generateEntryFile(name, template, version, packages = []) {
1543
1543
  const gqlAdapters = [];
1544
1544
  if (packages.includes("devtools")) {
1545
1545
  gqlImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
1546
- gqlAdapters.push(` new DevToolsAdapter(),`);
1546
+ gqlAdapters.push(` DevToolsAdapter(),`);
1547
1547
  }
1548
1548
  if (packages.includes("otel")) {
1549
1549
  gqlImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
1550
- gqlAdapters.push(` new OtelAdapter({ serviceName: '${name}' }),`);
1550
+ gqlAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
1551
1551
  }
1552
1552
  if (packages.includes("swagger")) {
1553
1553
  gqlImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
1554
- gqlAdapters.push(` new SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`);
1554
+ gqlAdapters.push(` SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`);
1555
1555
  }
1556
1556
  return `import 'reflect-metadata'
1557
1557
  // Side-effect import — registers the extended env schema with kickjs
@@ -1584,15 +1584,15 @@ ${gqlAdapters.length ? gqlAdapters.join("\n") + "\n" : ""} new GraphQLAdapter
1584
1584
  const cqrsAdapters = [];
1585
1585
  if (packages.includes("otel")) {
1586
1586
  cqrsImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
1587
- cqrsAdapters.push(` new OtelAdapter({ serviceName: '${name}' }),`);
1587
+ cqrsAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
1588
1588
  }
1589
1589
  if (packages.includes("devtools")) {
1590
1590
  cqrsImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
1591
- cqrsAdapters.push(` new DevToolsAdapter(),`);
1591
+ cqrsAdapters.push(` DevToolsAdapter(),`);
1592
1592
  }
1593
1593
  if (packages.includes("swagger")) {
1594
1594
  cqrsImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
1595
- cqrsAdapters.push(` new SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
1595
+ cqrsAdapters.push(` SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
1596
1596
  }
1597
1597
  if (packages.includes("graphql")) {
1598
1598
  cqrsImports.push(`import { GraphQLAdapter } from '@forinda/kickjs-graphql'`);
@@ -1611,7 +1611,7 @@ ${cqrsImports.length ? cqrsImports.join("\n") + "\n" : ""}import { modules } fro
1611
1611
 
1612
1612
  // Export the app for the Vite plugin (dev mode)
1613
1613
  export const app = await bootstrap({
1614
- modules,${cqrsImports.length ? `\n adapters: [\n${cqrsAdapters.join("\n")}\n // Uncomment for WebSocket support:\n // new WsAdapter(),\n // Uncomment when Redis is available:\n // new QueueAdapter({\n // provider: new BullMQProvider({ host: 'localhost', port: 6379 }),\n // }),\n ],` : `\n adapters: [\n // Uncomment for WebSocket support:\n // new WsAdapter(),\n // Uncomment when Redis is available:\n // new QueueAdapter({\n // provider: new BullMQProvider({ host: 'localhost', port: 6379 }),\n // }),\n ],`}
1614
+ modules,${cqrsImports.length ? `\n adapters: [\n${cqrsAdapters.join("\n")}\n // Uncomment for WebSocket support:\n // WsAdapter(),\n // Uncomment when Redis is available:\n // QueueAdapter({\n // provider: new BullMQProvider({ host: 'localhost', port: 6379 }),\n // }),\n ],` : `\n adapters: [\n // Uncomment for WebSocket support:\n // WsAdapter(),\n // Uncomment when Redis is available:\n // QueueAdapter({\n // provider: new BullMQProvider({ host: 'localhost', port: 6379 }),\n // }),\n ],`}
1615
1615
  })
1616
1616
  `;
1617
1617
  }
@@ -1620,15 +1620,15 @@ export const app = await bootstrap({
1620
1620
  const adapters = [];
1621
1621
  if (packages.includes("swagger")) {
1622
1622
  imports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
1623
- adapters.push(` new SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`);
1623
+ adapters.push(` SwaggerAdapter({ info: { title: '${name}', version: '${version}' } }),`);
1624
1624
  }
1625
1625
  if (packages.includes("devtools")) {
1626
1626
  imports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
1627
- adapters.push(` new DevToolsAdapter(),`);
1627
+ adapters.push(` DevToolsAdapter(),`);
1628
1628
  }
1629
1629
  if (packages.includes("otel")) {
1630
1630
  imports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
1631
- adapters.push(` new OtelAdapter({ serviceName: '${name}' }),`);
1631
+ adapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
1632
1632
  }
1633
1633
  if (packages.includes("graphql")) {
1634
1634
  imports.push(`import { GraphQLAdapter } from '@forinda/kickjs-graphql'`);
@@ -1652,15 +1652,15 @@ export const app = await bootstrap({ modules${adapters.length ? `,\n adapters:
1652
1652
  const restAdapters = [];
1653
1653
  if (packages.includes("devtools")) {
1654
1654
  restImports.push(`import { DevToolsAdapter } from '@forinda/kickjs-devtools'`);
1655
- restAdapters.push(` new DevToolsAdapter(),`);
1655
+ restAdapters.push(` DevToolsAdapter(),`);
1656
1656
  }
1657
1657
  if (packages.includes("swagger")) {
1658
1658
  restImports.push(`import { SwaggerAdapter } from '@forinda/kickjs-swagger'`);
1659
- restAdapters.push(` new SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
1659
+ restAdapters.push(` SwaggerAdapter({\n info: { title: '${name}', version: '${version}' },\n }),`);
1660
1660
  }
1661
1661
  if (packages.includes("otel")) {
1662
1662
  restImports.push(`import { OtelAdapter } from '@forinda/kickjs-otel'`);
1663
- restAdapters.push(` new OtelAdapter({ serviceName: '${name}' }),`);
1663
+ restAdapters.push(` OtelAdapter({ serviceName: '${name}' }),`);
1664
1664
  }
1665
1665
  return `import 'reflect-metadata'
1666
1666
  // Side-effect import — registers the extended env schema with kickjs
@@ -2980,6 +2980,8 @@ Copy \`.env.example\` to \`.env\` and configure:
2980
2980
  function generateClaude(name, template, pm) {
2981
2981
  return `# CLAUDE.md — ${name} Development Guide
2982
2982
 
2983
+ > **Read \`AGENTS.md\` first.** It is the canonical, multi-agent reference for this project (Claude, Copilot, Codex, Gemini, etc.). This file contains the same project context distilled for Claude, plus Claude-specific notes. When the two disagree on anything substantive, treat \`AGENTS.md\` as authoritative and flag the discrepancy.
2984
+
2983
2985
  ## Project Overview
2984
2986
 
2985
2987
  This is a **${{
@@ -3036,7 +3038,7 @@ generated \`KickRoutes\` namespace (refreshed on \`kick dev\` and \`kick typegen
3036
3038
  \`\`\`ts
3037
3039
  import { Controller, Get, Post, type Ctx } from '@forinda/kickjs'
3038
3040
 
3039
- @Controller('/users')
3041
+ @Controller()
3040
3042
  export class UserController {
3041
3043
  @Get('/')
3042
3044
  async findAll(ctx: Ctx<KickRoutes.UserController['findAll']>) {
@@ -3071,9 +3073,12 @@ export class UserService {
3071
3073
 
3072
3074
  ### Modules
3073
3075
 
3074
- Modules implement \`AppModule\` and wire controllers via \`buildRoutes()\`:
3076
+ Modules implement \`AppModule\` and wire controllers via \`buildRoutes()\`.
3077
+
3078
+ > **Naming matters.** Module files **must** be named \`<name>.module.ts\` and live under \`src/modules/\`. The Vite plugin auto-discovers files matching \`*.module.[tj]sx?\` for HMR — a misnamed file (e.g., \`projects.ts\`) won't trigger a graceful module rebuild on save and will require a full server restart. The CLI generator (\`kick g module <name>\`) follows this convention automatically.
3075
3079
 
3076
3080
  \`\`\`ts
3081
+ // src/modules/users/users.module.ts (named <feature>.module.ts)
3077
3082
  import { type AppModule, type ModuleRoutes, buildRoutes } from '@forinda/kickjs'
3078
3083
  import { UserController } from './user.controller'
3079
3084
 
@@ -3124,6 +3129,8 @@ ctx.notFound() // 404 Not Found
3124
3129
  ctx.badRequest(msg) // 400 Bad Request
3125
3130
  \`\`\`
3126
3131
 
3132
+ > **Context decorators** — when a middleware's only job is to populate \`ctx.set/get\` for the handler to read, prefer \`defineContextDecorator()\` over \`@Middleware()\`. Typed via \`ContextMeta\`, supports \`dependsOn\` ordering, validates the pipeline at boot. Full pattern reference in \`AGENTS.md\` and at <https://forinda.github.io/kick-js/guide/context-decorators>.
3133
+
3127
3134
  ## CLI Generators
3128
3135
 
3129
3136
  Generate code with the \`kick\` CLI:
@@ -3312,7 +3319,7 @@ Run tests:
3312
3319
  ## Decorators Reference
3313
3320
 
3314
3321
  ### Route Decorators
3315
- - \`@Controller('/path')\` — define controller prefix
3322
+ - \`@Controller()\` — mark a class as an HTTP controller (path comes from \`routes().path\`)
3316
3323
  - \`@Get('/'), @Post('/'), @Put('/'), @Delete('/'), @Patch('/')\` — HTTP methods
3317
3324
  - \`@Middleware(fn)\` — attach middleware
3318
3325
  - \`@Public()\` — skip authentication (requires @forinda/kickjs-auth)
@@ -3387,6 +3394,8 @@ ${template === "graphql" ? "| GraphQL resolvers | `src/resolvers/` |\n" : ""}| E
3387
3394
 
3388
3395
  ### Module Pattern (${template.toUpperCase()})
3389
3396
 
3397
+ > **Vite HMR auto-discovery contract:** module files **must** be named \`<name>.module.ts\` (or \`.tsx\`/\`.js\`/\`.jsx\`) and live under \`src/modules/\`. The Vite plugin scans for \`*.module.[tj]sx?\` to drive graceful HMR rebuilds; renaming a file to \`projects.ts\` (no \`.module\`) silently breaks HMR — saves trigger a full restart instead of a swap. The CLI generator (\`kick g module <name>\`) follows the convention; manual files must too.
3398
+
3390
3399
  Each module in \`src/modules/<name>/\` typically contains:
3391
3400
 
3392
3401
  ${template === "ddd" ? `\`\`\`
@@ -3457,7 +3466,7 @@ Then:
3457
3466
  If not using generators:
3458
3467
 
3459
3468
  - [ ] Create \`src/modules/<name>/<name>.controller.ts\`
3460
- - [ ] Add \`@Controller('/path')\` decorator
3469
+ - [ ] Add \`@Controller()\` decorator
3461
3470
  - [ ] Add route handlers with \`@Get()\`, \`@Post()\`, etc.
3462
3471
  - [ ] Create module file implementing \`AppModule\` with \`routes()\` returning \`{ path, router: buildRoutes(Controller), controller }\`
3463
3472
  - [ ] Register module in \`src/modules/index.ts\` (\`AppModuleClass[]\` array)
@@ -3519,8 +3528,8 @@ import { AuthAdapter, JwtStrategy } from '@forinda/kickjs-auth'
3519
3528
  bootstrap({
3520
3529
  modules,
3521
3530
  adapters: [
3522
- new AuthAdapter({
3523
- strategies: [new JwtStrategy({ secret: process.env.JWT_SECRET! })],
3531
+ AuthAdapter({
3532
+ strategies: [JwtStrategy({ secret: process.env.JWT_SECRET! })],
3524
3533
  }),
3525
3534
  ],
3526
3535
  })
@@ -3550,7 +3559,7 @@ import { WsAdapter } from '@forinda/kickjs-ws'
3550
3559
 
3551
3560
  bootstrap({
3552
3561
  modules,
3553
- adapters: [new WsAdapter()],
3562
+ adapters: [WsAdapter()],
3554
3563
  })
3555
3564
  \`\`\`
3556
3565
 
@@ -3654,7 +3663,7 @@ These work anywhere — scripts, plain files, outside \`@Service\`/\`@Controller
3654
3663
  ### HTTP Routes
3655
3664
  | Decorator | Purpose |
3656
3665
  |-----------|---------|
3657
- | \`@Controller('/path')\` | Define route prefix |
3666
+ | \`@Controller()\` | Define route prefix |
3658
3667
  | \`@Get('/'), @Post('/')\` | HTTP method handlers |
3659
3668
  | \`@Middleware(fn)\` | Attach middleware |
3660
3669
  | \`@Public()\` | Skip auth (requires auth adapter) |
@@ -3670,6 +3679,27 @@ These work anywhere — scripts, plain files, outside \`@Service\`/\`@Controller
3670
3679
  | \`@Inject('token')\` | Token-based injection |
3671
3680
  | \`@Value('VAR')\` | Inject env variable |
3672
3681
 
3682
+ ### Context Decorators
3683
+
3684
+ Typed, ordered way to populate \`ctx.set/get\` keys before the handler runs.
3685
+ Use this **instead of \`@Middleware()\`** when the middleware's only output
3686
+ is a value other code reads off \`ctx\`.
3687
+
3688
+ | Concept | Where it lives |
3689
+ |---------|----------------|
3690
+ | \`defineContextDecorator({ key, deps, dependsOn, optional, onError, resolve })\` | \`@forinda/kickjs\` |
3691
+ | Method/class decorator | \`@LoadX\` on a controller method/class |
3692
+ | Module hook | \`AppModule.contributors?(): ContributorRegistration[]\` |
3693
+ | Adapter hook | \`AppAdapter.contributors?(): ContributorRegistration[]\` |
3694
+ | Global registration | \`bootstrap({ contributors: [LoadX.registration] })\` |
3695
+ | Type augmentation | \`declare module '@forinda/kickjs' { interface ContextMeta { ... } }\` |
3696
+
3697
+ Precedence high → low: **method > class > module > adapter > global**.
3698
+ Cycles and missing \`dependsOn\` keys throw at \`app.setup()\` (boot fails
3699
+ fast). The \`onError\` hook is async-permitted.
3700
+
3701
+ Full guide: <https://forinda.github.io/kick-js/guide/context-decorators>.
3702
+
3673
3703
  ${template === "graphql" ? `### GraphQL
3674
3704
  | Decorator | Purpose |
3675
3705
  |-----------|---------|
@@ -3692,9 +3722,11 @@ ${template === "graphql" ? `### GraphQL
3692
3722
  2. **DI not working** — Ensure \`reflect-metadata\` is imported in \`src/index.ts\`
3693
3723
  3. **Tests failing randomly** — Missing \`Container.reset()\` in \`beforeEach\`
3694
3724
  4. **Routes not found** — Check controller path and module registration
3695
- 5. **HMR not working** — Verify \`vite.config.ts\` has \`hmr: true\`
3725
+ 5. **HMR not working** — Two checks: (a) \`vite.config.ts\` has \`hmr: true\`; (b) module file is named \`<name>.module.ts\` (or \`.tsx\`/\`.js\`/\`.jsx\`) and lives under \`src/modules/\`. The Vite plugin auto-discovers \`*.module.[tj]sx?\` for graceful HMR — a misnamed module file (e.g., \`projects.ts\`) silently degrades to a full restart on every save.
3696
3726
  6. **Decorators not working** — Check \`tsconfig.json\` has \`experimentalDecorators: true\`
3697
3727
  7. **\`config.get('YOUR_KEY')\` returns \`undefined\`** — \`src/index.ts\` is missing \`import './config'\`. That side-effect import registers the env schema with kickjs (\`loadEnv(envSchema)\` runs at module load). Without it, \`ConfigService\` falls back to the base schema (\`PORT\`/\`NODE_ENV\`/\`LOG_LEVEL\` only) and every user-defined key reads as \`undefined\`. \`@Value()\` may *appear* to work because of a raw \`process.env\` fallback, but Zod coercion and schema defaults are silently skipped — investigate \`src/index.ts\` and \`src/config/index.ts\` first.
3728
+ 8. **Used \`@Middleware()\` to compute a value for \`ctx\`** — prefer \`defineContextDecorator()\` (see Context Decorators above). It's typed via \`ContextMeta\`, supports \`dependsOn\` for ordering, and validates the pipeline at boot. \`@Middleware()\` is for response short-circuiting, stream mutation, and pre-route-matching work.
3729
+ 9. **Context contributor's \`dependsOn\` key not produced anywhere** — boot throws \`MissingContributorError\` naming the dependent and the route. Either remove the dep or register a contributor that produces the key (at any precedence level: method/class/module/adapter/global).
3698
3730
 
3699
3731
  ## CLI Commands Reference
3700
3732
 
@@ -3772,7 +3804,7 @@ async function initProject(options) {
3772
3804
  }
3773
3805
  }
3774
3806
  try {
3775
- const { runTypegen } = await import("./typegen-0X9tc3wa.mjs");
3807
+ const { runTypegen } = await import("./typegen-vI1eqGLK.mjs");
3776
3808
  await runTypegen({
3777
3809
  cwd: dir,
3778
3810
  allowDuplicates: true,
@@ -3870,7 +3902,10 @@ async function loadKickConfig(cwd) {
3870
3902
  try {
3871
3903
  const { pathToFileURL } = await import("node:url");
3872
3904
  const mod = await import(pathToFileURL(filepath).href);
3873
- return mod.default ?? mod;
3905
+ const config = mod.default ?? mod;
3906
+ const warnings = validateAssetMap(config, cwd);
3907
+ for (const warning of warnings) console.warn(` Warning: ${warning}`);
3908
+ return config;
3874
3909
  } catch (err) {
3875
3910
  if (filename.endsWith(".ts")) console.warn(`Warning: Failed to load ${filename}. TypeScript config files require a runtime loader (e.g. tsx, ts-node) or use kick.config.js/.mjs instead.`);
3876
3911
  continue;
@@ -3878,7 +3913,126 @@ async function loadKickConfig(cwd) {
3878
3913
  }
3879
3914
  return null;
3880
3915
  }
3916
+ /**
3917
+ * Validate `assetMap` entries on a loaded config. Returns a list of
3918
+ * human-readable warnings; the caller decides how to surface them
3919
+ * (typically `console.warn`). Never throws — `kick g` and other
3920
+ * unrelated commands should keep working even when the assetMap is
3921
+ * misconfigured.
3922
+ *
3923
+ * Checks:
3924
+ *
3925
+ * - Each entry's `src` is a non-empty string.
3926
+ * - The `src` directory exists on disk (otherwise the typegen + build
3927
+ * steps will fail later with cryptic errors).
3928
+ * - `dest` doesn't escape the project root (defensive — a `dest:
3929
+ * '../../etc'` typo could write files outside the workspace).
3930
+ * - The namespace key is a non-empty string and doesn't include a
3931
+ * `/` (would conflict with the `<namespace>/<key>` manifest format).
3932
+ */
3933
+ function validateAssetMap(config, cwd) {
3934
+ const warnings = [];
3935
+ if (!config?.assetMap) return warnings;
3936
+ const root = resolve(cwd);
3937
+ for (const [namespace, entry] of Object.entries(config.assetMap)) {
3938
+ if (!namespace || namespace.includes("/")) {
3939
+ warnings.push(`assetMap key '${namespace}' is invalid — must be a non-empty string without '/'`);
3940
+ continue;
3941
+ }
3942
+ if (typeof entry?.src !== "string" || entry.src.length === 0) {
3943
+ warnings.push(`assetMap.${namespace} is missing a non-empty 'src' field`);
3944
+ continue;
3945
+ }
3946
+ if (!existsSync(resolve(cwd, entry.src))) warnings.push(`assetMap.${namespace}.src ('${entry.src}') does not exist — typegen + build will fail`);
3947
+ if (entry.dest) {
3948
+ if (escapesRoot(resolve(cwd, entry.dest), root)) warnings.push(`assetMap.${namespace}.dest ('${entry.dest}') resolves outside the project root — refusing to copy`);
3949
+ }
3950
+ }
3951
+ return warnings;
3952
+ }
3953
+ /**
3954
+ * Returns true when `path` (absolute) resolves outside of `root`
3955
+ * (also absolute). Uses `path.relative` for accuracy:
3956
+ *
3957
+ * - The result is empty when paths are identical (inside).
3958
+ * - It starts with `..` when the path traverses outside the root.
3959
+ * - It's absolute (Windows: cross-drive) when there's no relative
3960
+ * path between them.
3961
+ *
3962
+ * Avoids the prefix-match pitfalls of `startsWith` (e.g. `/app`
3963
+ * matching `/app2/...`, or case-mismatches on macOS / Windows).
3964
+ */
3965
+ function escapesRoot(path, root) {
3966
+ const rel = relative(root, path);
3967
+ return rel === "" ? false : rel.startsWith("..") || isAbsolute(rel);
3968
+ }
3969
+ //#endregion
3970
+ //#region src/generator-extension/define.ts
3971
+ /**
3972
+ * Identity factory — returns the spec verbatim. Exists for type
3973
+ * inference and forward-compatibility (future fields can be added with
3974
+ * defaults).
3975
+ *
3976
+ * @example
3977
+ * ```ts
3978
+ * import { defineGenerator } from '@forinda/kickjs-cli'
3979
+ *
3980
+ * export default [
3981
+ * defineGenerator({
3982
+ * name: 'command',
3983
+ * description: 'Generate a CQRS command + handler',
3984
+ * files: (ctx) => [
3985
+ * {
3986
+ * path: `src/modules/${ctx.kebab}/commands/${ctx.kebab}.command.ts`,
3987
+ * content: `// command for ${ctx.pascal}`,
3988
+ * },
3989
+ * ],
3990
+ * }),
3991
+ * ]
3992
+ * ```
3993
+ */
3994
+ function defineGenerator(spec) {
3995
+ return spec;
3996
+ }
3997
+ //#endregion
3998
+ //#region src/generator-extension/context.ts
3999
+ /** Convert any string to snake_case (`UserPost` / `user-post` → `user_post`). */
4000
+ function toSnakeCase(name) {
4001
+ return toKebabCase(name).replace(/-/g, "_");
4002
+ }
4003
+ /**
4004
+ * Build a {@link GeneratorContext} from the raw name + invocation
4005
+ * arguments. Centralises the case-transformation logic so every plugin
4006
+ * generator sees the same shape regardless of how the name was typed
4007
+ * on the command line (`Post` vs `post` vs `user_post`).
4008
+ */
4009
+ function buildGeneratorContext(input) {
4010
+ const cwd = input.cwd ?? process.cwd();
4011
+ const usePlural = input.pluralize ?? true;
4012
+ const pascal = toPascalCase(input.name);
4013
+ const camel = toCamelCase(input.name);
4014
+ const kebab = toKebabCase(input.name);
4015
+ const snake = toSnakeCase(input.name);
4016
+ const ctx = {
4017
+ name: input.name,
4018
+ pascal,
4019
+ camel,
4020
+ kebab,
4021
+ snake,
4022
+ modulesDir: input.modulesDir ?? "src/modules",
4023
+ cwd,
4024
+ args: input.args ?? [],
4025
+ flags: input.flags ?? {}
4026
+ };
4027
+ if (usePlural) {
4028
+ const pluralKebab = pluralize(kebab);
4029
+ ctx.pluralKebab = pluralKebab;
4030
+ ctx.pluralPascal = toPascalCase(pluralKebab);
4031
+ ctx.pluralCamel = toCamelCase(pluralKebab);
4032
+ }
4033
+ return ctx;
4034
+ }
3881
4035
  //#endregion
3882
- export { defineConfig, generateAdapter, generateController, generateDto, generateGuard, generateMiddleware, generateModule, generateService, initProject, loadKickConfig, pluralize, toCamelCase, toKebabCase, toPascalCase };
4036
+ export { buildGeneratorContext, defineConfig, defineGenerator, generateAdapter, generateController, generateDto, generateGuard, generateMiddleware, generateModule, generateService, initProject, loadKickConfig, pluralize, toCamelCase, toKebabCase, toPascalCase };
3883
4037
 
3884
4038
  //# sourceMappingURL=index.mjs.map