@navios/commander 0.5.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/LICENSE +7 -0
- package/README.md +242 -0
- package/dist/src/commander.application.d.mts +29 -0
- package/dist/src/commander.application.d.mts.map +1 -0
- package/dist/src/commander.factory.d.mts +8 -0
- package/dist/src/commander.factory.d.mts.map +1 -0
- package/dist/src/decorators/cli-module.decorator.d.mts +7 -0
- package/dist/src/decorators/cli-module.decorator.d.mts.map +1 -0
- package/dist/src/decorators/command.decorator.d.mts +8 -0
- package/dist/src/decorators/command.decorator.d.mts.map +1 -0
- package/dist/src/decorators/index.d.mts +3 -0
- package/dist/src/decorators/index.d.mts.map +1 -0
- package/dist/src/index.d.mts +8 -0
- package/dist/src/index.d.mts.map +1 -0
- package/dist/src/interfaces/cli-module.interface.d.mts +5 -0
- package/dist/src/interfaces/cli-module.interface.d.mts.map +1 -0
- package/dist/src/interfaces/command-handler.interface.d.mts +4 -0
- package/dist/src/interfaces/command-handler.interface.d.mts.map +1 -0
- package/dist/src/interfaces/index.d.mts +3 -0
- package/dist/src/interfaces/index.d.mts.map +1 -0
- package/dist/src/interfaces/module.interface.d.mts +5 -0
- package/dist/src/interfaces/module.interface.d.mts.map +1 -0
- package/dist/src/metadata/cli-module.metadata.d.mts +11 -0
- package/dist/src/metadata/cli-module.metadata.d.mts.map +1 -0
- package/dist/src/metadata/command.metadata.d.mts +12 -0
- package/dist/src/metadata/command.metadata.d.mts.map +1 -0
- package/dist/src/metadata/index.d.mts +3 -0
- package/dist/src/metadata/index.d.mts.map +1 -0
- package/dist/src/services/cli-parser.service.d.mts +44 -0
- package/dist/src/services/cli-parser.service.d.mts.map +1 -0
- package/dist/src/services/index.d.mts +3 -0
- package/dist/src/services/index.d.mts.map +1 -0
- package/dist/src/services/module-loader.service.d.mts +33 -0
- package/dist/src/services/module-loader.service.d.mts.map +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsup.config.d.mts +3 -0
- package/dist/tsup.config.d.mts.map +1 -0
- package/dist/vitest.config.d.mts +3 -0
- package/dist/vitest.config.d.mts.map +1 -0
- package/lib/_tsup-dts-rollup.d.mts +456 -0
- package/lib/_tsup-dts-rollup.d.ts +456 -0
- package/lib/index.d.mts +98 -0
- package/lib/index.d.ts +98 -0
- package/lib/index.js +541 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +524 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +40 -0
- package/project.json +66 -0
- package/src/__tests__/commander.factory.e2e.spec.mts +965 -0
- package/src/commander.application.mts +159 -0
- package/src/commander.factory.mts +20 -0
- package/src/decorators/cli-module.decorator.mts +39 -0
- package/src/decorators/command.decorator.mts +29 -0
- package/src/decorators/index.mts +2 -0
- package/src/index.mts +7 -0
- package/src/interfaces/command-handler.interface.mts +3 -0
- package/src/interfaces/index.mts +2 -0
- package/src/interfaces/module.interface.mts +4 -0
- package/src/metadata/cli-module.metadata.mts +54 -0
- package/src/metadata/command.metadata.mts +54 -0
- package/src/metadata/index.mts +2 -0
- package/src/services/__tests__/cli-parser.service.spec.mts +404 -0
- package/src/services/cli-parser.service.mts +231 -0
- package/src/services/index.mts +2 -0
- package/src/services/module-loader.service.mts +120 -0
- package/tsconfig.json +18 -0
- package/tsconfig.lib.json +8 -0
- package/tsconfig.spec.json +13 -0
- package/tsup.config.mts +12 -0
- package/vitest.config.mts +9 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { ClassTypeWithInstance, InjectionToken } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import { Container, inject, Injectable } from '@navios/di'
|
|
4
|
+
|
|
5
|
+
import type { CommandHandler, Module } from './interfaces/index.mjs'
|
|
6
|
+
|
|
7
|
+
import { CliParserService, ModuleLoaderService } from './services/index.mjs'
|
|
8
|
+
|
|
9
|
+
export interface CommanderApplicationOptions {}
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class CommanderApplication {
|
|
13
|
+
private moduleLoader = inject(ModuleLoaderService)
|
|
14
|
+
private cliParser = inject(CliParserService)
|
|
15
|
+
protected container = inject(Container)
|
|
16
|
+
|
|
17
|
+
private appModule: ClassTypeWithInstance<Module> | null = null
|
|
18
|
+
private options: CommanderApplicationOptions = {}
|
|
19
|
+
|
|
20
|
+
isInitialized = false
|
|
21
|
+
|
|
22
|
+
async setup(
|
|
23
|
+
appModule: ClassTypeWithInstance<Module>,
|
|
24
|
+
options: CommanderApplicationOptions = {},
|
|
25
|
+
) {
|
|
26
|
+
this.appModule = appModule
|
|
27
|
+
this.options = options
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getContainer() {
|
|
31
|
+
return this.container
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async init() {
|
|
35
|
+
if (!this.appModule) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
'[Navios Commander] App module is not set. Call setup() first.',
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
await this.moduleLoader.loadModules(this.appModule)
|
|
41
|
+
this.isInitialized = true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async executeCommand(commandPath: string, options: any = {}) {
|
|
45
|
+
if (!this.isInitialized) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'[Navios Commander] Application is not initialized. Call init() first.',
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Use pre-collected command metadata from module loading
|
|
52
|
+
const commandWithMetadata = this.moduleLoader.getCommandByPath(commandPath)
|
|
53
|
+
|
|
54
|
+
if (!commandWithMetadata) {
|
|
55
|
+
throw new Error(`[Navios Commander] Command not found: ${commandPath}`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { class: commandClass, metadata } = commandWithMetadata
|
|
59
|
+
|
|
60
|
+
// Validate options with zod schema if provided
|
|
61
|
+
let validatedOptions = options
|
|
62
|
+
if (metadata.optionsSchema) {
|
|
63
|
+
validatedOptions = metadata.optionsSchema.parse(options)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get command instance and execute
|
|
67
|
+
const commandInstance = await this.container.get<CommandHandler>(
|
|
68
|
+
commandClass as unknown as InjectionToken<CommandHandler>,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if (!commandInstance.execute) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`[Navios Commander] Command ${commandPath} does not implement execute method`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await commandInstance.execute(validatedOptions)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getAllCommands() {
|
|
81
|
+
// Use pre-collected command metadata from module loading
|
|
82
|
+
const commandsMap = this.moduleLoader.getAllCommandsWithMetadata()
|
|
83
|
+
const commandsWithMetadata: Array<{
|
|
84
|
+
path: string
|
|
85
|
+
class: ClassTypeWithInstance<any>
|
|
86
|
+
}> = []
|
|
87
|
+
|
|
88
|
+
for (const [, { class: cmd, metadata }] of commandsMap) {
|
|
89
|
+
commandsWithMetadata.push({
|
|
90
|
+
path: metadata.path,
|
|
91
|
+
class: cmd,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return commandsWithMetadata
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Runs the CLI application by parsing process.argv and executing the command
|
|
100
|
+
* @param argv - Command-line arguments (defaults to process.argv)
|
|
101
|
+
*/
|
|
102
|
+
async run(argv: string[] = process.argv) {
|
|
103
|
+
if (!this.isInitialized) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
'[Navios Commander] Application is not initialized. Call init() first.',
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// First, try to extract the command path to get its schema
|
|
111
|
+
// We need to do a preliminary parse to find the command
|
|
112
|
+
const preliminaryParse = this.cliParser.parse(argv)
|
|
113
|
+
const commandWithMetadata = this.moduleLoader.getCommandByPath(
|
|
114
|
+
preliminaryParse.command,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// Re-parse with schema if available
|
|
118
|
+
const parsed = commandWithMetadata?.metadata.optionsSchema
|
|
119
|
+
? this.cliParser.parse(argv, commandWithMetadata.metadata.optionsSchema)
|
|
120
|
+
: preliminaryParse
|
|
121
|
+
|
|
122
|
+
// Handle special commands
|
|
123
|
+
if (
|
|
124
|
+
parsed.command === 'help' ||
|
|
125
|
+
parsed.options.help ||
|
|
126
|
+
parsed.options.h
|
|
127
|
+
) {
|
|
128
|
+
const commands = this.getAllCommands()
|
|
129
|
+
console.log(this.cliParser.formatCommandList(commands))
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Execute the command
|
|
134
|
+
await this.executeCommand(parsed.command, parsed.options)
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (error instanceof Error) {
|
|
137
|
+
console.error(`Error: ${error.message}`)
|
|
138
|
+
|
|
139
|
+
// Show available commands on error
|
|
140
|
+
if (error.message.includes('Command not found')) {
|
|
141
|
+
console.log(
|
|
142
|
+
'\n' + this.cliParser.formatCommandList(this.getAllCommands()),
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
throw error
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async dispose() {
|
|
151
|
+
if (this.moduleLoader) {
|
|
152
|
+
this.moduleLoader.dispose()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async close() {
|
|
157
|
+
await this.dispose()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ClassTypeWithInstance } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import { Container } from '@navios/di'
|
|
4
|
+
|
|
5
|
+
import type { CommanderApplicationOptions } from './commander.application.mjs'
|
|
6
|
+
import type { Module } from './interfaces/index.mjs'
|
|
7
|
+
|
|
8
|
+
import { CommanderApplication } from './commander.application.mjs'
|
|
9
|
+
|
|
10
|
+
export class CommanderFactory {
|
|
11
|
+
static async create(
|
|
12
|
+
appModule: ClassTypeWithInstance<Module>,
|
|
13
|
+
options: CommanderApplicationOptions = {},
|
|
14
|
+
) {
|
|
15
|
+
const container = new Container()
|
|
16
|
+
const app = await container.get(CommanderApplication)
|
|
17
|
+
await app.setup(appModule, options)
|
|
18
|
+
return app
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import { Injectable, InjectableScope, InjectionToken } from '@navios/di'
|
|
4
|
+
|
|
5
|
+
import { getCliModuleMetadata } from '../metadata/index.mjs'
|
|
6
|
+
|
|
7
|
+
export interface CliModuleOptions {
|
|
8
|
+
commands?: ClassType[] | Set<ClassType>
|
|
9
|
+
imports?: ClassType[] | Set<ClassType>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function CliModule(
|
|
13
|
+
{ commands = [], imports = [] }: CliModuleOptions = {
|
|
14
|
+
commands: [],
|
|
15
|
+
imports: [],
|
|
16
|
+
},
|
|
17
|
+
) {
|
|
18
|
+
return (target: ClassType, context: ClassDecoratorContext) => {
|
|
19
|
+
if (context.kind !== 'class') {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'[Navios Commander] @CliModule decorator can only be used on classes.',
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
// Register the module in the service locator
|
|
25
|
+
const token = InjectionToken.create(target)
|
|
26
|
+
const moduleMetadata = getCliModuleMetadata(target, context)
|
|
27
|
+
for (const command of commands) {
|
|
28
|
+
moduleMetadata.commands.add(command)
|
|
29
|
+
}
|
|
30
|
+
for (const importedModule of imports) {
|
|
31
|
+
moduleMetadata.imports.add(importedModule)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return Injectable({
|
|
35
|
+
token,
|
|
36
|
+
scope: InjectableScope.Singleton,
|
|
37
|
+
})(target, context)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/di'
|
|
2
|
+
import type { ZodObject } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { Injectable, InjectableScope, InjectionToken } from '@navios/di'
|
|
5
|
+
|
|
6
|
+
import { getCommandMetadata } from '../metadata/index.mjs'
|
|
7
|
+
|
|
8
|
+
export interface CommandOptions {
|
|
9
|
+
path: string
|
|
10
|
+
optionsSchema?: ZodObject
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Command({ path, optionsSchema }: CommandOptions) {
|
|
14
|
+
return function (target: ClassType, context: ClassDecoratorContext) {
|
|
15
|
+
if (context.kind !== 'class') {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'[Navios Commander] @Command decorator can only be used on classes.',
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
const token = InjectionToken.create(target)
|
|
21
|
+
if (context.metadata) {
|
|
22
|
+
getCommandMetadata(target, context, path, optionsSchema)
|
|
23
|
+
}
|
|
24
|
+
return Injectable({
|
|
25
|
+
token,
|
|
26
|
+
scope: InjectableScope.Singleton,
|
|
27
|
+
})(target, context)
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/index.mts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from '@navios/di'
|
|
2
|
+
export * from './commander.application.mjs'
|
|
3
|
+
export * from './commander.factory.mjs'
|
|
4
|
+
export * from './decorators/index.mjs'
|
|
5
|
+
export * from './interfaces/index.mjs'
|
|
6
|
+
export * from './metadata/index.mjs'
|
|
7
|
+
export * from './services/index.mjs'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
export const CliModuleMetadataKey = Symbol('CliModuleMetadataKey')
|
|
4
|
+
|
|
5
|
+
export interface CliModuleMetadata {
|
|
6
|
+
commands: Set<ClassType>
|
|
7
|
+
imports: Set<ClassType>
|
|
8
|
+
customAttributes: Map<string | symbol, any>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCliModuleMetadata(
|
|
12
|
+
target: ClassType,
|
|
13
|
+
context: ClassDecoratorContext,
|
|
14
|
+
): CliModuleMetadata {
|
|
15
|
+
if (context.metadata) {
|
|
16
|
+
const metadata = context.metadata[CliModuleMetadataKey] as
|
|
17
|
+
| CliModuleMetadata
|
|
18
|
+
| undefined
|
|
19
|
+
if (metadata) {
|
|
20
|
+
return metadata
|
|
21
|
+
} else {
|
|
22
|
+
const newMetadata: CliModuleMetadata = {
|
|
23
|
+
commands: new Set<ClassType>(),
|
|
24
|
+
imports: new Set<ClassType>(),
|
|
25
|
+
customAttributes: new Map<string | symbol, any>(),
|
|
26
|
+
}
|
|
27
|
+
context.metadata[CliModuleMetadataKey] = newMetadata
|
|
28
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
29
|
+
target[CliModuleMetadataKey] = newMetadata
|
|
30
|
+
return newMetadata
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new Error('[Navios Commander] Wrong environment.')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractCliModuleMetadata(
|
|
37
|
+
target: ClassType,
|
|
38
|
+
): CliModuleMetadata {
|
|
39
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
40
|
+
const metadata = target[CliModuleMetadataKey] as
|
|
41
|
+
| CliModuleMetadata
|
|
42
|
+
| undefined
|
|
43
|
+
if (!metadata) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`[Navios Commander] Module metadata not found for ${target.name}. Make sure to use @CliModule decorator.`,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
return metadata
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function hasCliModuleMetadata(target: ClassType): boolean {
|
|
52
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
53
|
+
return !!target[CliModuleMetadataKey]
|
|
54
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ClassType } from '@navios/di'
|
|
2
|
+
import type { ZodObject } from 'zod'
|
|
3
|
+
|
|
4
|
+
export const CommandMetadataKey = Symbol('CommandMetadataKey')
|
|
5
|
+
|
|
6
|
+
export interface CommandMetadata {
|
|
7
|
+
path: string
|
|
8
|
+
optionsSchema?: ZodObject
|
|
9
|
+
customAttributes: Map<string | symbol, any>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getCommandMetadata(
|
|
13
|
+
target: ClassType,
|
|
14
|
+
context: ClassDecoratorContext,
|
|
15
|
+
path: string,
|
|
16
|
+
optionsSchema?: ZodObject,
|
|
17
|
+
): CommandMetadata {
|
|
18
|
+
if (context.metadata) {
|
|
19
|
+
const metadata = context.metadata[CommandMetadataKey] as
|
|
20
|
+
| CommandMetadata
|
|
21
|
+
| undefined
|
|
22
|
+
if (metadata) {
|
|
23
|
+
return metadata
|
|
24
|
+
} else {
|
|
25
|
+
const newMetadata: CommandMetadata = {
|
|
26
|
+
path,
|
|
27
|
+
optionsSchema,
|
|
28
|
+
customAttributes: new Map<string | symbol, any>(),
|
|
29
|
+
}
|
|
30
|
+
context.metadata[CommandMetadataKey] = newMetadata
|
|
31
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
32
|
+
target[CommandMetadataKey] = newMetadata
|
|
33
|
+
return newMetadata
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw new Error('[Navios Commander] Wrong environment.')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function extractCommandMetadata(target: ClassType): CommandMetadata {
|
|
40
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
41
|
+
const metadata = target[CommandMetadataKey] as CommandMetadata | undefined
|
|
42
|
+
if (!metadata) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
'[Navios Commander] Command metadata not found. Make sure to use @Command decorator.',
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
return metadata
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function hasCommandMetadata(target: ClassType): boolean {
|
|
51
|
+
// @ts-expect-error We add a custom metadata key to the target
|
|
52
|
+
const metadata = target[CommandMetadataKey] as CommandMetadata | undefined
|
|
53
|
+
return !!metadata
|
|
54
|
+
}
|