@navios/commander 1.0.0-alpha.2 → 1.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +42 -18
  3. package/dist/src/commander.factory.d.mts +84 -10
  4. package/dist/src/commander.factory.d.mts.map +1 -1
  5. package/dist/src/commands/help.command.d.mts +18 -0
  6. package/dist/src/commands/help.command.d.mts.map +1 -0
  7. package/dist/src/commands/index.d.mts +2 -0
  8. package/dist/src/commands/index.d.mts.map +1 -0
  9. package/dist/src/decorators/cli-module.decorator.d.mts +29 -4
  10. package/dist/src/decorators/cli-module.decorator.d.mts.map +1 -1
  11. package/dist/src/decorators/command.decorator.d.mts +6 -1
  12. package/dist/src/decorators/command.decorator.d.mts.map +1 -1
  13. package/dist/src/define-environment.d.mts +31 -0
  14. package/dist/src/define-environment.d.mts.map +1 -0
  15. package/dist/src/index.d.mts +2 -1
  16. package/dist/src/index.d.mts.map +1 -1
  17. package/dist/src/interfaces/abstract-cli-adapter.interface.d.mts +53 -0
  18. package/dist/src/interfaces/abstract-cli-adapter.interface.d.mts.map +1 -0
  19. package/dist/src/interfaces/commander-execution-context.interface.d.mts +1 -1
  20. package/dist/src/interfaces/environment.interface.d.mts +30 -0
  21. package/dist/src/interfaces/environment.interface.d.mts.map +1 -0
  22. package/dist/src/interfaces/index.d.mts +2 -0
  23. package/dist/src/interfaces/index.d.mts.map +1 -1
  24. package/dist/src/metadata/command-entry.metadata.d.mts +31 -0
  25. package/dist/src/metadata/command-entry.metadata.d.mts.map +1 -0
  26. package/dist/src/metadata/command.metadata.d.mts +6 -1
  27. package/dist/src/metadata/command.metadata.d.mts.map +1 -1
  28. package/dist/src/metadata/index.d.mts +1 -1
  29. package/dist/src/metadata/index.d.mts.map +1 -1
  30. package/dist/src/services/cli-parser.service.d.mts +2 -12
  31. package/dist/src/services/cli-parser.service.d.mts.map +1 -1
  32. package/dist/src/services/command-registry.service.d.mts +89 -0
  33. package/dist/src/services/command-registry.service.d.mts.map +1 -0
  34. package/dist/src/services/commander-adapter.service.d.mts +56 -0
  35. package/dist/src/services/commander-adapter.service.d.mts.map +1 -0
  36. package/dist/src/services/index.d.mts +2 -1
  37. package/dist/src/services/index.d.mts.map +1 -1
  38. package/dist/src/tokens/execution-context.token.d.mts +1 -1
  39. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/lib/index.cjs +536 -8513
  42. package/lib/index.cjs.map +1 -1
  43. package/lib/index.d.cts +422 -298
  44. package/lib/index.d.cts.map +1 -1
  45. package/lib/index.d.mts +422 -298
  46. package/lib/index.d.mts.map +1 -1
  47. package/lib/index.mjs +502 -8284
  48. package/lib/index.mjs.map +1 -1
  49. package/package.json +2 -2
  50. package/src/__tests__/commander.factory.e2e.spec.mts +258 -68
  51. package/src/commander.factory.mts +109 -17
  52. package/src/commands/help.command.mts +37 -0
  53. package/src/commands/index.mts +1 -0
  54. package/src/decorators/cli-module.decorator.mts +58 -20
  55. package/src/decorators/command.decorator.mts +7 -1
  56. package/src/define-environment.mts +39 -0
  57. package/src/index.mts +2 -1
  58. package/src/interfaces/abstract-cli-adapter.interface.mts +52 -0
  59. package/src/interfaces/commander-execution-context.interface.mts +1 -1
  60. package/src/interfaces/environment.interface.mts +34 -0
  61. package/src/interfaces/index.mts +2 -0
  62. package/src/metadata/command-entry.metadata.mts +41 -0
  63. package/src/metadata/command.metadata.mts +7 -0
  64. package/src/metadata/index.mts +1 -1
  65. package/src/services/cli-parser.service.mts +14 -28
  66. package/src/services/command-registry.service.mts +217 -0
  67. package/src/services/commander-adapter.service.mts +209 -0
  68. package/src/services/index.mts +2 -1
  69. package/src/tokens/execution-context.token.mts +1 -1
  70. package/tsconfig.json +1 -1
  71. package/src/commander.application.mts +0 -303
  72. package/src/metadata/cli-module.metadata.mts +0 -100
  73. package/src/services/module-loader.service.mts +0 -231
@@ -1,13 +1,78 @@
1
- import type { ClassTypeWithInstance, NaviosModule } from '@navios/core'
1
+ import type {
2
+ ClassTypeWithInstance,
3
+ LogLevel,
4
+ NaviosApplication,
5
+ NaviosModule,
6
+ } from '@navios/core'
2
7
 
3
- import { Container } from '@navios/core'
8
+ import { ConsoleLogger, NaviosFactory } from '@navios/core'
4
9
 
5
- import type { CommanderApplicationOptions } from './commander.application.mjs'
10
+ import type { CliEnvironment } from './interfaces/environment.interface.mjs'
6
11
 
7
- import { CommanderApplication } from './commander.application.mjs'
12
+ import { defineCliEnvironment } from './define-environment.mjs'
8
13
 
9
14
  /**
10
- * Factory class for creating and configuring CLI applications.
15
+ * Logger display options for CLI applications.
16
+ * All options default to false for cleaner CLI output.
17
+ *
18
+ * @public
19
+ */
20
+ export interface CommanderLoggerOptions {
21
+ /**
22
+ * Enabled log levels.
23
+ * @default ['log', 'error', 'warn', 'debug', 'verbose', 'fatal']
24
+ */
25
+ logLevels?: LogLevel[]
26
+ /**
27
+ * If true, will print the process ID in the log message.
28
+ * @default false
29
+ */
30
+ showPid?: boolean
31
+ /**
32
+ * If true, will print the log level in the log message.
33
+ * @default true
34
+ */
35
+ showLogLevel?: boolean
36
+ /**
37
+ * If true, will print the prefix/app name in the log message.
38
+ * @default false
39
+ */
40
+ showPrefix?: boolean
41
+ /**
42
+ * If true, will print the context in the log message.
43
+ * @default true
44
+ */
45
+ showContext?: boolean
46
+ /**
47
+ * If true, will print the absolute timestamp in the log message.
48
+ * @default false
49
+ */
50
+ showTimestamp?: boolean
51
+ /**
52
+ * If enabled, will print timestamp difference between current and previous log message.
53
+ * @default false
54
+ */
55
+ showTimeDiff?: boolean
56
+ }
57
+
58
+ /**
59
+ * Configuration options for CommanderFactory.
60
+ *
61
+ * @public
62
+ */
63
+ export interface CommanderFactoryOptions {
64
+ /**
65
+ * Logger display options. These override the default CLI-friendly logger settings.
66
+ */
67
+ logger?: CommanderLoggerOptions
68
+ }
69
+
70
+ /**
71
+ * Factory class for creating CLI applications.
72
+ *
73
+ * This is a convenience wrapper around `NaviosFactory.create()` that
74
+ * configures everything needed for CLI usage. It sets up the CLI adapter
75
+ * and returns a typed `NaviosApplication<CliEnvironment>`.
11
76
  *
12
77
  * @example
13
78
  * ```typescript
@@ -17,32 +82,59 @@ import { CommanderApplication } from './commander.application.mjs'
17
82
  * async function bootstrap() {
18
83
  * const app = await CommanderFactory.create(AppModule)
19
84
  * await app.init()
20
- * await app.run(process.argv)
85
+ *
86
+ * const adapter = app.getAdapter()
87
+ * await adapter.run(process.argv)
88
+ *
21
89
  * await app.close()
22
90
  * }
23
91
  * ```
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * // Alternative: use NaviosFactory directly
96
+ * import { NaviosFactory } from '@navios/core'
97
+ * import { defineCliEnvironment, type CliEnvironment } from '@navios/commander'
98
+ *
99
+ * const app = await NaviosFactory.create<CliEnvironment>(AppModule, {
100
+ * adapter: defineCliEnvironment(),
101
+ * })
102
+ * ```
24
103
  */
25
104
  export class CommanderFactory {
26
105
  /**
27
- * Creates a new CommanderApplication instance and configures it with the provided module.
106
+ * Creates a new CLI application instance configured with the provided module.
28
107
  *
29
- * @param appModule - The root CLI module class that contains commands and/or imports other modules
30
- * @param options - Optional configuration options for the application
31
- * @returns A promise that resolves to a configured CommanderApplication instance
108
+ * @param appModule - The root CLI module class decorated with `@CliModule`
109
+ * @param options - Optional configuration options for the CLI application
110
+ * @returns A promise that resolves to a configured NaviosApplication instance
32
111
  *
33
112
  * @example
34
113
  * ```typescript
35
114
  * const app = await CommanderFactory.create(AppModule)
36
115
  * await app.init()
116
+ *
117
+ * const adapter = app.getAdapter()
118
+ * await adapter.run(process.argv)
37
119
  * ```
38
120
  */
39
- static async create(
40
- appModule: ClassTypeWithInstance<NaviosModule>,
41
- options: CommanderApplicationOptions = {},
42
- ) {
43
- const container = new Container()
44
- const app = await container.get(CommanderApplication)
45
- await app.setup(appModule, options)
121
+ static async create<TModule extends NaviosModule = NaviosModule>(
122
+ appModule: ClassTypeWithInstance<TModule>,
123
+ options: CommanderFactoryOptions = {},
124
+ ): Promise<NaviosApplication<CliEnvironment>> {
125
+ const app = await NaviosFactory.create<CliEnvironment>(appModule, {
126
+ adapter: defineCliEnvironment(),
127
+ logger: ConsoleLogger.create({
128
+ logLevels: options.logger?.logLevels,
129
+ showTimeDiff: options.logger?.showTimeDiff ?? false,
130
+ showPid: options.logger?.showPid ?? false,
131
+ showLogLevel: options.logger?.showLogLevel ?? true,
132
+ showPrefix: options.logger?.showPrefix ?? false,
133
+ showContext: options.logger?.showContext ?? true,
134
+ showTimestamp: options.logger?.showTimestamp ?? false,
135
+ }),
136
+ })
137
+
46
138
  return app
47
139
  }
48
140
  }
@@ -0,0 +1,37 @@
1
+ import { inject, Logger } from '@navios/core'
2
+
3
+ import { z } from 'zod'
4
+
5
+ import type { CommandHandler } from '../interfaces/command-handler.interface.mjs'
6
+
7
+ import { Command } from '../decorators/command.decorator.mjs'
8
+ import { CommandRegistryService } from '../services/command-registry.service.mjs'
9
+
10
+ const helpOptionsSchema = z.object({
11
+ command: z.string().optional(),
12
+ })
13
+
14
+ type HelpOptions = z.infer<typeof helpOptionsSchema>
15
+
16
+ /**
17
+ * Built-in help command that lists all available commands or shows help for a specific command.
18
+ *
19
+ * @public
20
+ */
21
+ @Command({
22
+ path: 'help',
23
+ description: 'Show available commands or help for a specific command',
24
+ optionsSchema: helpOptionsSchema,
25
+ })
26
+ export class HelpCommand implements CommandHandler<HelpOptions> {
27
+ private logger = inject(Logger, { context: 'Commander' })
28
+ private commandRegistry = inject(CommandRegistryService)
29
+
30
+ async execute(options: HelpOptions): Promise<void> {
31
+ if (options.command) {
32
+ this.logger.log(this.commandRegistry.formatCommandHelp(options.command))
33
+ } else {
34
+ this.logger.log(this.commandRegistry.formatCommandList())
35
+ }
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ export * from './help.command.mjs'
@@ -1,8 +1,11 @@
1
1
  import type { ClassType, Registry } from '@navios/core'
2
2
 
3
- import { Injectable, InjectableScope, InjectionToken } from '@navios/core'
3
+ import { Module, getModuleMetadata, getModuleCustomEntry } from '@navios/core'
4
4
 
5
- import { getCliModuleMetadata } from '../metadata/index.mjs'
5
+ import {
6
+ CommandEntryKey,
7
+ type CommandEntryValue,
8
+ } from '../metadata/command-entry.metadata.mjs'
6
9
 
7
10
  /**
8
11
  * Options for the `@CliModule` decorator.
@@ -16,10 +19,20 @@ export interface CliModuleOptions {
16
19
  */
17
20
  commands?: ClassType[] | Set<ClassType>
18
21
  /**
19
- * Array or Set of other CLI modules to import.
20
- * Imported modules' commands will be available in this module.
22
+ * Array or Set of controller classes for HTTP endpoints.
23
+ * Allows mixing HTTP and CLI functionality in the same module.
24
+ */
25
+ controllers?: ClassType[] | Set<ClassType>
26
+ /**
27
+ * Array or Set of other modules to import.
28
+ * Imported modules' commands and controllers will be available.
21
29
  */
22
30
  imports?: ClassType[] | Set<ClassType>
31
+ /**
32
+ * Guards to apply to all controllers in this module.
33
+ * Guards are executed in reverse order (last guard first).
34
+ */
35
+ guards?: ClassType[] | Set<ClassType>
23
36
  /**
24
37
  * Service override classes to import for side effects.
25
38
  * These classes are imported to ensure their @Injectable decorators execute,
@@ -42,7 +55,11 @@ export interface CliModuleOptions {
42
55
  /**
43
56
  * Decorator that marks a class as a CLI module.
44
57
  *
45
- * Modules organize commands and can import other modules to compose larger CLI applications.
58
+ * This decorator extends the standard @Module decorator, adding support for
59
+ * CLI commands while maintaining full compatibility with HTTP controllers.
60
+ * Modules organize commands and can import other modules to compose larger
61
+ * CLI applications.
62
+ *
46
63
  * The module can optionally implement `NaviosModule` interface for lifecycle hooks.
47
64
  *
48
65
  * @param options - Configuration options for the module
@@ -60,17 +77,32 @@ export interface CliModuleOptions {
60
77
  * })
61
78
  * export class AppModule {}
62
79
  * ```
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Mixed HTTP and CLI module
84
+ * @CliModule({
85
+ * controllers: [HealthController],
86
+ * commands: [MigrateCommand],
87
+ * imports: [DatabaseModule],
88
+ * })
89
+ * export class AppModule {}
90
+ * ```
63
91
  */
64
92
  export function CliModule(
65
93
  {
66
94
  commands = [],
95
+ controllers = [],
67
96
  imports = [],
97
+ guards = [],
68
98
  overrides = [],
69
99
  priority,
70
100
  registry,
71
101
  }: CliModuleOptions = {
72
102
  commands: [],
103
+ controllers: [],
73
104
  imports: [],
105
+ guards: [],
74
106
  overrides: [],
75
107
  },
76
108
  ) {
@@ -80,24 +112,30 @@ export function CliModule(
80
112
  '[Navios Commander] @CliModule decorator can only be used on classes.',
81
113
  )
82
114
  }
83
- // Register the module in the service locator
84
- const token = InjectionToken.create(target)
85
- const moduleMetadata = getCliModuleMetadata(target, context)
86
- for (const command of commands) {
87
- moduleMetadata.commands.add(command)
88
- }
89
- for (const importedModule of imports) {
90
- moduleMetadata.imports.add(importedModule)
91
- }
92
- for (const override of overrides) {
93
- moduleMetadata.overrides.add(override)
94
- }
95
115
 
96
- return Injectable({
97
- token,
98
- scope: InjectableScope.Singleton,
116
+ // Apply standard @Module decorator first
117
+ const result = Module({
118
+ controllers,
119
+ imports,
120
+ guards,
121
+ overrides,
99
122
  priority,
100
123
  registry,
101
124
  })(target, context)
125
+
126
+ // Get the module metadata that @Module just created
127
+ const metadata = getModuleMetadata(target, context)
128
+
129
+ // Store commands in customEntries
130
+ const commandSet = getModuleCustomEntry<CommandEntryValue>(
131
+ metadata,
132
+ CommandEntryKey,
133
+ () => new Set(),
134
+ )
135
+ for (const command of commands) {
136
+ commandSet.add(command)
137
+ }
138
+
139
+ return result
102
140
  }
103
141
  }
@@ -16,6 +16,11 @@ export interface CommandOptions {
16
16
  * Can be a single word (e.g., 'greet') or multi-word with colons (e.g., 'user:create', 'db:migrate').
17
17
  */
18
18
  path: string
19
+ /**
20
+ * Optional description of the command for help text.
21
+ * Displayed when users run `help` or `--help`.
22
+ */
23
+ description?: string
19
24
  /**
20
25
  * Optional Zod schema for validating command options.
21
26
  * If provided, options will be validated and parsed according to this schema.
@@ -65,6 +70,7 @@ export interface CommandOptions {
65
70
  */
66
71
  export function Command({
67
72
  path,
73
+ description,
68
74
  optionsSchema,
69
75
  priority,
70
76
  registry,
@@ -77,7 +83,7 @@ export function Command({
77
83
  }
78
84
  const token = InjectionToken.create(target)
79
85
  if (context.metadata) {
80
- getCommandMetadata(target, context, path, optionsSchema)
86
+ getCommandMetadata(target, context, path, description, optionsSchema)
81
87
  }
82
88
  return Injectable({
83
89
  token,
@@ -0,0 +1,39 @@
1
+ import type { AnyInjectableType, InjectionToken } from '@navios/core'
2
+
3
+ import { AdapterToken } from '@navios/core'
4
+
5
+ import { CommanderAdapterService } from './services/commander-adapter.service.mjs'
6
+ import { CommandRegistryService } from './services/command-registry.service.mjs'
7
+ import { CliParserService } from './services/cli-parser.service.mjs'
8
+
9
+ /**
10
+ * Defines the CLI environment configuration for use with NaviosFactory.
11
+ *
12
+ * This function returns the token mappings needed to configure a CLI application.
13
+ * Use it with `NaviosFactory.create()` to create a CLI application.
14
+ *
15
+ * @returns Environment configuration with token mappings
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import { NaviosFactory } from '@navios/core'
20
+ * import { defineCliEnvironment, type CliEnvironment } from '@navios/commander'
21
+ *
22
+ * const app = await NaviosFactory.create<CliEnvironment>(AppModule, {
23
+ * adapter: defineCliEnvironment(),
24
+ * })
25
+ * await app.init()
26
+ *
27
+ * const adapter = app.getAdapter() as AbstractCliAdapterInterface
28
+ * await adapter.run(process.argv)
29
+ * ```
30
+ */
31
+ export function defineCliEnvironment() {
32
+ const tokens = new Map<InjectionToken<any, undefined>, AnyInjectableType>([
33
+ [AdapterToken, CommanderAdapterService],
34
+ ])
35
+ return { tokens }
36
+ }
37
+
38
+ // Re-export services for direct access if needed
39
+ export { CommanderAdapterService, CommandRegistryService, CliParserService }
package/src/index.mts CHANGED
@@ -2,9 +2,10 @@
2
2
  export * from '@navios/core'
3
3
 
4
4
  // Export commander-specific exports
5
- export * from './commander.application.mjs'
5
+ export * from './commands/index.mjs'
6
6
  export * from './commander.factory.mjs'
7
7
  export * from './decorators/index.mjs'
8
+ export * from './define-environment.mjs'
8
9
  export * from './interfaces/index.mjs'
9
10
  export * from './metadata/index.mjs'
10
11
  export * from './services/index.mjs'
@@ -0,0 +1,52 @@
1
+ import type { AbstractAdapterInterface } from '@navios/core'
2
+
3
+ /**
4
+ * Interface for CLI adapters.
5
+ * Extends the base adapter interface with CLI-specific methods.
6
+ *
7
+ * @public
8
+ */
9
+ export interface AbstractCliAdapterInterface extends AbstractAdapterInterface {
10
+ /**
11
+ * Run the CLI application with the given arguments.
12
+ * Parses arguments and executes the matching command.
13
+ *
14
+ * @param argv - Command-line arguments array (defaults to `process.argv`)
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const adapter = app.getAdapter() as AbstractCliAdapterInterface
19
+ * await adapter.run(process.argv)
20
+ * ```
21
+ */
22
+ run(argv?: string[]): Promise<void>
23
+
24
+ /**
25
+ * Execute a command programmatically with the provided options.
26
+ *
27
+ * @param path - The command path (e.g., 'greet', 'user:create')
28
+ * @param options - The command options object
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * await adapter.executeCommand('user:create', {
33
+ * name: 'John',
34
+ * email: 'john@example.com',
35
+ * })
36
+ * ```
37
+ */
38
+ executeCommand(path: string, options: Record<string, unknown>): Promise<void>
39
+
40
+ /**
41
+ * Get all registered command paths and their class references.
42
+ *
43
+ * @returns Array of objects containing path and class
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const commands = adapter.getAllCommands()
48
+ * commands.forEach(({ path }) => console.log(path))
49
+ * ```
50
+ */
51
+ getAllCommands(): Array<{ path: string; class: unknown }>
52
+ }
@@ -8,7 +8,7 @@ import type { CommandMetadata } from '../metadata/command.metadata.mjs'
8
8
  *
9
9
  * @example
10
10
  * ```typescript
11
- * import { inject, Injectable } from '@navios/di'
11
+ * import { inject, Injectable } from '@navios/core'
12
12
  * import { CommandExecutionContext } from '@navios/commander'
13
13
  *
14
14
  * @Injectable()
@@ -0,0 +1,34 @@
1
+ import type { AdapterEnvironment } from '@navios/core'
2
+
3
+ import type { AbstractCliAdapterInterface } from './abstract-cli-adapter.interface.mjs'
4
+
5
+ /**
6
+ * Options for configuring the CLI adapter.
7
+ *
8
+ * @public
9
+ */
10
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
11
+ export interface CliAdapterOptions {
12
+ // Reserved for future options
13
+ }
14
+
15
+ /**
16
+ * Environment type definition for CLI adapters.
17
+ * Used with NaviosFactory.create<CliEnvironment>() for type-safe CLI applications.
18
+ *
19
+ * @public
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { NaviosFactory } from '@navios/core'
24
+ * import { defineCliEnvironment, type CliEnvironment } from '@navios/commander'
25
+ *
26
+ * const app = await NaviosFactory.create<CliEnvironment>(AppModule, {
27
+ * adapter: defineCliEnvironment(),
28
+ * })
29
+ * ```
30
+ */
31
+ export interface CliEnvironment extends AdapterEnvironment {
32
+ options: CliAdapterOptions
33
+ adapter: AbstractCliAdapterInterface
34
+ }
@@ -1,2 +1,4 @@
1
1
  export * from './command-handler.interface.mjs'
2
2
  export * from './commander-execution-context.interface.mjs'
3
+ export * from './abstract-cli-adapter.interface.mjs'
4
+ export * from './environment.interface.mjs'
@@ -0,0 +1,41 @@
1
+ import type { ClassType } from '@navios/core'
2
+
3
+ import { extractModuleMetadata } from '@navios/core'
4
+
5
+ /**
6
+ * Symbol key for storing commands in ModuleMetadata.customEntries.
7
+ * Used by @CliModule to store command classes.
8
+ *
9
+ * @public
10
+ */
11
+ export const CommandEntryKey = Symbol('CommandEntryKey')
12
+
13
+ /**
14
+ * Type for the command entry value stored in customEntries.
15
+ *
16
+ * @public
17
+ */
18
+ export type CommandEntryValue = Set<ClassType>
19
+
20
+ /**
21
+ * Extracts commands from a module's metadata.
22
+ * Returns empty set if no commands are defined.
23
+ *
24
+ * @param moduleClass - The module class decorated with @CliModule or @Module
25
+ * @returns Set of command classes registered in the module
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const commands = extractModuleCommands(AppModule)
30
+ * for (const command of commands) {
31
+ * console.log(command.name)
32
+ * }
33
+ * ```
34
+ */
35
+ export function extractModuleCommands(moduleClass: ClassType): Set<ClassType> {
36
+ const metadata = extractModuleMetadata(moduleClass)
37
+ return (
38
+ (metadata.customEntries.get(CommandEntryKey) as CommandEntryValue) ??
39
+ new Set()
40
+ )
41
+ }
@@ -17,6 +17,10 @@ export interface CommandMetadata {
17
17
  * The command path (e.g., 'greet', 'user:create').
18
18
  */
19
19
  path: string
20
+ /**
21
+ * Optional description of the command for help text.
22
+ */
23
+ description?: string
20
24
  /**
21
25
  * Optional Zod schema for validating command options.
22
26
  */
@@ -34,6 +38,7 @@ export interface CommandMetadata {
34
38
  * @param target - The command class
35
39
  * @param context - The decorator context
36
40
  * @param path - The command path
41
+ * @param description - Optional description for help text
37
42
  * @param optionsSchema - Optional Zod schema
38
43
  * @returns The command metadata
39
44
  */
@@ -41,6 +46,7 @@ export function getCommandMetadata(
41
46
  target: ClassType,
42
47
  context: ClassDecoratorContext,
43
48
  path: string,
49
+ description?: string,
44
50
  optionsSchema?: ZodObject,
45
51
  ): CommandMetadata {
46
52
  if (context.metadata) {
@@ -52,6 +58,7 @@ export function getCommandMetadata(
52
58
  } else {
53
59
  const newMetadata: CommandMetadata = {
54
60
  path,
61
+ description,
55
62
  optionsSchema,
56
63
  customAttributes: new Map<string | symbol, any>(),
57
64
  }
@@ -1,2 +1,2 @@
1
1
  export * from './command.metadata.mjs'
2
- export * from './cli-module.metadata.mjs'
2
+ export * from './command-entry.metadata.mjs'
@@ -286,20 +286,20 @@ export class CliParserService {
286
286
 
287
287
  /**
288
288
  * Checks if a Zod schema represents a boolean type
289
- * Unwraps ZodOptional and ZodDefault
289
+ * Unwraps ZodOptional and ZodDefault using Zod v4 API
290
290
  */
291
291
  private isSchemaBoolean(schema: ZodType): boolean {
292
292
  try {
293
293
  let currentSchema = schema
294
- const typeName = currentSchema.def.type
294
+ let typeName = currentSchema.def.type
295
295
 
296
- // Unwrap ZodOptional and ZodDefault
297
- if (typeName === 'optional' || typeName === 'default') {
298
- currentSchema = (currentSchema as any)?._def?.innerType || currentSchema
296
+ // Unwrap ZodOptional and ZodDefault using Zod v4's def.innerType
297
+ while (typeName === 'optional' || typeName === 'default') {
298
+ currentSchema = (currentSchema as any)?.def?.innerType || currentSchema
299
+ typeName = currentSchema.def.type
299
300
  }
300
301
 
301
- const innerTypeName = currentSchema.def.type
302
- return innerTypeName === 'boolean'
302
+ return typeName === 'boolean'
303
303
  } catch {
304
304
  return false
305
305
  }
@@ -307,36 +307,22 @@ export class CliParserService {
307
307
 
308
308
  /**
309
309
  * Checks if a Zod schema represents an array type
310
- * Unwraps ZodOptional and ZodDefault
310
+ * Unwraps ZodOptional and ZodDefault using Zod v4 API
311
311
  */
312
312
  private isSchemaArray(schema: ZodType): boolean {
313
313
  try {
314
314
  let currentSchema = schema
315
- const typeName = currentSchema.def.type
315
+ let typeName = currentSchema.def.type
316
316
 
317
- // Unwrap ZodOptional and ZodDefault
318
- if (typeName === 'optional' || typeName === 'default') {
319
- currentSchema = (currentSchema as any)?._def?.innerType || currentSchema
317
+ // Unwrap ZodOptional and ZodDefault using Zod v4's def.innerType
318
+ while (typeName === 'optional' || typeName === 'default') {
319
+ currentSchema = (currentSchema as any)?.def?.innerType || currentSchema
320
+ typeName = currentSchema.def.type
320
321
  }
321
322
 
322
- const innerTypeName = currentSchema.def.type
323
- return innerTypeName === 'array'
323
+ return typeName === 'array'
324
324
  } catch {
325
325
  return false
326
326
  }
327
327
  }
328
-
329
- /**
330
- * Formats help text listing all available commands.
331
- *
332
- * @param commands - Array of command objects with path and class
333
- * @returns Formatted string listing all commands
334
- */
335
- formatCommandList(commands: Array<{ path: string; class: any }>): string {
336
- const lines = ['Available commands:', '']
337
- for (const { path } of commands) {
338
- lines.push(` ${path}`)
339
- }
340
- return lines.join('\n')
341
- }
342
328
  }