Skip to content

Commit

Permalink
feat!: Remove addExtension in favor of extension option
Browse files Browse the repository at this point in the history
feat: Support preloading modules based on extension
  • Loading branch information
Tyler Kellen authored and phated committed Nov 22, 2021
1 parent 457121d commit 45418f4
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 71 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
v0.10.0:
date: 2014-05-06
changes:
- Remove `addExtension` in favor of `extension` option.
v0.9.7:
date: 2014-04-28
changes:
Expand Down
67 changes: 50 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ var Hacker = new Liftoff({
name: 'hacker',
moduleName: 'hacker',
configName: 'hackerfile',
addExtensions: ['.anything'],
extensions: {
'.js': null,
'.json': null,
'.coffee': 'coffee-script/require'
},
processTitle: 'hacker',
cwdFlag: 'cwd',
configPathFlag: 'hackerfile',
Expand Down Expand Up @@ -60,44 +64,76 @@ new Liftoff({name:'hacker'});

Sets which module your application expects to find locally when being run.

Type: `String`
Type: `String`
Default: `null`

#### opts.configName

Sets the name of the configuration file Liftoff will attempt to find. Case-insensitive.

Type: `String`
Type: `String`
Default: `null`

#### opts.addExtensions
#### 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.

Explicitly add custom extensions to include when searching for a configuration file. Node supports `.js`, `.json` & `.node` natively, so there is no need to add these.
Type: `Object`
Default: `{".js":null,".json":null}`

An example usage would be setting this to `['rc']`. With a configName of `.myapp`, Liftoff would then look for `.myapp{rc,.js,.json,.node}`
##### Examples

Type: `Array`
Default: `[]`
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 cwd before calling `launch`.
```js
var MyApp = new Liftoff({
name: 'myapp'
extensions: {
".js": null,
".json": null,
".coffee": "coffee-script/require"
}
});
```

In this example, Liftoff will look for `.myapp{rc}`.
```js
var MyApp = new Liftoff({
name: 'myapp',
configName: '.myapp',
extensions: {
"rc": null
}
});
```

In this example, Liftoff will automatically attempt to load the correct module for any extension supported by [node-interpret](https://github.com/tkellen/node-interpret) (as long as it does not require a register method).

```js
var MyApp = new Liftoff({
name: 'myapp',
extensions: require('interpret').extensions
});
```

#### opts.processTitle

Sets what the [process title](http://nodejs.org/api/process.html#process_process_title) will be.

Type: `String`
Type: `String`
Default: `null`

#### opts.cwdFlag

Sets what flag to use for altering the current working directory. For example, `myapp --cwd ../` would invoke your application as though you'd called it from the parent of your current directory.

Type: `String`
Type: `String`
Default: `cwd`

#### opts.configPathFlag

Sets what flag to use for defining the path to your configfile. For example, `myapp --myappfile /var/www/project/Myappfile.js` would explicitly specify the location of your config file. **Note:** Liftoff will assume the current working directory is the directory containing the config file unless an alternate location is specified using `cwdFlag`.

Type: `String`
Type: `String`
Default: `same as configName`

##### Examples
Expand All @@ -119,14 +155,14 @@ myapp --myappfile /Users/name/Myappfile.js --cwd /var/www/project2
Sets what flag to use for pre-loading modules. For example, `myapp --require coffee-script` would require a local version of coffee-script (if available) before attempting to find your configuration file. If your required module registers a new
[require.extension](http://nodejs.org/api/globals.html#globals_require_extensions), it will be included as an option when looking for a file matching `configName`.

Type: `String`
Type: `String`
Default: `"require"`

#### opts.completions(type)

A method to handle bash/zsh/whatever completions.

Type: `Function`
Type: `Function`
Default: `null`

### events
Expand Down Expand Up @@ -166,7 +202,6 @@ A function to start your application. When invoked, `this` will be your instanc
- `argv`: cli arguments, as parsed by [minimist](https://npmjs.org/package/minimist), or as passed in manually.
- `cwd`: the current working directory
- `preload`: an array of modules that liftoff tried to pre-load
- `validExtensions`: an array of supported extensions for your config file
- `configNameRegex`: the regular expression used to find your config file
- `configPath`: the full path to your configuration file (if found)
- `configBase`: the base directory of your configuration file (if found)
Expand All @@ -176,7 +211,7 @@ A function to start your application. When invoked, `this` will be your instanc
#### argv
Manually specify command line arguments. Useful for invoking the CLI programmatically.

Type: `Object`
Type: `Object`
Default: `null`

## Examples
Expand All @@ -188,5 +223,3 @@ To try the example, do the following:
2. Make a `Hackerfile.js` with some arbitrary javascript it.
3. Install hacker next to it with `npm install hacker`.
3. Run `hacker` while in the same parent folder.

For extra credit, try writing your `Hackerfile` in coffeescript. Then, run `hacker --require coffee-script`. Make sure you install coffee-script locally, though.
45 changes: 27 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const resolve = require('resolve');
const fileSearch = require('./lib/file_search');
const parseOptions = require('./lib/parse_options');
const silentRequire = require('./lib/silent_require');
const validExtensions = require('./lib/valid_extensions');

function Liftoff (opts) {
EE.call(this);
Expand Down Expand Up @@ -45,23 +44,15 @@ Liftoff.prototype.findCwd = function (argv) {
Liftoff.prototype.buildEnvironment = function (argv) {
argv = argv||{};

// attempt preloading modules
var preload = argv[this.preloadFlag];
if (preload) {
if (!Array.isArray(preload)) {
preload = [preload];
}
preload.forEach(function (dep) {
this.requireLocal(dep, this.findCwd(argv));
}, this);
}

// calculate cwd
var cwd = this.findCwd(argv);

// get modules we want to preload
var preload = argv[this.preloadFlag]||[];

// calculate config file name
var configNameRegex = this.configName;
var extensions = validExtensions(this.addExtensions);
var extensions = Object.keys(this.extensions);
if (configNameRegex instanceof RegExp) {
configNameRegex = configNameRegex.toString();
} else {
Expand All @@ -85,7 +76,8 @@ Liftoff.prototype.buildEnvironment = function (argv) {
configPath = fileSearch(configNameRegex, searchIn);
}

// if we have a config path, find the directory it resides in
// if we have a config path, save the directory it resides in
// and check to see if the extension requires a preloaded module
if (configPath) {
configPath = path.resolve(configPath);
var configBase = path.dirname(configPath);
Expand All @@ -99,11 +91,13 @@ Liftoff.prototype.buildEnvironment = function (argv) {
} catch (e) {}

// if we have a configuration but we failed to find a local module, maybe
// we are developing against ourselves? check the package.json sibling to
// our config to see if its `name` matches the module we're looking for
// we are developing against ourselves?
if (!modulePath && configBase) {
// check the package.json sibling to our config to see if its `name`
// matches the module we're looking for
modulePackage = silentRequire(fileSearch('package.json', [configBase]));
if (modulePackage && modulePackage.name === this.moduleName) {
// if it does, our module path is `main` inside package.json
modulePath = path.join(configBase, modulePackage.main||'index.js');
cwd = configBase;
} else {
Expand All @@ -112,11 +106,26 @@ Liftoff.prototype.buildEnvironment = function (argv) {
}
}

// preload module needed for config if any has been specified.
var preloadForExtension = this.extensions[path.extname(configPath)];
if (preloadForExtension) {
preload.push(preloadForExtension);
}

// preload modules, if any
if (preload.length) {
if (!Array.isArray(preload)) {
preload = [preload];
}
preload.forEach(function (dep) {
this.requireLocal(dep, this.findCwd(argv));
}, this);
}

return {
argv: argv,
cwd: cwd,
preload: preload||[],
validExtensions: extensions,
preload: preload,
configNameRegex: configNameRegex,
configPath: configPath,
configBase: configBase,
Expand Down
5 changes: 4 additions & 1 deletion lib/parse_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ module.exports = function (opts) {
preloadFlag: 'require',
completionFlag: 'completion',
completions: null,
addExtensions: [],
extensions: {
'.js': null,
'.json': null
},
searchPaths: []
};
opts = opts||{};
Expand Down
8 changes: 0 additions & 8 deletions lib/valid_extensions.js

This file was deleted.

Empty file.
Empty file.
45 changes: 33 additions & 12 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ var app = new Liftoff({
preloadFlag: 'require',
completionFlag: 'completion',
completion: function() {},
addExtensions: [],
extensions: {
'.js': null,
'.json': null,
'.coffee': 'coffee-script/register'
},
searchPaths: ['test/fixtures/search_path']
});

Expand Down Expand Up @@ -46,13 +50,29 @@ describe('Liftoff', function () {
describe('buildEnvironment', function () {

it('should attempt pre-loading local modules if they are requested', function () {
var called = false;
app.on('require', function () {
called = true;
var name, mod;
app.on('require', function (moduleName, module) {
name = moduleName;
mod = module;
});
var env = app.buildEnvironment({require:['coffee-script']});
expect(called).to.be.true;
expect(env.preload).to.deep.equal(['coffee-script']);
var env = app.buildEnvironment({require:['coffee-script/register']});
expect(name).to.equal('coffee-script/register');
expect(mod).to.equal(require('coffee-script/register'));
expect(env.preload).to.deep.equal(['coffee-script/register']);
});

it('should attempt pre-loading local modules based on extension option', function () {
var name, mod;
app.on('require', function (moduleName, module) {
name = moduleName;
mod = module;
});
var env = app.buildEnvironment({
mochafile: 'test/fixtures/coffee/mochafile.coffee'
});
expect(name).to.equal('coffee-script/register');
expect(mod).to.equal(require('coffee-script/register'));
expect(env.preload).to.deep.equal(['coffee-script/register']);
});

it('should use configName directly if it is a regex', function () {
Expand All @@ -64,12 +84,15 @@ describe('Liftoff', function () {
expect(test.buildEnvironment().configNameRegex.toString()).to.equal('/mocha/');
});

it('should use configName + addExtensions + extensions on require.extensions', function () {
it('should use configName + extensions', function () {
var test = new Liftoff({
name: NAME,
addExtensions: ['rc']
extensions: {
'.js': null,
'.json': null
}
});
expect(test.buildEnvironment().configNameRegex.toString()).to.equal('mochafile{rc,.js,.json,.node}');
expect(test.buildEnvironment().configNameRegex.toString()).to.equal('mochafile{.js,.json}');
});

it('should locate config using cwd', function () {
Expand Down Expand Up @@ -173,5 +196,3 @@ describe('Liftoff', function () {
require('./file_search');
require('./parse_options');
require('./silent_require');
require('./valid_extensions');

15 changes: 0 additions & 15 deletions test/valid_extensions.js

This file was deleted.

0 comments on commit 45418f4

Please sign in to comment.