Installations
npm install liftoff
Releases
Contributors
Developer
gulpjs
Developer Guide
Module System
CommonJS
Min. Node Version
>=10.13.0
Typescript Support
No
Node Version
20.5.0
NPM Version
9.8.0
Statistics
843 Stars
201 Commits
51 Forks
15 Watching
5 Branches
24 Contributors
Updated on 22 Nov 2024
Languages
JavaScript (100%)
Total Downloads
Cumulative downloads
Total Downloads
830,234,540
Last day
-5.3%
498,537
Compared to previous day
Last week
3.2%
2,927,231
Compared to previous week
Last month
13.3%
11,781,136
Compared to previous month
Last year
-0%
125,061,328
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
liftoff
Launch your command line tool with ease.
What is it?
See this blog post, check out this proof of concept, or read on.
Say you're writing a CLI tool. Let's call it hacker. You want to configure it using a Hackerfile
. This is node, so you install hacker
locally for each project you use it in. But, in order to get the hacker
command in your PATH, you also install it globally.
Now, when you run hacker
, you want to configure what it does using the Hackerfile
in your current directory, and you want it to execute using the local installation of your tool. Also, it'd be nice if the hacker
command was smart enough to traverse up your folders until it finds a Hackerfile
—for those times when you're not in the root directory of your project. Heck, you might even want to launch hacker
from a folder outside of your project by manually specifying a working directory. Liftoff manages this for you.
So, everything is working great. Now you can find your local hacker
and Hackerfile
with ease. Unfortunately, it turns out you've authored your Hackerfile
in coffee-script, or some other JS variant. In order to support that, you have to load the compiler for it, and then register the extension for it with node. Good news, Liftoff can do that, and a whole lot more, too.
Usage
1const Liftoff = require('liftoff');
2
3const Hacker = new Liftoff({
4 name: 'hacker',
5 processTitle: 'hacker',
6 moduleName: 'hacker',
7 configName: 'hackerfile',
8 extensions: {
9 '.js': null,
10 '.json': null,
11 '.coffee': 'coffee-script/register',
12 },
13 v8flags: ['--harmony'], // or v8flags: require('v8flags')
14});
15
16Hacker.prepare({}, function (env) {
17 Hacker.execute(env, function (env) {
18 // Do post-execute things
19 });
20});
API
constructor(opts)
Create an instance of Liftoff to invoke your application.
opts.name
Sugar for setting processTitle
, moduleName
, configName
automatically.
Type: String
Default: null
These are equivalent:
1const Hacker = Liftoff({
2 processTitle: 'hacker',
3 moduleName: 'hacker',
4 configName: 'hackerfile',
5});
1const Hacker = Liftoff({ name: 'hacker' });
Type: String
Default: null
opts.configName
Sets the name of the configuration file Liftoff will attempt to find. Case-insensitive.
Type: String
Default: null
opts.extensions
Set extensions to include when searching for a configuration file. If an external module is needed to load a given extension (e.g. .coffee
), the module name should be specified as the value for the key.
Type: Object
Default: {".js":null,".json":null}
Examples:
In this example Liftoff will look for myappfile{.js,.json,.coffee}
. If a config with the extension .coffee
is found, Liftoff will try to require coffee-script/require
from the current working directory.
1const MyApp = new Liftoff({
2 name: 'myapp',
3 extensions: {
4 '.js': null,
5 '.json': null,
6 '.coffee': 'coffee-script/register',
7 },
8});
In this example, Liftoff will look for .myapp{rc}
.
1const MyApp = new Liftoff({
2 name: 'myapp',
3 configName: '.myapp',
4 extensions: {
5 rc: null,
6 },
7});
In this example, Liftoff will automatically attempt to load the correct module for any javascript variant supported by interpret (as long as it does not require a register method).
1const MyApp = new Liftoff({
2 name: 'myapp',
3 extensions: require('interpret').jsVariants,
4});
opts.v8flags
Any flag specified here will be applied to node, not your program. Useful for supporting invocations like myapp --harmony command
, where --harmony
should be passed to node, not your program. This functionality is implemented using flagged-respawn. To support all v8flags, see v8flags.
Type: Array
or Function
Default: null
If this method is a function, it should take a node-style callback that yields an array of flags.
opts.processTitle
Sets what the process title will be.
Type: String
Default: null
opts.completions(type)
A method to handle bash/zsh/whatever completions.
Type: Function
Default: null
opts.configFiles
An array of configuration files to find with each value being a path arguments.
The order of the array indicates the priority that config file overrides are applied. See Config Files for the config file specification and description of overrides.
Note: This option is useful if, for example, you want to support an .apprc
file in addition to an appfile.js
. If you only need a single configuration file, you probably don't need this. In addition to letting you find multiple files, this option allows more fine-grained control over how configuration files are located.
Type: Array
Default: null
Path arguments
The fined
module accepts a string representing the path to search or an object with the following keys:
-
path
(required)The path to search. Using only a string expands to this property.
Type:
String
Default:
null
-
name
The basename of the file to find. Extensions are appended during lookup.
Type:
String
Default: Top-level key in
configFiles
-
extensions
The extensions to append to
name
during lookup. See also:opts.extensions
.Type:
String
orArray
orObject
Default: The value ofopts.extensions
-
cwd
The base directory of
path
(if relative).Type:
String
Default: The value of
opts.cwd
-
findUp
Whether the
path
should be traversed up to find the file.Type:
Boolean
Default:
false
Examples:
In this example Liftoff will look for the .hacker.js
file relative to the cwd
as declared in configFiles
.
1const MyApp = new Liftoff({ 2 name: 'hacker', 3 configFiles: [ 4 { name: '.hacker', path: '.' } 5 ], 6});
In this example, Liftoff will look for .hackerrc
in the home directory.
1const MyApp = new Liftoff({ 2 name: 'hacker', 3 configFiles: [ 4 { 5 name: '.hacker', 6 path: '~', 7 extensions: { 8 rc: null, 9 }, 10 }, 11 ], 12});
In this example, Liftoff will look in the cwd
and then lookup the tree for the .hacker.js
file.
1const MyApp = new Liftoff({ 2 name: 'hacker', 3 configFiles: [ 4 { 5 name: '.hacker', 6 path: '.', 7 findUp: true, 8 }, 9 ], 10});
In this example, Liftoff will use the home directory as the cwd
and looks for ~/.hacker.js
.
1const MyApp = new Liftoff({ 2 name: 'hacker', 3 configFiles: [ 4 { 5 name: '.hacker', 6 path: '.', 7 cwd: '~', 8 }, 9 ], 10});
prepare(opts, callback(env))
Prepares the environment for your application with provided options, and invokes your callback with the calculated environment as the first argument. The environment can be modified before using it as the first argument to execute
.
Example Configuration w/ Options Parsing:
1const Liftoff = require('liftoff'); 2const MyApp = new Liftoff({ name: 'myapp' }); 3const argv = require('minimist')(process.argv.slice(2)); 4const onExecute = function (env, argv) { 5 // Do post-execute things 6}; 7const onPrepare = function (env) { 8 console.log('my environment is:', env); 9 console.log('my liftoff config is:', this); 10 MyApp.execute(env, onExecute); 11}; 12MyApp.prepare( 13 { 14 cwd: argv.cwd, 15 configPath: argv.myappfile, 16 preload: argv.preload, 17 completion: argv.completion, 18 }, 19 onPrepare 20);
Example w/ modified environment
1const Liftoff = require('liftoff'); 2const Hacker = new Liftoff({ 3 name: 'hacker', 4 configFiles: [ 5 { name: '.hacker', path: '.', cwd: '~' } 6 ], 7}); 8const onExecute = function (env, argv) { 9 // Do post-execute things 10}; 11const onPrepare = function (env) { 12 const config = env.config['.hacker']; 13 Hacker.execute(env, config.forcedFlags, onExecute); 14}; 15Hacker.prepare({}, onPrepare);
opts.cwd
Change the current working directory for this execution. Relative paths are calculated against process.cwd()
.
Type: String
Default: process.cwd()
Example Configuration:
1const argv = require('minimist')(process.argv.slice(2)); 2MyApp.prepare( 3 { 4 cwd: argv.cwd, 5 }, 6 function (env) { 7 MyApp.execute(env, invoke); 8 } 9);
Matching CLI Invocation:
myapp --cwd ../
opts.configPath
Don't search for a config, use the one provided. Note: Liftoff will assume the current working directory is the directory containing the config file unless an alternate location is explicitly specified using cwd
.
Type: String
Default: null
Example Configuration:
1var argv = require('minimist')(process.argv.slice(2)); 2MyApp.prepare( 3 { 4 configPath: argv.myappfile, 5 }, 6 function (env) { 7 MyApp.execute(env, invoke); 8 } 9);
Matching CLI Invocation:
1myapp --myappfile /var/www/project/Myappfile.js
Examples using cwd
and configPath
together:
These are functionally identical:
1myapp --myappfile /var/www/project/Myappfile.js 2myapp --cwd /var/www/project
These can run myapp from a shared directory as though it were located in another project:
1myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project1 2myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project2
opts.preload
A string or array of modules to attempt requiring from the local working directory before invoking the execute callback.
Type: String|Array
Default: null
Example Configuration:
1var argv = require('minimist')(process.argv.slice(2)); 2MyApp.prepare( 3 { 4 preload: argv.preload, 5 }, 6 function (env) { 7 MyApp.execute(env, invoke); 8 } 9);
Matching CLI Invocation:
1myapp --preload coffee-script/register
callback(env)
A function called after your environment is prepared. A good place to modify the environment before calling execute
. When invoked, this
will be your instance of Liftoff. The env
param will contain the following keys:
cwd
: the current working directorypreload
: an array of modules that liftoff tried to pre-loadconfigNameSearch
: the config files searched forconfigPath
: the full path to your configuration file (if found)configBase
: the base directory of your configuration file (if found)modulePath
: the full path to the local module your project relies on (if found)modulePackage
: the contents of the local module's package.json (if found)configFiles
: an array of filepaths for each found config file (filepath values will be null if not found)config
: an array of loaded config objects in the same order asconfigFiles
execute(env, [forcedFlags], callback(env, argv))
A function to start your application, based on the env
given. Optionally takes an array of forcedFlags
, which will force a respawn with those node or V8 flags during startup. Invokes your callback with the environment and command-line arguments (minus node & v8 flags) after the application has been executed.
Example:
1const Liftoff = require('liftoff'); 2const MyApp = new Liftoff({ name: 'myapp' }); 3const onExecute = function (env, argv) { 4 // Do post-execute things 5 console.log('my environment is:', env); 6 console.log('my cli options are:', argv); 7 console.log('my liftoff config is:', this); 8}; 9const onPrepare = function (env) { 10 var forcedFlags = ['--trace-deprecation']; 11 MyApp.execute(env, forcedFlags, onExecute); 12}; 13MyApp.prepare({}, onPrepare);
callback(env, argv)
A function called after your application is executed. When invoked, this
will be your instance of Liftoff, argv
will be all command-line arguments (minus node & v8 flags), and env
will contain the following keys:
cwd
: the current working directorypreload
: an array of modules that liftoff tried to pre-loadconfigNameSearch
: the config files searched forconfigPath
: the full path to your configuration file (if found)configBase
: the base directory of your configuration file (if found)modulePath
: the full path to the local module your project relies on (if found)modulePackage
: the contents of the local module's package.json (if found)configFiles
: an array of filepaths for each found config file (filepath values will be null if not found)config
: an array of loaded config objects in the same order asconfigFiles
events
on('preload:before', function(name) {})
Emitted before a module is pre-load. (But for only a module which is specified by opts.preload
.)
1var Hacker = new Liftoff({ name: 'hacker', preload: 'coffee-script' }); 2Hacker.on('preload:before', function (name) { 3 console.log('Requiring external module: ' + name + '...'); 4});
on('preload:success', function(name, module) {})
Emitted when a module has been pre-loaded.
1var Hacker = new Liftoff({ name: 'hacker' }); 2Hacker.on('preload:success', function (name, module) { 3 console.log('Required external module: ' + name + '...'); 4 // automatically register coffee-script extensions 5 if (name === 'coffee-script') { 6 module.register(); 7 } 8});
on('preload:failure', function(name, err) {})
Emitted when a requested module cannot be preloaded.
1var Hacker = new Liftoff({ name: 'hacker' }); 2Hacker.on('preload:failure', function (name, err) { 3 console.log('Unable to load:', name, err); 4});
on('loader:success, function(name, module) {})
Emitted when a loader that matches an extension has been loaded.
1var Hacker = new Liftoff({ 2 name: 'hacker', 3 extensions: { 4 '.ts': 'ts-node/register', 5 }, 6}); 7Hacker.on('loader:success', function (name, module) { 8 console.log('Required external module: ' + name + '...'); 9});
on('loader:failure', function(name, err) {})
Emitted when no loader for an extension can be loaded. Emits an error for each failed loader.
1var Hacker = new Liftoff({ 2 name: 'hacker', 3 extensions: { 4 '.ts': 'ts-node/register', 5 }, 6}); 7Hacker.on('loader:failure', function (name, err) { 8 console.log('Unable to load:', name, err); 9});
on('respawn', function(flags, child) {})
Emitted when Liftoff re-spawns your process (when a v8flags
is detected).
1var Hacker = new Liftoff({ 2 name: 'hacker', 3 v8flags: ['--harmony'], 4}); 5Hacker.on('respawn', function (flags, child) { 6 console.log('Detected node flags:', flags); 7 console.log('Respawned to PID:', child.pid); 8});
Event will be triggered for this command:
hacker --harmony commmand
Config files
Liftoff supports a small definition of config files, but all details provided by users will be available in env.config
.
extends
All extends
properties will be traversed and become the basis for the resulting config object. Any path provided for extends
will be loaded with node's require
, so all extensions and loaders supported on the Liftoff instance will be available to them.
Field matching the configName
Users can override the configPath
via their config files by specifying a field with the same name as the primary configName
. For example, the hackerfile
property in a configFile
will resolve the configPath
and configBase
against the path.
preload
If specified as a string or array of strings, they will be added to the list of preloads in the environment.
Examples
Check out how gulp uses Liftoff.
For a bare-bones example, try the hacker project.
To try the example, do the following:
- Install the sample project
hacker
withnpm install -g hacker
. - Make a
Hackerfile.js
with some arbitrary javascript it. - Install hacker next to it with
npm install hacker
. - Run
hacker
while in the same parent folder.
License
MIT
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
0 existing vulnerabilities detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
security policy file detected
Details
- Info: security policy file detected: github.com/gulpjs/.github/SECURITY.md:1
- Info: Found linked content: github.com/gulpjs/.github/SECURITY.md:1
- Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/gulpjs/.github/SECURITY.md:1
- Info: Found text in security policy: github.com/gulpjs/.github/SECURITY.md:1
Reason
Found 1/28 approved changesets -- score normalized to 0
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/dev.yml:1
- Warn: no topLevel permission defined: .github/workflows/release.yml:1
- Info: no jobLevel write permissions found
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/dev.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/gulpjs/liftoff/dev.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/dev.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/gulpjs/liftoff/dev.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/dev.yml:39: update your workflow using https://app.stepsecurity.io/secureworkflow/gulpjs/liftoff/dev.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/dev.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/gulpjs/liftoff/dev.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/dev.yml:59: update your workflow using https://app.stepsecurity.io/secureworkflow/gulpjs/liftoff/dev.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/dev.yml:72: update your workflow using https://app.stepsecurity.io/secureworkflow/gulpjs/liftoff/dev.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/gulpjs/liftoff/release.yml/main?enable=pin
- Warn: npmCommand not pinned by hash: .github/workflows/dev.yml:51
- Info: 0 out of 3 GitHub-owned GitHubAction dependencies pinned
- Info: 0 out of 4 third-party GitHubAction dependencies pinned
- Info: 0 out of 1 npmCommand dependencies pinned
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
branch protection not enabled on development/release branches
Details
- Warn: branch protection not enabled for branch 'main'
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 7 are checked with a SAST tool
Score
4.1
/10
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More