Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@travetto/cli
Advanced tools
Install: @travetto/cli
npm install @travetto/cli
# or
yarn add @travetto/cli
The cli module represents the primary entry point for execution within the framework. One of the main goals for this module is extensibility, as adding new entry points is meant to be trivial. The framework leverages this module for exposing all executable tools and entry points. To see a high level listing of all supported commands, invoke trv --help
Terminal: General Usage
$ trv --help
Usage: [options] [command]
Commands:
doc Command line support for generating module docs.
doc:angular Generate documentation into the angular webapp under related/travetto.github.io
doc:mapping Generate module mapping for
email:compile CLI Entry point for running the email server
email:editor The email editor compilation service and output serving
email:test CLI Entry point for running the email server
lint Command line support for linting
lint:register Writes the lint configuration file
model:export Exports model schemas
model:install Installing models
openapi:client CLI for generating the cli client
openapi:spec CLI for outputting the open api spec to a local file
pack Standard pack support
pack:docker Standard docker support for pack
pack:lambda Standard lambda support for pack
pack:zip Standard zip support for pack
repo:exec Repo execution
repo:list Allows for listing of modules
repo:publish Publish all pending modules
repo:version Version all changed dependencies
repo:version-sync Enforces all packages to write out their versions and dependencies
rest:client Run client rest operation
run:double Doubles a number
run:rest Run a rest server as an application
scaffold Command to run scaffolding
service Allows for running services
test Launch test framework and execute tests
test:watch Invoke the test watcher
This listing is from the Travetto monorepo, and represents the majority of tools that can be invoked from the command line.
This module also has a tight integration with the VSCode plugin, allowing the editing experience to benefit from the commands defined. The most commonly used commands will be the ones packaged with the framework, but its also very easy to create new commands. With the correct configuration, these commands will also be exposed within VSCode.
At it's heart, a cli command is the contract defined by what flags, and what arguments the command supports. Within the framework this requires three criteria to be met:
support/
folder, and have a name that matches cli.*.ts
Code: Basic Command
import { CliCommand } from '@travetto/cli';
@CliCommand()
export class BasicCommand {
main() {
console.log('Hello');
}
}
Terminal: Basic Command Help
$ trv basic -h
Usage: basic [options]
Options:
-h, --help display help for command
The file name support/cli.<name>.ts
has a direct mapping to the cli command name. This hard mapping allows for the framework to be able to know which file to invoke without needing to load all command-related files.
Examples of mappings:
cli.test.ts
maps to test
cli.pack_docker.ts
maps to pack:docker
cli.email_template.ts
maps to email:template
The pattern is that underscores(_) translate to colons (:), and the cli.
prefix, and .ts
suffix are dropped.@CliCommand is a wrapper for @Schema, and so every class that uses the @CliCommand decorator is now a full @Schema class. The fields of the class represent the flags that are available to the command.
Code: Basic Command with Flag
import { CliCommand } from '@travetto/cli';
@CliCommand()
export class BasicCommand {
loud?: boolean;
main() {
console.log(this.loud ? 'HELLO' : 'Hello');
}
}
Terminal: Basic Command with Flag Help
$ trv basic:flag -h
Usage: basic:flag [options]
Options:
-l, --loud
-h, --help display help for command
As you can see the command now has the support of a basic boolean flag to determine if the response should be loud or not. The default value here is undefined/false, and so is an opt-in experience.
Terminal: Basic Command with Loud Flag
$ trv basic:flag --loud
HELLO
The @CliCommand supports the following data types for flags:
The main()
method is the entrypoint for the command, represents a series of parameters. Some will be required, some may be optional. The arguments support all types supported by the flags, and decorators can be provided using the decorators inline on parameters. Optional arguments in the method, will be optional at run time, and filled with the provided default values.
Code: Basic Command with Arg
import { CliCommand } from '@travetto/cli';
import { Max, Min } from '@travetto/schema';
@CliCommand()
export class BasicCommand {
main(@Min(1) @Max(10) volume: number = 1) {
console.log(volume > 7 ? 'HELLO' : 'Hello');
}
}
Terminal: Basic Command
$ trv basic:arg -h
Usage: basic:arg [options] [volume:number]
Options:
-h, --help display help for command
Terminal: Basic Command with Invalid Loud Arg
$ trv basic:arg 20
Execution failed:
* Argument volume is bigger than (10)
Usage: basic:arg [options] [volume:number]
Options:
-h, --help display help for command
Terminal: Basic Command with Loud Arg > 7
$ trv basic:arg 8
HELLO
Terminal: Basic Command without Arg
$ trv basic:arg
Hello
Additionally, if you provide a field as an array, it will collect all valid values (excludes flags, and any arguments past a --
).
Code: Basic Command with Arg List
import { CliCommand } from '@travetto/cli';
import { Max, Min } from '@travetto/schema';
@CliCommand()
export class BasicCommand {
reverse?: boolean;
main(@Min(1) @Max(10) volumes: number[]) {
console.log(volumes.sort((a, b) => (a - b) * (this.reverse ? -1 : 1)).join(' '));
}
}
Terminal: Basic Command
$ trv basic:arglist -h
Usage: basic:arglist [options] <volumes...:number>
Options:
-r, --reverse
-h, --help display help for command
Terminal: Basic Arg List
$ trv basic:arglist 10 5 3 9 8 1
1 3 5 8 9 10
Terminal: Basic Arg List with Invalid Number
$ trv basic:arglist 10 5 3 9 20 1
Execution failed:
* Argument volumes[4] is bigger than (10)
Usage: basic:arglist [options] <volumes...:number>
Options:
-r, --reverse
-h, --help display help for command
Terminal: Basic Arg List with Reverse
$ trv basic:arglist -r 10 5 3 9 8 1
10 9 8 5 3 1
By default, all fields are treated as flags and all parameters of main()
are treated as arguments within the validation process. Like the standard @Schema behavior, we can leverage the metadata of the fields/parameters to help provide additional customization/context for the users of the commands.
Code: Custom Command with Metadata
import { CliCommand } from '@travetto/cli';
import { Max, Min } from '@travetto/schema';
/**
* Custom Argument Command
*/
@CliCommand()
export class CustomCommand {
/**
* The message to send back to the user
* @alias -m
* @alias --message
*/
text: string = 'hello';
main(@Min(1) @Max(10) volume: number = 1) {
console.log(volume > 7 ? this.text.toUpperCase() : this.text);
}
}
Terminal: Custom Command Help
$ trv custom:arg -h
Usage: custom:arg [options] [volume:number]
Options:
-m, --message <string> The message to send back to the user (default: "hello")
-h, --help display help for command
Terminal: Custom Command Help with overridden Text
$ trv custom:arg 10 -m cUsToM
CUSTOM
Terminal: Custom Command Help with default Text
$ trv custom:arg 6
hello
In addition to standard flag overriding (e.g. /** @alias -m */
), the command execution also supports allowing environment variables to provide values (secondary to whatever is passed in on the command line).
Code: Custom Command with Env Var
import { CliCommand } from '@travetto/cli';
import { Max, Min } from '@travetto/schema';
/**
* Custom Argument Command
*/
@CliCommand()
export class CustomCommand {
/**
* The message to send back to the user
* @alias env.MESSAGE
*/
text: string = 'hello';
main(@Min(1) @Max(10) volume: number = 1) {
console.log(volume > 7 ? this.text.toUpperCase() : this.text);
}
}
Terminal: Custom Command Help
$ trv custom:env-arg -h
Usage: custom:env-arg [options] [volume:number]
Options:
-t, --text <string> The message to send back to the user (default: "hello")
-h, --help display help for command
Terminal: Custom Command Help with default Text
$ trv custom:env-arg 6
hello
Terminal: Custom Command Help with overridden Text
$ MESSAGE=CuStOm trv custom:env-arg 10
CUSTOM
Terminal: Custom Command Help with overridden Text
$ MESSAGE=CuStOm trv custom:env-arg 7
CuStOm
Sometimes its also convenient, especially with commands that support a variety of flags, to provide easy access to pre-defined sets of flags. Flag files represent a snapshot of command line arguments and flags, as defined in a file. When referenced, these inputs are essentially injected into the command line as if the user had typed them manually.
Code: Example Flag File
--host localhost
--port 3306
--username app
As you can see in this file, it provides easy access to predefine the host, port, and user flags.
Code: Using a Flag File
npx trv call:db +=base --password <custom>
The flag files can be included in one of a few ways:
+=<name>
- This translates into $<mod>/support/<name>.flags
, which is a convenient shorthand.+=<mod>/path/file.flags
- This is a path-related file that will be resolved from the module's location.+=/path/file.flags
- This is an absolute path that will be read from the root of the file system.
Ultimately, after resolution, the content of these files will be injected inline within the location.Code: Final arguments after Flag File resolution
npx trv call:db --host localhost --port 3306 --username app --password <custom>
By default, cli commands do not expose themselves to the VSCode extension, as the majority of them are not intended for that sort of operation. RESTful API does expose a cli target run:rest
that will show up, to help run/debug a rest application. Any command can mark itself as being a run target, and will be eligible for running from within the VSCode plugin. This is achieved by setting the runTarget
field on the @CliCommand decorator. This means the target will be visible within the editor tooling.
Code: Simple Run Target
import { CliCommand } from '@travetto/cli';
/**
* Simple Run Target
*/
@CliCommand({ runTarget: true })
export class RunCommand {
main(name: string) {
console.log(name);
}
}
Code: Anatomy of a Command
export interface CliCommandShape<T extends unknown[] = unknown[]> {
/**
* Parsed state
*/
_parsed?: ParsedState;
/**
* Action target of the command
*/
main(...args: T): OrProm<RunResponse>;
/**
* Run before main runs
*/
preMain?(): OrProm<void>;
/**
* Extra help
*/
help?(): OrProm<string[]>;
/**
* Run before help is displayed
*/
preHelp?(): OrProm<void>;
/**
* Is the command active/eligible for usage
*/
isActive?(): boolean;
/**
* Run before binding occurs
*/
preBind?(): OrProm<void>;
/**
* Run before validation occurs
*/
preValidate?(): OrProm<void>;
/**
* Validation method
*/
validate?(...args: T): OrProm<CliValidationError | CliValidationError[] | undefined>;
}
If the goal is to run a more complex application, which may include depending on Dependency Injection, we can take a look at RESTful API's target:
Code: Simple Run Target
import { DependencyRegistry } from '@travetto/di';
import { CliCommand, CliCommandShape, CliUtil } from '@travetto/cli';
import { ServerHandle } from '../src/types';
/**
* Run a rest server as an application
*/
@CliCommand({ runTarget: true, addModule: true, addEnv: true })
export class RunRestCommand implements CliCommandShape {
/** IPC debug is enabled */
debugIpc?: boolean;
/** Should the server be able to run with restart*/
canRestart?: boolean;
/** Port to run on */
port?: number;
preMain(): void {
if (this.port) {
process.env.REST_PORT = `${this.port}`;
}
}
async main(): Promise<ServerHandle | void> {
if (await CliUtil.debugIfIpc(this) || await CliUtil.runWithRestart(this)) {
return;
}
const { RestApplication } = await import('../src/application/rest.js');
return DependencyRegistry.runInstance(RestApplication);
}
}
As noted in the example above, fields
is specified in this execution, with support for module
, and env
. These env flag is directly tied to the GlobalEnv flags defined in the Base module.
The module
field is slightly more complex, but is geared towards supporting commands within a monorepo context. This flag ensures that a module is specified if running from the root of the monorepo, and that the module provided is real, and can run the desired command. When running from an explicit module folder in the monorepo, the module flag is ignored.
In addition to dependency injection, the command contract also allows for a custom validation function, which will have access to bound command (flags, and args) as well as the unknown arguments. When a command implements this method, any CliValidationError errors that are returned will be shared with the user, and fail to invoke the main
method.
Code: CliValidationError
export type CliValidationError = {
/**
* The error message
*/
message: string;
/**
* Source of validation
*/
source?: 'flag' | 'arg' | 'custom';
};
A simple example of the validation can be found in the doc
command:
Code: Simple Validation Example
async validate(): Promise<CliValidationError | undefined> {
const docFile = path.resolve(this.input);
if (!(await fs.stat(docFile).catch(() => false))) {
return { message: `input: ${this.input} does not exist`, source: 'flag' };
}
}
FAQs
CLI infrastructure for Travetto framework
The npm package @travetto/cli receives a total of 45 weekly downloads. As such, @travetto/cli popularity was classified as not popular.
We found that @travetto/cli demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.