This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+148
View File
@@ -0,0 +1,148 @@
import { ParsedOptions } from 'getopts';
import { Prompt, FakePrompt } from '@poppinss/prompts';
import { instantiate } from '@poppinss/cliui/build/api';
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { CommandArg, CommandFlag, KernelContract, CommandSettings, CommandContract, GeneratorContract } from '../Contracts';
/**
* Abstract base class other classes must extend
*/
export declare abstract class BaseCommand implements CommandContract {
application: ApplicationContract;
kernel: KernelContract;
/**
* Reference to the exit handler
*/
protected exitHandler?: () => void | Promise<void>;
/**
* Accepting AdonisJs application instance and kernel instance
*/
constructor(application: ApplicationContract, kernel: KernelContract);
/**
* Is the current command the main command executed from the
* CLI
*/
get isMain(): boolean;
/**
* Terminal is interactive
*/
get isInteractive(): boolean;
/**
* Command arguments
*/
static args: CommandArg[];
/**
* Command aliases
*/
static aliases: string[];
/**
* Command flags
*/
static flags: CommandFlag[];
/**
* Command name. The command will be registered using this name only. Make
* sure their aren't any spaces inside the command name.
*/
static commandName: string;
/**
* The description of the command displayed on the help screen.
* A good command will always have some description.
*/
static description: string;
/**
* Any settings a command wants to have. Helpful for third party
* tools to read the settings in lifecycle hooks and make
* certain decisions
*/
static settings: CommandSettings;
/**
* Whether or not the command has been booted
*/
static booted: boolean;
/**
* Boots the command by defining required static properties
*/
static boot(): void;
/**
* Define an argument directly on the command without using the decorator
*/
static $addArgument(options: Partial<CommandArg>): void;
/**
* Define a flag directly on the command without using the decorator
*/
static $addFlag(options: Partial<CommandFlag>): void;
/**
* Reference to cli ui
*/
ui: {
table: () => import("@poppinss/cliui/build/src/Table").Table;
tasks: {
(): import("@poppinss/cliui/build/src/Task/Manager").TaskManager;
verbose(): import("@poppinss/cliui/build/src/Task/Manager").TaskManager;
};
icons: {
tick: string;
cross: string;
bullet: string;
nodejs: string;
pointer: string;
info: string;
warning: string;
squareSmallFilled: string;
};
logger: import("@poppinss/cliui/build/src/Logger").Logger;
sticker: () => import("@poppinss/cliui/build/src/Instructions").Instructions;
instructions: () => import("@poppinss/cliui/build/src/Instructions").Instructions;
isInteractive: boolean;
supportsColors: boolean;
consoleRenderer: import("@poppinss/cliui/build/src/Renderer/Console").ConsoleRenderer;
testingRenderer: import("@poppinss/cliui/build/src/Renderer/Memory").MemoryRenderer;
};
/**
* Parsed options on the command. They only exist when the command
* is executed via kernel.
*/
parsed?: ParsedOptions;
/**
* The prompt for the command
*/
prompt: Prompt | FakePrompt;
/**
* Returns the instance of logger to log messages
*/
logger: import("@poppinss/cliui/build/src/Logger").Logger;
/**
* Reference to the colors
*/
colors: ReturnType<typeof instantiate>['logger']['colors'];
/**
* Generator instance to generate entity files
*/
generator: GeneratorContract;
/**
* Error raised by the command
*/
error?: any;
/**
* Command exit code
*/
exitCode?: number;
run?(...args: any[]): Promise<any>;
prepare?(...args: any[]): Promise<any>;
completed?(...args: any[]): Promise<any>;
/**
* Execute the command
*/
exec(): Promise<any>;
/**
* Register an onExit handler
*/
onExit(handler: () => void | Promise<void>): this;
/**
* Trigger exit
*/
exit(): Promise<void>;
/**
* Must be defined by the parent class
*/
handle?(...args: any[]): Promise<any>;
}
+195
View File
@@ -0,0 +1,195 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseCommand = void 0;
const prompts_1 = require("@poppinss/prompts");
const helpers_1 = require("@poppinss/utils/build/helpers");
const api_1 = require("@poppinss/cliui/build/api");
const utils_1 = require("@poppinss/utils");
const Generator_1 = require("../Generator");
/**
* Abstract base class other classes must extend
*/
class BaseCommand {
/**
* Accepting AdonisJs application instance and kernel instance
*/
constructor(application, kernel) {
this.application = application;
this.kernel = kernel;
/**
* Reference to cli ui
*/
this.ui = (0, api_1.instantiate)(this.kernel.isMockingConsoleOutput);
/**
* The prompt for the command
*/
this.prompt = this.kernel.isMockingConsoleOutput
? new prompts_1.FakePrompt()
: new prompts_1.Prompt();
/**
* Returns the instance of logger to log messages
*/
this.logger = this.ui.logger;
/**
* Reference to the colors
*/
this.colors = this.logger.colors;
/**
* Generator instance to generate entity files
*/
this.generator = new Generator_1.Generator(this);
}
/**
* Is the current command the main command executed from the
* CLI
*/
get isMain() {
return this.kernel.isMain(this);
}
/**
* Terminal is interactive
*/
get isInteractive() {
return this.kernel.isInteractive;
}
/**
* Boots the command by defining required static properties
*/
static boot() {
if (this.booted) {
return;
}
this.booted = true;
(0, utils_1.defineStaticProperty)(this, BaseCommand, {
propertyName: 'args',
defaultValue: [],
strategy: 'inherit',
});
(0, utils_1.defineStaticProperty)(this, BaseCommand, {
propertyName: 'aliases',
defaultValue: [],
strategy: 'inherit',
});
(0, utils_1.defineStaticProperty)(this, BaseCommand, {
propertyName: 'flags',
defaultValue: [],
strategy: 'inherit',
});
(0, utils_1.defineStaticProperty)(this, BaseCommand, {
propertyName: 'settings',
defaultValue: {},
strategy: 'inherit',
});
(0, utils_1.defineStaticProperty)(this, BaseCommand, {
propertyName: 'commandName',
defaultValue: '',
strategy: 'define',
});
(0, utils_1.defineStaticProperty)(this, BaseCommand, {
propertyName: 'description',
defaultValue: '',
strategy: 'define',
});
}
/**
* Define an argument directly on the command without using the decorator
*/
static $addArgument(options) {
if (!options.propertyName) {
throw new utils_1.Exception('"propertyName" is required to register a command argument', 500, 'E_MISSING_ARGUMENT_NAME');
}
const arg = Object.assign({
type: options.type || 'string',
propertyName: options.propertyName,
name: options.name || options.propertyName,
required: options.required === false ? false : true,
}, options);
this.args.push(arg);
}
/**
* Define a flag directly on the command without using the decorator
*/
static $addFlag(options) {
if (!options.propertyName) {
throw new utils_1.Exception('"propertyName" is required to register command flag', 500, 'E_MISSING_FLAG_NAME');
}
const flag = Object.assign({
name: options.name || helpers_1.string.snakeCase(options.propertyName).replace(/_/g, '-'),
propertyName: options.propertyName,
type: options.type || 'boolean',
}, options);
this.flags.push(flag);
}
/**
* Execute the command
*/
async exec() {
const hasRun = typeof this.run === 'function';
const hasHandle = typeof this.handle === 'function';
let commandResult;
/**
* Print depreciation warning
*/
if (hasHandle) {
process.emitWarning('DeprecationWarning', `${this.constructor.name}.handle() is deprecated. Define run() method instead`);
}
/**
* Run command and catch any raised exceptions
*/
try {
/**
* Run prepare method when exists on the command instance
*/
if (typeof this.prepare === 'function') {
await this.application.container.callAsync(this, 'prepare', []);
}
/**
* Execute the command handle or run method
*/
commandResult = await this.application.container.callAsync(this, hasRun ? 'run' : 'handle', []);
}
catch (error) {
this.error = error;
}
let errorHandled = false;
/**
* Run completed method when exists
*/
if (typeof this.completed === 'function') {
errorHandled = await this.application.container.callAsync(this, 'completed', []);
}
/**
* Throw error when error exists and the completed method didn't
* handled it
*/
if (this.error && !errorHandled) {
throw this.error;
}
return commandResult;
}
/**
* Register an onExit handler
*/
onExit(handler) {
this.exitHandler = handler;
return this;
}
/**
* Trigger exit
*/
async exit() {
if (typeof this.exitHandler === 'function') {
await this.exitHandler();
}
await this.kernel.exit(this);
}
}
exports.BaseCommand = BaseCommand;
+381
View File
@@ -0,0 +1,381 @@
import * as ui from '@poppinss/cliui';
import { ParsedOptions } from 'getopts';
import { PromptContract } from '@poppinss/prompts';
import { ApplicationContract, AppEnvironments } from '@ioc:Adonis/Core/Application';
/**
* Settings excepted by the command
*/
export declare type CommandSettings = {
loadApp?: boolean;
stayAlive?: boolean;
environment?: AppEnvironments;
} & {
[key: string]: any;
};
/**
* The types of flags can be defined on a command.
*/
export declare type FlagTypes = 'string' | 'number' | 'boolean' | 'array' | 'numArray';
/**
* The types of arguments can be defined on a command.
*/
export declare type ArgTypes = 'string' | 'spread';
/**
* The shape of command argument
*/
export declare type CommandArg = {
propertyName: string;
name: string;
type: ArgTypes;
required: boolean;
description?: string;
};
/**
* The shape of a command flag
*/
export declare type CommandFlag = {
propertyName: string;
name: string;
type: FlagTypes;
description?: string;
alias?: string;
};
/**
* The handler that handles the global
* flags
*/
export declare type GlobalFlagHandler = (value: any, parsed: ParsedOptions, command?: CommandConstructorContract) => any;
/**
* Shape of grouped commands. Required when displaying
* help
*/
export declare type CommandsGroup = {
group: string;
commands: SerializedCommand[];
}[];
/**
* The shared properties that exists on the command implementation
* as well as it's serialized version
*/
export declare type SerializedCommand = {
args: CommandArg[];
aliases: string[];
settings: CommandSettings;
flags: CommandFlag[];
commandName: string;
description: string;
};
/**
* Command constructor shape with it's static properties
*/
export interface CommandConstructorContract extends SerializedCommand {
new (application: ApplicationContract, kernel: KernelContract, ...args: any[]): CommandContract;
/**
* A boolean to know if the command has been booted or not. We initialize some
* static properties to the class during the boot process.
*/
booted: boolean;
/**
* Boot the command. You won't have to run this method by yourself. Ace will internally
* boot the commands by itself.
*/
boot(): void;
/**
* Add an argument directly on the command without using the decorator
*/
$addArgument(options: Partial<CommandArg>): void;
/**
* Add a flag directly on the command without using the decorator
*/
$addFlag(options: Partial<CommandFlag>): void;
}
/**
* The shape of command class
*/
export interface CommandContract {
parsed?: ParsedOptions;
error?: any;
exitCode?: number;
logger: typeof ui.logger;
prompt: PromptContract;
colors: typeof ui.logger.colors;
ui: typeof ui;
generator: GeneratorContract;
kernel: KernelContract;
/**
* The flag is set to true, when the command is executed as the main command
* from the terminal.
*
* However, set to false when command is executed programmatically.
*/
readonly isMain: boolean;
/**
* The flag is set to true, when the commandline is in interactive mode.
* Can be disabled manually via the kernel
*/
readonly isInteractive: boolean;
onExit(callback: () => Promise<void> | void): this;
exit(): Promise<void>;
exec(): Promise<any>;
handle?(...args: any[]): Promise<any>;
run?(...args: any[]): Promise<any>;
prepare?(...args: any[]): Promise<any>;
completed?(...args: any[]): Promise<any>;
}
/**
* Shape of the serialized command inside the manifest JSON file.
*/
export declare type ManifestCommand = SerializedCommand & {
commandPath: string;
};
/**
* Shape of defined aliases
*/
export declare type Aliases = {
[key: string]: string;
};
/**
* Shape of the manifest JSON file
*/
export declare type ManifestNode = {
commands: {
[command: string]: ManifestCommand;
};
aliases: Aliases;
};
/**
* Manifest loader interface
*/
export interface ManifestLoaderContract {
booted: boolean;
boot(): Promise<void>;
/**
* Returns the base path for a given command. Helps in loading
* the command relative from that path
*/
getCommandBasePath(commandName: string): string | undefined;
/**
* Returns manifest command node. One must load the command
* in order to use it
*/
getCommand(commandName: string): {
basePath: string;
command: ManifestCommand;
} | undefined;
/**
* Find if a command exists or not
*/
hasCommand(commandName: string): boolean;
/**
* Load command from the disk. Make sure to use [[hasCommand]] before
* calling this method
*/
loadCommand(commandName: string): Promise<CommandConstructorContract>;
/**
* Returns an array of manifest commands by concatenating the
* commands and aliases from all the manifest files
*/
getCommands(): {
commands: ManifestCommand[];
aliases: Aliases;
};
}
/**
* Callbacks for different style of hooks
*/
export declare type FindHookCallback = (command: SerializedCommand | null) => Promise<any> | any;
export declare type RunHookCallback = (command: CommandContract) => Promise<any> | any;
/**
* Shape of ace kernel
*/
export interface KernelContract {
/**
* The exit code to be used for exiting the process. One should use
* this to exit the process
*/
exitCode?: number;
/**
* Reference to the process error. It can come from the command, flags
* or any other intermediate code.
*/
error?: Error;
/**
* Reference to the default command. Feel free to overwrite it
*/
defaultCommand: CommandConstructorContract;
/**
* A map of locally registered commands
*/
commands: {
[name: string]: CommandConstructorContract;
};
/**
* Registered command aliases
*/
aliases: Aliases;
/**
* A map of global flags
*/
flags: {
[name: string]: CommandFlag & {
handler: GlobalFlagHandler;
};
};
/**
* Register before hooks
*/
before(action: 'run', callback: RunHookCallback): this;
before(action: 'find', callback: FindHookCallback): this;
before(action: 'run' | 'find', callback: RunHookCallback | FindHookCallback): this;
/**
* Register after hooks
*/
after(action: 'run', callback: RunHookCallback): this;
after(action: 'find', callback: FindHookCallback): this;
after(action: 'run' | 'find', callback: RunHookCallback | FindHookCallback): this;
/**
* Register a command directly via class
*/
register(commands: CommandConstructorContract[]): this;
/**
* Register a global flag
*/
flag(name: string, handler: GlobalFlagHandler, options: Partial<Exclude<CommandFlag, 'name' | 'propertyName'>>): this;
/**
* Register the manifest loader
*/
useManifest(manifestLoacder: ManifestLoaderContract): this;
/**
* Register an on exit callback listener. It should always
* exit the process
*/
onExit(callback: (kernel: this) => void | Promise<void>): this;
/**
* Preload the manifest file
*/
preloadManifest(): void;
/**
* Get command suggestions
*/
getSuggestions(name: string, distance?: number): string[];
/**
* Find a command using the command line `argv`
*/
find(argv: string[]): Promise<CommandConstructorContract | null>;
/**
* Run the default command
*/
runDefaultCommand(): Promise<any>;
/**
* Handle the command line argv to execute commands
*/
handle(args: string[]): Promise<any>;
/**
* Execute a command by its name and args
*/
exec(commandName: string, args: string[]): Promise<CommandContract>;
/**
* Print help for all commands or a given command
*/
printHelp(command?: CommandConstructorContract, commandsToAppend?: ManifestCommand[], aliasesToAppend?: Record<string, string>): void;
/**
* Find if a command is the main command. Main commands are executed
* directly from the terminal
*/
isMain(command: CommandContract): boolean;
/**
* Find if CLI process is interactive.
*/
isInteractive: boolean;
/**
* Toggle isInteractive state
*/
interactive(state: boolean): this;
/**
* Find if console output is mocked
*/
isMockingConsoleOutput: boolean;
/**
* Enforce mocking the console output. Command logs, tables, prompts
* will be mocked
*/
mockConsoleOutput(): this;
/**
* Trigger exit flow
*/
exit(command: CommandContract, error?: any): Promise<void>;
}
/**
* Template generator options
*/
export declare type GeneratorFileOptions = {
pattern?: 'pascalcase' | 'camelcase' | 'snakecase' | 'dashcase';
form?: 'singular' | 'plural';
formIgnoreList?: string[];
suffix?: string;
prefix?: string;
extname?: string;
};
/**
* Shape of the individual generator file
*/
export interface GeneratorFileContract {
state: 'persisted' | 'removed' | 'pending';
/**
* Define path to the stub template. You can also define inline text instead
* of relying on a template file, but do make sure to set `raw=true` inside
* the options when using inline text.
*/
stub(fileOrContents: string, options?: {
raw: boolean;
}): this;
/**
* Instruct to use mustache templating syntax, instead of template literals
*/
useMustache(): this;
/**
* The relative path to the destination directory.
*/
destinationDir(directory: string): this;
/**
* Define a custom application root. Otherwise `process.cwd()` is used.
*/
appRoot(directory: string): this;
/**
* Apply data to the stub
*/
apply(contents: any): this;
/**
* Get file properties as a JSON object
*/
toJSON(): {
filename: string;
filepath: string;
extension: string;
contents: string;
relativepath: string;
state: 'persisted' | 'removed' | 'pending';
};
}
/**
* Shape of the files generator
*/
export interface GeneratorContract {
/**
* Add a new file to the files generator. You can add multiple files
* together and they will be created when `run` is invoked.
*/
addFile(name: string, options?: GeneratorFileOptions): GeneratorFileContract;
/**
* Run the generator and create all files registered using `addFiles`
*/
run(): Promise<GeneratorFileContract[]>;
/**
* Clear the registered files from the generator
*/
clear(): void;
}
/**
* Filter function for filtering files during the `readdir` scan
*/
export declare type CommandsListFilterFn = ((name: string) => boolean) | string[];
+10
View File
@@ -0,0 +1,10 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
+12
View File
@@ -0,0 +1,12 @@
import { CommandArg } from '../Contracts';
export declare const args: {
/**
* Define argument that accepts string value
*/
string(options?: Partial<Omit<CommandArg, "type" | "propertyName">> | undefined): <TKey extends string, TTarget extends { [K in TKey]?: string | undefined; }>(target: TTarget, propertyName: TKey) => void;
/**
* Define argument that accepts multiple values. Must be
* the last argument.
*/
spread(options?: Partial<Omit<CommandArg, "type" | "propertyName">> | undefined): <TKey_1 extends string, TTarget_1 extends { [K_1 in TKey_1]?: string[] | undefined; }>(target: TTarget_1, propertyName: TKey_1) => void;
};
+37
View File
@@ -0,0 +1,37 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.args = void 0;
/**
* Adds arg to the list of command arguments with pre-defined
* type.
*/
function addArg(type, options) {
return function arg(target, propertyName) {
const Command = target.constructor;
Command.boot();
Command.$addArgument(Object.assign({ type, propertyName }, options));
};
}
exports.args = {
/**
* Define argument that accepts string value
*/
string(options) {
return addArg('string', options || {});
},
/**
* Define argument that accepts multiple values. Must be
* the last argument.
*/
spread(options) {
return addArg('spread', options || {});
},
};
+23
View File
@@ -0,0 +1,23 @@
import { CommandFlag } from '../Contracts';
export declare const flags: {
/**
* Create a flag that excepts string values
*/
string<T extends unknown>(options?: Partial<Omit<CommandFlag, "type" | "propertyName">> | undefined): <TKey extends string, TTarget extends { [K in TKey]: T; }>(target: TTarget, propertyName: TKey) => void;
/**
* Create a flag that excepts numeric values
*/
number<T_1 extends unknown>(options?: Partial<Omit<CommandFlag, "type" | "propertyName">> | undefined): <TKey_1 extends string, TTarget_1 extends { [K_1 in TKey_1]: T_1; }>(target: TTarget_1, propertyName: TKey_1) => void;
/**
* Create a flag that excepts boolean values
*/
boolean<T_2 extends unknown>(options?: Partial<Omit<CommandFlag, "type" | "propertyName">> | undefined): <TKey_2 extends string, TTarget_2 extends { [K_2 in TKey_2]: T_2; }>(target: TTarget_2, propertyName: TKey_2) => void;
/**
* Create a flag that excepts array of string values
*/
array<T_3 extends unknown>(options?: Partial<Omit<CommandFlag, "type" | "propertyName">> | undefined): <TKey_3 extends string, TTarget_3 extends { [K_3 in TKey_3]: T_3; }>(target: TTarget_3, propertyName: TKey_3) => void;
/**
* Create a flag that excepts array of numeric values
*/
numArray<T_4 extends unknown>(options?: Partial<Omit<CommandFlag, "type" | "propertyName">> | undefined): <TKey_4 extends string, TTarget_4 extends { [K_4 in TKey_4]: T_4; }>(target: TTarget_4, propertyName: TKey_4) => void;
};
+54
View File
@@ -0,0 +1,54 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.flags = void 0;
/**
* Pushes flag to the list of command flags with predefined
* types.
*/
function addFlag(type, options) {
return function flag(target, propertyName) {
const Command = target.constructor;
Command.boot();
Command.$addFlag(Object.assign({ type, propertyName }, options));
};
}
exports.flags = {
/**
* Create a flag that excepts string values
*/
string(options) {
return addFlag('string', options || {});
},
/**
* Create a flag that excepts numeric values
*/
number(options) {
return addFlag('number', options || {});
},
/**
* Create a flag that excepts boolean values
*/
boolean(options) {
return addFlag('boolean', options || {});
},
/**
* Create a flag that excepts array of string values
*/
array(options) {
return addFlag('array', options || {});
},
/**
* Create a flag that excepts array of numeric values
*/
numArray(options) {
return addFlag('numArray', options || {});
},
};
+58
View File
@@ -0,0 +1,58 @@
import { Exception } from '@poppinss/utils';
import { CommandConstructorContract } from '../Contracts';
/**
* Raised when a required argument is missing
*/
export declare class MissingArgumentException extends Exception {
command: CommandConstructorContract;
argumentName: string;
/**
* A required argument is missing
*/
static invoke(name: string, command: CommandConstructorContract): MissingArgumentException;
/**
* Handle itself
*/
handle(error: MissingArgumentException): void;
}
/**
* Raised when an the type of a flag is not as one of the excepted type
*/
export declare class InvalidFlagException extends Exception {
command?: CommandConstructorContract;
flagName: string;
expectedType: string;
/**
* Flag type validation failed.
*/
static invoke(prop: string, expected: string, command?: CommandConstructorContract): InvalidFlagException;
/**
* Handle itself
*/
handle(error: InvalidFlagException): void;
}
/**
* Raised when command is not registered with kernel
*/
export declare class InvalidCommandException extends Exception {
commandName: string;
suggestions: string[];
static invoke(commandName: string, suggestions: string[]): InvalidCommandException;
/**
* Handle itself
*/
handle(error: InvalidCommandException): void;
}
/**
* Raised when an unknown flag is defined
*/
export declare class UnknownFlagException extends Exception {
/**
* Unknown flag
*/
static invoke(prop: string): UnknownFlagException;
/**
* Handle itself
*/
handle(error: UnknownFlagException): void;
}
+118
View File
@@ -0,0 +1,118 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnknownFlagException = exports.InvalidCommandException = exports.InvalidFlagException = exports.MissingArgumentException = void 0;
const cliui_1 = require("@poppinss/cliui");
const utils_1 = require("@poppinss/utils");
/**
* Raised when a required argument is missing
*/
class MissingArgumentException extends utils_1.Exception {
/**
* A required argument is missing
*/
static invoke(name, command) {
const exception = new this(`Missing required argument "${name}"`, 500, 'E_MISSING_ARGUMENT');
exception.argumentName = name;
exception.command = command;
return exception;
}
/**
* Handle itself
*/
handle(error) {
cliui_1.logger.error(cliui_1.logger.colors.red(`Missing required argument "${error.argumentName}"`));
}
}
exports.MissingArgumentException = MissingArgumentException;
/**
* Raised when an the type of a flag is not as one of the excepted type
*/
class InvalidFlagException extends utils_1.Exception {
/**
* Flag type validation failed.
*/
static invoke(prop, expected, command) {
let article = 'a';
if (expected === 'number') {
expected = 'numeric';
}
if (expected === 'array') {
article = 'an';
expected = 'array of strings';
}
if (expected === 'numArray') {
article = 'an';
expected = 'array of numbers';
}
const exception = new this(`"${prop}" flag expects ${article} "${expected}" value`, 500, 'E_INVALID_FLAG');
exception.flagName = prop;
exception.command = command;
exception.expectedType = expected;
return exception;
}
/**
* Handle itself
*/
handle(error) {
cliui_1.logger.error(cliui_1.logger.colors.red(`Expected "--${error.flagName}" to be a valid "${error.expectedType}"`));
}
}
exports.InvalidFlagException = InvalidFlagException;
/**
* Raised when command is not registered with kernel
*/
class InvalidCommandException extends utils_1.Exception {
constructor() {
super(...arguments);
this.suggestions = [];
}
static invoke(commandName, suggestions) {
const exception = new this(`"${commandName}" is not a registered command`, 500, 'E_INVALID_COMMAND');
exception.commandName = commandName;
exception.suggestions = suggestions;
return exception;
}
/**
* Handle itself
*/
handle(error) {
cliui_1.logger.error(`"${error.commandName}" command not found`);
if (!error.suggestions.length) {
return;
}
cliui_1.logger.log('');
const suggestionLog = (0, cliui_1.sticker)().heading('Did you mean one of these?');
error.suggestions.forEach((commandName) => {
suggestionLog.add(cliui_1.logger.colors.yellow(commandName));
});
suggestionLog.render();
}
}
exports.InvalidCommandException = InvalidCommandException;
/**
* Raised when an unknown flag is defined
*/
class UnknownFlagException extends utils_1.Exception {
/**
* Unknown flag
*/
static invoke(prop) {
const exception = new this(`Unknown flag "${prop}"`, 500, 'E_INVALID_FLAG');
return exception;
}
/**
* Handle itself
*/
handle(error) {
cliui_1.logger.error(cliui_1.logger.colors.red(error.message));
}
}
exports.UnknownFlagException = UnknownFlagException;
+57
View File
@@ -0,0 +1,57 @@
import { GeneratorFileOptions, GeneratorFileContract } from '../Contracts';
/**
* Exposes the API to construct the output file content, path
* and template source.
*/
export declare class GeneratorFile implements GeneratorFileContract {
private name;
private options;
private stubContents;
private isStubRaw;
private templateData;
private customDestinationPath?;
private customAppRoot?;
private mustache;
state: 'persisted' | 'removed' | 'pending';
constructor(name: string, options?: GeneratorFileOptions);
/**
* Returns relative path for the file. Useful for
* printing log info
*/
private getFileRelativePath;
/**
* Set stub for the contents source. When `raw` is true, then string
* is considered as the raw content and not the file path.
*/
stub(fileOrContents: string, options?: {
raw: boolean;
}): this;
/**
* Optionally define destination directory from the project root.
*/
destinationDir(directory: string): this;
/**
* Define `appRoot`. This is just to shorten the logged
* file names. For example:
*/
appRoot(directory: string): this;
/**
* Instruct to use mustache
*/
useMustache(): this;
/**
* Variables for stub subsitution
*/
apply(contents: any): this;
/**
* Returns the file json
*/
toJSON(): {
filename: string;
filepath: string;
relativepath: string;
extension: string;
contents: any;
state: "persisted" | "removed" | "pending";
};
}
+129
View File
@@ -0,0 +1,129 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeneratorFile = void 0;
const path_1 = require("path");
const StringTransformer_1 = require("./StringTransformer");
const template_1 = require("../utils/template");
/**
* Exposes the API to construct the output file content, path
* and template source.
*/
class GeneratorFile {
constructor(name, options = {}) {
this.name = name;
this.options = options;
this.templateData = {};
this.mustache = false;
this.state = 'pending';
}
/**
* Returns relative path for the file. Useful for
* printing log info
*/
getFileRelativePath(filepath) {
if (this.customAppRoot) {
return filepath.replace(`${this.customAppRoot}${path_1.sep}`, '');
}
return filepath;
}
/**
* Set stub for the contents source. When `raw` is true, then string
* is considered as the raw content and not the file path.
*/
stub(fileOrContents, options) {
this.stubContents = fileOrContents;
this.isStubRaw = !!(options && options.raw);
return this;
}
/**
* Optionally define destination directory from the project root.
*/
destinationDir(directory) {
this.customDestinationPath = directory;
return this;
}
/**
* Define `appRoot`. This is just to shorten the logged
* file names. For example:
*/
appRoot(directory) {
this.customAppRoot = directory;
return this;
}
/**
* Instruct to use mustache
*/
useMustache() {
this.mustache = true;
return this;
}
/**
* Variables for stub subsitution
*/
apply(contents) {
this.templateData = contents;
return this;
}
/**
* Returns the file json
*/
toJSON() {
const extension = this.options.extname || '.ts';
const filename = new StringTransformer_1.StringTransformer((0, path_1.basename)(this.name))
.dropExtension()
.cleanSuffix(this.options.suffix)
.cleanPrefix(this.options.prefix)
.changeForm(this.options.form, this.options.formIgnoreList)
.addSuffix(this.options.suffix)
.addPrefix(this.options.prefix)
.changeCase(this.options.pattern)
.toValue();
const initialFilePath = this.name.replace((0, path_1.basename)(this.name), filename);
const appRoot = this.customAppRoot || process.cwd();
/**
* Computes the file absolute path, where the file will be created.
*
* 1. If `customDestinationPath` is not defined, we will merge the
* `appRoot` + `initialFilePath`.
*
* 2. If `customDestinationPath` is absolute, then we ignore the appRoot
* and merge `customDestinationPath` + `initialFilePath`
*
* 3. Otherwise we merge `appRoot` + `customDestinationPath` + `initialFilePath`.
*/
const filepath = this.customDestinationPath
? (0, path_1.isAbsolute)(this.customDestinationPath)
? (0, path_1.join)(this.customDestinationPath, initialFilePath)
: (0, path_1.join)(appRoot, this.customDestinationPath, initialFilePath)
: (0, path_1.join)(appRoot, initialFilePath);
/**
* Passing user values + the filename and extension
*/
const templateContents = Object.assign({ extension, filename }, this.templateData);
/**
* Contents of the template file
*/
const contents = this.stubContents
? this.isStubRaw
? (0, template_1.template)(this.stubContents, templateContents, undefined, this.mustache)
: (0, template_1.templateFromFile)(this.stubContents, templateContents, this.mustache)
: '';
return {
filename,
filepath: `${filepath}${extension}`,
relativepath: this.getFileRelativePath(`${filepath}${extension}`),
extension,
contents,
state: this.state,
};
}
}
exports.GeneratorFile = GeneratorFile;
+40
View File
@@ -0,0 +1,40 @@
/**
* Exposes the API to transform a string
*/
export declare class StringTransformer {
private input;
constructor(input: string);
/**
* Cleans suffix from the input.
*/
cleanSuffix(suffix?: string): this;
/**
* Cleans prefix from the input.
*/
cleanPrefix(prefix?: string): this;
/**
* Add suffix to the file name
*/
addSuffix(suffix?: string): this;
/**
* Add prefix to the file name
*/
addPrefix(prefix?: string): this;
/**
* Changes the name form by converting it to singular
* or plural case
*/
changeForm(form?: 'singular' | 'plural', ignoreList?: string[]): this;
/**
* Changes the input case
*/
changeCase(pattern?: 'pascalcase' | 'camelcase' | 'snakecase' | 'dashcase'): this;
/**
* Drops the extension from the input
*/
dropExtension(): this;
/**
* Returns the transformed value
*/
toValue(): string;
}
+114
View File
@@ -0,0 +1,114 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.StringTransformer = void 0;
const path_1 = require("path");
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
* Exposes the API to transform a string
*/
class StringTransformer {
constructor(input) {
this.input = input;
}
/**
* Cleans suffix from the input.
*/
cleanSuffix(suffix) {
if (!suffix) {
return this;
}
this.input = this.input.replace(new RegExp(`[-_]?${suffix}$`, 'i'), '');
return this;
}
/**
* Cleans prefix from the input.
*/
cleanPrefix(prefix) {
if (!prefix) {
return this;
}
this.input = this.input.replace(new RegExp(`^${prefix}[-_]?`, 'i'), '');
return this;
}
/**
* Add suffix to the file name
*/
addSuffix(suffix) {
if (!suffix) {
return this;
}
this.input = `${this.input}_${suffix}`;
return this;
}
/**
* Add prefix to the file name
*/
addPrefix(prefix) {
if (!prefix) {
return this;
}
this.input = `${prefix}_${this.input}`;
return this;
}
/**
* Changes the name form by converting it to singular
* or plural case
*/
changeForm(form, ignoreList) {
if (!form) {
return this;
}
/**
* Do not change form when word is in ignore list
*/
if ((ignoreList || []).find((word) => word.toLowerCase() === this.input.toLowerCase())) {
return this;
}
this.input = form === 'singular' ? helpers_1.string.singularize(this.input) : helpers_1.string.pluralize(this.input);
return this;
}
/**
* Changes the input case
*/
changeCase(pattern) {
switch (pattern) {
case 'camelcase':
this.input = helpers_1.string.camelCase(this.input);
return this;
case 'pascalcase':
const camelCase = helpers_1.string.camelCase(this.input);
this.input = `${camelCase.charAt(0).toUpperCase()}${camelCase.slice(1)}`;
return this;
case 'snakecase':
this.input = helpers_1.string.snakeCase(this.input);
return this;
case 'dashcase':
this.input = helpers_1.string.dashCase(this.input);
return this;
default:
return this;
}
}
/**
* Drops the extension from the input
*/
dropExtension() {
this.input = this.input.replace(new RegExp(`${(0, path_1.extname)(this.input)}$`), '');
return this;
}
/**
* Returns the transformed value
*/
toValue() {
return this.input;
}
}
exports.StringTransformer = StringTransformer;
+25
View File
@@ -0,0 +1,25 @@
import { GeneratorFile } from './File';
import { GeneratorFileOptions, GeneratorContract, CommandContract } from '../Contracts';
/**
* Exposes the API to generate entity files, like project
* `Controllers`, `Models` and so on.
*/
export declare class Generator implements GeneratorContract {
private command;
private destinationDir?;
private files;
constructor(command: CommandContract, destinationDir?: string | undefined);
/**
* Add a new file to the files generator. You can add multiple files
* together and they will be created when `run` is invoked.
*/
addFile(name: string, options?: GeneratorFileOptions): GeneratorFile;
/**
* Run the generator and create all files registered using `addFiles`
*/
run(): Promise<GeneratorFile[]>;
/**
* Clear the registered files from the generator
*/
clear(): void;
}
+61
View File
@@ -0,0 +1,61 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Generator = void 0;
const fs_extra_1 = require("fs-extra");
const File_1 = require("./File");
/**
* Exposes the API to generate entity files, like project
* `Controllers`, `Models` and so on.
*/
class Generator {
constructor(command, destinationDir) {
this.command = command;
this.destinationDir = destinationDir;
this.files = [];
}
/**
* Add a new file to the files generator. You can add multiple files
* together and they will be created when `run` is invoked.
*/
addFile(name, options) {
const file = new File_1.GeneratorFile(name, options);
if (this.destinationDir) {
file.destinationDir(this.destinationDir);
}
this.files.push(file);
return file;
}
/**
* Run the generator and create all files registered using `addFiles`
*/
async run() {
for (let file of this.files) {
const fileJSON = file.toJSON();
const exists = await (0, fs_extra_1.pathExists)(fileJSON.filepath);
if (exists) {
this.command.logger.action('create').skipped(fileJSON.relativepath, 'File already exists');
}
else {
await (0, fs_extra_1.outputFile)(fileJSON.filepath, fileJSON.contents);
file.state = 'persisted';
this.command.logger.action('create').succeeded(fileJSON.relativepath);
}
}
return this.files;
}
/**
* Clear the registered files from the generator
*/
clear() {
this.files = [];
}
}
exports.Generator = Generator;
+10
View File
@@ -0,0 +1,10 @@
import { BaseCommand } from '../BaseCommand';
import { CommandContract } from '../Contracts';
/**
* The help command for print the help output
*/
export declare class HelpCommand extends BaseCommand implements CommandContract {
static commandName: string;
static description: string;
run(): Promise<void>;
}
+23
View File
@@ -0,0 +1,23 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.HelpCommand = void 0;
const BaseCommand_1 = require("../BaseCommand");
/**
* The help command for print the help output
*/
class HelpCommand extends BaseCommand_1.BaseCommand {
async run() {
this.kernel.printHelp();
}
}
exports.HelpCommand = HelpCommand;
HelpCommand.commandName = 'help';
HelpCommand.description = 'See help for all the commands';
+14
View File
@@ -0,0 +1,14 @@
/**
* Exposes the API to register and execute async hooks
*/
export declare class Hooks {
private hooks;
/**
* Register hook for a given action and lifecycle
*/
add(lifecycle: 'before' | 'after', action: string, handler: (...args: any[]) => void | Promise<void>): this;
/**
* Execute hooks for a given action and lifecycle
*/
execute(lifecycle: 'before' | 'after', action: string, data: any): Promise<void>;
}
+48
View File
@@ -0,0 +1,48 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Hooks = void 0;
/**
* Exposes the API to register and execute async hooks
*/
class Hooks {
constructor() {
this.hooks = {
before: new Map(),
after: new Map(),
};
}
/**
* Register hook for a given action and lifecycle
*/
add(lifecycle, action, handler) {
const handlers = this.hooks[lifecycle].get(action);
if (handlers) {
handlers.push(handler);
}
else {
this.hooks[lifecycle].set(action, [handler]);
}
return this;
}
/**
* Execute hooks for a given action and lifecycle
*/
async execute(lifecycle, action, data) {
const handlers = this.hooks[lifecycle].get(action);
if (!handlers) {
return;
}
for (let handler of handlers) {
await handler(data);
}
}
}
exports.Hooks = Hooks;
+180
View File
@@ -0,0 +1,180 @@
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { ManifestLoader } from '../Manifest/Loader';
import { CommandFlag, KernelContract, CommandContract, ManifestCommand, RunHookCallback, FindHookCallback, GlobalFlagHandler, CommandConstructorContract } from '../Contracts';
/**
* Ace kernel class is used to register, find and invoke commands by
* parsing `process.argv.splice(2)` value.
*/
export declare class Kernel implements KernelContract {
application: ApplicationContract;
/**
* Reference to hooks class to execute lifecycle
* hooks
*/
private hooks;
/**
* Reference to the manifest loader. If defined, we will give preference
* to the manifest files.
*/
private manifestLoader;
/**
* The command that started the process
*/
private entryCommand?;
/**
* The state of the kernel
*/
private state;
/**
* Exit handler for gracefully exiting the process
*/
private exitHandler;
/**
* Find if CLI process is interactive. This flag can be
* toggled programmatically
*/
isInteractive: boolean;
/**
* Find if console output is mocked
*/
isMockingConsoleOutput: boolean;
/**
* The default command that will be invoked when no command is
* defined
*/
defaultCommand: CommandConstructorContract;
/**
* List of registered commands
*/
commands: {
[name: string]: CommandConstructorContract;
};
aliases: {
[alias: string]: string;
};
/**
* List of registered flags
*/
flags: {
[name: string]: CommandFlag & {
handler: GlobalFlagHandler;
};
};
/**
* The exit code for the process
*/
exitCode?: number;
/**
* The error collected as part of the running commands or executing
* flags
*/
error?: any;
constructor(application: ApplicationContract);
/**
* Executing global flag handlers. The global flag handlers are
* not async as of now, but later we can look into making them
* async.
*/
private executeGlobalFlagsHandlers;
/**
* Returns an array of all registered commands
*/
private getAllCommandsAndAliases;
/**
* Processes the args and sets values on the command instance
*/
private processCommandArgsAndFlags;
/**
* Execute the main command. For calling commands within commands,
* one must call "kernel.exec".
*/
private execMain;
/**
* Handles exiting the process
*/
private exitProcess;
/**
* Register a before hook
*/
before(action: 'run', callback: RunHookCallback): this;
before(action: 'find', callback: FindHookCallback): this;
/**
* Register an after hook
*/
after(action: 'run', callback: RunHookCallback): this;
after(action: 'find', callback: FindHookCallback): this;
/**
* Register an array of command constructors
*/
register(commands: CommandConstructorContract[]): this;
/**
* Register a global flag. It can be defined in combination with
* any command.
*/
flag(name: string, handler: GlobalFlagHandler, options: Partial<Exclude<CommandFlag, 'name' | 'propertyName'>>): this;
/**
* Use manifest instance to lazy load commands
*/
useManifest(manifestLoader: ManifestLoader): this;
/**
* Register an exit handler
*/
onExit(callback: (kernel: this) => void | Promise<void>): this;
/**
* Returns an array of command names suggestions for a given name.
*/
getSuggestions(name: string, distance?: number): string[];
/**
* Preload the manifest file. Re-running this method twice will
* result in a noop
*/
preloadManifest(): Promise<void>;
/**
* Finds the command from the command line argv array. If command for
* the given name doesn't exists, then it will return `null`.
*
* Does executes the before and after hooks regardless of whether the
* command has been found or not
*/
find(argv: string[]): Promise<CommandConstructorContract | null>;
/**
* Run the default command. The default command doesn't accept
* and args or flags.
*/
runDefaultCommand(): Promise<any>;
/**
* Find if a command is the main command. Main commands are executed
* directly from the terminal.
*/
isMain(command: CommandContract): boolean;
/**
* Enforce mocking the console output. Command logs, tables, prompts
* will be mocked
*/
mockConsoleOutput(): this;
/**
* Toggle interactive state
*/
interactive(state: boolean): this;
/**
* Execute a command as a sub-command. Do not call "handle" and
* always use this method to invoke command programatically
*/
exec(commandName: string, args: string[]): Promise<CommandContract>;
/**
* Makes instance of a given command by processing command line arguments
* and setting them on the command instance
*/
handle(argv: string[]): Promise<void>;
/**
* Print the help screen for a given command or all commands/flags
*/
printHelp(command?: CommandConstructorContract, commandsToAppend?: ManifestCommand[], aliasesToAppend?: Record<string, string>): void;
/**
* Trigger kernel to exit the process. The call to this method
* is ignored when command is not same the `entryCommand`.
*
* In other words, subcommands cannot trigger exit
*/
exit(command: CommandContract, error?: any): Promise<void>;
}
+609
View File
@@ -0,0 +1,609 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Kernel = void 0;
const Hooks_1 = require("../Hooks");
const Parser_1 = require("../Parser");
const HelpCommand_1 = require("../HelpCommand");
const Exceptions_1 = require("../Exceptions");
const help_1 = require("../utils/help");
const validateCommand_1 = require("../utils/validateCommand");
const cliui_1 = require("@poppinss/cliui");
/**
* Ace kernel class is used to register, find and invoke commands by
* parsing `process.argv.splice(2)` value.
*/
class Kernel {
constructor(application) {
this.application = application;
/**
* Reference to hooks class to execute lifecycle
* hooks
*/
this.hooks = new Hooks_1.Hooks();
/**
* The state of the kernel
*/
this.state = 'idle';
/**
* Exit handler for gracefully exiting the process
*/
this.exitHandler = (kernel) => {
if (kernel.error && typeof kernel.error.handle === 'function') {
kernel.error.handle(kernel.error);
}
else if (kernel.error) {
cliui_1.logger.fatal(kernel.error);
}
process.exit(kernel.exitCode === undefined ? 0 : kernel.exitCode);
};
/**
* Find if CLI process is interactive. This flag can be
* toggled programmatically
*/
this.isInteractive = cliui_1.isInteractive;
/**
* Find if console output is mocked
*/
this.isMockingConsoleOutput = false;
/**
* The default command that will be invoked when no command is
* defined
*/
this.defaultCommand = HelpCommand_1.HelpCommand;
/**
* List of registered commands
*/
this.commands = {};
this.aliases = this.application.rcFile.commandsAliases;
/**
* List of registered flags
*/
this.flags = {};
}
/**
* Executing global flag handlers. The global flag handlers are
* not async as of now, but later we can look into making them
* async.
*/
executeGlobalFlagsHandlers(argv, command) {
const globalFlags = Object.keys(this.flags);
const parsedOptions = new Parser_1.Parser(this.flags).parse(argv, command);
globalFlags.forEach((name) => {
const value = parsedOptions[name];
/**
* Flag was not specified
*/
if (value === undefined) {
return;
}
/**
* Calling the handler
*/
this.flags[name].handler(parsedOptions[name], parsedOptions, command);
});
}
/**
* Returns an array of all registered commands
*/
getAllCommandsAndAliases() {
let commands = Object.keys(this.commands).map((name) => this.commands[name]);
let aliases = {};
/**
* Concat manifest commands when they exists
*/
if (this.manifestLoader && this.manifestLoader.booted) {
const { commands: manifestCommands, aliases: manifestAliases } = this.manifestLoader.getCommands();
commands = commands.concat(manifestCommands);
aliases = Object.assign(aliases, manifestAliases);
}
return {
commands,
aliases: Object.assign(aliases, this.aliases),
};
}
/**
* Processes the args and sets values on the command instance
*/
async processCommandArgsAndFlags(commandInstance, args) {
const parser = new Parser_1.Parser(this.flags);
const command = commandInstance.constructor;
/**
* Parse the command arguments. The `parse` method will raise exception if flag
* or arg is not
*/
const parsedOptions = parser.parse(args, command);
/**
* We validate the command arguments after the global flags have been
* executed. It is required, since flags may have nothing to do
* with the validaty of command itself
*/
command.args.forEach((arg, index) => {
parser.validateArg(arg, index, parsedOptions, command);
});
/**
* Creating a new command instance and setting
* parsed options on it.
*/
commandInstance.parsed = parsedOptions;
/**
* Setup command instance argument and flag
* properties.
*/
for (let i = 0; i < command.args.length; i++) {
const arg = command.args[i];
const defaultValue = commandInstance[arg.propertyName];
if (arg.type === 'spread') {
const value = parsedOptions._.slice(i);
/**
* Set the property value to arguments defined via the CLI
* If no arguments are supplied, then use the default value assigned to the class property
* If the default value is undefined, then assign an empty array
*/
commandInstance[arg.propertyName] = value.length
? value
: defaultValue !== undefined
? defaultValue
: [];
break;
}
else {
const value = parsedOptions._[i];
commandInstance[arg.propertyName] = value !== undefined ? value : defaultValue;
}
}
/**
* Set flag value on the command instance
*/
for (let flag of command.flags) {
const flagValue = parsedOptions[flag.name];
if (flagValue !== undefined) {
commandInstance[flag.propertyName] = flagValue;
}
}
}
/**
* Execute the main command. For calling commands within commands,
* one must call "kernel.exec".
*/
async execMain(commandName, args) {
const command = await this.find([commandName]);
/**
* Command not found. So execute global flags handlers and
* raise an exception
*/
if (!command) {
this.executeGlobalFlagsHandlers(args);
throw Exceptions_1.InvalidCommandException.invoke(commandName, this.getSuggestions(commandName));
}
/**
* Make an instance of the command
*/
const commandInstance = await this.application.container.makeAsync(command, [
this.application,
this,
]);
/**
* Execute global flags
*/
this.executeGlobalFlagsHandlers(args, command);
/**
* Process the arguments and flags for the command
*/
await this.processCommandArgsAndFlags(commandInstance, args);
/**
* Keep a reference to the entry command. So that we know if we
* want to entertain `.exit` or not
*/
this.entryCommand = commandInstance;
/**
* Execute before run hooks
*/
await this.hooks.execute('before', 'run', commandInstance);
/**
* Execute command
*/
return commandInstance.exec();
}
/**
* Handles exiting the process
*/
async exitProcess(error) {
/**
* Check for state to avoid exiting the process multiple times
*/
if (this.state === 'completed') {
return;
}
this.state = 'completed';
/**
* Re-assign error if entry command exists and has error
*/
if (!error && this.entryCommand && this.entryCommand.error) {
error = this.entryCommand.error;
}
/**
* Execute the after run hooks. Wrapping inside try/catch since this is the
* cleanup handler for the process and must handle all exceptions
*/
try {
if (this.entryCommand) {
await this.hooks.execute('after', 'run', this.entryCommand);
}
}
catch (hookError) {
error = hookError;
}
/**
* Assign error to the kernel instance
*/
if (error) {
this.error = error;
}
/**
* Figure out the exit code for the process
*/
const exitCode = error ? 1 : 0;
const commandExitCode = this.entryCommand && this.entryCommand.exitCode;
this.exitCode = commandExitCode === undefined ? exitCode : commandExitCode;
try {
await this.exitHandler(this);
}
catch (exitHandlerError) {
cliui_1.logger.warning('Expected exit handler to exit the process. Instead it raised an exception');
cliui_1.logger.fatal(exitHandlerError);
}
}
before(action, callback) {
this.hooks.add('before', action, callback);
return this;
}
after(action, callback) {
this.hooks.add('after', action, callback);
return this;
}
/**
* Register an array of command constructors
*/
register(commands) {
commands.forEach((command) => {
command.boot();
(0, validateCommand_1.validateCommand)(command);
this.commands[command.commandName] = command;
/**
* Registering command aliaes
*/
command.aliases.forEach((alias) => (this.aliases[alias] = command.commandName));
});
return this;
}
/**
* Register a global flag. It can be defined in combination with
* any command.
*/
flag(name, handler, options) {
this.flags[name] = Object.assign({
name,
propertyName: name,
handler,
type: 'boolean',
}, options);
return this;
}
/**
* Use manifest instance to lazy load commands
*/
useManifest(manifestLoader) {
this.manifestLoader = manifestLoader;
return this;
}
/**
* Register an exit handler
*/
onExit(callback) {
this.exitHandler = callback;
return this;
}
/**
* Returns an array of command names suggestions for a given name.
*/
getSuggestions(name, distance = 3) {
const leven = require('leven');
const { commands, aliases } = this.getAllCommandsAndAliases();
const suggestions = commands
.filter(({ commandName }) => leven(name, commandName) <= distance)
.map(({ commandName }) => commandName);
return suggestions.concat(Object.keys(aliases).filter((alias) => leven(name, alias) <= distance));
}
/**
* Preload the manifest file. Re-running this method twice will
* result in a noop
*/
async preloadManifest() {
/**
* Load manifest commands when instance of manifest loader exists.
*/
if (this.manifestLoader) {
await this.manifestLoader.boot();
}
}
/**
* Finds the command from the command line argv array. If command for
* the given name doesn't exists, then it will return `null`.
*
* Does executes the before and after hooks regardless of whether the
* command has been found or not
*/
async find(argv) {
/**
* ----------------------------------------------------------------------------
* Even though in `Unix` the command name may appear in between or at last, with
* ace we always want the command name to be the first argument. However, the
* arguments to the command itself can appear in any sequence. For example:
*
* Works
* - node ace make:controller foo
* - node ace make:controller --http foo
*
* Doesn't work
* - node ace foo make:controller
* ----------------------------------------------------------------------------
*/
const [commandName] = argv;
/**
* Command name from the registered aliases
*/
const aliasCommandName = this.aliases[commandName];
/**
* Manifest commands gets preference over manually registered commands.
*
* - We check the manifest loader is register
* - The manifest loader has the command
* - Or the manifest loader has the alias command
*/
const commandNode = this.manifestLoader
? this.manifestLoader.hasCommand(commandName)
? this.manifestLoader.getCommand(commandName)
: this.manifestLoader.hasCommand(aliasCommandName)
? this.manifestLoader.getCommand(aliasCommandName)
: undefined
: undefined;
if (commandNode) {
commandNode.command.aliases = commandNode.command.aliases || [];
if (aliasCommandName && !commandNode.command.aliases.includes(commandName)) {
commandNode.command.aliases.push(commandName);
}
await this.hooks.execute('before', 'find', commandNode.command);
const command = await this.manifestLoader.loadCommand(commandNode.command.commandName);
await this.hooks.execute('after', 'find', command);
return command;
}
else {
/**
* Try to find command inside manually registered command or fallback
* to null
*/
const command = this.commands[commandName] || this.commands[aliasCommandName] || null;
/**
* Share main command name as an alias with the command
*/
if (command) {
command.aliases = command.aliases || [];
if (aliasCommandName && !command.aliases.includes(commandName)) {
command.aliases.push(commandName);
}
}
/**
* Executing before and after together to be compatible
* with the manifest find before and after hooks
*/
await this.hooks.execute('before', 'find', command);
await this.hooks.execute('after', 'find', command);
return command;
}
}
/**
* Run the default command. The default command doesn't accept
* and args or flags.
*/
async runDefaultCommand() {
this.defaultCommand.boot();
(0, validateCommand_1.validateCommand)(this.defaultCommand);
/**
* Execute before/after find hooks
*/
await this.hooks.execute('before', 'find', this.defaultCommand);
await this.hooks.execute('after', 'find', this.defaultCommand);
/**
* Make the command instance using the container
*/
const commandInstance = await this.application.container.makeAsync(this.defaultCommand, [
this.application,
this,
]);
/**
* Execute before run hook
*/
await this.hooks.execute('before', 'run', commandInstance);
/**
* Keep a reference to the entry command
*/
this.entryCommand = commandInstance;
/**
* Execute the command
*/
return commandInstance.exec();
}
/**
* Find if a command is the main command. Main commands are executed
* directly from the terminal.
*/
isMain(command) {
return !!this.entryCommand && this.entryCommand === command;
}
/**
* Enforce mocking the console output. Command logs, tables, prompts
* will be mocked
*/
mockConsoleOutput() {
this.isMockingConsoleOutput = true;
return this;
}
/**
* Toggle interactive state
*/
interactive(state) {
this.isInteractive = state;
return this;
}
/**
* Execute a command as a sub-command. Do not call "handle" and
* always use this method to invoke command programatically
*/
async exec(commandName, args) {
const command = await this.find([commandName]);
/**
* Command not found.
*/
if (!command) {
throw Exceptions_1.InvalidCommandException.invoke(commandName, this.getSuggestions(commandName));
}
/**
* Make an instance of command and keep a reference of it as `this.entryCommand`
*/
const commandInstance = await this.application.container.makeAsync(command, [
this.application,
this,
]);
/**
* Process args and flags for the command
*/
await this.processCommandArgsAndFlags(commandInstance, args);
let commandError;
/**
* Wrapping the command execution inside a try/catch, so that
* we can run the after hooks regardless of success or
* failure
*/
try {
await this.hooks.execute('before', 'run', commandInstance);
await commandInstance.exec();
}
catch (error) {
commandError = error;
}
/**
* Execute after hooks
*/
await this.hooks.execute('after', 'run', commandInstance);
/**
* Re-throw error (if any)
*/
if (commandError) {
throw commandError;
}
return commandInstance;
}
/**
* Makes instance of a given command by processing command line arguments
* and setting them on the command instance
*/
async handle(argv) {
if (this.state !== 'idle') {
return;
}
this.state = 'running';
try {
/**
* Preload the manifest file to load the manifest files
*/
this.preloadManifest();
/**
* Branch 1
* Run default command and invoke the exit handler
*/
if (!argv.length) {
await this.runDefaultCommand();
await this.exitProcess();
return;
}
/**
* Branch 2
* No command has been mentioned and hence execute all the global flags
* invoke the exit handler
*/
const hasMentionedCommand = !argv[0].startsWith('-');
if (!hasMentionedCommand) {
this.executeGlobalFlagsHandlers(argv);
await this.exitProcess();
return;
}
/**
* Branch 3
* Execute the given command as the main command
*/
const [commandName, ...args] = argv;
await this.execMain(commandName, args);
/**
* Exit the process if there isn't any entry command
*/
if (!this.entryCommand) {
await this.exitProcess();
return;
}
const entryCommandConstructor = this.entryCommand.constructor;
/**
* Exit the process if entry command isn't a stayalive command. Stayalive
* commands should call `this.exit` to exit the process.
*/
if (!entryCommandConstructor.settings.stayAlive) {
await this.exitProcess();
}
}
catch (error) {
await this.exitProcess(error);
}
}
/**
* Print the help screen for a given command or all commands/flags
*/
printHelp(command, commandsToAppend, aliasesToAppend) {
let { commands, aliases } = this.getAllCommandsAndAliases();
/**
* Append additional commands and aliases for help screen only
*/
if (commandsToAppend) {
commands = commands.concat(commandsToAppend);
}
if (aliasesToAppend) {
aliases = Object.assign({}, aliases, aliasesToAppend);
}
if (command) {
(0, help_1.printHelpFor)(command, aliases);
}
else {
const flags = Object.keys(this.flags).map((name) => this.flags[name]);
(0, help_1.printHelp)(commands, flags, aliases);
}
}
/**
* Trigger kernel to exit the process. The call to this method
* is ignored when command is not same the `entryCommand`.
*
* In other words, subcommands cannot trigger exit
*/
async exit(command, error) {
if (command !== this.entryCommand) {
return;
}
await this.exitProcess(error);
}
}
exports.Kernel = Kernel;
+29
View File
@@ -0,0 +1,29 @@
/**
* Exposes the API to generate the ace manifest file. The manifest file
* contains the meta data of all the registered commands. This speeds
* up the boot cycle of ace
*/
export declare class ManifestGenerator {
private basePath;
private commands;
/**
* Here we keep track of processed command files to prevent loops. Mainly when using
* listDirectoryFiles to export command paths from directory and not excluding current file.
*/
private processedFiles;
constructor(basePath: string, commands: string[]);
/**
* Loads a given command from the disk. A command line can recursively
* exposed sub command paths. But they should be resolvable using
* the base path
*/
private loadCommand;
/**
* Loads all the commands from the disk recursively.
*/
private loadCommands;
/**
* Generates and writes the ace manifest file to the base path
*/
generate(): Promise<void>;
}
+96
View File
@@ -0,0 +1,96 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ManifestGenerator = void 0;
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const validateCommand_1 = require("../utils/validateCommand");
/**
* Exposes the API to generate the ace manifest file. The manifest file
* contains the meta data of all the registered commands. This speeds
* up the boot cycle of ace
*/
class ManifestGenerator {
constructor(basePath, commands) {
this.basePath = basePath;
this.commands = commands;
/**
* Here we keep track of processed command files to prevent loops. Mainly when using
* listDirectoryFiles to export command paths from directory and not excluding current file.
*/
this.processedFiles = new Set();
}
/**
* Loads a given command from the disk. A command line can recursively
* exposed sub command paths. But they should be resolvable using
* the base path
*/
async loadCommand(commandPath) {
if ((0, path_1.isAbsolute)(commandPath)) {
throw new utils_1.Exception('Absolute path to a command is not allowed when generating the manifest file');
}
const resolvedPath = (0, helpers_1.resolveFrom)(this.basePath, commandPath);
if (this.processedFiles.has(resolvedPath)) {
return [];
}
const commandOrSubCommandsPaths = (0, utils_1.esmRequire)(resolvedPath);
this.processedFiles.add(resolvedPath);
if (Array.isArray(commandOrSubCommandsPaths)) {
return this.loadCommands(commandOrSubCommandsPaths);
}
/**
* File export has command constructor
*/
(0, validateCommand_1.validateCommand)(commandOrSubCommandsPaths, commandPath);
return [
{
command: commandOrSubCommandsPaths,
commandPath,
},
];
}
/**
* Loads all the commands from the disk recursively.
*/
async loadCommands(commandPaths) {
let commands = [];
for (const commandPath of commandPaths) {
const command = await this.loadCommand(commandPath);
commands = commands.concat(command);
}
return commands;
}
/**
* Generates and writes the ace manifest file to the base path
*/
async generate() {
const commands = await this.loadCommands(this.commands);
const manifest = commands.reduce((result, { command, commandPath }) => {
const manifestNode = {
settings: command.settings || {},
commandPath: commandPath.replace(new RegExp(`${(0, path_1.extname)(commandPath)}$`), ''),
commandName: command.commandName,
description: command.description,
args: command.args,
aliases: command.aliases,
flags: command.flags,
};
result.commands[command.commandName] = manifestNode;
command.aliases.forEach((alias) => {
result.aliases[alias] = command.commandName;
});
return result;
}, { commands: {}, aliases: {} });
await (0, fs_extra_1.outputJSON)((0, path_1.join)(this.basePath, 'ace-manifest.json'), manifest, { spaces: 2 });
}
}
exports.ManifestGenerator = ManifestGenerator;
+57
View File
@@ -0,0 +1,57 @@
import { Aliases, ManifestCommand, ManifestLoaderContract, CommandConstructorContract } from '../Contracts';
/**
* The manifest loader exposes the API to load ace commands from one
* or more manifest files.
*/
export declare class ManifestLoader implements ManifestLoaderContract {
private files;
/**
* An array of defined manifest files
*/
private manifestFiles;
booted: boolean;
constructor(files: {
basePath: string;
manifestAbsPath: string;
}[]);
/**
* Loads the manifest file from the disk
*/
private loadManifestFile;
/**
* Returns the command manifest node for a give command
*/
private getCommandManifest;
/**
* Boot manifest loader to read all manifest files from the disk
*/
boot(): Promise<void>;
/**
* Returns base path for a given command
*/
getCommandBasePath(commandName: string): string | undefined;
/**
* Returns manifest command node. One must load the command
* in order to use it
*/
getCommand(commandName: string): {
basePath: string;
command: ManifestCommand;
} | undefined;
/**
* Find if a command exists or not
*/
hasCommand(commandName: string): boolean;
/**
* Load command from the disk. Make sure to use [[hasCommand]] before
* calling this method
*/
loadCommand(commandName: string): Promise<CommandConstructorContract>;
/**
* Returns an array of manifest commands
*/
getCommands(): {
commands: ManifestCommand[];
aliases: Aliases;
};
}
+112
View File
@@ -0,0 +1,112 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ManifestLoader = void 0;
const fs_extra_1 = require("fs-extra");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const validateCommand_1 = require("../utils/validateCommand");
/**
* The manifest loader exposes the API to load ace commands from one
* or more manifest files.
*/
class ManifestLoader {
constructor(files) {
this.files = files;
/**
* An array of defined manifest files
*/
this.manifestFiles = [];
this.booted = false;
}
/**
* Loads the manifest file from the disk
*/
async loadManifestFile(file) {
const manifestCommands = await (0, fs_extra_1.readJSON)(file.manifestAbsPath);
/**
* Find if we are dealing with an old or the new manifest file
*/
const isNewManifestFile = manifestCommands['commands'] && manifestCommands['aliases'];
const commands = isNewManifestFile ? manifestCommands['commands'] : manifestCommands;
const aliases = isNewManifestFile ? manifestCommands['aliases'] : {};
return { basePath: file.basePath, commands, aliases };
}
/**
* Returns the command manifest node for a give command
*/
getCommandManifest(commandName) {
return this.manifestFiles.find(({ commands, aliases }) => {
const aliasCommandName = aliases[commandName];
return commands[commandName] || commands[aliasCommandName];
});
}
/**
* Boot manifest loader to read all manifest files from the disk
*/
async boot() {
if (this.booted) {
return;
}
this.booted = true;
this.manifestFiles = await Promise.all(this.files.map((file) => this.loadManifestFile(file)));
}
/**
* Returns base path for a given command
*/
getCommandBasePath(commandName) {
return this.getCommandManifest(commandName)?.basePath;
}
/**
* Returns manifest command node. One must load the command
* in order to use it
*/
getCommand(commandName) {
const manifestCommands = this.getCommandManifest(commandName);
if (!manifestCommands) {
return;
}
const aliasCommandName = manifestCommands.aliases[commandName];
const command = manifestCommands.commands[commandName] || manifestCommands.commands[aliasCommandName];
return {
basePath: manifestCommands.basePath,
command: command,
};
}
/**
* Find if a command exists or not
*/
hasCommand(commandName) {
return !!this.getCommandBasePath(commandName);
}
/**
* Load command from the disk. Make sure to use [[hasCommand]] before
* calling this method
*/
async loadCommand(commandName) {
const { basePath, command } = this.getCommand(commandName);
const commandConstructor = (0, utils_1.esmRequire)((0, helpers_1.resolveFrom)(basePath, command.commandPath));
(0, validateCommand_1.validateCommand)(commandConstructor);
return commandConstructor;
}
/**
* Returns an array of manifest commands
*/
getCommands() {
return this.manifestFiles.reduce((result, { commands, aliases }) => {
Object.keys(commands).forEach((commandName) => {
result.commands = result.commands.concat(commands[commandName]);
});
Object.assign(result.aliases, aliases);
return result;
}, { commands: [], aliases: {} });
}
}
exports.ManifestLoader = ManifestLoader;
+80
View File
@@ -0,0 +1,80 @@
import getopts from 'getopts';
import { CommandArg, CommandFlag, GlobalFlagHandler, CommandConstructorContract } from '../Contracts';
/**
* The job of the parser is to parse the command line values by taking
* the command `args`, `flags` and `globalFlags` into account.
*/
export declare class Parser {
private registeredFlags;
constructor(registeredFlags: {
[name: string]: CommandFlag & {
handler: GlobalFlagHandler;
};
});
/**
* Validate all the flags against the flags registered by the command
* or as global flags and disallow unknown flags.
*/
private scanForUnknownFlags;
/**
* Processes ace command flag to set the options for `getopts`.
* We just define the `alias` with getopts coz their default,
* string and boolean options produces the behavior we don't
* want.
*/
private preProcessFlag;
/**
* Casts a flag value to a boolean. The casting logic is driven
* by the behavior of "getopts"
*/
private castToBoolean;
/**
* Cast the value to a string. The casting logic is driven
* by the behavior of "getopts"
*
* - Convert numbers to string
* - Do not convert boolean to a string, since a flag without a value
* gets a boolean value, which is invalid
*/
private castToString;
/**
* Cast value to an array of string. The casting logic is driven
* by the behavior of "getopts"
*
* - Numeric values are converted to string of array
* - A string value is splitted by comma and trimmed.
* - An array is casted to an array of string values
*/
private castToArray;
/**
* Cast value to an array of numbers. The casting logic is driven
* by the behavior of "getopts".
*
* - Numeric values are wrapped to an array.
* - String is splitted by comma and each value is casted to a number
* - Each array value is casted to a number.
*/
private castToNumArray;
/**
* Cast value to a number. The casting logic is driven
* by the behavior of "getopts"
*
* - Boolean values are not allowed
* - A string is converted to a number
*/
private castToNumer;
/**
* Casts value of a flag to it's expected data type. These values
* are then later validated to ensure that casting was successful.
*/
processFlag(flag: CommandFlag, parsed: getopts.ParsedOptions, command?: CommandConstructorContract): void;
/**
* Validates the value to ensure that values are defined for
* required arguments.
*/
validateArg(arg: CommandArg, index: number, parsed: getopts.ParsedOptions, command: CommandConstructorContract): void;
/**
* Parses argv and executes the command and global flags handlers
*/
parse(argv: string[], command?: CommandConstructorContract): getopts.ParsedOptions;
}
+287
View File
@@ -0,0 +1,287 @@
"use strict";
/*
* @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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Parser = void 0;
const getopts_1 = __importDefault(require("getopts"));
const Exceptions_1 = require("../Exceptions");
/**
* The job of the parser is to parse the command line values by taking
* the command `args`, `flags` and `globalFlags` into account.
*/
class Parser {
constructor(registeredFlags) {
this.registeredFlags = registeredFlags;
}
/**
* Validate all the flags against the flags registered by the command
* or as global flags and disallow unknown flags.
*/
scanForUnknownFlags(parsed, flagsAndAliases) {
Object.keys(parsed).forEach((key) => {
if (key === '_') {
return;
}
const hasFlag = flagsAndAliases.find((value) => value === key);
if (!hasFlag) {
throw Exceptions_1.UnknownFlagException.invoke(key);
}
});
}
/**
* Processes ace command flag to set the options for `getopts`.
* We just define the `alias` with getopts coz their default,
* string and boolean options produces the behavior we don't
* want.
*/
preProcessFlag(flag, options) {
/**
* Register alias (when exists)
*/
if (flag.alias) {
options.alias[flag.alias] = flag.name;
}
}
/**
* Casts a flag value to a boolean. The casting logic is driven
* by the behavior of "getopts"
*/
castToBoolean(value) {
if (typeof value === 'boolean') {
return value;
}
if (value === 'true' || value === '=true') {
return true;
}
return undefined;
}
/**
* Cast the value to a string. The casting logic is driven
* by the behavior of "getopts"
*
* - Convert numbers to string
* - Do not convert boolean to a string, since a flag without a value
* gets a boolean value, which is invalid
*/
castToString(value) {
if (typeof value === 'number') {
value = String(value);
}
if (typeof value === 'string' && value.trim()) {
return value;
}
return undefined;
}
/**
* Cast value to an array of string. The casting logic is driven
* by the behavior of "getopts"
*
* - Numeric values are converted to string of array
* - A string value is splitted by comma and trimmed.
* - An array is casted to an array of string values
*/
castToArray(value) {
if (typeof value === 'number') {
value = String(value);
}
if (typeof value === 'string') {
return value.split(',').filter((prop) => prop.trim());
}
if (Array.isArray(value)) {
/**
* This will also convert numeric values to a string. The behavior
* is same as string flag type.
*/
return value.map((prop) => String(prop));
}
return undefined;
}
/**
* Cast value to an array of numbers. The casting logic is driven
* by the behavior of "getopts".
*
* - Numeric values are wrapped to an array.
* - String is splitted by comma and each value is casted to a number
* - Each array value is casted to a number.
*/
castToNumArray(value) {
if (typeof value === 'number') {
return [value];
}
if (typeof value === 'string') {
return value.split(',').map((one) => Number(one));
}
if (Array.isArray(value)) {
return value.map((prop) => Number(prop));
}
return undefined;
}
/**
* Cast value to a number. The casting logic is driven
* by the behavior of "getopts"
*
* - Boolean values are not allowed
* - A string is converted to a number
*/
castToNumer(value) {
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string') {
// Possibility of NaN here
return Number(value);
}
return undefined;
}
/**
* Casts value of a flag to it's expected data type. These values
* are then later validated to ensure that casting was successful.
*/
processFlag(flag, parsed, command) {
let value = parsed[flag.name];
/**
* Check for the value with the alias, if it undefined
* by the name
*/
if (value === undefined && flag.alias) {
value = parsed[flag.alias];
}
/**
* Still undefined??
*
* It is fine. Flags are optional anyways
*/
if (value === undefined) {
return;
}
/**
* Handle boolean values. It should be a valid boolean
* data type or a string value of `'true'`.
*/
if (flag.type === 'boolean') {
value = this.castToBoolean(value);
if (value === undefined) {
throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
}
}
/**
* Handle string value. It should be a valid and not empty.
* Either remove the flag or provide a value
*/
if (flag.type === 'string') {
value = this.castToString(value);
if (value === undefined) {
throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
}
}
/**
* Handle numeric values. The flag should have a value and
* a valid number.
*/
if (flag.type === 'number') {
value = this.castToNumer(value);
if (value === undefined || isNaN(value)) {
throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
}
}
/**
* Parse the value to be an array of strings
*/
if (flag.type === 'array') {
value = this.castToArray(value);
if (!value || !value.length) {
throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
}
}
/**
* Parse the value to be an array of numbers
*/
if (flag.type === 'numArray') {
value = this.castToNumArray(value);
if (!value || !value.length) {
throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
}
/**
* Find if array has NaN values
*/
if (value.findIndex((one) => isNaN(one)) > -1) {
throw Exceptions_1.InvalidFlagException.invoke(flag.name, flag.type, command);
}
}
parsed[flag.name] = value;
if (flag.alias) {
parsed[flag.alias] = value;
}
}
/**
* Validates the value to ensure that values are defined for
* required arguments.
*/
validateArg(arg, index, parsed, command) {
const value = parsed._[index];
if (value === undefined && arg.required) {
throw Exceptions_1.MissingArgumentException.invoke(arg.name, command);
}
}
/**
* Parses argv and executes the command and global flags handlers
*/
parse(argv, command) {
let options = { alias: {}, boolean: [], default: {}, string: [] };
const flagsAndAliases = [];
const globalFlags = Object.keys(this.registeredFlags).map((name) => this.registeredFlags[name]);
/**
* Build options from global flags
*/
globalFlags.forEach((flag) => {
this.preProcessFlag(flag, options);
flagsAndAliases.push(flag.name);
flag.alias && flagsAndAliases.push(flag.alias);
});
/**
* Build options from command flags
*/
if (command) {
command.flags.forEach((flag) => {
this.preProcessFlag(flag, options);
flagsAndAliases.push(flag.name);
flag.alias && flagsAndAliases.push(flag.alias);
});
}
/**
* Parsing argv with the previously built options
*/
const parsed = (0, getopts_1.default)(argv, options);
/**
* Scan and report unknown flag as exception
*/
if (command) {
this.scanForUnknownFlags(parsed, flagsAndAliases);
}
/**
* Validating global flags (if any)
*/
globalFlags.forEach((flag) => {
this.processFlag(flag, parsed);
});
/**
* Validating command flags (if command is defined)
*/
if (command) {
command.flags.forEach((flag) => {
this.processFlag(flag, parsed);
});
}
return parsed;
}
}
exports.Parser = Parser;
+4
View File
@@ -0,0 +1,4 @@
/**
* Handles the command errors and prints them to the console.
*/
export declare function handleError(error: any, callback?: (error: any) => void | Promise<void>): void;
+28
View File
@@ -0,0 +1,28 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleError = void 0;
const cliui_1 = require("@poppinss/cliui");
/**
* Handles the command errors and prints them to the console.
*/
// eslint-disable-next-line no-shadow
function handleError(error, callback) {
if (typeof callback === 'function') {
callback(error);
}
else if (typeof error.handle === 'function') {
error.handle(error);
}
else {
cliui_1.logger.fatal(error);
}
}
exports.handleError = handleError;
+10
View File
@@ -0,0 +1,10 @@
import { Aliases, CommandFlag, SerializedCommand } from '../Contracts';
/**
* Prints help for all the commands by sorting them in alphabetical order
* and grouping them as per their namespace.
*/
export declare function printHelp(commands: SerializedCommand[], flags: CommandFlag[], aliases: Aliases): void;
/**
* Prints help for a single command
*/
export declare function printHelpFor(command: SerializedCommand, aliases: Aliases): void;
+270
View File
@@ -0,0 +1,270 @@
"use strict";
/*
* @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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.printHelpFor = exports.printHelp = void 0;
const cliui_1 = require("@poppinss/cliui");
const term_size_1 = __importDefault(require("term-size"));
const sortAndGroupCommands_1 = require("./sortAndGroupCommands");
/**
* Converts a line to rows at a specific width
*/
function lineToRows(text, width) {
const rows = [];
let row = [];
let wordsCount = 0;
text.split(' ').forEach((word) => {
if (wordsCount + (word.length + 1) > width) {
/**
* Push the number of whitespace left after the existing current
* and the terminal space. We need to do this, coz we at times
* have whitespace when the upcoming word may break into next
* lines
*/
row.push(new Array(width - wordsCount + 1).join(' '));
/**
* Push the existing row to the rows
*/
rows.push(row.join(' '));
/**
* Row is empty now
*/
row = [];
/**
* Row has zero words
*/
wordsCount = 0;
}
/**
* Increase the words count + 1. The extra one is for the
* whitspace between the words
*/
wordsCount += word.length + 1;
/**
* Collect word inside the row
*/
row.push(word);
});
/**
* Handle the orphan row
*/
if (row.length) {
rows.push(row.join(' '));
}
return rows;
}
/**
* Converts the description to multiple lines fitting into
* a given column size
*/
function descriptionToRows(description, options) {
return lineToRows(description, options.descriptionColumnsSize)
.map((column, index) => {
return index > 0 ? `${new Array(options.nameColumnSize + 1).join(' ')}${column}` : column;
})
.join('');
}
/**
* Wraps the command arg inside `<>` or `[]` brackets based upon if it's
* required or not.
*/
function wrapArg(arg) {
const displayName = arg.type === 'spread' ? `...${arg.name}` : arg.name;
return arg.required ? `<${displayName}>` : `[${displayName}]`;
}
/**
* Returns an array of flags for displaying the help screen
*/
function getFlagsForDisplay(flags) {
return flags.map(({ name, type, alias, description }) => {
/**
* Display name is the way we want to display a single flag in the
* list of flags
*/
const displayName = alias ? `-${alias}, --${name}` : `--${name}`;
/**
* The type hints the user about the expectation on the flag type. We only
* print the type, when flag is not a boolean.
*/
let displayType = '';
switch (type) {
case 'array':
displayType = 'string[]';
break;
case 'numArray':
displayType = 'number[]';
break;
case 'string':
displayType = 'string';
break;
case 'boolean':
displayType = 'boolean';
break;
case 'number':
displayType = 'number';
break;
}
return {
displayName,
displayType,
description,
width: displayName.length + displayType.length,
};
});
}
/**
* Returns an array of args for displaying the help screen
*/
function getArgsForDisplay(args) {
return args.map(({ name, description }) => {
return {
displayName: name,
description: description,
width: name.length,
};
});
}
/**
* Returns an array of commands for display
*/
function getCommandsForDisplay(commands, aliases) {
return commands.map(({ commandName, description }) => {
const commandAliases = getCommandAliases(commandName, aliases);
const aliasesString = commandAliases.length ? ` [${commandAliases.join(', ')}]` : '';
return {
displayName: `${commandName}${aliasesString}`,
description,
width: commandName.length + aliasesString.length,
};
});
}
/**
* Returns the aliases for a given command
*/
function getCommandAliases(commandName, aliases) {
return Object.keys(aliases).reduce((commandAliases, alias) => {
if (aliases[alias] === commandName) {
commandAliases.push(alias);
}
return commandAliases;
}, []);
}
/**
* Prints help for all the commands by sorting them in alphabetical order
* and grouping them as per their namespace.
*/
function printHelp(commands, flags, aliases) {
const flagsList = getFlagsForDisplay(flags);
const commandsList = getCommandsForDisplay(commands, aliases);
/**
* Get width of longest command name.
*/
const maxWidth = Math.max.apply(Math, flagsList.concat(commandsList).map(({ width }) => width));
/**
* Size of the terminal columns. Max width is the width of the command
* name and the extra four is whitespace around the command name.
*
* This gives the columns size for the description section
*/
const descriptionColumnsSize = (0, term_size_1.default)().columns - (maxWidth + 4);
/**
* Sort commands and group them, so that we can print them as per
* the namespace they belongs to
*/
(0, sortAndGroupCommands_1.sortAndGroupCommands)(commands).forEach(({ group, commands: groupCommands }) => {
console.log('');
if (group === 'root') {
console.log(cliui_1.logger.colors.bold(cliui_1.logger.colors.yellow('Available commands')));
}
else {
console.log(cliui_1.logger.colors.bold(cliui_1.logger.colors.yellow(group)));
}
groupCommands.forEach(({ commandName, description }) => {
const commandAliases = getCommandAliases(commandName, aliases);
const aliasesString = commandAliases.length ? ` [${commandAliases.join(', ')}]` : '';
const displayName = `${commandName}${aliasesString}`;
const whiteSpace = ''.padEnd(maxWidth - displayName.length, ' ');
const descriptionRows = descriptionToRows(description, {
nameColumnSize: maxWidth + 4,
descriptionColumnsSize,
});
console.log(` ${cliui_1.logger.colors.green(displayName)} ${whiteSpace} ${cliui_1.logger.colors.dim(descriptionRows)}`);
});
});
if (flagsList.length) {
console.log('');
console.log(cliui_1.logger.colors.bold(cliui_1.logger.colors.yellow('Global Flags')));
flagsList.forEach(({ displayName, displayType, description = '', width }) => {
const whiteSpace = ''.padEnd(maxWidth - width, ' ');
const descriptionRows = descriptionToRows(description, {
nameColumnSize: maxWidth + 4,
descriptionColumnsSize,
});
console.log(` ${cliui_1.logger.colors.green(displayName)} ${cliui_1.logger.colors.dim(displayType)}${whiteSpace} ${cliui_1.logger.colors.dim(descriptionRows)}`);
});
}
}
exports.printHelp = printHelp;
/**
* Prints help for a single command
*/
function printHelpFor(command, aliases) {
if (command.description) {
console.log('');
console.log(command.description);
}
console.log('');
console.log(`${cliui_1.logger.colors.yellow('Usage:')} ${command.commandName} ${cliui_1.logger.colors.dim(command.args.map(wrapArg).join(' '))}`);
const flags = getFlagsForDisplay(command.flags);
const args = getArgsForDisplay(command.args);
/**
* Getting max width to keep flags and args symmetric
*/
const maxWidth = Math.max.apply(Math, flags.concat(args).map(({ width }) => width));
/**
* Size of the terminal columns. Max width is the width of the command
* name and the extra four is whitespace around the command name.
*
* This gives the columns size for the description section
*/
const descriptionColumnsSize = (0, term_size_1.default)().columns - (maxWidth + 5);
const commandAliases = getCommandAliases(command.commandName, aliases);
if (commandAliases.length) {
console.log('');
console.log(`${cliui_1.logger.colors.yellow('Aliases:')} ${cliui_1.logger.colors.green(commandAliases.join(', '))}`);
}
if (args.length) {
console.log('');
console.log(cliui_1.logger.colors.bold(cliui_1.logger.colors.yellow('Arguments')));
args.forEach(({ displayName, description = '', width }) => {
const whiteSpace = ''.padEnd(maxWidth - width, ' ');
const descriptionRow = descriptionToRows(description, {
nameColumnSize: maxWidth + 5,
descriptionColumnsSize,
});
console.log(` ${cliui_1.logger.colors.green(displayName)} ${whiteSpace} ${cliui_1.logger.colors.dim(descriptionRow)}`);
});
}
if (flags.length) {
console.log('');
console.log(cliui_1.logger.colors.bold(cliui_1.logger.colors.yellow('Flags')));
flags.forEach(({ displayName, displayType, description = '', width }) => {
const whiteSpace = ''.padEnd(maxWidth - width, ' ');
const descriptionRow = descriptionToRows(description, {
nameColumnSize: maxWidth + 5,
descriptionColumnsSize,
});
console.log(` ${cliui_1.logger.colors.green(displayName)} ${cliui_1.logger.colors.dim(displayType)}${whiteSpace} ${cliui_1.logger.colors.dim(descriptionRow)}`);
});
}
}
exports.printHelpFor = printHelpFor;
+6
View File
@@ -0,0 +1,6 @@
import { CommandsListFilterFn } from '../Contracts';
/**
* Returns an array of Javascript files inside the current directory in
* relative to the application root.
*/
export declare function listDirectoryFiles(scanDirectory: string, appRoot: string, filesToIgnore?: CommandsListFilterFn): string[];
+48
View File
@@ -0,0 +1,48 @@
"use strict";
/*
* @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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listDirectoryFiles = void 0;
const slash_1 = __importDefault(require("slash"));
const path_1 = require("path");
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
* Checks if the file exists inside the array. Also an extension
* agnostic check is performed to handle `.ts` and `.js` files
* both
*/
function filesFilter(fileName, filesToIgnore) {
if (filesToIgnore.includes(fileName)) {
return true;
}
fileName = fileName.replace((0, path_1.extname)(fileName), '');
return filesToIgnore.includes(fileName);
}
/**
* Returns an array of Javascript files inside the current directory in
* relative to the application root.
*/
function listDirectoryFiles(scanDirectory, appRoot, filesToIgnore) {
return (0, helpers_1.fsReadAll)(scanDirectory)
.filter((name) => !name.endsWith('.json')) // remove .json files
.map((name) => {
const relativePath = (0, path_1.relative)(appRoot, (0, path_1.join)(scanDirectory, name));
return (0, slash_1.default)(relativePath.startsWith('../') ? relativePath : `./${relativePath}`);
})
.filter((name) => {
if (typeof filesToIgnore === 'function') {
return filesToIgnore(name);
}
return Array.isArray(filesToIgnore) ? !filesFilter(name, filesToIgnore) : true;
});
}
exports.listDirectoryFiles = listDirectoryFiles;
+7
View File
@@ -0,0 +1,7 @@
import { CommandsGroup, SerializedCommand } from '../Contracts';
/**
* Loops over the commands and converts them to an array of sorted groups with
* nested commands inside them. The grouping is done using the command
* namespace seperated with `:`. Example: `make:controller`
*/
export declare function sortAndGroupCommands(commands: SerializedCommand[]): CommandsGroup;
+67
View File
@@ -0,0 +1,67 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.sortAndGroupCommands = void 0;
/**
* Loops over the commands and converts them to an array of sorted groups with
* nested commands inside them. The grouping is done using the command
* namespace seperated with `:`. Example: `make:controller`
*/
function sortAndGroupCommands(commands) {
/**
* Create a group of commands using it's namespace
*/
const groupsLiteral = commands.reduce((result, command) => {
const tokens = command.commandName.split(':');
/**
* Use the command namespace or move it inside the `root` group when
* it is not namespaced.
*/
const group = tokens.length > 1 ? tokens.shift() : 'root';
result[group] = result[group] || [];
result[group].push(command);
return result;
}, {});
/**
* Convert the object literal groups and it's command to an
* array of sorted groups and commands
*/
return Object.keys(groupsLiteral)
.sort((prev, curr) => {
if (prev === 'root') {
return -1;
}
if (curr === 'root') {
return 1;
}
if (curr > prev) {
return -1;
}
if (curr < prev) {
return 1;
}
return 0;
})
.map((name) => {
return {
group: name,
commands: groupsLiteral[name].sort((prev, curr) => {
if (curr.commandName > prev.commandName) {
return -1;
}
if (curr.commandName < prev.commandName) {
return 1;
}
return 0;
}),
};
});
}
exports.sortAndGroupCommands = sortAndGroupCommands;
+10
View File
@@ -0,0 +1,10 @@
/**
* Process string as a template literal string and processes
* data
*/
export declare function template(tpl: string, data: Object, filename?: string, isMustache?: boolean): any;
/**
* Loads template file from the disk and process it contents
* using the [[template]] method
*/
export declare function templateFromFile(file: string, data: object, isMustache: boolean): string;
+56
View File
@@ -0,0 +1,56 @@
"use strict";
/*
* @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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.templateFromFile = exports.template = void 0;
const vm_1 = require("vm");
const mustache_1 = __importDefault(require("mustache"));
const fs_1 = require("fs");
const STACK_REGEXP = /evalmachine\.<anonymous>:(\d+)(?::(\d+))?\n/;
const STACK_REGEXP_ALL = new RegExp(STACK_REGEXP.source, 'g');
/**
* Process string as a template literal string and processes
* data
*/
function template(tpl, data, filename = 'eval', isMustache = false) {
if (isMustache) {
return mustache_1.default.render(tpl, data);
}
try {
return (0, vm_1.runInNewContext)('`' + tpl + '`', data);
}
catch (error) {
const positions = error.stack.match(STACK_REGEXP_ALL);
if (!positions) {
throw error;
}
const position = [filename];
const tokens = positions.pop().match(STACK_REGEXP);
if (tokens[1]) {
position.push(tokens[1]);
}
if (tokens[2]) {
position.push(tokens[2]);
}
throw new Error(`Error in template ${position.join(':')}\n${error.message}`);
}
}
exports.template = template;
/**
* Loads template file from the disk and process it contents
* using the [[template]] method
*/
function templateFromFile(file, data, isMustache) {
const contents = (0, fs_1.readFileSync)(file, 'utf8');
return template(contents, data, file, isMustache);
}
exports.templateFromFile = templateFromFile;
+6
View File
@@ -0,0 +1,6 @@
import { CommandConstructorContract } from '../Contracts';
/**
* Validates the command static properties to ensure that all the
* values are correctly defined for a command to be executed.
*/
export declare function validateCommand(command: any, commandPath?: string): asserts command is CommandConstructorContract;
+60
View File
@@ -0,0 +1,60 @@
"use strict";
/*
* @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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateCommand = void 0;
const utils_1 = require("@poppinss/utils");
/**
* Validates the command static properties to ensure that all the
* values are correctly defined for a command to be executed.
*/
function validateCommand(command, commandPath) {
if (!command.name) {
throw new utils_1.Exception(`Invalid command"${commandPath ? ` ${commandPath}` : ''}". Make sure the command is exported using the "export default"`);
}
/**
* Ensure command has a name, a boot method and args property
*/
if (!command.commandName || typeof command.boot !== 'function') {
throw new utils_1.Exception(`Invalid command "${command.name}". Make sure to define the static property "commandName"`);
}
/**
* Boot command
*/
command.boot();
/**
* Ensure command has args and flags after the boot method
*/
if (!Array.isArray(command.args) || !Array.isArray(command.flags)) {
throw new utils_1.Exception(`Invalid command "${command.name}". Make sure it extends the BaseCommand`);
}
let optionalArg;
/**
* Validate for optional args and spread args
*/
command.args.forEach((arg, index) => {
/**
* Ensure optional arguments comes after required
* arguments
*/
if (optionalArg && arg.required) {
throw new utils_1.Exception(`Optional argument "${optionalArg.name}" must be after the required argument "${arg.name}"`);
}
/**
* Ensure spread arg is the last arg
*/
if (arg.type === 'spread' && command.args.length > index + 1) {
throw new utils_1.Exception(`Spread argument "${arg.name}" must be at last position`);
}
if (!arg.required) {
optionalArg = arg;
}
});
}
exports.validateCommand = validateCommand;