Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
`bower-locker` is a node command line tool for providing **"pseudo"-locking** capability for a project leveraging
[bower](https://bower.io/).

Bower doesn't inherently provide a locking mechanism (see https://github.com/bower/bower/issues/505).
Bower doesn't inherently provide a locking mechanism (see https://github.com/bower/bower/issues/505).

Bower does allow you to specify a specific version or commit for a given dependency, and a way to specify how you would like to resolve any conflicts (i.e., within the **resolutions** block). This can be effective but it is tedious to do manually to both **"lock"** the versions, and the **"unlock"** the versions to get newer updates.

Expand Down Expand Up @@ -37,21 +37,23 @@ This will install a global command of `bower-locker`.

### lock
```bash
bower-locker lock
bower-locker lock
```
Expects to run from within a folder that contains a `bower.json` and a `./bower_components/` folder.

If there is no `bower_components` folder yet, just run `bower install` first to generate it.

It should save a copy of `bower.json` as `bower-locker.bower.json` and then change `bower.json` to be a "locked" version with an additional "bowerLocker" section.
It should save a copy of `bower.json` as `bower-locker.bower.json` and then change `bower.json` to be a "locked" version with an additional "bowerLocker" section.

The "bowerLocker" property object that contains the "lastUpdated" timestamp for when the locked version was generated. It also contains a "versions" property object within "bowerLocker" which records the versions that were locked in as a version number to more easily know what version we are using for each dependency.

Using the `-v` flag will output the bower dependency versions that are being locked.

Using the `-s` flag will preserve the "dependencies"/"devDependencies" distinction and omit extraneous (unsaved) packages in `bower_components`.

### unlock
```bash
bower-locker unlock
bower-locker unlock
```
Expects to run from within a folder that contains a `bower.json` and a `bower-locker.bower.json`.file.

Expand All @@ -61,7 +63,7 @@ Use this command to unlock the bower file for manual updates and edits. When do

### validate
```bash
bower-locker validate
bower-locker validate
```
Expects to run from within a folder that contains a `bower.json` and a `./bower_components/` folder.

Expand All @@ -73,7 +75,7 @@ Run validate to make sure that all `bower_components` were installed as expected

### status
```bash
bower-locker status
bower-locker status
```
Expects to run from within a folder that contains a `bower.json`.

Expand Down
58 changes: 56 additions & 2 deletions bower-locker-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ function mapDependencyData(bowerInfo) {
commit: bowerInfo._resolution !== undefined ? bowerInfo._resolution.commit : undefined,
release: bowerInfo._release,
src: bowerInfo._source,
originalSrc: bowerInfo._originalSource
originalSrc: bowerInfo._originalSource,
dependencies: Object.keys(bowerInfo.dependencies || {}),
devDependencies: Object.keys(bowerInfo.devDependencies || {})
};
}

Expand Down Expand Up @@ -58,7 +60,7 @@ function getBowerFolder() {

/**
* Function to return the metadata for all the dependencies loaded within the `bower_components` directory
* @returns {Object} Returns dependency object for each dependency containing (dirName, commit, release, src, etc.)
* @returns {Array} Returns dependency object for each dependency containing (dirName, commit, release, src, etc.)
*/
function getAllDependencies() {
var folder = './' + getBowerFolder();
Expand All @@ -77,8 +79,60 @@ function getAllDependencies() {
return dependencyInfos;
}

/**
* Recursively collects dependency info for root dependencies and their dependencies. Collected dependencies are
* removed from dependenciesMap to avoid duplication
* @param {Object} dependenciesMap Map of all uncollected dependencies
* @param {Array} roots Dependency names
* @returns {Array} dependency infos
*/
function collectDependencies(dependenciesMap, roots) {
return roots.reduce(function(dependencies, dependencyName) {
if (dependenciesMap[dependencyName]) {
var dependencyInfo = dependenciesMap[dependencyName];
dependenciesMap[dependencyName] = null;
dependencies.push(dependencyInfo);
dependencies = dependencies.concat(collectDependencies(dependenciesMap, dependencyInfo.dependencies));
}

return dependencies;
}, []);
}

/**
* Function to return the metadata for dependencies loaded within the `bower_components` directory, bucketted by
* the type of saved reference to the dependencies in bower.json, i.e., dependencies, devDependencies, or unsaved
* @returns {Object} Returns object with `dependencies`, `devDependencies`, and `unsaved` arrays containing dependency
* objects for each project. Projects that are directly or indirectly required by both dependencies and
* devDependencies are returned only in `dependencies` to avoid duplication
*/
function getDependenciesByRef() {
var dependenciesMap = getAllDependencies().reduce(function(dependenciesMap, dep) {
dependenciesMap[dep.dirName] = dep;
return dependenciesMap;
}, {});

var projectInfo = getDependency('./bower.json');
var dependencies = collectDependencies(dependenciesMap, projectInfo.dependencies);
var devDependencies = collectDependencies(dependenciesMap, projectInfo.devDependencies);
var unsaved = Object.keys(dependenciesMap).reduce(function(unsaved, key) {
if (dependenciesMap[key]) {
unsaved.push(dependenciesMap[key]);
}

return unsaved;
}, []);

return {
dependencies: dependencies,
devDependencies: devDependencies,
unsaved: unsaved
};
}

module.exports = {
getAllDependencies: getAllDependencies,
getDependenciesByRef: getDependenciesByRef,
getDependency: getDependency,
mapDependencyData: mapDependencyData
};
65 changes: 55 additions & 10 deletions bower-locker-lock.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,30 @@ var formatConfig = {
size: 2
};

/**
* Comparison function for sorting dependencies by their directory name
* @param {Object} dep1 First dependency info object to compare
* @param {Object} dep2 Second dependency info object to compare
* @returns {Number} -1 if dep1 should precede dep2, 0 if they have equivalent directory names, 1 if dep1 should
* follow dep2
*/
function compareByDirName(dep1, dep2) {
return dep1.dirName < dep2.dirName ? -1 : dep1.dirName > dep2.dirName ? 1 : 0;
}

/**
* Function to lock `bower.json` with the components that currently exist within the `bower_components` directory
* This is accomplished by:
* * Saving a copy of `bower.json` as `bower-locker.unlocked.json`
* * Getting a list of ALL flattened dependencies, current versions and commit ids within the `bower_components`
* * Load the `bower.json` into memory as a JS object for manipulation
* * Override the `dependencies` and `resolutions` blocks with values specific to the current versions
* * Override the `dependencies`, `resolutions`, and optionally `devDependencies` blocks with values specific to the
* current versions
* * Save the updated (i.e., locked) `bower.json`
* @param {Boolean} isVerbose Flag to indicate whether we should log verbosely or not
* @param {Boolean} saved Flag to indicate whether to only lock saved dependencies and separate devDependencies
*/
function lock(isVerbose) {
function lock(isVerbose, saved) {
if (isVerbose) {
console.log('Start locking ...');
}
Expand All @@ -34,27 +47,59 @@ function lock(isVerbose) {
process.exit(1);
}

// Load all dependencies from the bower_components folder
var dependencies = bowerInfo.getAllDependencies();

// Create new bower config from existing
bowerConfig.bowerLocker = {lastUpdated: (new Date()).toISOString(), lockedVersions: {}};
bowerConfig.resolutions = {};
bowerConfig.dependencies = {};
// Remove devDependency section to prevent version collision
delete bowerConfig.devDependencies;

dependencies.forEach(function(dep) {
function addDependency(dependencyMapName, dep) {
// NOTE: Use dirName as the dependency name as it is more accurate than .bower.json properties
var name = dep.dirName;
var version = dep.commit !== undefined ? dep.commit : dep.release;
bowerConfig.dependencies[name] = dep.src + '#' + version; // _source
bowerConfig[dependencyMapName][name] = dep.src + '#' + version; // _source
bowerConfig.resolutions[name] = version;
bowerConfig.bowerLocker.lockedVersions[name] = dep.release;
if (isVerbose) {
console.log(' %s (%s): %s', name, dep.release, dep.commit);
}
});
}

if (saved) {
bowerConfig.devDependencies = {};
var allDependencies = bowerInfo.getDependenciesByRef();

// Sort for consistent ordering/cleaner diffs
allDependencies.dependencies.sort(compareByDirName);
allDependencies.devDependencies.sort(compareByDirName);

if (isVerbose) {
console.log('Dependencies:');
}

allDependencies.dependencies.forEach(addDependency.bind(null, 'dependencies'));

if (isVerbose) {
console.log('\nDev Dependencies:');
}

allDependencies.devDependencies.forEach(addDependency.bind(null, 'devDependencies'));

if (allDependencies.unsaved.length) {
if (isVerbose) {
var unsavedNames = allDependencies.unsaved.map((dep) => ' ' + dep.dirName + '\n').join('');
console.warn('\nThe following unsaved dependencies have not been locked:\n' + unsavedNames);
} else {
console.warn('Found unsaved dependencies in bower_components. These are not locked.\n' +
'Run with --verbose or use \'bower-locker validate\' for more details.');
}
}
} else {
// Remove devDependency section to prevent version collision
delete bowerConfig.devDependencies;
var allDependencies = bowerInfo.getAllDependencies();
allDependencies.forEach(addDependency.bind(null, 'dependencies'));
}

// Create copy of original bower.json
fs.writeFileSync('bower-locker.bower.json', bowerConfigStr, {encoding: 'utf8'});
// Replace bower.json with 'locked' version
Expand Down
3 changes: 2 additions & 1 deletion bower-locker.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ program
.command('validate', 'validate that the currently locked bower.json matches the bower_components')
.command('status', 'show the current status of the bower.json whether locked or not')
.option('-v, --verbose', 'turn on verbose output')
.option('-s, --saved', 'lock only saved dependencies and devDependencies, warn on unsaved projects')
.action(function(cmd) {
if (cmd in bowerLocker) {
return bowerLocker[cmd](program.verbose);
return bowerLocker[cmd](program.verbose, program.saved);
} else {
console.error("Unknown bower-lock command. Run 'bower-lock -h' to see options.");
process.exit(1);
Expand Down