Skip to content

Commit

Permalink
feat: add support for defining default command
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Dec 20, 2019
1 parent c0d6b72 commit 9ab2a25
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 15 deletions.
9 changes: 6 additions & 3 deletions src/BaseCommand/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ import { Prompt, FakePrompt } from '@poppinss/prompts'
import { ApplicationContract } from '@ioc:Adonis/Core/Application'

import { Generator } from '../Generator'
import { CommandContract, CommandArg, CommandFlag } from '../Contracts'
import { CommandContract, CommandArg, CommandFlag, KernelContract } from '../Contracts'

/**
* Abstract base class other classes must extend
*/
export abstract class BaseCommand implements CommandContract {
/**
* Accepting AdonisJs application instance.
* Accepting AdonisJs application instance and kernel instance
*/
constructor (public application: ApplicationContract) {
constructor (
public application: ApplicationContract,
public kernel: KernelContract,
) {
}

/**
Expand Down
48 changes: 48 additions & 0 deletions src/Contracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export interface CommandContract {
prompt: PromptContract,
colors: Colors,
generator: GeneratorContract,
kernel: KernelContract,
handle (...args: any[]): Promise<void>,
}

Expand All @@ -130,12 +131,59 @@ export type ManifestNode = {
[command: string]: ManifestCommand,
}

/**
* Manifest interface
*/
export interface ManifestContract {
loadCommand (commandPath: string): { command: CommandConstructorContract, commandPath: string }
lookupCommands (commandPath: string)
generate (commandPaths: string[]): Promise<void>
load (): Promise<ManifestNode>
}

/**
* Callbacks for different style of hooks
*/
export type FindHookCallback = (command: SerializedCommandContract | null) => Promise<void> | void
export type RunHookCallback = (command: CommandContract) => Promise<void> | void

/**
* Shape of ace kernel
*/
export interface KernelContract {
manifestCommands?: ManifestNode
defaultCommand: CommandConstructorContract
commands: { [name: string]: CommandConstructorContract }
flags: { [name: string]: CommandFlag & { handler: GlobalFlagHandler } }

before (action: 'run', callback: RunHookCallback): this
before (action: 'find', callback: FindHookCallback): this
before (action: 'run' | 'find', callback: RunHookCallback | FindHookCallback)

after (action: 'run', callback: RunHookCallback): this
after (action: 'find', callback: FindHookCallback): this
after (action: 'run' | 'find', callback: RunHookCallback | FindHookCallback): this

register (commands: CommandConstructorContract[]): this
getSuggestions (name: string, distance?: number): string[]

flag (
name: string,
handler: GlobalFlagHandler,
options: Partial<Exclude<CommandFlag, 'name' | 'propertyName'>>,
): this

find (argv: string[]): Promise<CommandConstructorContract | null>
runCommand (argv: string[], commandInstance: CommandContract): Promise<any>
handle (argv: string[]): Promise<any>
exec (commandName: string, args: string[]): Promise<any>

preloadManifest (): void
useManifest (manifest: ManifestContract): this

printHelp (command?: CommandConstructorContract): void
}

/**
* Template generator options
*/
Expand Down
23 changes: 23 additions & 0 deletions src/HelpCommand/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* @adonisjs/ace
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { BaseCommand } from '../BaseCommand'
import { CommandContract } from '../Contracts'

/**
* The help command for print the help output
*/
export class HelpCommand extends BaseCommand implements CommandContract {
public static commandName = 'help'
public static description = 'See help for all the commands'

public async handle () {
this.kernel.printHelp()
}
}
29 changes: 24 additions & 5 deletions src/Kernel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import { Hooks } from '../Hooks'
import { Parser } from '../Parser'
import { Manifest } from '../Manifest'
import { HelpCommand } from '../HelpCommand'
import { printHelp, printHelpFor } from '../utils/help'
import { validateCommand } from '../utils/validateCommand'
import { InvalidCommandException } from '../Exceptions/InvalidCommandException'
import { printHelp, printHelpFor } from '../utils/help'

import {
CommandFlag,
ManifestNode,
KernelContract,
CommandContract,
ManifestCommand,
RunHookCallback,
Expand All @@ -32,7 +34,7 @@ import {
* Ace kernel class is used to register, find and invoke commands by
* parsing `process.argv.splice(2)` value.
*/
export class Kernel {
export class Kernel implements KernelContract {
/**
* List of registered commands
*/
Expand Down Expand Up @@ -62,7 +64,11 @@ export class Kernel {
*/
private hooks = new Hooks()

// private defaultCommand: CommandConstructorContract
/**
* The default command that will be invoked when no defined is
* defined
*/
public defaultCommand: CommandConstructorContract = HelpCommand

constructor (public application: ApplicationContract) {
}
Expand Down Expand Up @@ -323,8 +329,18 @@ export class Kernel {
* and setting them on the command instance
*/
public async handle (argv: string[]) {
/**
* Execute the default command when no command is mentioned
*/
if (!argv.length) {
return
this.defaultCommand.$boot()
validateCommand(this.defaultCommand)

const commandInstance = this.application.container.make(
this.defaultCommand as any,
[this.application as any, this as any],
)
return this.runCommand([], commandInstance)
}

await this.preloadManifest()
Expand Down Expand Up @@ -359,7 +375,10 @@ export class Kernel {
throw InvalidCommandException.invoke(commandName)
}

const commandInstance = this.application.container.make(command as any, [this.application as any])
const commandInstance = this.application.container.make(
command as any,
[this.application as any, this as any],
)
return this.runCommand([commandName].concat(args), commandInstance)
}

Expand Down
4 changes: 2 additions & 2 deletions src/Manifest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ import { esmRequire } from '@poppinss/utils'
import { join, isAbsolute, extname } from 'path'

import { validateCommand } from '../utils/validateCommand'
import { ManifestNode, CommandConstructorContract } from '../Contracts'
import { CommandValidationException } from '../Exceptions/CommandValidationException'
import { MissingManifestFileException } from '../Exceptions/MissingManifestFileException'
import { ManifestNode, CommandConstructorContract, ManifestContract } from '../Contracts'

/**
* Manifest class drastically improves the commands performance, by generating
* a manifest file for all the commands and lazy load only the executed
* command.
*/
export class Manifest {
export class Manifest implements ManifestContract {
constructor (private basePath: string) {
}

Expand Down
124 changes: 122 additions & 2 deletions test/kernel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,8 +1144,8 @@ test.group('Kernel | IoC container', () => {
class Install extends BaseCommand {
public static commandName = 'install'

constructor (public application: Application, public foo: Foo) {
super(application)
constructor (public application: Application, public _kernel, public foo: Foo) {
super(application, _kernel)
}

public async handle () {
Expand Down Expand Up @@ -1182,3 +1182,123 @@ test.group('Kernel | IoC container', () => {
await kernel.handle(['install'])
})
})

test.group('Kernel | defaultCommand', () => {
test('set custom default command', async (assert) => {
assert.plan(1)

class Help extends BaseCommand {
public static commandName = 'help'
public async handle () {
assert.isTrue(true)
}
}

const app = new Application(__dirname, new Ioc(), {}, {})
const kernel = new Kernel(app)
kernel.defaultCommand = Help
await kernel.handle([])
})

test('execute before after hooks for the default command', async (assert) => {
assert.plan(3)

class Help extends BaseCommand {
public static commandName = 'help'
public async handle () {
assert.isTrue(true)
}
}

const app = new Application(__dirname, new Ioc(), {}, {})
const kernel = new Kernel(app)

kernel.before('run', () => {
assert.isTrue(true)
})

kernel.after('run', () => {
assert.isTrue(true)
})

kernel.defaultCommand = Help
await kernel.handle([])
})
})

test.group('Kernel | exec', () => {
test('exec command by name', async (assert) => {
assert.plan(1)

class Foo extends BaseCommand {
public static commandName = 'foo'
public async handle () {
assert.isTrue(true)
}
}

const app = new Application(__dirname, new Ioc(), {}, {})
const kernel = new Kernel(app)
kernel.register([Foo])

await kernel.exec('foo', [])
})

test('pass arguments and flags to command using exec', async (assert) => {
assert.plan(2)

class Foo extends BaseCommand {
public static commandName = 'foo'

@args.string()
public name: string

@flags.boolean()
public isAdmin: boolean

public async handle () {
assert.isTrue(this.isAdmin)
assert.equal(this.name, 'virk')
}
}

const app = new Application(__dirname, new Ioc(), {}, {})
const kernel = new Kernel(app)
kernel.register([Foo])

await kernel.exec('foo', ['virk', '--is-admin=true'])
})

test('exec find and run hooks for the command using exec', async (assert) => {
assert.plan(5)

class Foo extends BaseCommand {
public static commandName = 'foo'
public async handle () {
assert.isTrue(true)
}
}

const app = new Application(__dirname, new Ioc(), {}, {})
const kernel = new Kernel(app)
kernel.register([Foo])

kernel.before('run', () => {
assert.isTrue(true)
})

kernel.after('run', () => {
assert.isTrue(true)
})

kernel.before('find', () => {
assert.isTrue(true)
})

kernel.after('find', () => {
assert.isTrue(true)
})

await kernel.exec('foo', [])
})
})
6 changes: 3 additions & 3 deletions test/manifest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,13 @@ test.group('Manifest', (group) => {
import { inject } from '@adonisjs/fold'
import { BaseCommand } from '../../../src/BaseCommand'
@inject([null, 'App/Foo'])
@inject([null, null, 'App/Foo'])
export default class Greet extends BaseCommand {
public static commandName = 'greet'
public static description = 'Greet a user'
constructor (public rawMode, public foo) {
super(rawMode)
constructor (public app, public kernel, public foo) {
super(app, kernel)
}
public async handle () {
Expand Down

0 comments on commit 9ab2a25

Please sign in to comment.