Skip to content

Commit

Permalink
Merge pull request #7 from lewisdawson/r1.1.0
Browse files Browse the repository at this point in the history
R1.1.0 merge to master
  • Loading branch information
lewisdawson committed Aug 22, 2015
2 parents 3250c98 + 5d263f1 commit 5b2662f
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 238 deletions.
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: node_js
node_js:
- "0.12"
- "0.10"
notifications:
email:
on_success: never
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## slinker

[![Build Status](https://travis-ci.org/lewisdawson/slinker.svg)](https://travis-ci.org/lewisdawson/slinker)

A simple package used to symlink [Browserify](http://browserify.org/) dependencies as well as node.js submodule dependencies. At a high level, slinker takes a list of local node.js submodules (directories) and adds a symlink for each submodule to the `node_modules` (or equivalent) folder.

## Why??
Expand Down Expand Up @@ -74,6 +76,38 @@ Slinker contains a number of configuration parameters that can be used to custom

An `Array` of submodule names that can be found within the `modulesBasePath`.

###### Relative Paths

A submodule name can also be a path to a subdirectory, relative to the `modulesBasePath` directory. The inner-most subdirectory name is used for the name of the symlink. For example:

```javascript
slinker.link({
modules: ['path/to/models'],
modulesBasePath: __dirname,
symlinkPrefix: '@',
nodeModulesPath: path.join(__dirname, 'node_modules'),
// other configs below
});
```

This will result in a symlink named `@models`, linked to `__dirname/path/to/models`, under the `__dirname/node_modules` directory.

###### module Definition Object

A submodule name can also be aliased if you prefer that the symlink is a name other than the actual submodule name. To utilize the aliasing, an Object that contains the `module` and the `alias` properties. The `module` property is the name/path of the submodule while the `alias` property is the alias name of the the symlink to be used. For example:

```javascript
slinker.link({
modules: [{ module: 'models', alias: 'awesome_models'}],
modulesBasePath: __dirname,
symlinkPrefix: '@',
nodeModulesPath: path.join(__dirname, 'node_modules'),
// other configs below
});
```

This will create a symlink in the `nodeModulesPath` directory named `@awesome_models` that points to the `__dirname/models` directory. The `module` property must still be a valid module name or relative path that is relative to the `modulesBasePath` directory.

#### modulesBasePath

The `String` path under which all submodules will be searched for. By default, the directory under which slinker is being invoked will be used. If you want the base path to be the current directory of Slinker's invocation, you can use `__dirname`.
Expand Down
284 changes: 185 additions & 99 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,156 +4,242 @@
'use strict';

var _ = require('underscore'),
path = require('path'),
fs = require('fs'),
symlinksCreated = [],
slinkerDefaults;
path = require('path'),
fs = require('fs'),
symlinksCreated = [],
slinkerDefaults;

slinkerDefaults = {
modules: [],
symlinkPrefix: '@',
nodeModulesPath: './node_modules'
modules: [],
symlinkPrefix: '@',
nodeModulesPath: './node_modules'
};

/**
* @param {Object} slinkerOptions
* The options Object that was passed to the Slinker invocation
* The options Object that was passed to the Slinker invocation
* @param {Object} slinkerOptions
* The options Object used to configure the symlink behavior
* The options Object used to configure the symlink behavior
* @param {String} symlinkConfig.module
* The name of the module to create a symlink for
* The name of the module to create a symlink for
* @param {String} symlinkConfig.modulePath
* The path to the physical module that's used for symlink creation (includes the module name)
* The path to the physical module that's used for symlink creation (includes the module name)
* @param {String} symlinkConfig.symlinkNodeModulesPath
* The path to the location where the symlink will reside (includes the module name)
* The path to the location where the symlink will reside (includes the module name)
* @param {Boolean} exists
* A Boolean value that indicates if the symlink already exists
* A Boolean value that indicates if the symlink already exists
*/
function createSymlinkIfNotExists(slinkerOptions, symlinkConfig, exists) {
if(!exists) {
fs.symlink(symlinkConfig.modulePath, symlinkConfig.symlinkNodeModulesPath, 'file', function(err) {
if(err) {
console.log("Error creating symlink for module '" + symlinkConfig.module + "'! " + err);
invokeOnError(slinkerOptions, err);
} else {
console.log("Symlink for module '" + symlinkConfig.module + "' created.");
onSymlinkCreated(slinkerOptions, symlinkConfig.module);
}
});
} else {
console.log("Symlink for module '" + symlinkConfig.module + "' already exists. No op.");
onSymlinkCreated(slinkerOptions, symlinkConfig.module);
}
if (!exists) {
fs.symlink(symlinkConfig.modulePath, symlinkConfig.symlinkNodeModulesPath, 'file', function(err) {
if (err) {
console.log("Error creating symlink for module '" + symlinkConfig.moduleAlias + "'! " + err);
invokeOnError(slinkerOptions, err);
} else {
console.log("Symlink for module '" + symlinkConfig.moduleAlias + "' created.");
onSymlinkCreated(slinkerOptions, symlinkConfig.module);
}
});
} else {
console.log("Symlink for module '" + symlinkConfig.moduleAlias + "' already exists. No op.");
onSymlinkCreated(slinkerOptions, symlinkConfig.moduleAlias);
}
}

/**
* Invoked each time a symlink is created.
*
* @param {Object} slinkerOptions
* The options passed to slinker
* @param {String} module
* The name of the module that was created
* The options passed to slinker
* @param {String} moduleAlias
* The name of the module that was created
*/
function onSymlinkCreated(slinkerOptions, module) {
symlinksCreated.push(module);
invokeOnComplete(slinkerOptions);
function onSymlinkCreated(slinkerOptions, moduleAlias) {
symlinksCreated.push(moduleAlias);
invokeOnComplete(slinkerOptions);
}

/**
* Invoked when slinker has finished creating all symlinks. If an onComplete callback has
* been specified, it is invoked.
*
* @param {Object} slinkerOptions
* The options passed to slinker
* The options passed to slinker
*/
function invokeOnComplete(slinkerOptions) {
if(typeof slinkerOptions.onComplete === 'function' && slinkerOptions.modules.length === symlinksCreated.length) {
slinkerOptions.onComplete();
}
if (typeof slinkerOptions.onComplete === 'function' && slinkerOptions.modules.length === symlinksCreated.length) {
slinkerOptions.onComplete();
}
}

/**
* Invoked when slinker encounters an error during symlink creation. If an onError callback has
* been specified, it is invoked.
*
* @param {Object} slinkerOptions
* The options passed to slinker
* The options passed to slinker
* @param {String} error
* The error that occurred
* The error that occurred
*/
function invokeOnError(slinkerOptions, error) {
if(typeof slinkerOptions.onError === 'function') {
slinkerOptions.onError(error);
}
if (typeof slinkerOptions.onError === 'function') {
slinkerOptions.onError(error);
}
}

/**
* Checks the preconditions when slinker is invoked. Returns true if all preconditions have been
* met and slinker should be invoked.
*
* options {Object} options
* The options passed to slinker
* The options passed to slinker
*/
function checkPreconditions(options) {
if(!options) {
throw Error("'options' must be specified!");
}

if(!(options.modules instanceof Array)) {
throw Error("'options.modules' must be an array!");
}

// If the modules array is empty, immediately call the onComplete() if it exists
if(!options.modules.length) {
if(options.onComplete) {
invokeOnComplete(options);
}

return false;
}

return true;
if (!options) {
throw Error("'options' must be specified!");
}

if (!(options.modules instanceof Array)) {
throw Error("'options.modules' must be an array!");
}

// If the modules array is empty, immediately call the onComplete() if it exists
if (!options.modules.length) {
if (options.onComplete) {
invokeOnComplete(options);
}

return false;
}

return true;
}

/**
* @param module
* The module parameter to determine the type of
* @returns True if the parameter module is an Object, other false.
*/
function isModuleObject(module) {
return (module && typeof(module) === 'object' && !Array.isArray(module));
}

/**
* @param module
* The module string or object
* @returns The module's path for the parameter module. If the parameter module is a JS object, then the `module`
* property for the module is returned.
*/
function getModulePathProperty(module) {
var path = module;

if (isModuleObject(module)) {
path = module.module;

if (!path) {
throw 'A module Object must contain the \'module\' definition!';
}
}

return path;
}

/**
* @param module
* The module definition to retrieve the alias from
* @returns The alias for the module if it's a JS object. Otherwise, return null.
*/
function getModuleAliasProperty(module) {
var name = null;

if (isModuleObject(module) && module.alias) {
name = module.alias;
}

return name;
}

/**
* Determines from the module path, what the name of the symlink should be. The deepest name in the path is used. For
* example, if the module is `my/module/path/is/cool`, then the symlink name would be `cool`. If the module is an
* object and the object contains the `alias` property, then the `alias` property is used for the symlink name.
*
* @param module
* The module path or object used to determine the name of the symlink
* @return The name of the symlink, derived from the deepest path of the module or the module `alias`
*/
function getSymlinkNameFromModule(module) {
var name = null,
splitModule;

name = getModuleAliasProperty(module);
// If the module is not an Object or the alias wasn't specified, split the module path (if necessary)
if (name == null) {
module = getModulePathProperty(module);
splitModule = module.split(path.sep);

if (splitModule.length === 1) {
return module;
}

return splitModule[splitModule.length - 1];
}

return name;
}

module.exports = {

/**
*
* @param {Object} options
* An Object that contains the symlinkConfigurations used for symlinking
* @param {Array} options.modules
* An array that contains the name of each module (directory) to symlink
* @param {String} options.modulesBasePath
* The base path under which all modules that are to be symlinked reside
* @param {String} options.symlinkPrefix
* The prefix to use when creating a symlink
* @param {String} options.nodeModulesPath
* The path to the node_modules directory where all symlinks will be created
* @param {Function} options.onComplete
* A callback that is invoked once all symlinks have been created
* @param {Function} options.onError
* A callback that is invoked if an error occurred while attempting to create a symlink
*/
link: function(options) {
options = _.defaults(options, slinkerDefaults);

if(checkPreconditions(options)) {
_.each(options.modules, function(module) {
var modulePath,
symlinkNodeModulesPath;

// The actual path to the file
modulePath = path.join(options.modulesBasePath, module);
// The path to the symlink under the node_modules directory
symlinkNodeModulesPath = path.join(options.nodeModulesPath, options.symlinkPrefix + module);

fs.exists(symlinkNodeModulesPath, _.bind(createSymlinkIfNotExists, this, options, {
module: module,
modulePath: modulePath,
symlinkNodeModulesPath: symlinkNodeModulesPath
}));
});
}
}
/**
* Resets the array of modules that was created.
*/
reset: function() {
symlinksCreated = [];
},

/**
*
* @param {Object} options
* An Object that contains the symlinkConfigurations used for symlinking
* @param {Array} options.modules
* An array that contains the name of each module (directory) to symlink
* @param {String} options.modulesBasePath
* The base path under which all modules that are to be symlinked reside
* @param {String} options.symlinkPrefix
* The prefix to use when creating a symlink
* @param {String} options.nodeModulesPath
* The path to the node_modules directory where all symlinks will be created
* @param {Function} options.onComplete
* A callback that is invoked once all symlinks have been created
* @param {Function} options.onError
* A callback that is invoked if an error occurred while attempting to create a symlink
*/
link: function(options) {
options = _.defaults(options, slinkerDefaults);

if (checkPreconditions(options)) {
_.each(options.modules, function(module) {
var modulePath,
moduleAlias,
symlinkNodeModulesPath;

modulePath = getModulePathProperty(module);
// The actual path to the file
modulePath = path.join(options.modulesBasePath, modulePath);

// Get the alias namve for the module
moduleAlias = getSymlinkNameFromModule(module);
// The path to the symlink under the node_modules directory
symlinkNodeModulesPath =
path.join(options.nodeModulesPath, options.symlinkPrefix + moduleAlias);

fs.exists(symlinkNodeModulesPath, _.bind(createSymlinkIfNotExists, this, options, {
moduleAlias: moduleAlias,
modulePath: modulePath,
symlinkNodeModulesPath: symlinkNodeModulesPath
}));
});
}
}

};
Loading

0 comments on commit 5b2662f

Please sign in to comment.