Compare commits

..

5 Commits

Author SHA1 Message Date
Adam Stankiewicz
965a53db21 Update lerna version 2016-07-02 12:46:06 +02:00
Adam Stankiewicz
1334ff67ae Configure CI properly for lerna 2016-07-02 12:46:06 +02:00
Adam Stankiewicz
afb27f446a Fix npm test command of bower-config 2016-07-02 12:46:06 +02:00
Adam Stankiewicz
fe011b8462 Add lerna 2016-07-02 12:46:06 +02:00
Adam Stankiewicz
096fc3da0d Move bower project to packages/bower 2016-07-02 12:46:06 +02:00
275 changed files with 19455 additions and 28112 deletions

View File

@@ -15,4 +15,5 @@ trim_trailing_whitespace = false
insert_final_newline = false
[{package,bower}.json]
indent_style = space
indent_size = 2

View File

@@ -17,7 +17,7 @@
"strict": 0,
"semi": 0,
"comma-spacing": 2,
"quote-props": [2, "as-needed"],
"quote-props": [2, "consistent", { "keywords": true }],
"quotes": [2, "single", "avoid-escape"],
"indent": [2, 4],
"no-cond-assign": [ 2, "except-parens" ],
@@ -41,7 +41,7 @@
"no-new-wrappers": 2,
"no-invalid-this": 0,
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
"space-infix-ops": 2,
"keyword-spacing": 2,
"new-parens": 2,

17
.gitignore vendored
View File

@@ -1,15 +1,2 @@
!lib/bin
/node_modules
/npm-debug.log
/test/assets/package-*/
/test/assets/temp-*/
/test/reports
/test/tmp/
/bower.json
/component.json
/bower_components
/test/sample
!/test/sample/bower.json
/npm-shrinkwrap.json
npm-debug.log*
lerna-debug.log*

View File

@@ -1,2 +0,0 @@
**/node_modules/**
**/test/assets/**

View File

@@ -2,31 +2,22 @@ sudo: false
language: node_js
# Use node 8 for build
node_js:
- "8"
# Then test with specific node version
env:
- TEST_NODE_VERSION="0.10"
- TEST_NODE_VERSION="0.12"
- TEST_NODE_VERSION="4"
- TEST_NODE_VERSION="6"
- TEST_NODE_VERSION="8"
- TEST_NODE_VERSION="9"
before_install:
- node --version
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.5.1
- export PATH=$HOME/.yarn/bin:$PATH
cache:
yarn: true
- "0.10"
- "0.11"
- "0.12"
- "4.0"
- "4.1"
- "4.2"
- "5"
- "6"
install:
- yarn
- nvm install $TEST_NODE_VERSION
- npm install -g grunt
- node --version
- npm --version
- git --version
- svn --version | head -n 1
- npm install
os:
- osx
@@ -36,8 +27,7 @@ matrix:
fast_finish: true
allow_failures:
- os: osx
- env: "NODE_VERSION=0.11"
script:
- nvm use $TEST_NODE_VERSION
- node --version && npm --version && git --version && svn --version | head -n 1
- grunt travis
- npm test

View File

@@ -1,26 +1,5 @@
# Changelog
## Newer releases
Please see: https://github.com/bower/bower/releases
## 1.8.0 - 2016-11-07
- Download tar archives from GitHub when possible (#2263)
- Change default shorthand resolver for github from `git://` to `https://`
- Fix ssl handling by not setting GIT_SSL_NO_VERIFY=false (#2361)
- Allow for removing components with url instead of name (#2368)
- Show in warning message location of malformed bower.json (#2357)
- Improve handling of non-semver versions in git resolver (#2316)
- Fix handling of cached releases pluginResolverFactory (#2356)
- Allow to type the entire version when conflict occured (#2243)
- Allow `owner/reponame` shorthand for registering components (#2248)
- Allow single-char repo names and package names (#2249)
- Make `bower version` no longer honor `version` in bower.json (#2232)
- Add `postinstall` hook (#2252)
- Allow for `@` instead of `#` for `install` and `info` commands (#2322)
- Upgrade all bundled modules
## 1.7.9 - 2016-04-05
- Show warnings for invalid bower.json fields
@@ -104,7 +83,7 @@ https://github.com/npm/npm/issues/11227
- Update bower config
- Loads the .bowerrc file from the cwd specified on the command line
- Allow the use of environment variables in .bowerrc ([#41](https://github.com/bower/config/issues/41))
- Allow for array notation in ENV variables ([#44](https://github.com/bower/config/issues/44))
- Allow for array notation in ENV variables ([#44](https://github.com/bower/config/issues/44))
## 1.6.9 - 2015-12-04

View File

@@ -18,7 +18,7 @@ Bower is a large community project with many different developers contributing a
## Team Meetings
We communicate through a channel on Discord https://discord.gg/0fFM7QF0KpZRh2cY
We communicate through a channel on slack: https://gitter.im/bower
If you'd like to attend the meetings, please fill the [support form](http://goo.gl/forms/P1ndzCNoiG), and you'll get an invite.
@@ -29,9 +29,9 @@ The issue tracker is the preferred channel for [bug reports](#bugs),
requests](#pull-requests), but please respect the following restrictions:
* Please **do not** use the issue tracker for personal support requests. Use
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower),
[Discord Channel](https://discordapp.com/channels/119103197720739842/123728452816732160),
[Mailing List](http://groups.google.com/group/twitter-bower),
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower)
[Gitter Channel](https://gitter.im/bower/bower)
[Mailing List](http://groups.google.com/group/twitter-bower)
(twitter-bower@googlegroups.com), or
[#bower](http://webchat.freenode.net/?channels=bower) on Freenode.

View File

@@ -1,283 +0,0 @@
var tmp = require('tmp');
var childProcess = require('child_process');
var arraydiff = require('arr-diff');
var fs = require('fs');
var wrench = require('wrench');
var inquirer = require('inquirer');
var path = require('path');
module.exports = function(grunt) {
require('load-grunt-tasks')(grunt);
grunt.initConfig({
eslint: {
files: [
'Gruntfile.js',
'bin/*',
'lib/**/*.js',
'test/**/*.js',
'!test/assets/**/*',
'!test/reports/**/*',
'!test/sample/**/*',
'!test/tmp/**/*'
]
},
simplemocha: {
options: {
reporter: 'spec',
timeout: '15000'
},
full: {
src: ['test/test.js']
},
short: {
options: {
reporter: 'dot'
},
src: ['test/test.js']
}
},
exec: {
assets: {
command: 'node test/packages.js && node test/packages-svn.js'
},
'assets-force': {
command:
'node test/packages.js --force && node test/packages-svn.js --force'
},
cover: {
command:
'node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- --timeout 30000 -R dot test/test.js'
},
coveralls: {
command: 'npm run coveralls < test/reports/lcov.info',
exitCodes: [0, 1, 2, 3] // Alow for failure for coverage report
}
},
watch: {
files: ['<%= eslint.files %>'],
tasks: ['eslint', 'simplemocha:short']
}
});
grunt.registerTask('assets', ['exec:assets-force']);
grunt.registerTask('test', ['eslint', 'exec:assets', 'simplemocha:full']);
grunt.registerTask('cover', 'exec:cover');
grunt.registerTask('travis', [
'eslint',
'exec:assets',
'exec:cover',
'exec:coveralls'
]);
grunt.registerTask('default', 'test');
grunt.task.registerTask(
'publish',
'Perform final checks and publish Bower',
function() {
var npmVersion = JSON.parse(
childProcess.execSync('npm version --json').toString()
).npm.split('.');
var npmMajor = parseInt(npmVersion[0], 10);
var npmMinor = parseInt(npmVersion[1], 10);
var jsonPackage = require('./package');
if (npmMajor !== 3 || npmMinor < 5) {
grunt.log.writeln(
'You need to use at least npm@3.5 to publish bower.'
);
grunt.log.writeln(
'It is because npm 2.x produces too long paths that Windows does not handle.'
);
grunt.log.writeln('Please upgrade it: npm install -g npm');
process.exit(1);
}
var version = jsonPackage.version;
var changelog = fs.readFileSync('./CHANGELOG.md');
if (changelog.indexOf('## ' + version) === -1) {
grunt.log.writeln(
'Please add changelog.md entry for this bower version (' +
version +
')'
);
var lastRelease = childProcess
.execSync('git tag | tail -1')
.toString()
.trim();
grunt.log.writeln(
'Commits since last release (' + lastRelease + '): \n'
);
grunt.log.writeln(
childProcess
.execSync('git log --oneline ' + lastRelease + '..')
.toString()
);
process.exit(1);
}
if (
childProcess
.execSync('git rev-parse --abbrev-ref HEAD')
.toString()
.trim() !== 'master'
) {
grunt.log.writeln(
'You need to release bower from the "master" branch'
);
process.exit(1);
}
if (process.env.SKIP_TESTS !== '1') {
grunt.log.writeln('Reinstalling dependencies...');
childProcess.execSync('rm -rf node_modules && npm install', {
stdio: [0, 1, 2]
});
grunt.log.writeln('Running test suite...');
childProcess.execSync('grunt test', { stdio: [0, 1, 2] });
}
var dir = tmp.dirSync().name;
wrench.copyDirSyncRecursive(__dirname, dir, {
forceDelete: true,
include: function(path) {
return !path.match(/node_modules|\.git|test/);
}
});
grunt.log.writeln('Installing production dependencies...');
childProcess.execSync('npm install --production --silent', {
cwd: dir,
stdio: [0, 1, 2]
});
delete jsonPackage.dependencies;
delete jsonPackage.devDependencies;
delete jsonPackage.scripts;
fs.writeFileSync(
path.resolve(dir, 'package.json'),
JSON.stringify(jsonPackage, null, ' ') + '\n'
);
grunt.log.writeln('Moving node_modules to lib directory...');
wrench.copyDirSyncRecursive(
path.resolve(dir, 'node_modules'),
path.resolve(dir, 'lib', 'node_modules')
);
wrench.rmdirSyncRecursive(path.resolve(dir, 'node_modules'));
grunt.log.writeln('Testing bower on sample project...');
childProcess.execSync(
'cd test/sample && rm -rf bower_components && ' +
dir +
'/bin/bower install --force',
{ stdio: [0, 1, 2] }
);
var expectedPackages = (
'SHA-1 ace-builds almond angular angular-animate angular-bootstrap angular-charts angular-contenteditable ' +
'angular-deckgrid angular-fullscreen angular-gravatar angular-hotkeys angular-local-storage angular-marked ' +
'angular-moment angular-sanitize angular-touch angular-ui-router angular-ui-sortable ' +
'angulartics asEvented bootstrap coffee-script d3 es6-shim font-awesome howler jquery ' +
'jquery-ui jquery-waypoints js-beautify lodash lz-string marked moment ng-file-upload peerjs ' +
'requirejs restangular slimScroll slimScrollHorizontal venturocket-angular-slider'
).split(' ');
var installedPackages = fs.readdirSync(
'./test/sample/bower_components'
);
var installedDiff = arraydiff(expectedPackages, installedPackages);
if (installedDiff.length > 0) {
grunt.log.writeln(
'ERROR. Some packages were not installed by bower: '
);
grunt.log.writeln(installedDiff.join(', '));
process.exit(1);
}
grunt.log.writeln('\nBower production bundle installed in:');
grunt.log.writeln(dir + '\n');
var questions = [
{
type: 'confirm',
name: 'review',
message: 'Did you review all the changes with "git diff"?',
default: false
},
{
type: 'confirm',
name: 'changelog',
message:
'Are you sure the CHANGELOG.md contains all changes?',
default: false
},
{
type: 'confirm',
name: 'tests',
message:
'Are you sure all tests are passing on Travis and Appveyor?',
default: false
},
{
type: 'confirm',
name: 'publish',
message:
'Are you SURE you want to publish ' +
jsonPackage.name +
'@' +
jsonPackage.version +
'?',
default: false
}
];
var done = this.async();
inquirer.prompt(questions, function(answers) {
if (
!answers.review ||
!answers.changelog ||
!answers.tests ||
!answers.publish
) {
grunt.log.writeln(
'Please publish bower after you fix this issue'
);
process.exit(1);
}
grunt.log.writeln(
'\nPlease remember to tag this relese, and add a release on Github!'
);
grunt.log.writeln(
'\nAlso, please remember to test published Bower one more time!'
);
grunt.log.writeln('\nPublishing Bower...');
childProcess.execSync('npm publish --tag beta', {
cwd: dir,
stdio: [0, 1, 2]
});
done();
});
}
);
};

View File

@@ -1,14 +1,13 @@
# Bower - A package manager for the web
[![Backers on Open Collective](https://opencollective.com/bower/backers/badge.svg)](#backers)
[![Sponsors on Open Collective](https://opencollective.com/bower/sponsors/badge.svg)](#sponsors)
> ..psst! While Bower is maintained, we recommend [yarn](https://yarnpkg.com/) and [webpack](https://webpack.js.org/) for new front-end projects!
> Bower needs your help. If you're willing to help, please say hello to team@bower.io or [donate](https://salt.bountysource.com/teams/bower)
[![Unix CI](https://img.shields.io/travis/bower/bower/master.svg?maxAge=2592000)](https://travis-ci.org/bower/bower)
[![Windows CI](https://img.shields.io/appveyor/ci/bower/bower/master.svg)](https://ci.appveyor.com/project/bower/bower)
[![Coverage Status](https://img.shields.io/coveralls/bower/bower.svg)](https://coveralls.io/r/bower/bower?branch=master)
[![Discord chat](https://img.shields.io/badge/discord-join%20chat%20%E2%86%92-brightgreen.svg?style=flat)](https://discord.gg/0fFM7QF0KpZRh2cY)
[![Issue Stats](http://issuestats.com/github/bower/bower/badge/pr?style=flat)](http://issuestats.com/github/bower/bower)
[![Issue Stats](http://issuestats.com/github/bower/bower/badge/issue?style=flat)](http://issuestats.com/github/bower/bower)
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
@@ -120,81 +119,8 @@ Note that on Windows for tests to pass you need to configure Git before cloning:
git config --global core.autocrlf input
```
## Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/bower#backer)]
<a href="https://opencollective.com/bower/backer/0/website" target="_blank"><img src="https://opencollective.com/bower/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/1/website" target="_blank"><img src="https://opencollective.com/bower/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/2/website" target="_blank"><img src="https://opencollective.com/bower/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/3/website" target="_blank"><img src="https://opencollective.com/bower/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/4/website" target="_blank"><img src="https://opencollective.com/bower/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/5/website" target="_blank"><img src="https://opencollective.com/bower/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/6/website" target="_blank"><img src="https://opencollective.com/bower/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/7/website" target="_blank"><img src="https://opencollective.com/bower/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/8/website" target="_blank"><img src="https://opencollective.com/bower/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/9/website" target="_blank"><img src="https://opencollective.com/bower/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/10/website" target="_blank"><img src="https://opencollective.com/bower/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/11/website" target="_blank"><img src="https://opencollective.com/bower/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/12/website" target="_blank"><img src="https://opencollective.com/bower/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/13/website" target="_blank"><img src="https://opencollective.com/bower/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/14/website" target="_blank"><img src="https://opencollective.com/bower/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/15/website" target="_blank"><img src="https://opencollective.com/bower/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/16/website" target="_blank"><img src="https://opencollective.com/bower/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/17/website" target="_blank"><img src="https://opencollective.com/bower/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/18/website" target="_blank"><img src="https://opencollective.com/bower/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/19/website" target="_blank"><img src="https://opencollective.com/bower/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/20/website" target="_blank"><img src="https://opencollective.com/bower/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/21/website" target="_blank"><img src="https://opencollective.com/bower/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/22/website" target="_blank"><img src="https://opencollective.com/bower/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/23/website" target="_blank"><img src="https://opencollective.com/bower/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/24/website" target="_blank"><img src="https://opencollective.com/bower/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/25/website" target="_blank"><img src="https://opencollective.com/bower/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/26/website" target="_blank"><img src="https://opencollective.com/bower/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/27/website" target="_blank"><img src="https://opencollective.com/bower/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/28/website" target="_blank"><img src="https://opencollective.com/bower/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/bower/backer/29/website" target="_blank"><img src="https://opencollective.com/bower/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/bower#sponsor)]
<a href="https://opencollective.com/bower/sponsor/0/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/1/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/2/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/3/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/4/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/5/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/6/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/7/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/8/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/9/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/10/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/11/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/12/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/13/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/14/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/15/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/16/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/17/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/18/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/19/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/20/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/21/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/22/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/23/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/24/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/25/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/26/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/27/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/28/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/bower/sponsor/29/website" target="_blank"><img src="https://opencollective.com/bower/sponsor/29/avatar.svg"></a>
## License
Copyright (c) 2012-present Twitter and [other contributors](https://github.com/bower/bower/graphs/contributors)
Copyright (c) 2016 Twitter and [other contributors](https://github.com/bower/bower/graphs/contributors)
Licensed under the MIT License

View File

@@ -12,10 +12,11 @@ environment:
matrix:
- nodejs_version: "0.10"
- nodejs_version: "0.12"
- nodejs_version: "4"
- nodejs_version: "4.0"
- nodejs_version: "4.1"
- nodejs_version: "4.2"
- nodejs_version: "5"
- nodejs_version: "6"
- nodejs_version: "8"
- nodejs_version: "9"
# Finish on first failed build
matrix:
@@ -23,16 +24,14 @@ matrix:
# Install node, display versions, install dependencies
install:
- ps: Install-Product node 8
- ps: Install-Product node $env:nodejs_version
- node --version && npm --version
- git --version && svn --version
- npm install -g yarn grunt
- yarn
- ps: Install-Product node $env:nodejs_version
- npm install
# Post-install test scripts.
test_script:
- cmd: npm run ci
- cmd: npm test
# Make clone much faster
shallow_clone: true
@@ -43,4 +42,4 @@ deploy: off
# Cache node modules, and refresh if package.json changes
cache:
- "%LOCALAPPDATA%\\Yarn"
- node_modules -> package.json

10
lerna.json Normal file
View File

@@ -0,0 +1,10 @@
{
"lerna": "2.0.0-beta.23",
"version": "independent",
"publishConfig": {
"ignore": [
"ignored-file",
"*.md"
]
}
}

View File

@@ -1,342 +0,0 @@
var mout = require('mout');
var fs = require('../util/fs');
var path = require('path');
var Q = require('q');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var cmd = require('../util/cmd');
var createError = require('../util/createError');
function init(logger, config) {
var project;
config = config || {};
if (!config.cwd) {
config.cwd = process.cwd();
}
config = defaultConfig(config);
// This command requires interactive to be enabled
if (!config.interactive) {
throw createError('Register requires an interactive shell', 'ENOINT', {
details:
'Note that you can manually force an interactive shell with --config.interactive'
});
}
project = new Project(config, logger);
// Start with existing JSON details
return (
readJson(project, logger)
// Fill in defaults
.then(setDefaults.bind(null, config))
// Now prompt user to make changes
.then(promptUser.bind(null, logger))
// Set ignore based on the response
.spread(setIgnore.bind(null, config))
// Set dependencies based on the response
.spread(setDependencies.bind(null, project))
// All done!
.spread(saveJson.bind(null, project, logger))
);
}
function readJson(project, logger) {
return project.hasJson().then(function(json) {
if (json) {
logger.warn(
'existing',
'The existing ' +
path.basename(json) +
' file will be used and filled in'
);
}
return project.getJson();
});
}
function saveJson(project, logger, json) {
// Cleanup empty props (null values, empty strings, objects and arrays)
mout.object.forOwn(json, function(value, key) {
if (!validConfigValue(value)) {
delete json[key];
}
});
logger.info('json', 'Generated json', { json: json });
// Confirm the json with the user
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Looks good?',
default: true
}).then(function(good) {
if (!good) {
return null;
}
// Save json (true forces file creation)
return project.saveJson(true);
});
}
// Test if value is of a type supported by bower.json[0] - Object, Array, String, Boolean - or a Number
// [0]: https://github.com/bower/bower.json-spec
function validConfigValue(val) {
return (
mout.lang.isObject(val) ||
mout.lang.isArray(val) ||
mout.lang.isString(val) ||
mout.lang.isBoolean(val) ||
mout.lang.isNumber(val)
);
}
function setDefaults(config, json) {
var name;
var promise = Q.resolve();
// Name
if (!json.name) {
json.name = path.basename(config.cwd);
}
// Main
if (!json.main) {
// Remove '.js' from the end of the package name if it is there
name = path.basename(json.name, '.js');
if (fs.existsSync(path.join(config.cwd, 'index.js'))) {
json.main = 'index.js';
} else if (fs.existsSync(path.join(config.cwd, name + '.js'))) {
json.main = name + '.js';
}
}
// Homepage
if (!json.homepage) {
// Set as GitHub homepage if it's a GitHub repository
promise = promise.then(function() {
return cmd('git', ['config', '--get', 'remote.origin.url'])
.spread(function(stdout) {
var pair;
stdout = stdout.trim();
if (!stdout) {
return;
}
pair = GitHubResolver.getOrgRepoPair(stdout);
if (pair) {
json.homepage =
'https://github.com/' + pair.org + '/' + pair.repo;
}
})
.fail(function() {});
});
}
if (!json.authors) {
promise = promise.then(function() {
// Get the user name configured in git
return cmd('git', [
'config',
'--get',
'--global',
'user.name'
]).spread(
function(stdout) {
var gitEmail;
var gitName = stdout.trim();
// Abort if no name specified
if (!gitName) {
return;
}
// Get the user email configured in git
return cmd('git', [
'config',
'--get',
'--global',
'user.email'
])
.spread(
function(stdout) {
gitEmail = stdout.trim();
},
function() {}
)
.then(function() {
json.authors = gitName;
json.authors += gitEmail
? ' <' + gitEmail + '>'
: '';
});
},
function() {}
);
});
}
return promise.then(function() {
return json;
});
}
function promptUser(logger, json) {
var questions = [
{
name: 'name',
message: 'name',
default: json.name,
type: 'input'
},
{
name: 'description',
message: 'description',
default: json.description,
type: 'input'
},
{
name: 'main',
message: 'main file',
default: json.main,
type: 'input'
},
{
name: 'keywords',
message: 'keywords',
default: json.keywords ? json.keywords.toString() : null,
type: 'input'
},
{
name: 'authors',
message: 'authors',
default: json.authors ? json.authors.toString() : null,
type: 'input'
},
{
name: 'license',
message: 'license',
default: json.license || 'MIT',
type: 'input'
},
{
name: 'homepage',
message: 'homepage',
default: json.homepage,
type: 'input'
},
{
name: 'dependencies',
message: 'set currently installed components as dependencies?',
default:
!mout.object.size(json.dependencies) &&
!mout.object.size(json.devDependencies),
type: 'confirm'
},
{
name: 'ignore',
message: 'add commonly ignored files to ignore list?',
default: true,
type: 'confirm'
},
{
name: 'private',
message:
'would you like to mark this package as private which prevents it from being accidentally published to the registry?',
default: !!json.private,
type: 'confirm'
}
];
return Q.nfcall(logger.prompt.bind(logger), questions).then(function(
answers
) {
json.name = answers.name;
json.description = answers.description;
json.main = answers.main;
json.keywords = toArray(answers.keywords);
json.authors = toArray(answers.authors, ',');
json.license = answers.license;
json.homepage = answers.homepage;
json.private = answers.private || null;
return [json, answers];
});
}
function toArray(value, splitter) {
var arr = value.split(splitter || /[\s,]/);
// Trim values
arr = arr.map(function(item) {
return item.trim();
});
// Filter empty values
arr = arr.filter(function(item) {
return !!item;
});
return arr.length ? arr : null;
}
function setIgnore(config, json, answers) {
if (answers.ignore) {
json.ignore = mout.array.combine(json.ignore || [], [
'**/.*',
'node_modules',
'bower_components',
config.directory,
'test',
'tests'
]);
}
return [json, answers];
}
function setDependencies(project, json, answers) {
if (answers.dependencies) {
return project.getTree().spread(function(tree, flattened, extraneous) {
if (extraneous.length) {
json.dependencies = {};
// Add extraneous as dependencies
extraneous.forEach(function(extra) {
var jsonEndpoint;
// Skip linked packages
if (extra.linked) {
return;
}
jsonEndpoint = endpointParser.decomposed2json(
extra.endpoint
);
mout.object.mixIn(json.dependencies, jsonEndpoint);
});
}
return [json, answers];
});
}
return [json, answers];
}
// -------------------
init.readOptions = function(argv) {
return [];
};
module.exports = init;

View File

@@ -1,155 +0,0 @@
var Configstore = require('configstore');
var GitHub = require('github');
var Q = require('q');
var createError = require('../util/createError');
var defaultConfig = require('../config');
function login(logger, options, config) {
var configstore = new Configstore('bower-github');
config = defaultConfig(config);
var promise;
options = options || {};
if (options.token) {
promise = Q.resolve({ token: options.token });
} else {
// This command requires interactive to be enabled
if (!config.interactive) {
logger.emit(
'error',
createError('Login requires an interactive shell', 'ENOINT', {
details:
'Note that you can manually force an interactive shell with --config.interactive'
})
);
return;
}
var questions = [
{
name: 'username',
message: 'Username',
type: 'input',
default: configstore.get('username')
},
{
name: 'password',
message: 'Password',
type: 'password'
}
];
var github = new GitHub({
version: '3.0.0'
});
promise = Q.nfcall(logger.prompt.bind(logger), questions).then(function(
answers
) {
configstore.set('username', answers.username);
github.authenticate({
type: 'basic',
username: answers.username,
password: answers.password
});
return Q.ninvoke(github.authorization, 'create', {
scopes: ['user', 'repo'],
note:
'Bower command line client (' +
new Date().toISOString() +
')'
});
});
}
return promise.then(
function(result) {
configstore.set('accessToken', result.token);
logger.info(
'EAUTH',
'Logged in as ' + configstore.get('username'),
{}
);
return result;
},
function(error) {
var message;
try {
message = JSON.parse(error.message).message;
} catch (e) {
message = 'Authorization failed';
}
var questions = [
{
name: 'otpcode',
message: 'Two-Factor Auth Code',
type: 'input'
}
];
if (
message === 'Must specify two-factor authentication OTP code.'
) {
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function(answers) {
return Q.ninvoke(github.authorization, 'create', {
scopes: ['user', 'repo'],
note:
'Bower command line client (' +
new Date().toISOString() +
')',
headers: {
'X-GitHub-OTP': answers.otpcode
}
});
})
.then(
function(result) {
configstore.set('accessToken', result.token);
logger.info(
'EAUTH',
'Logged in as ' + configstore.get('username'),
{}
);
return result;
},
function() {
logger.emit('error', createError(message, 'EAUTH'));
}
);
} else {
logger.emit('error', createError(message, 'EAUTH'));
}
}
);
}
// -------------------
login.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(
{
token: { type: String, shorthand: 't' }
},
argv
);
delete options.argv;
return [options];
};
module.exports = login;

View File

@@ -1,60 +0,0 @@
var mout = require('mout');
var Project = require('../core/Project');
var defaultConfig = require('../config');
function prune(logger, options, config) {
var project;
options = options || {};
config = defaultConfig(config);
project = new Project(config, logger);
return clean(project, options);
}
function clean(project, options, removed) {
removed = removed || {};
// Continually call clean until there is no more extraneous
// dependencies to remove
return project
.getTree(options)
.spread(function(tree, flattened, extraneous) {
var names = extraneous.map(function(extra) {
return extra.endpoint.name;
});
// Uninstall extraneous
return project
.uninstall(names, options)
.then(function(uninstalled) {
// Are we done?
if (!mout.object.size(uninstalled)) {
return removed;
}
// Not yet, recurse!
mout.object.mixIn(removed, uninstalled);
return clean(project, options, removed);
});
});
}
// -------------------
prune.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(
{
production: { type: Boolean, shorthand: 'p' }
},
argv
);
delete options.argv;
return [options];
};
module.exports = prune;

View File

@@ -1,100 +0,0 @@
var Q = require('q');
var chalk = require('chalk');
var PackageRepository = require('../core/PackageRepository');
var createError = require('../util/createError');
var defaultConfig = require('../config');
function register(logger, name, source, config) {
var repository;
var registryClient;
var force;
var url;
var githubSourceRegex = /^\w[\w-]*\/\w[\w-]*$/;
var getGithubUrl = function(source) {
return 'git@github.com:' + source + '.git';
};
config = defaultConfig(config);
force = config.force;
name = (name || '').trim();
source = (source || '').trim();
url = source.match(githubSourceRegex) ? getGithubUrl(source) : source;
// Bypass any cache
config.offline = false;
config.force = true;
return Q.try(function() {
// Verify name and url
if (!name || !url) {
throw createError(
'Usage: bower register <name> <url>',
'EINVFORMAT'
);
}
// Attempt to resolve the package referenced by the URL to ensure
// everything is ok before registering
repository = new PackageRepository(config, logger);
return repository.fetch({ name: name, source: url, target: '*' });
})
.spread(function(canonicalDir, pkgMeta) {
if (pkgMeta.private) {
throw createError(
'The package you are trying to register is marked as private',
'EPRIV'
);
}
// If non interactive or user forced, bypass confirmation
if (!config.interactive || force) {
return true;
}
// Confirm if the user really wants to register
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message:
'Registering a package will make it installable via the registry (' +
chalk.cyan.underline(config.registry.register) +
'), continue?',
default: true
});
})
.then(function(result) {
// If user response was negative, abort
if (!result) {
return;
}
// Register
registryClient = repository.getRegistryClient();
logger.action('register', url, {
name: name,
url: url
});
return Q.nfcall(
registryClient.register.bind(registryClient),
name,
url
);
});
}
// -------------------
register.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
var url = options.argv.remain[2];
return [name, url];
};
module.exports = register;

View File

@@ -1,132 +0,0 @@
var mout = require('mout');
var Q = require('q');
var Project = require('../core/Project');
var defaultConfig = require('../config');
function uninstall(logger, names, options, config) {
if (!names.length) {
return new Q();
}
var project;
options = options || {};
config = defaultConfig(config);
project = new Project(config, logger);
return project.getTree(options).spread(function(tree, flattened) {
// Uninstall nodes
return (
project
.uninstall(names, options)
// Clean out non-shared uninstalled dependencies
.then(function(uninstalled) {
var names = Object.keys(uninstalled);
var children = [];
// Grab the dependencies of packages that were uninstalled
mout.object.forOwn(flattened, function(node) {
if (names.indexOf(node.endpoint.name) !== -1) {
children.push.apply(
children,
mout.object.keys(node.dependencies)
);
}
});
// Clean them!
return clean(project, children, uninstalled);
})
);
});
}
function clean(project, names, removed) {
removed = removed || {};
return project.getTree().spread(function(tree, flattened) {
var nodes = [];
var dependantsCounter = {};
// Grab the nodes of each specified name
mout.object.forOwn(flattened, function(node) {
if (names.indexOf(node.endpoint.name) !== -1) {
nodes.push(node);
}
});
// Walk the down the tree, gathering dependants of the packages
project.walkTree(
tree,
function(node, nodeName) {
if (names.indexOf(nodeName) !== -1) {
dependantsCounter[nodeName] =
dependantsCounter[nodeName] || 0;
dependantsCounter[nodeName] += node.nrDependants;
}
},
true
);
// Filter out those that have no dependants
nodes = nodes.filter(function(node) {
return !dependantsCounter[node.endpoint.name];
});
// Are we done?
if (!nodes.length) {
return Q.resolve(removed);
}
// Grab the nodes after filtering
names = nodes.map(function(node) {
return node.endpoint.name;
});
// Uninstall them
return (
project
.uninstall(names)
// Clean out non-shared uninstalled dependencies
.then(function(uninstalled) {
var children;
mout.object.mixIn(removed, uninstalled);
// Grab the dependencies of packages that were uninstalled
children = [];
nodes.forEach(function(node) {
children.push.apply(
children,
mout.object.keys(node.dependencies)
);
});
// Recurse!
return clean(project, children, removed);
})
);
});
}
// -------------------
uninstall.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(
{
save: { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' }
},
argv
);
var names = options.argv.remain.slice(1);
delete options.argv;
return [names, options];
};
module.exports = uninstall;

View File

@@ -1,90 +0,0 @@
var chalk = require('chalk');
var Q = require('q');
var defaultConfig = require('../config');
var PackageRepository = require('../core/PackageRepository');
var createError = require('../util/createError');
function unregister(logger, name, config) {
if (!name) {
return;
}
var repository;
var registryClient;
var force;
config = defaultConfig(config);
force = config.force;
// Bypass any cache
config.offline = false;
config.force = true;
// Trim name
name = name.trim();
repository = new PackageRepository(config, logger);
if (!config.accessToken) {
return logger.emit(
'error',
createError(
'Use "bower login" with collaborator credentials',
'EFORBIDDEN'
)
);
}
return Q.resolve()
.then(function() {
// If non interactive or user forced, bypass confirmation
if (!config.interactive || force) {
return true;
}
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message:
'You are about to remove component "' +
chalk.cyan.underline(name) +
'" from the bower registry (' +
chalk.cyan.underline(config.registry.register) +
'). It is generally considered bad behavior to remove versions of a library that others are depending on. Are you really sure?',
default: false
});
})
.then(function(result) {
// If user response was negative, abort
if (!result) {
return;
}
registryClient = repository.getRegistryClient();
logger.action('unregister', name, { name: name });
return Q.nfcall(
registryClient.unregister.bind(registryClient),
name
);
})
.then(function(result) {
logger.info('Package unregistered', name);
return result;
});
}
// -------------------
unregister.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
return [name];
};
module.exports = unregister;

View File

@@ -1,210 +0,0 @@
var semver = require('semver');
var which = require('which');
var fs = require('../util/fs');
var path = require('path');
var Q = require('q');
var execFile = require('child_process').execFile;
var defaultConfig = require('../config');
var createError = require('../util/createError');
function version(logger, versionArg, options, config) {
options = options || {};
config = defaultConfig(config);
return bump(logger, config, versionArg, options.message);
}
function bump(logger, config, versionArg, message) {
var cwd = config.cwd || process.cwd();
var newVersion;
if (!versionArg) {
throw createError('No <version> agrument provided', 'EREADOPTIONS');
}
return driver
.check(cwd)
.then(function() {
return Q.all([driver.versions(cwd), driver.currentVersion(cwd)]);
})
.spread(function(versions, currentVersion) {
currentVersion = currentVersion || '0.0.0';
if (semver.valid(versionArg)) {
newVersion = semver.valid(versionArg);
} else {
newVersion = semver.inc(currentVersion, versionArg);
if (!newVersion) {
throw createError(
'Invalid <version> argument: ' + versionArg,
'EINVALIDVERSION',
{ version: versionArg }
);
}
}
newVersion =
currentVersion[0] === 'v' ? 'v' + newVersion : newVersion;
if (versions) {
versions.forEach(function(version) {
if (semver.eq(version, newVersion)) {
throw createError(
'Version exists: ' + newVersion,
'EVERSIONEXISTS',
{ versions: versions, newVersion: newVersion }
);
}
});
}
return driver.bump(cwd, newVersion, message).then(function() {
return {
oldVersion: currentVersion,
newVersion: newVersion
};
});
})
.then(function(result) {
logger.info(
'version',
'Bumped package version from ' +
result.oldVersion +
' to ' +
result.newVersion,
result
);
return result.newVersion;
});
}
var driver = {
check: function(cwd) {
function checkGit(cwd) {
var gitDir = path.join(cwd, '.git');
return Q.nfcall(fs.stat, gitDir).then(
function(stat) {
if (stat.isDirectory()) {
return checkGitStatus(cwd);
}
return false;
},
function() {
//Ignore not found .git directory
return false;
}
);
}
function checkGitStatus(cwd) {
return Q.nfcall(which, 'git')
.fail(function(err) {
err.code = 'ENOGIT';
throw err;
})
.then(function() {
return Q.nfcall(
execFile,
'git',
['status', '--porcelain'],
{ env: process.env, cwd: cwd }
);
})
.then(function(value) {
var stdout = value[0];
var lines = filterModifiedStatusLines(stdout);
if (lines.length) {
throw createError(
'Version bump requires clean working directory',
'EWORKINGDIRECTORYDIRTY'
);
}
return true;
});
}
function filterModifiedStatusLines(stdout) {
return stdout
.trim()
.split('\n')
.filter(function(line) {
return line.trim() && !line.match(/^\?\? /);
})
.map(function(line) {
return line.trim();
});
}
return checkGit(cwd).then(function(hasGit) {
if (!hasGit) {
throw createError(
'Version bump currently supports only git repositories',
'ENOTGITREPOSITORY'
);
}
});
},
versions: function(cwd) {
return Q.nfcall(execFile, 'git', ['tag'], {
env: process.env,
cwd: cwd
}).then(
function(res) {
var versions = res[0].split(/\r?\n/).filter(semver.valid);
return versions;
},
function() {
return [];
}
);
},
currentVersion: function(cwd) {
return Q.nfcall(execFile, 'git', ['describe', '--abbrev=0', '--tags'], {
env: process.env,
cwd: cwd
}).then(
function(res) {
var version = res[0].split(/\r?\n/).filter(semver.valid)[0];
return version;
},
function() {
return undefined;
}
);
},
bump: function(cwd, tag, message) {
message = message || tag;
message = message.replace(/%s/g, tag);
return Q.nfcall(
execFile,
'git',
['commit', '-m', message, '--allow-empty'],
{ env: process.env, cwd: cwd }
).then(function() {
return Q.nfcall(execFile, 'git', ['tag', tag, '-am', message], {
env: process.env,
cwd: cwd
});
});
}
};
version.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(
{
message: { type: String, shorthand: 'm' }
},
argv
);
return [options.argv.remain[1], options];
};
module.exports = version;

File diff suppressed because it is too large Load Diff

View File

@@ -1,304 +0,0 @@
var mout = require('mout');
var Q = require('q');
var ResolveCache = require('./ResolveCache');
var resolverFactory = require('./resolverFactory');
var createError = require('../util/createError');
var RegistryClient = require('bower-registry-client');
function PackageRepository(config, logger) {
var registryOptions;
this._config = config;
this._logger = logger;
// Instantiate the registry
registryOptions = mout.object.deepMixIn({}, this._config);
registryOptions.cache = this._config.storage.registry;
this._registryClient = new RegistryClient(registryOptions, logger);
// Instantiate the resolve cache
this._resolveCache = new ResolveCache(this._config);
}
// -----------------
PackageRepository.prototype.fetch = function(decEndpoint) {
var logger;
var that = this;
var isTargetable;
var info = {
decEndpoint: decEndpoint
};
// Create a new logger that pipes everything to ours that will be
// used to fetch
logger = this._logger.geminate();
// Intercept all logs, adding additional information
logger.intercept(function(log) {
that._extendLog(log, info);
});
return (
this._getResolver(decEndpoint, logger)
// Decide if we retrieve from the cache or not
// Also decide if we validate the cached entry or not
.then(function(resolver) {
info.resolver = resolver;
isTargetable = resolver.constructor.isTargetable;
if (!resolver.isCacheable()) {
return that._resolve(resolver, logger);
}
// If force flag is used, bypass cache, but write to cache anyway
if (that._config.force) {
logger.action(
'resolve',
resolver.getSource() + '#' + resolver.getTarget()
);
return that._resolve(resolver, logger);
}
// Note that we use the resolver methods to query the
// cache because transformations/normalisations can occur
return (
that._resolveCache
.retrieve(resolver.getSource(), resolver.getTarget())
// Decide if we can use the one from the resolve cache
.spread(function(canonicalDir, pkgMeta) {
// If there's no package in the cache
if (!canonicalDir) {
// And the offline flag is passed, error out
if (that._config.offline) {
throw createError(
'No cached version for ' +
resolver.getSource() +
'#' +
resolver.getTarget(),
'ENOCACHE',
{
resolver: resolver
}
);
}
// Otherwise, we have to resolve it
logger.info(
'not-cached',
resolver.getSource() +
(resolver.getTarget()
? '#' + resolver.getTarget()
: '')
);
logger.action(
'resolve',
resolver.getSource() +
'#' +
resolver.getTarget()
);
return that._resolve(resolver, logger);
}
info.canonicalDir = canonicalDir;
info.pkgMeta = pkgMeta;
logger.info(
'cached',
resolver.getSource() +
(pkgMeta._release
? '#' + pkgMeta._release
: '')
);
// If offline flag is used, use directly the cached one
if (that._config.offline) {
return [canonicalDir, pkgMeta, isTargetable];
}
// Otherwise check for new contents
logger.action(
'validate',
(pkgMeta._release
? pkgMeta._release + ' against '
: '') +
resolver.getSource() +
(resolver.getTarget()
? '#' + resolver.getTarget()
: '')
);
return resolver
.hasNew(pkgMeta)
.then(function(hasNew) {
// If there are no new contents, resolve to
// the cached one
if (!hasNew) {
return [
canonicalDir,
pkgMeta,
isTargetable
];
}
// Otherwise resolve to the newest one
logger.info(
'new',
'version for ' +
resolver.getSource() +
'#' +
resolver.getTarget()
);
logger.action(
'resolve',
resolver.getSource() +
'#' +
resolver.getTarget()
);
return that._resolve(resolver, logger);
});
})
);
})
// If something went wrong, also extend the error
.fail(function(err) {
that._extendLog(err, info);
throw err;
})
);
};
PackageRepository.prototype.versions = function(source) {
// Resolve the source using the factory because the
// source can actually be a registry name
return this._getResolver({ source: source }).then(
function(resolver) {
// If offline, resolve using the cached versions
if (this._config.offline) {
return this._resolveCache.versions(resolver.getSource());
}
// Otherwise, fetch remotely
return resolver.constructor.versions(resolver.getSource());
}.bind(this)
);
};
PackageRepository.prototype.eliminate = function(pkgMeta) {
return Q.all([
this._resolveCache.eliminate(pkgMeta),
Q.nfcall(
this._registryClient.clearCache.bind(this._registryClient),
pkgMeta.name
)
]);
};
PackageRepository.prototype.clear = function() {
return Q.all([
this._resolveCache.clear(),
Q.nfcall(this._registryClient.clearCache.bind(this._registryClient))
]);
};
PackageRepository.prototype.reset = function() {
this._resolveCache.reset();
this._registryClient.resetCache();
};
PackageRepository.prototype.list = function() {
return this._resolveCache.list();
};
PackageRepository.prototype.getRegistryClient = function() {
return this._registryClient;
};
PackageRepository.prototype.getResolveCache = function() {
return this._resolveCache;
};
PackageRepository.clearRuntimeCache = function() {
ResolveCache.clearRuntimeCache();
RegistryClient.clearRuntimeCache();
resolverFactory.clearRuntimeCache();
};
// ---------------------
PackageRepository.prototype._getResolver = function(decEndpoint, logger) {
logger = logger || this._logger;
// Get the appropriate resolver
return resolverFactory(
decEndpoint,
{ config: this._config, logger: logger },
this._registryClient
);
};
PackageRepository.prototype._resolve = function(resolver, logger) {
var that = this;
// Resolve the resolver
return (
resolver
.resolve()
// Store in the cache
.then(function(canonicalDir) {
if (!resolver.isCacheable()) {
return canonicalDir;
}
return that._resolveCache.store(
canonicalDir,
resolver.getPkgMeta()
);
})
// Resolve promise with canonical dir and package meta
.then(function(dir) {
var pkgMeta = resolver.getPkgMeta();
logger.info(
'resolved',
resolver.getSource() +
(pkgMeta._release ? '#' + pkgMeta._release : '')
);
return [dir, pkgMeta, resolver.constructor.isTargetable()];
})
);
};
PackageRepository.prototype._extendLog = function(log, info) {
log.data = log.data || {};
// Store endpoint info in each log
if (info.decEndpoint) {
log.data.endpoint = mout.object.pick(info.decEndpoint, [
'name',
'source',
'target'
]);
}
// Store the resolver info in each log
if (info.resolver) {
log.data.resolver = {
name: info.resolver.getName(),
source: info.resolver.getSource(),
target: info.resolver.getTarget()
};
}
// Store the canonical dir and its meta in each log
if (info.canonicalDir) {
log.data.canonicalDir = info.canonicalDir;
log.data.pkgMeta = info.pkgMeta;
}
return log;
};
module.exports = PackageRepository;

File diff suppressed because it is too large Load Diff

View File

@@ -1,440 +0,0 @@
var fs = require('../util/fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var mkdirp = require('mkdirp');
var rimraf = require('../util/rimraf');
var LRU = require('lru-cache');
var lockFile = require('lockfile');
var md5 = require('md5-hex');
var semver = require('../util/semver');
var readJson = require('../util/readJson');
var copy = require('../util/copy');
function ResolveCache(config) {
// TODO: Make some config entries, such as:
// - Max MB
// - Max versions per source
// - Max MB per source
// - etc..
this._config = config;
this._dir = this._config.storage.packages;
this._lockDir = this._config.storage.packages;
mkdirp.sync(this._lockDir);
// Cache is stored/retrieved statically to ensure singularity
// among instances
this._cache = this.constructor._cache.get(this._dir);
if (!this._cache) {
this._cache = new LRU({
max: 100,
maxAge: 60 * 5 * 1000 // 5 minutes
});
this.constructor._cache.set(this._dir, this._cache);
}
// Ensure dir is created
mkdirp.sync(this._dir);
}
// -----------------
ResolveCache.prototype.retrieve = function(source, target) {
var sourceId = md5(source);
var dir = path.join(this._dir, sourceId);
var that = this;
target = target || '*';
return this._getVersions(sourceId)
.spread(function(versions) {
var suitable;
// If target is a semver, find a suitable version
if (semver.validRange(target)) {
suitable = semver.maxSatisfying(versions, target, true);
if (suitable) {
return suitable;
}
}
// If target is '*' check if there's a cached '_wildcard'
if (target === '*') {
return mout.array.find(versions, function(version) {
return version === '_wildcard';
});
}
// Otherwise check if there's an exact match
return mout.array.find(versions, function(version) {
return version === target;
});
})
.then(function(version) {
var canonicalDir;
if (!version) {
return [];
}
// Resolve with canonical dir and package meta
canonicalDir = path.join(dir, encodeURIComponent(version));
return that._readPkgMeta(canonicalDir).then(
function(pkgMeta) {
return [canonicalDir, pkgMeta];
},
function() {
// If there was an error, invalidate the in-memory cache,
// delete the cached package and try again
that._cache.del(sourceId);
return Q.nfcall(rimraf, canonicalDir).then(function() {
return that.retrieve(source, target);
});
}
);
});
};
ResolveCache.prototype.store = function(canonicalDir, pkgMeta) {
var sourceId;
var release;
var dir;
var pkgLock;
var promise;
var that = this;
promise = pkgMeta ? Q.resolve(pkgMeta) : this._readPkgMeta(canonicalDir);
return promise
.then(function(pkgMeta) {
sourceId = md5(pkgMeta._source);
release = that._getPkgRelease(pkgMeta);
dir = path.join(that._dir, sourceId, release);
pkgLock = path.join(
that._lockDir,
sourceId + '-' + release + '.lock'
);
// Check if destination directory exists to prevent issuing lock at all times
return Q.nfcall(fs.stat, dir)
.fail(function(err) {
var lockParams = { wait: 250, retries: 25, stale: 60000 };
return Q.nfcall(lockFile.lock, pkgLock, lockParams)
.then(function() {
// Ensure other process didn't start copying files before lock was created
return Q.nfcall(fs.stat, dir).fail(function(err) {
// If stat fails, it is expected to return ENOENT
if (err.code !== 'ENOENT') {
throw err;
}
// Create missing directory and copy files there
return Q.nfcall(mkdirp, path.dirname(dir)).then(
function() {
return Q.nfcall(
fs.rename,
canonicalDir,
dir
).fail(function(err) {
// If error is EXDEV it means that we are trying to rename
// across different drives, so we copy and remove it instead
if (err.code !== 'EXDEV') {
throw err;
}
return copy.copyDir(
canonicalDir,
dir
);
});
}
);
});
})
.finally(function() {
lockFile.unlockSync(pkgLock);
});
})
.finally(function() {
// Ensure no tmp dir is left on disk.
return Q.nfcall(rimraf, canonicalDir);
});
})
.then(function() {
var versions = that._cache.get(sourceId);
// Add it to the in memory cache
// and sort the versions afterwards
if (versions && versions.indexOf(release) === -1) {
versions.push(release);
that._sortVersions(versions);
}
// Resolve with the final location
return dir;
});
};
ResolveCache.prototype.eliminate = function(pkgMeta) {
var sourceId = md5(pkgMeta._source);
var release = this._getPkgRelease(pkgMeta);
var dir = path.join(this._dir, sourceId, release);
var that = this;
return Q.nfcall(rimraf, dir).then(function() {
var versions = that._cache.get(sourceId) || [];
mout.array.remove(versions, release);
// If this was the last package in the cache,
// delete the parent folder (source)
// For extra security, check against the file system
// if this was really the last package
if (!versions.length) {
that._cache.del(sourceId);
return that._getVersions(sourceId).spread(function(versions) {
if (!versions.length) {
// Do not keep in-memory cache if it's completely
// empty
that._cache.del(sourceId);
return Q.nfcall(rimraf, path.dirname(dir));
}
});
}
});
};
ResolveCache.prototype.clear = function() {
return Q.nfcall(rimraf, this._dir)
.then(
function() {
return Q.nfcall(fs.mkdir, this._dir);
}.bind(this)
)
.then(
function() {
this._cache.reset();
}.bind(this)
);
};
ResolveCache.prototype.reset = function() {
this._cache.reset();
return this;
};
ResolveCache.prototype.versions = function(source) {
var sourceId = md5(source);
return this._getVersions(sourceId).spread(function(versions) {
return versions.filter(function(version) {
return semver.valid(version);
});
});
};
ResolveCache.prototype.list = function() {
var promises;
var dirs = [];
var that = this;
// Get the list of directories
return (
Q.nfcall(fs.readdir, this._dir)
.then(function(sourceIds) {
promises = sourceIds.map(function(sourceId) {
return Q.nfcall(
fs.readdir,
path.join(that._dir, sourceId)
).then(
function(versions) {
versions.forEach(function(version) {
var dir = path.join(
that._dir,
sourceId,
version
);
dirs.push(dir);
});
},
function(err) {
// Ignore lurking files, e.g.: .DS_Store if the user
// has navigated throughout the cache
if (err.code === 'ENOTDIR' && err.path) {
return Q.nfcall(rimraf, err.path);
}
throw err;
}
);
});
return Q.all(promises);
})
// Read every package meta
.then(function() {
promises = dirs.map(function(dir) {
return that._readPkgMeta(dir).then(
function(pkgMeta) {
return {
canonicalDir: dir,
pkgMeta: pkgMeta
};
},
function() {
// If it fails to read, invalidate the in memory
// cache for the source and delete the entry directory
var sourceId = path.basename(path.dirname(dir));
that._cache.del(sourceId);
return Q.nfcall(rimraf, dir);
}
);
});
return Q.all(promises);
})
// Sort by name ASC & release ASC
.then(function(entries) {
// Ignore falsy entries due to errors reading
// package metas
entries = entries.filter(function(entry) {
return !!entry;
});
return entries.sort(function(entry1, entry2) {
var pkgMeta1 = entry1.pkgMeta;
var pkgMeta2 = entry2.pkgMeta;
var comp = pkgMeta1.name.localeCompare(pkgMeta2.name);
// Sort by name
if (comp) {
return comp;
}
// Sort by version
if (pkgMeta1.version && pkgMeta2.version) {
return semver.compare(
pkgMeta1.version,
pkgMeta2.version
);
}
if (pkgMeta1.version) {
return -1;
}
if (pkgMeta2.version) {
return 1;
}
// Sort by target
return pkgMeta1._target.localeCompare(pkgMeta2._target);
});
})
);
};
// ------------------------
ResolveCache.clearRuntimeCache = function() {
// Note that _cache refers to the static _cache variable
// that holds other caches per dir!
// Do not confuse it with the instance cache
// Clear cache of each directory
this._cache.forEach(function(cache) {
cache.reset();
});
// Clear root cache
this._cache.reset();
};
// ------------------------
ResolveCache.prototype._getPkgRelease = function(pkgMeta) {
var release =
pkgMeta.version ||
(pkgMeta._target === '*' ? '_wildcard' : pkgMeta._target);
// Encode some dangerous chars such as / and \
release = encodeURIComponent(release);
return release;
};
ResolveCache.prototype._readPkgMeta = function(dir) {
var filename = path.join(dir, '.bower.json');
return readJson(filename).spread(function(json) {
return json;
});
};
ResolveCache.prototype._getVersions = function(sourceId) {
var dir;
var versions = this._cache.get(sourceId);
var that = this;
if (versions) {
return Q.resolve([versions, true]);
}
dir = path.join(this._dir, sourceId);
return Q.nfcall(fs.readdir, dir).then(
function(versions) {
// Sort and cache in memory
that._sortVersions(versions);
versions = versions.map(decodeURIComponent);
that._cache.set(sourceId, versions);
return [versions, false];
},
function(err) {
// If the directory does not exists, resolve
// as an empty array
if (err.code === 'ENOENT') {
versions = [];
that._cache.set(sourceId, versions);
return [versions, false];
}
throw err;
}
);
};
ResolveCache.prototype._sortVersions = function(versions) {
// Sort DESC
versions.sort(function(version1, version2) {
var validSemver1 = semver.valid(version1);
var validSemver2 = semver.valid(version2);
// If both are semvers, compare them
if (validSemver1 && validSemver2) {
return semver.rcompare(version1, version2);
}
// If one of them are semvers, give higher priority
if (validSemver1) {
return -1;
}
if (validSemver2) {
return 1;
}
// Otherwise they are considered equal
return 0;
});
};
// ------------------------
ResolveCache._cache = new LRU({
max: 5,
maxAge: 60 * 30 * 1000 // 30 minutes
});
module.exports = ResolveCache;

View File

@@ -1,279 +0,0 @@
var Q = require('q');
var fs = require('../util/fs');
var path = require('path');
var mout = require('mout');
var resolvers = require('./resolvers');
var createError = require('../util/createError');
var resolve = require('../util/resolve');
var pluginResolverFactory = require('./resolvers/pluginResolverFactory');
function createInstance(decEndpoint, options, registryClient) {
decEndpoint = mout.object.pick(decEndpoint, ['name', 'target', 'source']);
options.version = require('../version');
return getConstructor(decEndpoint, options, registryClient).spread(function(
ConcreteResolver,
decEndpoint
) {
return new ConcreteResolver(
decEndpoint,
options.config,
options.logger
);
});
}
function getConstructor(decEndpoint, options, registryClient) {
var source = decEndpoint.source;
var config = options.config;
// Below we try a series of async tests to guess the type of resolver to use
// If a step was unable to guess the resolver, it returns undefined
// If a step can guess the resolver, it returns with constructor of resolver
var promise = Q.resolve();
var addResolver = function(resolverFactory) {
promise = promise.then(function(result) {
if (result === undefined) {
return resolverFactory(decEndpoint, options);
} else {
return result;
}
});
};
// Plugin resolvers.
//
// It requires each resolver defined in config.resolvers and calls
// its "match" to check if given resolves supports given decEndpoint
addResolver(function() {
var selectedResolver;
var resolverNames;
if (Array.isArray(config.resolvers)) {
resolverNames = config.resolvers;
} else if (!!config.resolvers) {
resolverNames = config.resolvers.split(',');
} else {
resolverNames = [];
}
var resolverPromises = resolverNames.map(function(resolverName) {
var resolver = resolvers[resolverName];
if (resolver === undefined) {
var resolverPath = resolve(resolverName, { cwd: config.cwd });
if (resolverPath === undefined) {
throw createError(
'Bower resolver not found: ' + resolverName,
'ENORESOLVER'
);
}
resolver = pluginResolverFactory(
require(resolverPath),
options
);
}
return function() {
if (selectedResolver === undefined) {
var match = resolver.match.bind(resolver);
return Q.fcall(match, source).then(function(result) {
if (result) {
return (selectedResolver = resolver);
}
});
} else {
return selectedResolver;
}
};
});
return resolverPromises
.reduce(Q.when, new Q(undefined))
.then(function(resolver) {
if (resolver) {
return Q.fcall(
resolver.locate.bind(resolver),
decEndpoint.source
).then(function(result) {
if (result && result !== decEndpoint.source) {
decEndpoint.source = result;
decEndpoint.registry = true;
return getConstructor(
decEndpoint,
options,
registryClient
);
} else {
return [resolver, decEndpoint];
}
});
}
});
});
// Git case: git git+ssh, git+http, git+https
// .git at the end (probably ssh shorthand)
// git@ at the start
addResolver(function() {
if (
/^git(\+(ssh|https?))?:\/\//i.test(source) ||
/\.git\/?$/i.test(source) ||
/^git@/i.test(source)
) {
decEndpoint.source = source.replace(/^git\+/, '');
// If it's a GitHub repository, return the specialized resolver
if (resolvers.GitHub.getOrgRepoPair(source)) {
return [resolvers.GitHub, decEndpoint];
}
return [resolvers.GitRemote, decEndpoint];
}
});
// SVN case: svn, svn+ssh, svn+http, svn+https, svn+file
addResolver(function() {
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
return [resolvers.Svn, decEndpoint];
}
});
// URL case
addResolver(function() {
if (/^https?:\/\//i.exec(source)) {
return [resolvers.Url, decEndpoint];
}
});
// If source is ./ or ../ or an absolute path
addResolver(function() {
var absolutePath = path.resolve(config.cwd, source);
if (
/^\.\.?[\/\\]/.test(source) ||
/^~\//.test(source) ||
path.normalize(source).replace(/[\/\\]+$/, '') === absolutePath
) {
return (
Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
.then(function(stats) {
decEndpoint.source = absolutePath;
if (stats.isDirectory()) {
return Q.resolve([resolvers.GitFs, decEndpoint]);
}
throw new Error('Not a Git repository');
})
// If not, check if source is a valid Subversion repository
.fail(function() {
return Q.nfcall(
fs.stat,
path.join(absolutePath, '.svn')
).then(function(stats) {
decEndpoint.source = absolutePath;
if (stats.isDirectory()) {
return Q.resolve([resolvers.Svn, decEndpoint]);
}
throw new Error('Not a Subversion repository');
});
})
// If not, check if source is a valid file/folder
.fail(function() {
return Q.nfcall(fs.stat, absolutePath).then(function() {
decEndpoint.source = absolutePath;
return Q.resolve([resolvers.Fs, decEndpoint]);
});
})
);
}
});
// Check if is a shorthand and expand it
addResolver(function() {
// Check if the shorthandResolver is falsy
if (!config.shorthandResolver) {
return;
}
// Skip ssh and/or URL with auth
if (/[:@]/.test(source)) {
return;
}
// Ensure exactly only one "/"
var parts = source.split('/');
if (parts.length === 2) {
decEndpoint.source = mout.string.interpolate(
config.shorthandResolver,
{
shorthand: source,
owner: parts[0],
package: parts[1]
}
);
return getConstructor(decEndpoint, options, registryClient);
}
});
// As last resort, we try the registry
addResolver(function() {
if (!registryClient) {
return;
}
return Q.nfcall(
registryClient.lookup.bind(registryClient),
source
).then(function(entry) {
if (!entry) {
throw createError(
'Package ' + source + ' not found',
'ENOTFOUND'
);
}
decEndpoint.registry = true;
if (!decEndpoint.name) {
decEndpoint.name = decEndpoint.source;
}
decEndpoint.source = entry.url;
return getConstructor(decEndpoint, options);
});
});
addResolver(function() {
throw createError(
'Could not find appropriate resolver for ' + source,
'ENORESOLVER'
);
});
return promise;
}
function clearRuntimeCache() {
mout.object.values(resolvers).forEach(function(ConcreteResolver) {
ConcreteResolver.clearRuntimeCache();
});
}
module.exports = createInstance;
module.exports.getConstructor = getConstructor;
module.exports.clearRuntimeCache = clearRuntimeCache;

View File

@@ -1,102 +0,0 @@
var util = require('util');
var Q = require('q');
var mout = require('mout');
var path = require('path');
var GitResolver = require('./GitResolver');
var copy = require('../../util/copy');
var cmd = require('../../util/cmd');
function GitFsResolver(decEndpoint, config, logger) {
GitResolver.call(this, decEndpoint, config, logger);
// Ensure absolute path
this._source = path.resolve(this._config.cwd, this._source);
}
util.inherits(GitFsResolver, GitResolver);
mout.object.mixIn(GitFsResolver, GitResolver);
// -----------------
// Override the checkout function to work with the local copy
GitFsResolver.prototype._checkout = function() {
var resolution = this._resolution;
// The checkout process could be similar to the GitRemoteResolver by prepending file:// to the source
// But from my performance measures, it's faster to copy the folder and just checkout in there
this._logger.action(
'checkout',
resolution.tag || resolution.branch || resolution.commit,
{
resolution: resolution,
to: this._tempDir
}
);
// Copy files to the temporary directory first
return (
this._copy()
.then(
cmd.bind(
cmd,
'git',
[
'checkout',
'-f',
resolution.tag || resolution.branch || resolution.commit
],
{ cwd: this._tempDir }
)
)
// Cleanup unstaged files
.then(
cmd.bind(cmd, 'git', ['clean', '-f', '-d'], {
cwd: this._tempDir
})
)
);
};
GitFsResolver.prototype._copy = function() {
return copy.copyDir(this._source, this._tempDir);
};
// -----------------
// Grab refs locally
GitFsResolver.refs = function(source) {
var value;
// TODO: Normalize source because of the various available protocols?
value = this._cache.refs.get(source);
if (value) {
return Q.resolve(value);
}
value = cmd('git', ['show-ref', '--tags', '--heads'], {
cwd: source
}).spread(
function(stdout) {
var refs;
refs = stdout
.toString()
.trim() // Trim trailing and leading spaces
.replace(/[\t ]+/g, ' ') // Standardize spaces (some git versions make tabs, other spaces)
.split(/[\r\n]+/); // Split lines into an array
// Update the refs with the actual refs
this._cache.refs.set(source, refs);
return refs;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
this._cache.refs.set(source, value);
return value;
};
module.exports = GitFsResolver;

View File

@@ -1,191 +0,0 @@
var util = require('util');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var GitRemoteResolver = require('./GitRemoteResolver');
var download = require('../../util/download');
var extract = require('../../util/extract');
var createError = require('../../util/createError');
function GitHubResolver(decEndpoint, config, logger) {
var pair;
GitRemoteResolver.call(this, decEndpoint, config, logger);
// Grab the org/repo
// /xxxxx/yyyyy.git or :xxxxx/yyyyy.git (.git is optional)
pair = GitHubResolver.getOrgRepoPair(this._source);
if (!pair) {
throw createError('Invalid GitHub URL', 'EINVEND', {
details: this._source + ' does not seem to be a valid GitHub URL'
});
}
this._org = pair.org;
this._repo = pair.repo;
// Ensure trailing for all protocols
if (!mout.string.endsWith(this._source, '.git')) {
this._source += '.git';
}
// Use https:// rather than git:// if on a proxy
if (this._config.proxy || this._config.httpsProxy) {
this._source = this._source.replace('git://', 'https://');
}
// Enable shallow clones for GitHub repos
this._shallowClone = function() {
return Q.resolve(true);
};
}
util.inherits(GitHubResolver, GitRemoteResolver);
mout.object.mixIn(GitHubResolver, GitRemoteResolver);
// -----------------
GitHubResolver.prototype._checkout = function() {
var msg;
var name =
this._resolution.tag ||
this._resolution.branch ||
this._resolution.commit;
var tarballUrl =
'https://github.com/' +
this._org +
'/' +
this._repo +
'/archive/' +
name +
'.tar.gz';
var file = path.join(this._tempDir, 'archive.tar.gz');
var reqHeaders = {};
var that = this;
if (this._config.userAgent) {
reqHeaders['User-Agent'] = this._config.userAgent;
}
this._logger.action('download', tarballUrl, {
url: that._source,
to: file
});
// Download tarball
return download(tarballUrl, file, {
ca: this._config.ca.default,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders
})
.progress(function(state) {
// Retry?
if (state.retry) {
msg =
'Download of ' +
tarballUrl +
' failed with ' +
state.error.code +
', ';
msg += 'retrying in ' + (state.delay / 1000).toFixed(1) + 's';
that._logger.debug('error', state.error.message, {
error: state.error
});
return that._logger.warn('retry', msg);
}
// Progress
msg =
'received ' + (state.received / 1024 / 1024).toFixed(1) + 'MB';
if (state.total) {
msg +=
' of ' +
(state.total / 1024 / 1024).toFixed(1) +
'MB downloaded, ';
msg += state.percent + '%';
}
that._logger.info('progress', msg);
})
.then(
function() {
// Extract archive
that._logger.action('extract', path.basename(file), {
archive: file,
to: that._tempDir
});
return (
extract(file, that._tempDir)
// Fallback to standard git clone if extraction failed
.fail(function(err) {
msg =
'Decompression of ' +
path.basename(file) +
' failed' +
(err.code ? ' with ' + err.code : '') +
', ';
msg += 'trying with git..';
that._logger.debug('error', err.message, {
error: err
});
that._logger.warn('retry', msg);
return that
._cleanTempDir()
.then(
GitRemoteResolver.prototype._checkout.bind(
that
)
);
})
);
// Fallback to standard git clone if download failed
},
function(err) {
msg =
'Download of ' +
tarballUrl +
' failed' +
(err.code ? ' with ' + err.code : '') +
', ';
msg += 'trying with git..';
that._logger.debug('error', err.message, { error: err });
that._logger.warn('retry', msg);
return that
._cleanTempDir()
.then(GitRemoteResolver.prototype._checkout.bind(that));
}
);
};
GitHubResolver.prototype._savePkgMeta = function(meta) {
// Set homepage if not defined
if (!meta.homepage) {
meta.homepage = 'https://github.com/' + this._org + '/' + this._repo;
}
return GitRemoteResolver.prototype._savePkgMeta.call(this, meta);
};
// ----------------
GitHubResolver.getOrgRepoPair = function(url) {
var match;
match = url.match(
/(?:@|:\/\/)github.com[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?\/?$/i
);
if (!match) {
return null;
}
return {
org: match[1],
repo: match[2]
};
};
module.exports = GitHubResolver;

View File

@@ -1,434 +0,0 @@
var util = require('util');
var path = require('path');
var Q = require('q');
var rimraf = require('../../util/rimraf');
var mkdirp = require('mkdirp');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var hasGit;
// Check if git is installed
try {
which.sync('git');
hasGit = true;
} catch (ex) {
hasGit = false;
}
function GitResolver(decEndpoint, config, logger) {
// Set template dir to the empty directory so that user templates are not run
// This environment variable is not multiple config aware but it's not documented
// anyway
mkdirp.sync(config.storage.empty);
process.env.GIT_TEMPLATE_DIR = config.storage.empty;
if (!config.strictSsl) {
process.env.GIT_SSL_NO_VERIFY = 'true';
}
if (!config.interactive) {
process.env.GIT_TERMINAL_PROMPT = '0';
if (!process.env.SSH_ASKPASS) {
process.env.SSH_ASKPASS = 'echo';
}
}
Resolver.call(this, decEndpoint, config, logger);
if (!hasGit) {
throw createError('git is not installed or not in the PATH', 'ENOGIT');
}
}
util.inherits(GitResolver, Resolver);
mout.object.mixIn(GitResolver, Resolver);
// -----------------
GitResolver.prototype._hasNew = function(pkgMeta) {
var oldResolution = pkgMeta._resolution || {};
return this._findResolution().then(function(resolution) {
// Check if resolution types are different
if (oldResolution.type !== resolution.type) {
return true;
}
// If resolved to a version, there is new content if the tags are not equal
if (
resolution.type === 'version' &&
semver.neq(resolution.tag, oldResolution.tag)
) {
return true;
}
// As last check, we compare both commit hashes
return resolution.commit !== oldResolution.commit;
});
};
GitResolver.prototype._resolve = function() {
var that = this;
return this._findResolution().then(function() {
return (
that
._checkout()
// Always run cleanup after checkout to ensure that .git is removed!
// If it's not removed, problems might arise when the "tmp" module attempts
// to delete the temporary folder
.fin(function() {
return that._cleanup();
})
);
});
};
// -----------------
// Abstract functions that should be implemented by concrete git resolvers
GitResolver.prototype._checkout = function() {
throw new Error('_checkout not implemented');
};
GitResolver.refs = function(source) {
throw new Error('refs not implemented');
};
// -----------------
GitResolver.prototype._findResolution = function(target) {
var err;
var self = this.constructor;
var that = this;
target = target || this._target || '*';
// Target is a commit, so it's a stale target (not a moving target)
// There's nothing to do in this case
if (/^[a-f0-9]{40}$/.test(target)) {
this._resolution = { type: 'commit', commit: target };
return Q.resolve(this._resolution);
}
// Target is a range/version
if (semver.validRange(target)) {
return self.versions(this._source, true).then(function(versions) {
var versionsArr, version, index;
// If there are no tags and target is *,
// fallback to the latest commit on master
if (!versions.length && target === '*') {
return that._findResolution('master');
}
versionsArr = versions.map(function(obj) {
return obj.version;
});
// Find a satisfying version, enabling strict match so that pre-releases
// have lower priority over normal ones when target is *
index = semver.maxSatisfyingIndex(versionsArr, target, true);
if (index !== -1) {
version = versions[index];
return (that._resolution = {
type: 'version',
tag: version.tag,
commit: version.commit
});
}
// Check if there's an exact branch/tag with this name as last resort
return Q.all([
self.branches(that._source),
self.tags(that._source)
]).spread(function(branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return (that._resolution = {
type: 'tag',
tag: target,
commit: tags[target]
});
}
if (mout.object.hasOwn(branches, target)) {
return (that._resolution = {
type: 'branch',
branch: target,
commit: branches[target]
});
}
throw createError(
'No tag found that was able to satisfy ' + target,
'ENORESTARGET',
{
details: !versions.length
? 'No versions found in ' + that._source
: 'Available versions in ' +
that._source +
': ' +
versions
.map(function(version) {
return version.version;
})
.join(', ')
}
);
});
});
}
// Otherwise, target is either a tag or a branch
return Q.all([self.branches(that._source), self.tags(that._source)]).spread(
function(branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return (that._resolution = {
type: 'tag',
tag: target,
commit: tags[target]
});
}
if (mout.object.hasOwn(branches, target)) {
return (that._resolution = {
type: 'branch',
branch: target,
commit: branches[target]
});
}
if (/^[a-f0-9]{4,40}$/.test(target)) {
if (target.length < 12) {
that._logger.warn(
'short-sha',
'Consider using longer commit SHA to avoid conflicts'
);
}
that._resolution = { type: 'commit', commit: target };
return that._resolution;
}
branches = Object.keys(branches);
tags = Object.keys(tags);
err = createError(
'Tag/branch ' + target + ' does not exist',
'ENORESTARGET'
);
err.details = !tags.length
? 'No tags found in ' + that._source
: 'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length
? 'No branches found in ' + that._source
: 'Available branches: ' + branches.join(', ');
throw err;
}
);
};
GitResolver.prototype._cleanup = function() {
var gitFolder = path.join(this._tempDir, '.git');
return Q.nfcall(rimraf, gitFolder);
};
GitResolver.prototype._savePkgMeta = function(meta) {
var version;
if (this._resolution.type === 'version') {
version = semver.clean(this._resolution.tag);
// Warn if the package meta version is different than the resolved one
if (
typeof meta.version === 'string' &&
semver.valid(meta.version) &&
semver.neq(meta.version, version)
) {
this._logger.warn(
'mismatch',
'Version declared in the json (' +
meta.version +
') is different than the resolved one (' +
version +
')',
{
resolution: this._resolution,
pkgMeta: meta
}
);
}
// Ensure package meta version is the same as the resolution
meta.version = version;
} else {
// If resolved to a target that is not a version,
// remove the version from the meta
delete meta.version;
}
// Save version/tag/commit in the release
// Note that we can't store branches because _release is supposed to be
// an unique id of this ref.
meta._release =
version ||
this._resolution.tag ||
this._resolution.commit.substr(0, 10);
// Save resolution to be used in hasNew later
meta._resolution = this._resolution;
return Resolver.prototype._savePkgMeta.call(this, meta);
};
// ------------------------------
GitResolver.versions = function(source, extra) {
var value = this._cache.versions.get(source);
if (value) {
return Q.resolve(value).then(
function() {
var versions = this._cache.versions.get(source);
// If no extra information was requested,
// resolve simply with the versions
if (!extra) {
versions = versions.map(function(version) {
return version.version;
});
}
return versions;
}.bind(this)
);
}
value = this.tags(source).then(
function(tags) {
var tag;
var version;
var versions = [];
// For each tag
for (tag in tags) {
version = semver.clean(tag);
if (version) {
versions.push({
version: version,
tag: tag,
commit: tags[tag]
});
}
}
// Sort them by DESC order
versions.sort(function(a, b) {
return semver.rcompare(a.version, b.version);
});
this._cache.versions.set(source, versions);
// Call the function again to keep it DRY
return this.versions(source, extra);
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
this._cache.versions.set(source, value);
return value;
};
GitResolver.tags = function(source) {
var value = this._cache.tags.get(source);
if (value) {
return Q.resolve(value);
}
value = this.refs(source).then(
function(refs) {
var tags = {};
// For each line in the refs, match only the tags
refs.forEach(function(line) {
var match = line.match(/^([a-f0-9]{40})\s+refs\/tags\/(\S+)/);
if (match && !mout.string.endsWith(match[2], '^{}')) {
tags[match[2]] = match[1];
}
});
this._cache.tags.set(source, tags);
return tags;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
this._cache.tags.set(source, value);
return value;
};
GitResolver.branches = function(source) {
var value = this._cache.branches.get(source);
if (value) {
return Q.resolve(value);
}
value = this.refs(source).then(
function(refs) {
var branches = {};
// For each line in the refs, extract only the heads
// Organize them in an object where keys are branches and values
// the commit hashes
refs.forEach(function(line) {
var match = line.match(/^([a-f0-9]{40})\s+refs\/heads\/(\S+)/);
if (match) {
branches[match[2]] = match[1];
}
});
this._cache.branches.set(source, branches);
return branches;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
this._cache.branches.set(source, value);
return value;
};
GitResolver.clearRuntimeCache = function() {
// Reset cache for branches, tags, etc
mout.object.forOwn(GitResolver._cache, function(lru) {
lru.reset();
});
};
GitResolver._cache = {
branches: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
tags: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
versions: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
refs: new LRU({ max: 50, maxAge: 5 * 60 * 1000 })
};
module.exports = GitResolver;

View File

@@ -1,491 +0,0 @@
var util = require('util');
var Q = require('q');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var cmd = require('../../util/cmd');
var hasSvn;
// Check if svn is installed
try {
which.sync('svn');
hasSvn = true;
} catch (ex) {
hasSvn = false;
}
function SvnResolver(decEndpoint, config, logger) {
Resolver.call(this, decEndpoint, config, logger);
if (!hasSvn) {
throw createError('svn is not installed or not in the PATH', 'ENOSVN');
}
}
util.inherits(SvnResolver, Resolver);
mout.object.mixIn(SvnResolver, Resolver);
// -----------------
SvnResolver.getSource = function(source) {
var uri = this._source || source;
return uri
.replace(/^svn\+(https?|file):\/\//i, '$1://') // Change svn+http or svn+https or svn+file to http(s), file respectively
.replace('svn://', 'http://') // Change svn to http
.replace(/\/+$/, ''); // Remove trailing slashes
};
SvnResolver.prototype._hasNew = function(pkgMeta) {
var oldResolution = pkgMeta._resolution || {};
return this._findResolution().then(function(resolution) {
// Check if resolution types are different
if (oldResolution.type !== resolution.type) {
return true;
}
// If resolved to a version, there is new content if the tags are not equal
if (
resolution.type === 'version' &&
semver.neq(resolution.tag, oldResolution.tag)
) {
return true;
}
// As last check, we compare both commit hashes
return resolution.commit !== oldResolution.commit;
});
};
SvnResolver.prototype._resolve = function() {
var that = this;
return this._findResolution().then(function() {
return that._export();
});
};
// -----------------
SvnResolver.prototype._export = function() {
var promise;
var timer;
var reporter;
var that = this;
var resolution = this._resolution;
this.source = SvnResolver.getSource(this._source);
this._logger.action(
'export',
resolution.tag || resolution.branch || resolution.commit,
{
resolution: resolution,
to: this._tempDir
}
);
if (resolution.type === 'commit') {
promise = cmd('svn', [
'export',
'--force',
'--non-interactive',
this._source + '/trunk',
'-r' + resolution.commit,
this._tempDir
]);
} else if (resolution.type === 'branch' && resolution.branch === 'trunk') {
promise = cmd('svn', [
'export',
'--force',
'--non-interactive',
this._source + '/trunk',
this._tempDir
]);
} else if (resolution.type === 'branch') {
promise = cmd('svn', [
'export',
'--force',
'--non-interactive',
this._source + '/branches/' + resolution.branch,
this._tempDir
]);
} else {
promise = cmd('svn', [
'export',
'--force',
'--non-interactive',
this._source + '/tags/' + resolution.tag,
this._tempDir
]);
}
// Throttle the progress reporter to 1 time each sec
reporter = mout.fn.throttle(function(data) {
var lines;
lines = data.split(/[\r\n]+/);
lines.forEach(function(line) {
if (/\d{1,3}\%/.test(line)) {
// TODO: There are some strange chars that appear once in a while (\u001b[K)
// Trim also those?
that._logger.info('progress', line.trim());
}
});
}, 1000);
// Start reporting progress after a few seconds
timer = setTimeout(function() {
promise.progress(reporter);
}, 8000);
return (
promise
// Add additional proxy information to the error if necessary
.fail(function(err) {
throw err;
})
// Clear timer at the end
.fin(function() {
clearTimeout(timer);
reporter.cancel();
})
);
};
// -----------------
SvnResolver.prototype._findResolution = function(target) {
var err;
var self = this.constructor;
var that = this;
target = target || this._target || '*';
this._source = SvnResolver.getSource(this._source);
// Target is a revision, so it's a stale target (not a moving target)
// There's nothing to do in this case
if (/^r\d+/.test(target)) {
target = target.split('r');
this._resolution = { type: 'commit', commit: target[1] };
return Q.resolve(this._resolution);
}
// Target is a range/version
if (semver.validRange(target)) {
return self.versions(this._source, true).then(function(versions) {
var versionsArr, version, index;
versionsArr = versions.map(function(obj) {
return obj.version;
});
// If there are no tags and target is *,
// fallback to the latest commit on trunk
if (!versions.length && target === '*') {
return that._findResolution('trunk');
}
versionsArr = versions.map(function(obj) {
return obj.version;
});
// Find a satisfying version, enabling strict match so that pre-releases
// have lower priority over normal ones when target is *
index = semver.maxSatisfyingIndex(versionsArr, target, true);
if (index !== -1) {
version = versions[index];
return (that._resolution = {
type: 'version',
tag: version.tag,
commit: version.commit
});
}
// Check if there's an exact branch/tag with this name as last resort
return Q.all([
self.branches(that._source),
self.tags(that._source)
]).spread(function(branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return (that._resolution = {
type: 'tag',
tag: target,
commit: tags[target]
});
}
if (mout.object.hasOwn(branches, target)) {
return (that._resolution = {
type: 'branch',
branch: target,
commit: branches[target]
});
}
throw createError(
'No tag found that was able to satisfy ' + target,
'ENORESTARGET',
{
details: !versions.length
? 'No versions found in ' + that._source
: 'Available versions in ' +
that._source +
': ' +
versions
.map(function(version) {
return version.version;
})
.join(', ')
}
);
});
});
}
// Otherwise, target is either a tag or a branch
return Q.all([self.branches(that._source), self.tags(that._source)]).spread(
function(branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return (that._resolution = {
type: 'tag',
tag: target,
commit: tags[target]
});
}
if (mout.object.hasOwn(branches, target)) {
return (that._resolution = {
type: 'branch',
branch: target,
commit: branches[target]
});
}
branches = Object.keys(branches);
tags = Object.keys(tags);
err = createError(
'target ' + target + ' does not exist',
'ENORESTARGET'
);
err.details = !tags.length
? 'No tags found in ' + that._source
: 'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length
? 'No branches found in ' + that._source
: 'Available branches: ' + branches.join(', ');
throw err;
}
);
};
SvnResolver.prototype._savePkgMeta = function(meta) {
var version;
if (this._resolution.type === 'version') {
version = semver.clean(this._resolution.tag);
// Warn if the package meta version is different than the resolved one
if (
typeof meta.version === 'string' &&
semver.neq(meta.version, version)
) {
this._logger.warn(
'mismatch',
'Version declared in the json (' +
meta.version +
') is different than the resolved one (' +
version +
')',
{
resolution: this._resolution,
pkgMeta: meta
}
);
}
// Ensure package meta version is the same as the resolution
meta.version = version;
} else {
// If resolved to a target that is not a version,
// remove the version from the meta
delete meta.version;
}
// Save version/tag/commit in the release
// Note that we can't store branches because _release is supposed to be
// an unique id of this ref.
meta._release = version || this._resolution.tag || this._resolution.commit;
// Save resolution to be used in hasNew later
meta._resolution = this._resolution;
return Resolver.prototype._savePkgMeta.call(this, meta);
};
// ------------------------------
SvnResolver.versions = function(source, extra) {
source = SvnResolver.getSource(source);
var value = this._cache.versions.get(source);
if (value) {
return Q.resolve(value).then(
function() {
var versions = this._cache.versions.get(source);
// If no extra information was requested,
// resolve simply with the versions
if (!extra) {
versions = versions.map(function(version) {
return version.version;
});
}
return versions;
}.bind(this)
);
}
value = this.tags(source).then(
function(tags) {
var tag;
var version;
var versions = [];
// For each tag
for (tag in tags) {
version = semver.clean(tag);
if (version) {
versions.push({
version: version,
tag: tag,
commit: tags[tag]
});
}
}
// Sort them by DESC order
versions.sort(function(a, b) {
return semver.rcompare(a.version, b.version);
});
this._cache.versions.set(source, versions);
// Call the function again to keep it DRY
return this.versions(source, extra);
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
this._cache.versions.set(source, value);
return value;
};
SvnResolver.tags = function(source) {
source = SvnResolver.getSource(source);
var value = this._cache.tags.get(source);
if (value) {
return Q.resolve(value);
}
value = cmd('svn', [
'list',
source + '/tags',
'--verbose',
'--non-interactive'
]).spread(
function(stout) {
var tags = SvnResolver.parseSubversionListOutput(stout.toString());
this._cache.tags.set(source, tags);
return tags;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
this._cache.tags.set(source, value);
return value;
};
SvnResolver.branches = function(source) {
source = SvnResolver.getSource(source);
var value = this._cache.branches.get(source);
if (value) {
return Q.resolve(value);
}
value = cmd('svn', [
'list',
source + '/branches',
'--verbose',
'--non-interactive'
]).spread(
function(stout) {
var branches = SvnResolver.parseSubversionListOutput(
stout.toString()
);
// trunk is a branch!
branches.trunk = '*';
this._cache.branches.set(source, branches);
return branches;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
this._cache.branches.set(source, value);
return value;
};
SvnResolver.parseSubversionListOutput = function(stout) {
var entries = {};
var lines = stout.trim().split(/[\r\n]+/);
// For each line in the refs, match only the branches
lines.forEach(function(line) {
var match = line.match(/\s+([0-9]+)\s.+\s([\w.$-]+)\//i);
if (match && match[2] !== '.') {
entries[match[2]] = match[1];
}
});
return entries;
};
SvnResolver.clearRuntimeCache = function() {
// Reset cache for branches, tags, etc
mout.object.forOwn(SvnResolver._cache, function(lru) {
lru.reset();
});
};
SvnResolver._cache = {
branches: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
tags: new LRU({ max: 50, maxAge: 5 * 60 * 1000 }),
versions: new LRU({ max: 50, maxAge: 5 * 60 * 1000 })
};
module.exports = SvnResolver;

View File

@@ -1,385 +0,0 @@
var Q = require('q');
var path = require('path');
var fs = require('../../util/fs');
var object = require('mout/object');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var readJson = require('../../util/readJson');
var removeIgnores = require('../../util/removeIgnores');
function pluginResolverFactory(resolverFactory, bower) {
bower = bower || {};
if (typeof resolverFactory !== 'function') {
throw createError(
'Resolver has "' +
typeof resolverFactory +
'" type instead of "function" type.',
'ERESOLERAPI'
);
}
var resolver = resolverFactory(bower);
function maxSatisfyingVersion(versions, target) {
var versionsArr, index;
versionsArr = versions.map(function(obj) {
return obj.version;
});
// Find a satisfying version, enabling strict match so that pre-releases
// have lower priority over normal ones when target is *
index = semver.maxSatisfyingIndex(versionsArr, target, true);
if (index !== -1) {
return versions[index];
}
}
function PluginResolver(decEndpoint) {
this._decEndpoint = decEndpoint;
}
// @private
PluginResolver.prototype.getEndpoint = function() {
return object.merge(this._decEndpoint, {
name: this.getName(),
source: this.getSource(),
target: this.getTarget()
});
};
PluginResolver.prototype.getSource = function() {
return this._decEndpoint.source;
};
PluginResolver.prototype.getTarget = function() {
return this._decEndpoint.target || '*';
};
PluginResolver.prototype.getName = function() {
if (!this._decEndpoint.name && typeof resolver.getName === 'function') {
return resolver.getName.call(resolver, this.getSource());
} else if (!this._decEndpoint.name) {
return path.basename(this.getSource());
} else {
return this._decEndpoint.name;
}
};
PluginResolver.prototype.getPkgMeta = function() {
return this._pkgMeta;
};
// -----------------
// Plugin Resolver is always considered potentially cacheable
// The "resolve" method decides whether to use cached or fetch new version.
PluginResolver.prototype.isCacheable = function() {
return true;
};
// Not only it's always potentially cacheable, but also always potenially new.
// The "resolve" handles logic of re-downloading target if needed.
PluginResolver.prototype.hasNew = function(pkgMeta) {
if (this.hasNewPromise) {
return this.hasNewPromise;
}
this._pkgMeta = pkgMeta;
return (this.hasNewPromise = this.resolve().then(function(result) {
return result !== undefined;
}));
};
PluginResolver.prototype.resolve = function() {
if (this.resolvePromise) {
return this.resolvePromise;
}
var that = this;
return (this.resolvePromise = Q.fcall(function() {
var target = that.getTarget();
// It means that we can accept ranges as targets
if (that.constructor.isTargetable()) {
that._release = target;
if (semver.validRange(target)) {
return Q.fcall(
resolver.releases.bind(resolver),
that.getSource()
).then(function(result) {
if (!result) {
throw createError(
'Resolver did not provide releases of package.'
);
}
var releases = (that._releases = result);
var versions = releases.filter(function(target) {
return semver.clean(target.version);
});
var maxRelease = maxSatisfyingVersion(versions, target);
if (maxRelease) {
that._version = maxRelease.version;
that._release = that._decEndpoint.target =
maxRelease.target;
} else {
throw createError(
'No version found that was able to satisfy ' +
target,
'ENORESTARGET',
{
details: !versions.length
? 'No versions found in ' +
that.getSource()
: 'Available versions: ' +
versions
.map(function(version) {
return version.version;
})
.join(', ')
}
);
}
});
}
} else {
if (semver.validRange(target) && target !== '*') {
return Q.reject(
createError(
'Resolver does not accept version ranges (' +
target +
')'
)
);
}
}
})
.then(function() {
// We pass old _resolution (if hasNew has been called before contents).
// So resolver can decide wheter use cached version of contents new one.
if (typeof resolver.fetch !== 'function') {
throw createError(
'Resolver does not implement the "fetch" method.'
);
}
var cached = {};
if (that._releases) {
cached.releases = that._releases;
}
if (that._pkgMeta) {
cached.endpoint = {
name: that._pkgMeta.name,
source: that._pkgMeta._source,
target: that._pkgMeta._target
};
cached.release = that._pkgMeta._release;
cached.version = that._pkgMeta.version;
cached.resolution = that._pkgMeta._resolution || {};
}
return Q.fcall(
resolver.fetch.bind(resolver),
that.getEndpoint(),
cached
);
})
.then(function(result) {
// Empty result means to re-use existing resolution
if (!result) {
return;
} else {
if (!result.tempPath) {
throw createError(
'Resolver did not provide path to extracted contents of package.'
);
}
that._tempDir = result.tempPath;
return that._readJson(that._tempDir).then(function(meta) {
return that
._applyPkgMeta(meta, result)
.then(that._savePkgMeta.bind(that, meta, result))
.then(function() {
return that._tempDir;
});
});
}
}));
};
PluginResolver.prototype._readJson = function(dir) {
var that = this;
return readJson(dir, {
assume: { name: that.getName() },
logger: bower.logger
}).spread(function(json, deprecated) {
if (deprecated) {
bower.logger.warn(
'deprecated',
'Package ' +
that.getName() +
' is using the deprecated ' +
deprecated
);
}
return json;
});
};
PluginResolver.prototype._applyPkgMeta = function(meta, result) {
// Check if name defined in the json is different
// If so and if the name was "guessed", assume the json name
if (meta.name !== this._name) {
this._name = meta.name;
}
// Handle ignore property, deleting all files from the temporary directory
// If no ignores were specified, simply resolve
if (
result.removeIgnores === false ||
!meta.ignore ||
!meta.ignore.length
) {
return Q.resolve(meta);
}
// Otherwise remove them from the temp dir
return removeIgnores(this._tempDir, meta).then(function() {
return meta;
});
};
PluginResolver.prototype._savePkgMeta = function(meta, result) {
var that = this;
meta._source = that.getSource();
meta._target = that.getTarget();
if (result.resolution) {
meta._resolution = result.resolution;
}
if (that._release) {
meta._release = that._release;
}
if (that._version) {
meta.version = that._version;
} else {
delete meta.version;
}
// Stringify contents
var contents = JSON.stringify(meta, null, 2);
return Q.nfcall(
fs.writeFile,
path.join(this._tempDir, '.bower.json'),
contents
).then(function() {
return (that._pkgMeta = meta);
});
};
// It is used only by "bower info". It returns all semver versions.
PluginResolver.versions = function(source) {
return Q.fcall(resolver.releases.bind(resolver), source).then(function(
result
) {
if (!result) {
throw createError(
'Resolver did not provide releases of package.'
);
}
var releases = (this._releases = result);
var versions = releases.map(function(version) {
return semver.clean(version.version);
});
versions = versions.filter(function(version) {
return version;
});
versions.sort(function(a, b) {
return semver.rcompare(a, b);
});
return versions;
});
};
PluginResolver.isTargetable = function() {
// If resolver doesn't define versions function, it's not targetable..
return typeof resolver.releases === 'function';
};
PluginResolver.clearRuntimeCache = function() {
resolver = resolverFactory(bower);
};
PluginResolver.match = function(source) {
if (typeof resolver.match !== 'function') {
throw createError(
'Resolver is missing "match" method.',
'ERESOLVERAPI'
);
}
var match = resolver.match.bind(resolver);
return Q.fcall(match, source).then(function(result) {
if (typeof result !== 'boolean') {
throw createError(
'Resolver\'s "match" method should return a boolean',
'ERESOLVERAPI'
);
}
return result;
});
};
PluginResolver.locate = function(source) {
if (typeof resolver.locate !== 'function') {
return source;
}
return Q.fcall(resolver.locate.bind(resolver), source).then(function(
result
) {
if (typeof result !== 'string') {
throw createError(
'Resolver\'s "locate" method should return a string',
'ERESOLVERAPI'
);
}
return result;
});
};
return PluginResolver;
}
module.exports = pluginResolverFactory;

View File

@@ -1,13 +0,0 @@
var chalk = require('chalk');
var templateColors = ['yellow', 'green', 'cyan', 'red', 'white', 'magenta'];
function colors(Handlebars) {
templateColors.forEach(function(color) {
Handlebars.registerHelper(color, function(context) {
return chalk[color](context.fn(this));
});
});
}
module.exports = colors;

View File

@@ -1,64 +0,0 @@
var fs = require('./fs');
var path = require('path');
var Q = require('q');
var mkdirp = require('mkdirp');
var createError = require('./createError');
var isWin = process.platform === 'win32';
function createLink(src, dst, type) {
var dstDir = path.dirname(dst);
// Create directory
return (
Q.nfcall(mkdirp, dstDir)
// Check if source exists
.then(function() {
return Q.nfcall(fs.stat, src).fail(function(error) {
if (error.code === 'ENOENT') {
throw createError(
'Failed to create link to ' + path.basename(src),
'ENOENT',
{
details:
src +
' does not exist or points to a non-existent file'
}
);
}
throw error;
});
})
// Create symlink
.then(function(stat) {
type = type || (stat.isDirectory() ? 'dir' : 'file');
return Q.nfcall(fs.symlink, src, dst, type).fail(function(err) {
if (!isWin || err.code !== 'EPERM') {
throw err;
}
// Try with type "junction" on Windows
// Junctions behave equally to true symlinks and can be created in
// non elevated terminal (well, not always..)
return Q.nfcall(fs.symlink, src, dst, 'junction').fail(
function(err) {
throw createError(
'Unable to create link to ' +
path.basename(src),
err.code,
{
details:
err.message.trim() +
'\n\nTry running this command in an elevated terminal (run as root/administrator).'
}
);
}
);
});
})
);
}
module.exports = createLink;

View File

@@ -1,153 +0,0 @@
var progress = require('request-progress');
var request = require('request');
var Q = require('q');
var mout = require('mout');
var retry = require('retry');
var createError = require('./createError');
var createWriteStream = require('fs-write-stream-atomic');
var destroy = require('destroy');
var errorCodes = [
'EADDRINFO',
'ETIMEDOUT',
'ECONNRESET',
'ESOCKETTIMEDOUT',
'ENOTFOUND'
];
function download(url, file, options) {
var operation;
var deferred = Q.defer();
var progressDelay = 8000;
options = mout.object.mixIn(
{
retries: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 35000,
randomize: true,
progressDelay: progressDelay,
gzip: true
},
options || {}
);
// Retry on network errors
operation = retry.operation(options);
operation.attempt(function() {
Q.fcall(fetch, url, file, options)
.then(function(response) {
deferred.resolve(response);
})
.progress(function(status) {
deferred.notify(status);
})
.fail(function(error) {
// Save timeout before retrying to report
var timeout = operation._timeouts[0];
// Reject if error is not a network error
if (errorCodes.indexOf(error.code) === -1) {
return deferred.reject(error);
}
// Next attempt will start reporting download progress immediately
progressDelay = 0;
// This will schedule next retry or return false
if (operation.retry(error)) {
deferred.notify({
retry: true,
delay: timeout,
error: error
});
} else {
deferred.reject(error);
}
});
});
return deferred.promise;
}
function fetch(url, file, options) {
var deferred = Q.defer();
var contentLength;
var bytesDownloaded = 0;
var reject = function(error) {
deferred.reject(error);
};
var req = progress(request(url, options), {
delay: options.progressDelay
})
.on('response', function(response) {
contentLength = Number(response.headers['content-length']);
var status = response.statusCode;
if (status < 200 || status >= 300) {
return deferred.reject(
createError('Status code of ' + status, 'EHTTP')
);
}
var writeStream = createWriteStream(file);
var errored = false;
// Change error listener so it cleans up writeStream before exiting
req.removeListener('error', reject);
req.on('error', function(error) {
errored = true;
destroy(req);
destroy(writeStream);
// Wait for writeStream to cleanup after itself...
// TODO: Maybe there's a better way?
setTimeout(function() {
deferred.reject(error);
}, 50);
});
writeStream.on('finish', function() {
if (!errored) {
destroy(req);
deferred.resolve(response);
}
});
req.pipe(writeStream);
})
.on('data', function(data) {
bytesDownloaded += data.length;
})
.on('progress', function(state) {
deferred.notify(state);
})
.on('error', reject)
.on('end', function() {
// Check if the whole file was downloaded
// In some unstable connections the ACK/FIN packet might be sent in the
// middle of the download
// See: https://github.com/joyent/node/issues/6143
if (contentLength && bytesDownloaded < contentLength) {
req.emit(
'error',
createError(
'Transfer closed with ' +
(contentLength - bytesDownloaded) +
' bytes remaining to read',
'EINCOMPLETE'
)
);
}
});
return deferred.promise;
}
module.exports = download;

View File

@@ -1,278 +0,0 @@
var path = require('path');
var fs = require('./fs');
var zlib = require('zlib');
var DecompressZip = require('decompress-zip');
var tar = require('tar-fs');
var Q = require('q');
var mout = require('mout');
var junk = require('junk');
var createError = require('./createError');
var createWriteStream = require('fs-write-stream-atomic');
var destroy = require('destroy');
var tmp = require('tmp');
// This forces the default chunk size to something small in an attempt
// to avoid issue #314
zlib.Z_DEFAULT_CHUNK = 1024 * 8;
var extractors;
var extractorTypes;
extractors = {
'.zip': extractZip,
'.tar': extractTar,
'.tar.gz': extractTarGz,
'.tgz': extractTarGz,
'.gz': extractGz,
'application/zip': extractZip,
'application/x-zip': extractZip,
'application/x-zip-compressed': extractZip,
'application/x-tar': extractTar,
'application/x-tgz': extractTarGz,
'application/x-gzip': extractGz
};
extractorTypes = Object.keys(extractors);
function extractZip(archive, dst) {
var deferred = Q.defer();
new DecompressZip(archive)
.on('error', deferred.reject)
.on('extract', deferred.resolve.bind(deferred, dst))
.extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
});
return deferred.promise;
}
function extractTar(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(
tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
})
)
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
function extractTarGz(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', reject)
.pipe(
tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
})
)
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
function extractGz(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', reject)
.pipe(createWriteStream(dst))
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
return deferred.promise;
}
function isSymlink(entry) {
return entry.type === 'SymbolicLink';
}
function filterSymlinks(entry) {
return entry.type !== 'SymbolicLink';
}
function getExtractor(archive) {
// Make the archive lower case to match against the types
// This ensures that upper-cased extensions work
archive = archive.toLowerCase();
var type = mout.array.find(extractorTypes, function(type) {
return mout.string.endsWith(archive, type);
});
return type ? extractors[type] : null;
}
function isSingleDir(dir) {
return Q.nfcall(fs.readdir, dir).then(function(files) {
var singleDir;
// Remove any OS specific files from the files array
// before checking its length
files = files.filter(junk.isnt);
if (files.length !== 1) {
return false;
}
singleDir = path.join(dir, files[0]);
return Q.nfcall(fs.stat, singleDir).then(function(stat) {
return stat.isDirectory() ? singleDir : false;
});
});
}
function moveDirectory(srcDir, destDir) {
return Q.nfcall(fs.readdir, srcDir)
.then(function(files) {
var promises = files.map(function(file) {
var src = path.join(srcDir, file);
var dst = path.join(destDir, file);
return Q.nfcall(fs.rename, src, dst);
});
return Q.all(promises);
})
.then(function() {
return Q.nfcall(fs.rmdir, srcDir);
});
}
// -----------------------------
function canExtract(src, mimeType) {
if (mimeType && mimeType !== 'application/octet-stream') {
return !!getExtractor(mimeType);
}
return !!getExtractor(src);
}
// Available options:
// - keepArchive: true to keep the archive afterwards (defaults to false)
// - keepStructure: true to keep the extracted structure unchanged (defaults to false)
function extract(src, dst, opts) {
var extractor;
var promise;
opts = opts || {};
extractor = getExtractor(src);
// Try to get extractor from mime type
if (!extractor && opts.mimeType) {
extractor = getExtractor(opts.mimeType);
}
// If extractor is null, then the archive type is unknown
if (!extractor) {
return Q.reject(
createError(
'File ' + src + ' is not a known archive',
'ENOTARCHIVE'
)
);
}
// Extract to a temporary directory in case of file name clashes
return Q.nfcall(tmp.dir, {
template: dst + '-XXXXXX',
mode: 0777 & ~process.umask()
})
.then(function(tempDir) {
// nfcall may return multiple callback arguments as an array
return Array.isArray(tempDir) ? tempDir[0] : tempDir;
})
.then(function(tempDir) {
// Check archive file size
promise = Q.nfcall(fs.stat, src).then(function(stat) {
if (stat.size <= 8) {
throw createError(
'File ' + src + ' is an invalid archive',
'ENOTARCHIVE'
);
}
// Extract archive
return extractor(src, tempDir);
});
// Remove archive
if (!opts.keepArchive) {
promise = promise.then(function() {
return Q.nfcall(fs.unlink, src);
});
}
// Move contents from the temporary directory
// If the contents are a single directory (and we're not preserving structure),
// move its contents directly instead.
promise = promise
.then(function() {
return isSingleDir(tempDir);
})
.then(function(singleDir) {
if (singleDir && !opts.keepStructure) {
return moveDirectory(singleDir, dst);
} else {
return moveDirectory(tempDir, dst);
}
});
// Resolve promise to the dst dir
return promise.then(function() {
return dst;
});
});
}
module.exports = extract;
module.exports.canExtract = canExtract;

View File

@@ -1,55 +0,0 @@
var path = require('path');
var bowerJson = require('bower-json');
var Q = require('q');
// The valid options are the same as bower-json#readFile.
// If the "assume" option is passed, it will be used if no json file was found
// This promise is resolved with [json, deprecated, assumed]
// - json: The read json
// - deprecated: The deprecated filename being used or false otherwise
// - assumed: True if a dummy json was returned if no json file was found, false otherwise
function readJson(file, options) {
options = options || {};
// Read
return Q.nfcall(bowerJson.read, file, options).spread(
function(json, jsonFile) {
var deprecated;
if (options.logger) {
var issues = bowerJson.getIssues(json);
if (issues.warnings.length > 0) {
options.logger.warn('invalid-meta', 'for:' + jsonFile);
}
issues.warnings.forEach(function(warning) {
options.logger.warn('invalid-meta', warning);
});
}
jsonFile = path.basename(jsonFile);
deprecated = jsonFile === 'component.json' ? jsonFile : false;
return [json, deprecated, false];
},
function(err) {
// No json file was found, assume one
if (err.code === 'ENOENT' && options.assume) {
return [bowerJson.parse(options.assume, options), false, true];
}
err.details = err.message;
if (err.file) {
err.message = 'Failed to read ' + err.file;
err.data = { filename: err.file };
} else {
err.message = 'Failed to read json from ' + file;
}
throw err;
}
);
}
module.exports = readJson;

View File

@@ -1,12 +0,0 @@
var version = require('../version');
module.exports =
'node/' +
process.version +
' ' +
process.platform +
' ' +
process.arch +
' ' +
';Bower ' +
version;

View File

@@ -1,22 +0,0 @@
var Q = require('q');
var fs = require('./fs');
function validLink(file) {
// Ensures that a file is a symlink that points
// to a valid file
return Q.nfcall(fs.lstat, file)
.then(function(lstat) {
if (!lstat.isSymbolicLink()) {
return [false];
}
return Q.nfcall(fs.stat, file).then(function(stat) {
return [stat];
});
})
.fail(function(err) {
return [false, err];
});
}
module.exports = validLink;

View File

@@ -1,111 +1,10 @@
{
"name": "bower",
"version": "1.8.3",
"description": "The browser package manager",
"author": "Twitter",
"license": "MIT",
"repository": "bower/bower",
"main": "lib",
"bin": "bin/bower",
"homepage": "http://bower.io",
"engines": {
"node": ">=0.10.0"
},
"keywords": [
"bower"
],
"dependencies": {
"abbrev": "^1.0.5",
"archy": "1.0.0",
"bower-config": "^1.4.1",
"bower-endpoint-parser": "^0.2.2",
"bower-json": "^0.8.1",
"bower-logger": "^0.2.2",
"bower-registry-client": "^1.0.0",
"cardinal": "0.4.4",
"chalk": "^1.0.0",
"chmodr": "^1.0.2",
"configstore": "^2.0.0",
"decompress-zip": "^0.2.1",
"destroy": "^1.0.3",
"findup-sync": "^0.3.0",
"fs-write-stream-atomic": "1.0.8",
"fstream": "^1.0.3",
"fstream-ignore": "^1.0.2",
"github": "^0.2.3",
"glob": "^4.3.2",
"graceful-fs": "^4.1.3",
"handlebars": "^4.0.5",
"inquirer": "0.10.0",
"is-root": "^1.0.0",
"junk": "^1.0.0",
"lockfile": "^1.0.0",
"lru-cache": "^2.5.0",
"md5-hex": "^1.0.2",
"mkdirp": "0.5.0",
"mout": "^0.11.0",
"nopt": "^3.0.1",
"opn": "^4.0.0",
"p-throttler": "0.1.1",
"promptly": "0.2.0",
"q": "^1.1.2",
"request": "2.67.0",
"request-progress": "0.3.1",
"requireg": "^0.1.5",
"resolve": "^1.1.7",
"retry": "0.6.1",
"rimraf": "^2.2.8",
"semver": "^2.3.0",
"semver-utils": "^1.1.1",
"shell-quote": "^1.4.2",
"stringify-object": "^1.0.0",
"tar-fs": "^1.4.1",
"tmp": "0.0.28",
"update-notifier": "^0.6.0",
"user-home": "^1.1.0",
"which": "^1.0.8"
},
"private": true,
"devDependencies": {
"arr-diff": "^2.0.0",
"chai": "^3.5.0",
"coveralls": "^2.11.9",
"expect.js": "^0.3.1",
"grunt": "^1.0.1",
"grunt-cli": "^1.1.0",
"grunt-contrib-watch": "^1.0.0",
"grunt-eslint": "^18.1.0",
"grunt-exec": "^0.4.7",
"grunt-simple-mocha": "^0.4.1",
"husky": "^0.14.3",
"in-publish": "^2.0.0",
"istanbul": "^0.4.3",
"lint-staged": "^7.0.0",
"load-grunt-tasks": "^3.5.0",
"mocha": "^2.5.3",
"multiline": "^1.0.2",
"nock": "^9.2.3",
"node-uuid": "^1.4.7",
"prettier": "^1.11.1",
"proxyquire": "^1.7.9",
"spawn-sync": "1.0.15",
"wrench": "^1.5.8"
"lerna": "2.0.0-beta.23"
},
"scripts": {
"test": "grunt test",
"ci": "grunt travis",
"coveralls": "coveralls",
"prepublish": "in-publish && echo 'You need to use \"grunt publish\" to publish bower' && false || not-in-publish",
"format": "prettier --write --single-quote --tab-width 4 '**/*.js'",
"precommit": "lint-staged"
},
"lint-staged": {
"*.js": [
"prettier --single-quote --tab-width 4",
"git add"
]
},
"files": [
"bin",
"lib"
]
"postinstall": "lerna bootstrap",
"test": "lerna run test"
}
}

View File

@@ -1,21 +1,8 @@
module.exports = function(grunt) {
'use strict';
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
grunt.initConfig({
jshint: {
options: {
jshintrc: '.jshintrc'
},
files: [
'Gruntfile.js',
'bin/*',
'lib/**/*.js',
'test/**/*.js',
'!test/assets/**/*',
'!test/reports/**/*',
'!test/tmp/**/*'
]
},
simplemocha: {
options: {
reporter: 'spec',
@@ -33,22 +20,28 @@ module.exports = function(grunt) {
},
exec: {
cover: {
command:
'STRICT_REQUIRE=1 node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- --timeout 30000 -R dot test/test.js'
command: 'STRICT_REQUIRE=1 node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- --timeout 30000 -R dot test/test.js'
},
coveralls: {
command:
'node node_modules/.bin/coveralls < test/reports/lcov.info'
command: 'node node_modules/.bin/coveralls < test/reports/lcov.info'
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'simplemocha:short']
files: [
'Gruntfile.js',
'bin/*',
'lib/**/*.js',
'test/**/*.js',
'!test/assets/**/*',
'!test/reports/**/*',
'!test/tmp/**/*'
],
tasks: ['simplemocha:short']
}
});
grunt.registerTask('test', ['jshint', 'simplemocha:full']);
grunt.registerTask('test', ['simplemocha:full']);
grunt.registerTask('cover', 'exec:cover');
grunt.registerTask('travis', ['jshint', 'exec:cover', 'exec:coveralls']);
grunt.registerTask('travis', ['exec:cover', 'exec:coveralls']);
grunt.registerTask('default', 'test');
};

View File

@@ -10,7 +10,7 @@
"registry": {
"search": [
"http://localhost:8000",
"https://registry.bower.io"
"https://bower.herokuapp.com"
]
}
}

View File

@@ -12,12 +12,12 @@ function Config(cwd) {
this._config = {};
}
Config.prototype.load = function(overwrites) {
Config.prototype.load = function (overwrites) {
this._config = rc('bower', this._cwd);
this._config = object.merge(
expand(this._config || {}),
expand(overwrites || {})
expand(this._config || {}),
expand(overwrites || {})
);
this._config = normalise(this._config);
@@ -27,8 +27,8 @@ Config.prototype.load = function(overwrites) {
return this;
};
Config.prototype.restore = function() {
this._proxy.restore();
Config.prototype.restore = function () {
this._proxy.restore();
};
function readCertFile(path) {
@@ -44,14 +44,10 @@ function readCertFile(path) {
certificates = path;
}
return certificates
.split(sep)
.filter(function(s) {
return !s.match(/^\s*$/);
})
.map(function(s) {
return s + sep;
});
return certificates.
split(sep).
filter(function(s) { return !s.match(/^\s*$/); }).
map(function(s) { return s + sep; });
}
function loadCAs(caConfig) {
@@ -72,15 +68,15 @@ function loadCAs(caConfig) {
}
}
Config.prototype.toObject = function() {
Config.prototype.toObject = function () {
return lang.deepClone(this._config);
};
Config.create = function(cwd) {
Config.create = function (cwd) {
return new Config(cwd);
};
Config.read = function(cwd, overrides) {
Config.read = function (cwd, overrides) {
var config = Config.create(cwd);
return config.load(overrides).toObject();
};
@@ -90,13 +86,13 @@ function normalise(config) {
// Some backwards compatible things..
if (config.shorthandResolver) {
config.shorthandResolver = config.shorthandResolver
.replace(/\{\{\{/g, '{{')
.replace(/\}\}\}/g, '}}');
config.shorthandResolver = config.shorthandResolver
.replace(/\{\{\{/g, '{{')
.replace(/\}\}\}/g, '}}');
}
// Ensure that every registry endpoint does not end with /
config.registry.search = config.registry.search.map(function(url) {
config.registry.search = config.registry.search.map(function (url) {
return url.replace(/\/+$/, '');
});
config.registry.register = config.registry.register.replace(/\/+$/, '');

View File

@@ -2,44 +2,43 @@ var path = require('path');
var paths = require('./paths');
// Guess proxy defined in the env
var proxy = process.env.HTTP_PROXY || process.env.http_proxy || null;
var proxy = process.env.HTTP_PROXY
|| process.env.http_proxy
|| null;
var httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy || proxy;
var httpsProxy = process.env.HTTPS_PROXY
|| process.env.https_proxy
|| proxy;
var noProxy = process.env.NO_PROXY || process.env.no_proxy;
var noProxy = process.env.NO_PROXY
|| process.env.no_proxy;
// Use a well known user agent (in this case, curl) when using a proxy,
// to avoid potential filtering on many corporate proxies with blank or unknown agents
var userAgent =
!proxy && !httpsProxy
? 'node/' +
process.version +
' ' +
process.platform +
' ' +
process.arch
: 'curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5';
var userAgent = !proxy && !httpsProxy
? 'node/' + process.version + ' ' + process.platform + ' ' + process.arch
: 'curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5';
var defaults = {
directory: 'bower_components',
registry: 'https://registry.bower.io',
'directory': 'bower_components',
'registry': 'https://bower.herokuapp.com',
'shorthand-resolver': 'https://github.com/{{owner}}/{{package}}.git',
tmp: paths.tmp,
proxy: proxy,
'tmp': paths.tmp,
'proxy': proxy,
'https-proxy': httpsProxy,
'no-proxy': noProxy,
timeout: 30000,
ca: { search: [] },
'timeout': 30000,
'ca': { search: [] },
'strict-ssl': true,
'user-agent': userAgent,
color: true,
interactive: null,
storage: {
'color': true,
'interactive': null,
'storage': {
packages: path.join(paths.cache, 'packages'),
links: path.join(paths.data, 'links'),
completion: path.join(paths.data, 'completion'),
registry: path.join(paths.cache, 'registry'),
empty: path.join(paths.data, 'empty') // Empty dir, used in GIT_TEMPLATE_DIR among others
empty: path.join(paths.data, 'empty') // Empty dir, used in GIT_TEMPLATE_DIR among others
}
};

View File

@@ -6,7 +6,7 @@ function camelCase(config) {
var camelCased = {};
// Camel case
object.forOwn(config, function(value, key) {
object.forOwn(config, function (value, key) {
// Ignore null values
if (value == null) {
return;
@@ -22,33 +22,30 @@ function camelCase(config) {
// Function to replace ${VAR} - style variables
// with values set in the environment
// This function expects to be passed a string
function doEnvReplaceStr(f) {
// Un-tildify
var untildify = require('untildify');
f = untildify(f);
function doEnvReplaceStr (f) {
// replace any ${ENV} values with the appropriate environ.
var envExpr = /(\\*)\$\{([^}]+)\}/g;
return f.replace(envExpr, function(orig, esc, name) {
esc = esc.length && esc.length % 2;
if (esc) return orig;
if (undefined === process.env[name]) {
throw new Error(
'Environment variable used in .bowerrc is not defined: ' + orig
);
}
// Un-tildify
var untildify = require('untildify');
f = untildify(f);
return process.env[name];
});
// replace any ${ENV} values with the appropriate environ.
var envExpr = /(\\*)\$\{([^}]+)\}/g;
return f.replace(envExpr, function (orig, esc, name) {
esc = esc.length && esc.length % 2;
if (esc) return orig;
if (undefined === process.env[name]) {
throw new Error('Environment variable used in .bowerrc is not defined: ' + orig);
}
return process.env[name];
});
}
function envReplace(config) {
var envReplaced = {};
if (lang.isArray(config)) {
envReplaced = [];
}
object.forOwn(config, function(value, key) {
object.forOwn(config, function (value, key) {
// Ignore null values
if (value == null) {
return;
@@ -57,19 +54,19 @@ function envReplace(config) {
// Ignore 'scripts'
// These hooks run within the shell
// environment variable expansion is not required
if (key === 'scripts' && lang.isPlainObject(value)) {
if ( key === 'scripts' && lang.isPlainObject(value) ){
envReplaced[key] = value;
return;
}
// Perform variable replacements based on var type
if (lang.isPlainObject(value)) {
if ( lang.isPlainObject(value) ) {
envReplaced[key] = envReplace(value);
} else if (lang.isArray(value)) {
envReplaced[key] = envReplace(value);
} else if (lang.isString(value)) {
}
else if ( lang.isString(value) ) {
envReplaced[key] = doEnvReplaceStr(value);
} else {
}
else {
envReplaced[key] = value;
}
});
@@ -89,8 +86,7 @@ function expand(config) {
publish: config.registry
};
} else if (typeof config.registry === 'object') {
config.registry.default =
config.registry.default || 'https://registry.bower.io';
config.registry.default = config.registry.default || 'https://bower.herokuapp.com';
config.registry = {
default: config.registry.default,

View File

@@ -4,16 +4,8 @@ var osenv = require('osenv');
var crypto = require('crypto');
function generateFakeUser() {
var uid =
process.pid +
'-' +
Date.now() +
'-' +
Math.floor(Math.random() * 1000000);
return crypto
.createHash('md5')
.update(uid)
.digest('hex');
var uid = process.pid + '-' + Date.now() + '-' + Math.floor(Math.random() * 1000000);
return crypto.createHash('md5').update(uid).digest('hex');
}
// Assume XDG defaults
@@ -38,7 +30,7 @@ if (process.platform === 'win32') {
paths.config = paths.config || path.join(base, 'config');
paths.data = paths.data || path.join(base, 'data');
paths.cache = paths.cache || path.join(base, 'cache');
// Fallbacks for other operating systems
// Fallbacks for other operating systems
} else {
base = path.resolve(home || tmp);

View File

@@ -2,78 +2,78 @@
// process.env uppercase proxy variables to them with the ability
// to restore the original values later
var EnvProxy = function() {
this.restoreFrom = {};
this.restoreFrom = {};
};
EnvProxy.prototype.set = function(config) {
this.config = config;
EnvProxy.prototype.set = function (config) {
this.config = config;
// Override environment defaults if proxy config options are set
// This will make requests.js follow the proxies in config
if (Object.prototype.hasOwnProperty.call(config, 'noProxy')) {
this.restoreFrom.NO_PROXY = process.env.NO_PROXY;
this.restoreFrom.no_proxy = process.env.no_proxy;
delete process.env.no_proxy;
process.env.NO_PROXY = config.noProxy;
}
// Override environment defaults if proxy config options are set
// This will make requests.js follow the proxies in config
if (Object.prototype.hasOwnProperty.call(config, 'noProxy')) {
this.restoreFrom.NO_PROXY = process.env.NO_PROXY;
this.restoreFrom.no_proxy = process.env.no_proxy;
delete process.env.no_proxy;
process.env.NO_PROXY = config.noProxy;
}
if (Object.prototype.hasOwnProperty.call(config, 'proxy')) {
this.restoreFrom.HTTP_PROXY = process.env.HTTP_PROXY;
this.restoreFrom.http_proxy = process.env.http_proxy;
delete process.env.http_proxy;
process.env.HTTP_PROXY = config.proxy;
}
if (Object.prototype.hasOwnProperty.call(config, 'proxy')) {
this.restoreFrom.HTTP_PROXY = process.env.HTTP_PROXY;
this.restoreFrom.http_proxy = process.env.http_proxy;
delete process.env.http_proxy;
process.env.HTTP_PROXY = config.proxy;
}
if (Object.prototype.hasOwnProperty.call(config, 'httpsProxy')) {
this.restoreFrom.HTTPS_PROXY = process.env.HTTPS_PROXY;
this.restoreFrom.https_proxy = process.env.https_proxy;
delete process.env.https_proxy;
process.env.HTTPS_PROXY = config.httpsProxy;
}
if (Object.prototype.hasOwnProperty.call(config, 'httpsProxy')) {
this.restoreFrom.HTTPS_PROXY = process.env.HTTPS_PROXY;
this.restoreFrom.https_proxy = process.env.https_proxy;
delete process.env.https_proxy;
process.env.HTTPS_PROXY = config.httpsProxy;
}
};
EnvProxy.prototype.restore = function() {
if (Object.prototype.hasOwnProperty.call(this.config, 'noProxy')) {
if (this.restoreFrom.NO_PROXY !== undefined) {
process.env.NO_PROXY = this.restoreFrom.NO_PROXY;
} else {
delete process.env.NO_PROXY;
}
if (this.restoreFrom.no_proxy !== undefined) {
process.env.no_proxy = this.restoreFrom.no_proxy;
} else {
delete process.env.no_proxy;
}
EnvProxy.prototype.restore = function () {
if (Object.prototype.hasOwnProperty.call(this.config, 'noProxy')) {
if (this.restoreFrom.NO_PROXY !== undefined) {
process.env.NO_PROXY = this.restoreFrom.NO_PROXY;
} else {
delete process.env.NO_PROXY;
}
if (Object.prototype.hasOwnProperty.call(this.config, 'proxy')) {
if (this.restoreFrom.HTTP_PROXY !== undefined) {
process.env.HTTP_PROXY = this.restoreFrom.HTTP_PROXY;
} else {
delete process.env.HTTP_PROXY;
}
if (this.restoreFrom.no_proxy !== undefined) {
process.env.no_proxy = this.restoreFrom.no_proxy;
} else {
delete process.env.no_proxy;
}
}
if (this.restoreFrom.http_proxy !== undefined) {
process.env.http_proxy = this.restoreFrom.http_proxy;
} else {
delete process.env.http_proxy;
}
if (Object.prototype.hasOwnProperty.call(this.config, 'proxy')) {
if (this.restoreFrom.HTTP_PROXY !== undefined) {
process.env.HTTP_PROXY = this.restoreFrom.HTTP_PROXY;
} else {
delete process.env.HTTP_PROXY;
}
if (Object.prototype.hasOwnProperty.call(this.config, 'httpsProxy')) {
if (this.restoreFrom.HTTPS_PROXY !== undefined) {
process.env.HTTPS_PROXY = this.restoreFrom.HTTPS_PROXY;
} else {
delete process.env.HTTPS_PROXY;
}
if (this.restoreFrom.https_proxy !== undefined) {
process.env.https_proxy = this.restoreFrom.https_proxy;
} else {
delete process.env.https_proxy;
}
if (this.restoreFrom.http_proxy !== undefined) {
process.env.http_proxy = this.restoreFrom.http_proxy;
} else {
delete process.env.http_proxy;
}
}
if (Object.prototype.hasOwnProperty.call(this.config, 'httpsProxy')) {
if (this.restoreFrom.HTTPS_PROXY !== undefined) {
process.env.HTTPS_PROXY = this.restoreFrom.HTTPS_PROXY;
} else {
delete process.env.HTTPS_PROXY;
}
if (this.restoreFrom.https_proxy !== undefined) {
process.env.https_proxy = this.restoreFrom.https_proxy;
} else {
delete process.env.https_proxy;
}
}
};
module.exports = EnvProxy;

View File

@@ -16,7 +16,7 @@ function rc(name, cwd, argv) {
argv = argv || optimist.argv;
// Parse --config.foo=false
argvConfig = object.map(argv.config || {}, function(value) {
argvConfig = object.map(argv.config || {}, function (value) {
return value === 'false' ? false : value;
});
@@ -106,14 +106,14 @@ function env(prefix) {
prefix = prefix.toLowerCase();
object.forOwn(process.env, function(value, key) {
object.forOwn(process.env, function (value, key) {
key = key.toLowerCase();
if (string.startsWith(key, prefix)) {
var parsedKey = key
.substr(prefixLength)
.replace(/__/g, '.') // __ is used for nesting
.replace(/_/g, '-'); // _ is used as a - separator
.substr(prefixLength)
.replace(/__/g, '.') // __ is used for nesting
.replace(/_/g, '-'); // _ is used as a - separator
//use a convention patern to accept array from process.env
//e.g. export bower_registry__search='["http://abc.com","http://def.com"]'
@@ -122,9 +122,10 @@ function env(prefix) {
if (!match || match.length === 0) {
targetValue = value;
} else {
targetValue = match[1].split(',').map(function(m) {
return m.trim();
});
targetValue = match[1].split(',')
.map(function(m) {
return m.trim();
});
}
object.set(obj, parsedKey, targetValue);
}
@@ -136,7 +137,7 @@ function env(prefix) {
function find(filename, dir) {
var files = [];
var walk = function(filename, dir) {
var walk = function (filename, dir) {
var file = path.join(dir, filename);
var parent = path.dirname(dir);

View File

@@ -4,7 +4,7 @@
"description": "The Bower config reader and writer.",
"author": "Twitter",
"license": "MIT",
"repository": "https://github.com/bower/bower/tree/master/packages/bower-config",
"repository": "bower/config",
"main": "lib/Config",
"homepage": "http://bower.io",
"engines": {

View File

@@ -4,12 +4,7 @@
},
"storage" : {
"packages" : "${_BOWERRC_MY_PACKAGES}",
"registry" : {
"register": "~/.bower-test/registry",
"search": [
"${_BOWERRC_MY_USER}:${_BOWERRC_MY_PASS}"
]
}
"registry" : "~/.bower-test/registry"
},
"tmp" : "${_BOWERRC_MY_TMP}"
}

View File

@@ -17,17 +17,17 @@ var tmpLocation = path.join(
uuid.v4().slice(0, 8)
);
after(function() {
after(function () {
rimraf.sync(tmpLocation);
});
exports.TempDir = (function() {
function TempDir(defaults) {
function TempDir (defaults) {
this.path = path.join(tmpLocation, uuid.v4());
this.defaults = defaults;
}
TempDir.prototype.create = function(files, defaults) {
TempDir.prototype.create = function (files, defaults) {
var that = this;
defaults = defaults || this.defaults || {};
@@ -42,7 +42,7 @@ exports.TempDir = (function() {
};
if (files) {
object.forOwn(files, function(contents, filepath) {
object.forOwn(files, function (contents, filepath) {
if (typeof contents === 'object') {
contents = JSON.stringify(contents, null, ' ') + '\n';
}
@@ -56,7 +56,7 @@ exports.TempDir = (function() {
return this;
};
TempDir.prototype.prepare = function(files) {
TempDir.prototype.prepare = function (files) {
rimraf.sync(this.path);
mkdirp.sync(this.path);
this.create(files);
@@ -65,7 +65,7 @@ exports.TempDir = (function() {
};
// TODO: Rewrite to synchronous form
TempDir.prototype.prepareGit = function(revisions) {
TempDir.prototype.prepareGit = function (revisions) {
var that = this;
revisions = object.merge(revisions || {}, this.defaults);
@@ -76,56 +76,51 @@ exports.TempDir = (function() {
var promise = new Q();
object.forOwn(revisions, function(files, tag) {
promise = promise
.then(function() {
return that.git('init');
})
.then(function() {
that.glob('./!(.git)').map(function(removePath) {
var fullPath = path.join(that.path, removePath);
object.forOwn(revisions, function (files, tag) {
promise = promise.then(function () {
return that.git('init');
}).then(function () {
that.glob('./!(.git)').map(function (removePath) {
var fullPath = path.join(that.path, removePath);
rimraf.sync(fullPath);
});
that.create(files, {});
})
.then(function() {
return that.git('add', '-A');
})
.then(function() {
return that.git('commit', '-m"commit"');
})
.then(function() {
return that.git('tag', tag);
rimraf.sync(fullPath);
});
that.create(files, {});
}).then(function () {
return that.git('add', '-A');
}).then(function () {
return that.git('commit', '-m"commit"');
}).then(function () {
return that.git('tag', tag);
});
});
return promise;
};
TempDir.prototype.glob = function(pattern) {
TempDir.prototype.glob = function (pattern) {
return glob.sync(pattern, {
cwd: this.path,
dot: true
});
};
TempDir.prototype.getPath = function(name) {
TempDir.prototype.getPath = function (name) {
return path.join(this.path, name);
};
TempDir.prototype.read = function(name) {
TempDir.prototype.read = function (name) {
return fs.readFileSync(this.getPath(name), 'utf8');
};
TempDir.prototype.readJson = function(name) {
TempDir.prototype.readJson = function (name) {
return JSON.parse(this.read(name));
};
TempDir.prototype.exists = function(name) {
TempDir.prototype.exists = function (name) {
return fs.existsSync(path.join(this.path, name));
};
return TempDir;
})();
})();

View File

@@ -1,64 +1,66 @@
var assert = require('assert');
var path = require('path');
describe('NPM Config on package.json', function() {
beforeEach(function() {
describe('NPM Config on package.json', function () {
beforeEach(function () {
delete process.env.npm_package_config_bower_directory;
delete process.env.npm_package_config_bower_colors;
delete process.env.npm_package_config_bower_resolvers;
});
it('defaults registry entries to default registry', function() {
it('defaults registry entries to default registry', function () {
var config = require('../lib/Config').read(null, {});
assert.deepEqual(config.registry, {
default: 'https://registry.bower.io',
search: ['https://registry.bower.io'],
register: 'https://registry.bower.io',
publish: 'https://registry.bower.io'
'default': 'https://bower.herokuapp.com',
'search': [
'https://bower.herokuapp.com'
],
'register': 'https://bower.herokuapp.com',
'publish': 'https://bower.herokuapp.com'
});
});
it('can change default registry', function() {
var config = require('../lib/Config').read(null, {
registry: 'https://foobar'
});
it('can change default registry', function () {
var config = require('../lib/Config').read(null, { registry: 'https://foobar' });
assert.deepEqual(config.registry, {
default: 'https://foobar',
search: ['https://foobar'],
register: 'https://foobar',
publish: 'https://foobar'
'default': 'https://foobar',
'search': [
'https://foobar',
],
'register': 'https://foobar',
'publish': 'https://foobar'
});
});
it('can override single entries in registry configuration', function() {
var config = require('../lib/Config').read(null, {
registry: { search: 'https://foobar' }
});
it('can override single entries in registry configuration', function () {
var config = require('../lib/Config').read(null, { registry: { search: 'https://foobar' } });
assert.deepEqual(config.registry, {
default: 'https://registry.bower.io',
search: ['https://foobar'],
register: 'https://registry.bower.io',
publish: 'https://registry.bower.io'
'default': 'https://bower.herokuapp.com',
'search': [
'https://foobar',
],
'register': 'https://bower.herokuapp.com',
'publish': 'https://bower.herokuapp.com'
});
});
it('can override single entries in registry configuration and defaults', function() {
var config = require('../lib/Config').read(null, {
registry: { default: 'https://fizfuz', search: 'https://foobar' }
});
it('can override single entries in registry configuration and defaults', function () {
var config = require('../lib/Config').read(null, { registry: { default: 'https://fizfuz', search: 'https://foobar' } });
assert.deepEqual(config.registry, {
default: 'https://fizfuz',
search: ['https://foobar'],
register: 'https://fizfuz',
publish: 'https://fizfuz'
'default': 'https://fizfuz',
'search': [
'https://foobar',
],
'register': 'https://fizfuz',
'publish': 'https://fizfuz'
});
});
it('allows for not providing cwd', function() {
it('allows for not providing cwd', function () {
var config = require('../lib/Config').read();
config.tmp = '/foo/bar';
@@ -66,22 +68,24 @@ describe('NPM Config on package.json', function() {
delete config.storage;
assert.deepEqual(config, {
directory: 'bower_components',
registry: {
default: 'https://registry.bower.io',
search: ['https://registry.bower.io'],
register: 'https://registry.bower.io',
publish: 'https://registry.bower.io'
},
shorthandResolver: 'https://github.com/{{owner}}/{{package}}.git',
tmp: '/foo/bar',
timeout: 30000,
ca: {
search: []
},
strictSsl: true,
userAgent: 'firefox',
color: true
'directory': 'bower_components',
'registry': {
'default': 'https://bower.herokuapp.com',
'search': [
'https://bower.herokuapp.com'
],
'register': 'https://bower.herokuapp.com',
'publish': 'https://bower.herokuapp.com'
},
'shorthandResolver': 'https://github.com/{{owner}}/{{package}}.git',
'tmp': '/foo/bar',
'timeout': 30000,
'ca': {
'search': []
},
'strictSsl': true,
'userAgent': 'firefox',
'color': true
});
});
@@ -92,49 +96,41 @@ describe('NPM Config on package.json', function() {
assert(Array.isArray(caData), name + ' should be an array');
assert.equal(2, caData.length);
caData.forEach(function(c, i) {
assert(
c.match(r),
name +
'[' +
i +
'] should contain a certificate. Given: ' +
JSON.stringify(c)
);
assert(c.match(r),
name + '[' + i + '] should contain a certificate. Given: ' + JSON.stringify(c));
});
}
describe('Setting process.env.npm_package_config', function() {
describe('Setting process.env.npm_package_config', function () {
process.env.npm_package_config_bower_directory = 'npm-path';
process.env.npm_package_config_bower_colors = 'false';
process.env.npm_package_config_bower_resolvers = '[foo,bar,baz]';
var config = require('../lib/Config').read();
it('should return "npm-path" for "bower_directory"', function() {
it('should return "npm-path" for "bower_directory"', function () {
assert.equal('npm-path', config.directory);
});
it('should return "false" for "bower_colors"', function() {
it('should return "false" for "bower_colors"', function () {
assert.equal('false', config.colors);
});
it('should expand array "false" for "bower_resolvers"', function() {
it('should expand array "false" for "bower_resolvers"', function () {
assert.deepEqual(['foo', 'bar', 'baz'], config.resolvers);
});
});
describe('Specifying custom CA', function() {
it('should read the CA file', function() {
var config = require('../lib/Config').read(
path.resolve('test/assets/custom-ca')
);
['register', 'publish', 'default'].forEach(function(p) {
it('should read the CA file', function() {
var config = require('../lib/Config')
.read(path.resolve('test/assets/custom-ca'));
['register', 'publish', 'default'].forEach(function (p) {
assertCAContents(config.ca[p], 'config.ca.' + p);
});
assert(
Array.isArray(config.ca.search),
'ca property search should be an array'
);
assert(Array.isArray(config.ca.search),
'ca property search should be an array');
config.ca.search.forEach(function(c, i) {
assertCAContents(c, 'config.ca.search[' + i + ']');
@@ -142,26 +138,23 @@ describe('NPM Config on package.json', function() {
});
it('should backward-support certificate inside .bowerrc', function() {
var config = require('../lib/Config').read(
path.resolve('test/assets/custom-ca-embed')
);
var config = require('../lib/Config')
.read(path.resolve('test/assets/custom-ca-embed'));
['register', 'publish', 'default'].forEach(function(p) {
['register', 'publish', 'default'].forEach(function (p) {
assertCAContents(config.ca[p], 'config.ca.' + p);
});
assert(
Array.isArray(config.ca.search),
'ca property search should be an array'
);
assert(Array.isArray(config.ca.search),
'ca property search should be an array');
config.ca.search.forEach(function(c, i) {
assertCAContents(c, 'config.ca.search[' + i + ']');
});
});
});
describe('setting ENV variables', function() {
beforeEach(function() {
describe('setting ENV variables', function () {
beforeEach(function () {
delete process.env.no_proxy;
delete process.env.http_proxy;
delete process.env.https_proxy;
@@ -170,7 +163,7 @@ describe('NPM Config on package.json', function() {
delete process.env.HTTPS_PROXY;
});
it('sets env variables', function() {
it('sets env variables', function () {
require('../lib/Config').read('test/assets/env-variables');
assert.equal(process.env.HTTP_PROXY, 'http://HTTP_PROXY');
@@ -182,7 +175,7 @@ describe('NPM Config on package.json', function() {
assert.equal(process.env.no_proxy, undefined);
});
it('restores env variables', function() {
it('restores env variables', function () {
process.env.HTTP_PROXY = 'a';
process.env.HTTPS_PROXY = 'b';
process.env.NO_PROXY = 'c';
@@ -190,9 +183,7 @@ describe('NPM Config on package.json', function() {
process.env.https_proxy = 'e';
process.env.no_proxy = 'f';
var config = require('../lib/Config')
.create('test/assets/env-variables')
.load();
var config = require('../lib/Config').create('test/assets/env-variables').load();
config.restore();
assert.equal(process.env.HTTP_PROXY, 'a');
@@ -204,10 +195,8 @@ describe('NPM Config on package.json', function() {
assert.equal(process.env.no_proxy, 'f');
});
it('restores env variables if they are undefined', function() {
var config = require('../lib/Config')
.create('test/assets/env-variables')
.load();
it('restores env variables if they are undefined', function () {
var config = require('../lib/Config').create('test/assets/env-variables').load();
config.restore();
assert.equal(process.env.HTTP_PROXY, undefined);
@@ -219,7 +208,7 @@ describe('NPM Config on package.json', function() {
assert.equal(process.env.no_proxy, undefined);
});
it('allows for overriding options', function() {
it('allows for overriding options', function () {
require('../lib/Config').read('test/assets/env-variables', {
httpsProxy: 'http://other-proxy.local'
});
@@ -231,33 +220,25 @@ describe('NPM Config on package.json', function() {
});
describe('Allow ${ENV} variables in .bowerrc', function() {
it('sets values from process.env', function() {
process.env._BOWERRC_MY_PACKAGES = 'a';
process.env._BOWERRC_MY_TMP = '/tmp/b';
process.env._BOWERRC_MY_USER = 'username';
process.env._BOWERRC_MY_PASS = 'password';
var config = require('../lib/Config').read(
'test/assets/env-variables-values'
);
var config = require('../lib/Config').read('test/assets/env-variables-values');
assert.equal('a', config.storage.packages);
assert.equal('/tmp/b', config.tmp);
assert.equal('username:password', config.storage.registry.search[0]);
assert.equal('${_myshellvar}', config.scripts.postinstall);
});
});
describe('untildify paths in .bowerrc', function() {
it('resolve ~/ in .bowerrc', function() {
var config = require('../lib/Config').read(
'test/assets/env-variables-values'
);
var config = require('../lib/Config').read('test/assets/env-variables-values');
var untildify = require('untildify');
assert.equal(
untildify('~/.bower-test/registry'),
config.storage.registry.register
);
assert.equal(untildify('~/.bower-test/registry') , config.storage.registry);
});
});

View File

@@ -1,3 +1,3 @@
describe('util', function() {
describe('util', function () {
require('./rc');
});
});

View File

@@ -29,6 +29,7 @@ describe('rc', function() {
'.bowerrc/foo': {
key: 'bar'
}
});
it('correctly reads .bowerrc files', function() {
@@ -59,21 +60,22 @@ describe('rc', function() {
expect(config.key2).to.eql(undefined);
});
it('loads the .bowerrc file from the cwd specified on the command line', function() {
it('loads the .bowerrc file from the cwd specified on the command line', function(){
var argv = {
config: {
cwd: tempDir.path + '/other_dir/'
'config': {
'cwd': tempDir.path + '/other_dir/'
}
};
var config = rc('bower', tempDir.path, argv);
expect(config.key).to.eql('othervalue');
});
it('throws an easy to understand error if .bowerrc is a dir', function() {
// Gotta wrap this to catch the error
var config = function() {
var config = function () {
rc('bower', tempDirBowerrc.path);
};

View File

@@ -59,10 +59,10 @@ function json2decomposed(key, value) {
// If # was found, the source was specified
if (split.length > 1) {
endpoint += (split[0] || key) + '#' + split[1];
// Check if value looks like a source
// Check if value looks like a source
} else if (isSource(value)) {
endpoint += value + '#*';
// Otherwise use the key as the source
// Otherwise use the key as the source
} else {
endpoint += key + '#' + split[0];
}
@@ -85,7 +85,7 @@ function decomposed2json(decEndpoint) {
}
// Add source only if different than the name
if (source !== name) {
if (source !== name) {
value += source;
}
@@ -100,7 +100,7 @@ function decomposed2json(decEndpoint) {
value += target;
}
}
// Otherwise append only if not a wildcard or source does not look like a source
// Otherwise append only if not a wildcard or source does not look like a source
} else if (!isWildcard(target) || !isSource(source)) {
value += '#' + (target || '*');
}
@@ -119,7 +119,7 @@ function isWildcard(target) {
}
function isSource(value) {
return /[\/\\@]/.test(value);
return (/[\/\\@]/).test(value);
}
module.exports.decompose = decompose;

View File

@@ -9,7 +9,10 @@
"url": "https://github.com/bower/endpoint-parser/blob/master/LICENSE"
}
],
"repository": "https://github.com/bower/bower/tree/master/packages/bower-endpoint-parser",
"repository": {
"type": "git",
"url": "git://github.com/bower/endpoint-parser.git"
},
"main": "index.js",
"engines": {
"node": ">=0.8.0"

View File

@@ -3,68 +3,29 @@ var lang = require('mout/lang');
var object = require('mout/object');
var endpointParser = require('../');
describe('endpoint-parser', function() {
describe('.decompose', function() {
it('should decompose endpoints correctly', function() {
describe('endpoint-parser', function () {
describe('.decompose', function () {
it('should decompose endpoints correctly', function () {
var suite = {
'jquery#~2.0.0': {
name: '',
source: 'jquery',
target: '~2.0.0'
},
'jquery#~2.0.0': { name: '', source: 'jquery', target: '~2.0.0' },
'jquery#*': { name: '', source: 'jquery', target: '*' },
'jquery#latest': { name: '', source: 'jquery', target: '*' },
'jquery#3dc50c62fe2d2d01afc58e7ad42236a35acff4d8': {
name: '',
source: 'jquery',
target: '3dc50c62fe2d2d01afc58e7ad42236a35acff4d8'
},
'jquery#master': {
name: '',
source: 'jquery',
target: 'master'
},
'backbone=backbone-amd#~1.0.0': {
name: 'backbone',
source: 'backbone-amd',
target: '~1.0.0'
},
'backbone=backbone-amd#latest': {
name: 'backbone',
source: 'backbone-amd',
target: '*'
},
'backbone=backbone-amd#*': {
name: 'backbone',
source: 'backbone-amd',
target: '*'
},
'http://twitter.github.io/bootstrap/assets/bootstrap.zip': {
name: '',
source:
'http://twitter.github.io/bootstrap/assets/bootstrap.zip',
target: '*'
},
'bootstrap=http://twitter.github.io/bootstrap/assets/bootstrap.zip': {
name: 'bootstrap',
source:
'http://twitter.github.io/bootstrap/assets/bootstrap.zip',
target: '*'
},
'bootstrap=http://twitter.github.io/bootstrap/assets/bootstrap.zip#latest': {
name: 'bootstrap',
source:
'http://twitter.github.io/bootstrap/assets/bootstrap.zip',
target: '*'
}
'jquery#3dc50c62fe2d2d01afc58e7ad42236a35acff4d8': { name: '', source: 'jquery', target: '3dc50c62fe2d2d01afc58e7ad42236a35acff4d8' },
'jquery#master': { name: '', source: 'jquery', target: 'master' },
'backbone=backbone-amd#~1.0.0': { name: 'backbone', source: 'backbone-amd', target: '~1.0.0' },
'backbone=backbone-amd#latest': { name: 'backbone', source: 'backbone-amd', target: '*' },
'backbone=backbone-amd#*': { name: 'backbone', source: 'backbone-amd', target: '*' },
'http://twitter.github.io/bootstrap/assets/bootstrap.zip': { name: '', source: 'http://twitter.github.io/bootstrap/assets/bootstrap.zip', target: '*' },
'bootstrap=http://twitter.github.io/bootstrap/assets/bootstrap.zip': { name: 'bootstrap', source: 'http://twitter.github.io/bootstrap/assets/bootstrap.zip', target: '*' },
'bootstrap=http://twitter.github.io/bootstrap/assets/bootstrap.zip#latest': { name: 'bootstrap', source: 'http://twitter.github.io/bootstrap/assets/bootstrap.zip', target: '*' }
};
object.forOwn(suite, function(decEndpoint, endpoint) {
object.forOwn(suite, function (decEndpoint, endpoint) {
expect(endpointParser.decompose(endpoint)).to.eql(decEndpoint);
});
});
it('should trim sources and targets', function() {
it('should trim sources and targets', function () {
var decEndpoint = endpointParser.decompose('foo= source # ~1.0.2 ');
expect(decEndpoint.source).to.equal('source');
expect(decEndpoint.target).to.equal('~1.0.2');
@@ -79,135 +40,77 @@ describe('endpoint-parser', function() {
});
});
describe('.compose', function() {
it('should compose endpoints correctly', function() {
describe('.compose', function () {
it('should compose endpoints correctly', function () {
var suite = {
'jquery#~2.0.0': {
name: '',
source: 'jquery',
target: '~2.0.0'
},
jquery: [
{ name: '', source: 'jquery', target: '*' },
{ name: '', source: 'jquery', target: 'latest' },
{ name: '', source: 'jquery', target: '' }
],
'jquery#3dc50c62fe2d2d01afc58e7ad42236a35acff4d8': {
name: '',
source: 'jquery',
target: '3dc50c62fe2d2d01afc58e7ad42236a35acff4d8'
},
'jquery#master': {
name: '',
source: 'jquery',
target: 'master'
},
'backbone=backbone-amd#~1.0.0': {
name: 'backbone',
source: 'backbone-amd',
target: '~1.0.0'
},
'backbone=backbone-amd': [
{ name: 'backbone', source: 'backbone-amd', target: '*' },
{ name: 'backbone', source: 'backbone-amd', target: '*' },
{ name: 'backbone', source: 'backbone-amd', target: '' }
],
'http://twitter.github.io/bootstrap/assets/bootstrap.zip': {
name: '',
source:
'http://twitter.github.io/bootstrap/assets/bootstrap.zip',
target: '*'
},
'bootstrap=http://twitter.github.io/bootstrap/assets/bootstrap.zip': {
name: 'bootstrap',
source:
'http://twitter.github.io/bootstrap/assets/bootstrap.zip',
target: '*'
}
'jquery#~2.0.0': { name: '', source: 'jquery', target: '~2.0.0' },
'jquery': [{ name: '', source: 'jquery', target: '*' }, { name: '', source: 'jquery', target: 'latest' }, { name: '', source: 'jquery', target: '' }],
'jquery#3dc50c62fe2d2d01afc58e7ad42236a35acff4d8': { name: '', source: 'jquery', target: '3dc50c62fe2d2d01afc58e7ad42236a35acff4d8' },
'jquery#master': { name: '', source: 'jquery', target: 'master' },
'backbone=backbone-amd#~1.0.0': { name: 'backbone', source: 'backbone-amd', target: '~1.0.0' },
'backbone=backbone-amd': [{ name: 'backbone', source: 'backbone-amd', target: '*' }, { name: 'backbone', source: 'backbone-amd', target: '*' }, { name: 'backbone', source: 'backbone-amd', target: '' }],
'http://twitter.github.io/bootstrap/assets/bootstrap.zip': { name: '', source: 'http://twitter.github.io/bootstrap/assets/bootstrap.zip', target: '*' },
'bootstrap=http://twitter.github.io/bootstrap/assets/bootstrap.zip': { name: 'bootstrap', source: 'http://twitter.github.io/bootstrap/assets/bootstrap.zip', target: '*' }
};
object.forOwn(suite, function(decEndpoints, endpoint) {
object.forOwn(suite, function (decEndpoints, endpoint) {
decEndpoints = lang.toArray(decEndpoints);
decEndpoints.forEach(function(decEndpoint) {
expect(endpointParser.compose(decEndpoint)).to.equal(
endpoint
);
decEndpoints.forEach(function (decEndpoint) {
expect(endpointParser.compose(decEndpoint)).to.equal(endpoint);
});
});
});
it('should trim values', function() {
expect(
endpointParser.compose({
name: ' foo ',
source: ' bar ',
target: ' ~1.0.2 '
})
).to.equal('foo=bar#~1.0.2');
it('should trim values', function () {
expect(endpointParser.compose({
name: ' foo ',
source: ' bar ',
target: ' ~1.0.2 '
})).to.equal('foo=bar#~1.0.2');
expect(
endpointParser.compose({
name: ' foo ',
source: ' foo ',
target: ' ~1.0.2 '
})
).to.equal('foo=foo#~1.0.2');
expect(endpointParser.compose({
name: ' foo ',
source: ' foo ',
target: ' ~1.0.2 '
})).to.equal('foo=foo#~1.0.2');
expect(
endpointParser.compose({
name: ' foo ',
source: ' foo ',
target: ' * '
})
).to.equal('foo=foo');
expect(endpointParser.compose({
name: ' foo ',
source: ' foo ',
target: ' * '
})).to.equal('foo=foo');
expect(
endpointParser.compose({
name: ' foo ',
source: ' foo ',
target: ' * '
})
).to.equal('foo=foo');
expect(endpointParser.compose({
name: ' foo ',
source: ' foo ',
target: ' * '
})).to.equal('foo=foo');
expect(
endpointParser.compose({
name: ' ',
source: ' foo ',
target: ''
})
).to.equal('foo');
expect(endpointParser.compose({
name: ' ',
source: ' foo ',
target: ''
})).to.equal('foo');
});
});
describe('.json2decomposed', function() {
describe('.json2decomposed', function () {
var expected = [
{ name: 'jquery', source: 'jquery', target: '~1.9.1' },
{ name: 'foo', source: 'foo', target: '*' },
{ name: 'bar', source: 'bar', target: '*' },
{ name: 'baz', source: 'baz', target: '~0.2.0' },
{ name: 'backbone', source: 'backbone-amd', target: '~1.0.0' },
{
name: 'backbone2',
source: 'backbone=backbone-amd',
target: '~1.0.0'
},
{
name: 'bootstrap',
source: 'http://twitter.github.io/bootstrap/assets/bootstrap',
target: '*'
},
{
name: 'bootstrap2',
source: 'http://twitter.github.io/bootstrap/assets/bootstrap',
target: '*'
},
{ name: 'backbone2', source: 'backbone=backbone-amd', target: '~1.0.0' },
{ name: 'bootstrap', source: 'http://twitter.github.io/bootstrap/assets/bootstrap', target: '*' },
{ name: 'bootstrap2', source: 'http://twitter.github.io/bootstrap/assets/bootstrap', target: '*' },
{ name: 'ssh', source: 'git@example.com', target: '*' },
{ name: 'git', source: 'git://example.com', target: '*' },
{ name: 'path', source: '/foo', target: '*' },
{ name: 'winpath', source: 'c:\\foo', target: '*' }
];
it('should decompose json endpoints correctly', function() {
it('should decompose json endpoints correctly', function () {
var dependencies = {
jquery: '~1.9.1',
foo: 'latest',
@@ -215,10 +118,8 @@ describe('endpoint-parser', function() {
baz: '#~0.2.0',
backbone: 'backbone-amd#~1.0.0',
backbone2: 'backbone=backbone-amd#~1.0.0',
bootstrap:
'http://twitter.github.io/bootstrap/assets/bootstrap',
bootstrap2:
'http://twitter.github.io/bootstrap/assets/bootstrap#*',
bootstrap: 'http://twitter.github.io/bootstrap/assets/bootstrap',
bootstrap2: 'http://twitter.github.io/bootstrap/assets/bootstrap#*',
ssh: 'git@example.com',
git: 'git://example.com',
path: '/foo',
@@ -226,15 +127,13 @@ describe('endpoint-parser', function() {
};
var x = 0;
object.forOwn(dependencies, function(value, key) {
expect(endpointParser.json2decomposed(key, value)).to.eql(
expected[x]
);
object.forOwn(dependencies, function (value, key) {
expect(endpointParser.json2decomposed(key, value)).to.eql(expected[x]);
x += 1;
});
});
it('should trim values', function() {
it('should trim values', function () {
var dependencies = {
' jquery ': ' ~1.9.1 ',
' foo ': ' latest ',
@@ -242,10 +141,8 @@ describe('endpoint-parser', function() {
' baz ': '# ~0.2.0 ',
' backbone ': ' backbone-amd#~1.0.0 ',
' backbone2 ': ' backbone=backbone-amd # ~1.0.0 ',
' bootstrap ':
' http://twitter.github.io/bootstrap/assets/bootstrap',
' bootstrap2 ':
' http://twitter.github.io/bootstrap/assets/bootstrap # *',
' bootstrap ': ' http://twitter.github.io/bootstrap/assets/bootstrap',
' bootstrap2 ': ' http://twitter.github.io/bootstrap/assets/bootstrap # *',
' ssh ': ' git@example.com ',
' git ': ' git://example.com ',
' path ': ' /foo ',
@@ -253,15 +150,13 @@ describe('endpoint-parser', function() {
};
var x = 0;
object.forOwn(dependencies, function(value, key) {
expect(endpointParser.json2decomposed(key, value)).to.eql(
expected[x]
);
object.forOwn(dependencies, function (value, key) {
expect(endpointParser.json2decomposed(key, value)).to.eql(expected[x]);
x += 1;
});
});
it('should error out if key is not specified', function() {
it('should error out if key is not specified', function () {
try {
endpointParser.json2decomposed(null);
throw new Error('Should have failed');
@@ -280,7 +175,7 @@ describe('endpoint-parser', function() {
});
});
describe('.decomposed2json', function() {
describe('.decomposed2json', function () {
var expected = [
{ jquery: '~1.9.1' },
{ foo: '*' },
@@ -290,19 +185,15 @@ describe('endpoint-parser', function() {
{ jqueryy: 'jquery-x#*' },
{ jqueryy: 'jquery-x#*' },
{ backbone: 'backbone-amd#~1.0.0' },
{ backbone: 'backbone=backbone-amd#~1.0.0' },
{
bootstrap: 'http://twitter.github.io/bootstrap/assets/bootstrap'
},
{
bootstrap: 'http://twitter.github.io/bootstrap/assets/bootstrap'
},
{ backbone : 'backbone=backbone-amd#~1.0.0' },
{ bootstrap: 'http://twitter.github.io/bootstrap/assets/bootstrap' },
{ bootstrap: 'http://twitter.github.io/bootstrap/assets/bootstrap' },
{ ssh: 'git@example.com' },
{ git: 'git://example.com' },
{ ckeditor: '#full/4.3.3' }
];
it('should compose endpoints to json correctly', function() {
it('should compose endpoints to json correctly', function () {
var decEndpoints = [
{ name: 'jquery', source: 'jquery', target: '~1.9.1' },
{ name: 'foo', source: 'foo', target: 'latest' },
@@ -312,38 +203,22 @@ describe('endpoint-parser', function() {
{ name: 'jqueryy', source: 'jquery-x', target: '' },
{ name: 'jqueryy', source: 'jquery-x', target: '*' },
{ name: 'backbone', source: 'backbone-amd', target: '~1.0.0' },
{
name: 'backbone',
source: 'backbone=backbone-amd',
target: '~1.0.0'
},
{
name: 'bootstrap',
source:
'http://twitter.github.io/bootstrap/assets/bootstrap',
target: ''
},
{
name: 'bootstrap',
source:
'http://twitter.github.io/bootstrap/assets/bootstrap',
target: '*'
},
{ name: 'backbone', source: 'backbone=backbone-amd', target: '~1.0.0' },
{ name: 'bootstrap', source: 'http://twitter.github.io/bootstrap/assets/bootstrap', target: '' },
{ name: 'bootstrap', source: 'http://twitter.github.io/bootstrap/assets/bootstrap', target: '*' },
{ name: 'ssh', source: 'git@example.com', target: '*' },
{ name: 'git', source: 'git://example.com', target: '*' },
{ name: 'ckeditor', source: 'ckeditor', target: 'full/4.3.3' }
];
var x = 0;
decEndpoints.forEach(function(decEndpoint) {
expect(endpointParser.decomposed2json(decEndpoint)).to.eql(
expected[x]
);
decEndpoints.forEach(function (decEndpoint) {
expect(endpointParser.decomposed2json(decEndpoint)).to.eql(expected[x]);
x += 1;
});
});
it('should trim values', function() {
it('should trim values', function () {
var decEndpoints = [
{ name: ' jquery ', source: ' jquery ', target: ' ~1.9.1 ' },
{ name: 'foo', source: ' foo', target: ' latest ' },
@@ -352,48 +227,24 @@ describe('endpoint-parser', function() {
{ name: ' jqueryx ', source: ' jquery ', target: ' ~1.9.1 ' },
{ name: ' jqueryy ', source: ' jquery-x ', target: ' ' },
{ name: ' jqueryy ', source: ' jquery-x ', target: ' * ' },
{
name: ' backbone ',
source: ' backbone-amd ',
target: ' ~1.0.0 '
},
{
name: ' backbone ',
source: ' backbone=backbone-amd ',
target: ' ~1.0.0 '
},
{
name: ' bootstrap ',
source:
' http://twitter.github.io/bootstrap/assets/bootstrap ',
target: ' '
},
{
name: ' bootstrap ',
source:
' http://twitter.github.io/bootstrap/assets/bootstrap ',
target: ' * '
},
{ name: ' backbone ', source: ' backbone-amd ', target: ' ~1.0.0 ' },
{ name: ' backbone ', source: ' backbone=backbone-amd ', target: ' ~1.0.0 ' },
{ name: ' bootstrap ', source: ' http://twitter.github.io/bootstrap/assets/bootstrap ', target: ' ' },
{ name: ' bootstrap ', source: ' http://twitter.github.io/bootstrap/assets/bootstrap ', target: ' * ' },
{ name: ' ssh ', source: ' git@example.com ', target: ' * ' },
{ name: ' git ', source: ' git://example.com ', target: ' * ' }
];
var x = 0;
decEndpoints.forEach(function(decEndpoint) {
expect(endpointParser.decomposed2json(decEndpoint)).to.eql(
expected[x]
);
decEndpoints.forEach(function (decEndpoint) {
expect(endpointParser.decomposed2json(decEndpoint)).to.eql(expected[x]);
x += 1;
});
});
it('should throw an error if name is empty', function() {
it('should throw an error if name is empty', function () {
try {
endpointParser.decomposed2json({
name: '',
source: 'jquery',
target: '*'
});
endpointParser.decomposed2json({ name: '', source: 'jquery', target: '*' });
throw new Error('Should have failed');
} catch (e) {
expect(e.code).to.equal('EINVEND');
@@ -401,11 +252,7 @@ describe('endpoint-parser', function() {
}
try {
endpointParser.decomposed2json({
name: ' ',
source: 'jquery',
target: '*'
});
endpointParser.decomposed2json({ name: ' ', source: 'jquery', target: '*' });
throw new Error('Should have failed');
} catch (e) {
expect(e.code).to.equal('EINVEND');

View File

@@ -1,7 +1,10 @@
module.exports = function(grunt) {
'use strict';
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
// Project configuration.
grunt.initConfig({
jshint: {
files: [
'Gruntfile.js',
@@ -35,19 +38,19 @@ module.exports = function(grunt) {
exec: {
cover: {
command:
'STRICT_REQUIRE=1 node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- --timeout 30000 -R dot test/test.js'
command: 'STRICT_REQUIRE=1 node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- --timeout 30000 -R dot test/test.js'
},
coveralls: {
command:
'node node_modules/.bin/coveralls < test/reports/lcov.info'
command: 'node node_modules/.bin/coveralls < test/reports/lcov.info'
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'simplemocha:short']
}
});
// Default task.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2012-present Twitter and other contributors
Copyright (c) 2016 Twitter and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -14,14 +14,14 @@ function read(file, options, callback) {
}
// Check if file is a directory
fs.stat(file, function(err, stat) {
fs.stat(file, function (err, stat) {
if (err) {
return callback(err);
}
// It's a directory, so we find the json inside it
if (stat.isDirectory()) {
return find(file, function(err, file) {
return find(file, function (err, file) {
if (err) {
return callback(err);
}
@@ -31,7 +31,7 @@ function read(file, options, callback) {
}
// Otherwise read it
fs.readFile(file, function(err, contents) {
fs.readFile(file, function (err, contents) {
var json;
if (err) {
@@ -99,14 +99,11 @@ function readSync(file, options) {
}
function parse(json, options) {
options = deepExtend(
{
normalize: false,
validate: true,
clone: false
},
options || {}
);
options = deepExtend({
normalize: false,
validate: true,
clone: false
}, options || {});
// Clone
if (options.clone) {
@@ -140,9 +137,7 @@ function getIssues(json) {
errors.push('No "name" property set');
} else {
if (!/^[a-zA-Z0-9_@][a-zA-Z0-9_@\.\- \/]*$/.test(json.name)) {
errors.push(
'Name must be lowercase, can contain digits, dots, dashes, "@" or spaces'
);
errors.push('Name must be lowercase, can contain digits, dots, dashes, "@" or spaces');
}
if (json.name.length > 50) {
@@ -150,9 +145,7 @@ function getIssues(json) {
}
if (!/^[a-z0-9_][a-z0-9_\.\-]*$/.test(json.name)) {
warnings.push(
'The "name" is recommended to be lowercase, can contain digits, dots, dashes'
);
warnings.push('The "name" is recommended to be lowercase, can contain digits, dots, dashes');
}
if (/^[\.-]/.test(json.name)) {
@@ -165,9 +158,7 @@ function getIssues(json) {
}
if (json.description && json.description.length > 140) {
warnings.push(
'The "description" is too long, the limit is 140 characters'
);
warnings.push('The "description" is too long, the limit is 140 characters');
}
if (json.main !== undefined) {
@@ -176,29 +167,21 @@ function getIssues(json) {
main = [main];
}
if (!(main instanceof Array)) {
errors.push(
'The "main" field has to be either an Array or a String'
);
errors.push('The "main" field has to be either an Array or a String');
} else {
var ext2files = {};
main.forEach(function(filename) {
main.forEach(function (filename) {
if (typeof filename !== 'string') {
errors.push('The "main" Array has to contain only Strings');
}
if (/[*]/.test(filename)) {
warnings.push(
'The "main" field cannot contain globs (example: "*.js")'
);
warnings.push('The "main" field cannot contain globs (example: "*.js")');
}
if (/[.]min[.][^/]+$/.test(filename)) {
warnings.push(
'The "main" field cannot contain minified files'
);
warnings.push('The "main" field cannot contain minified files');
}
if (isAsset(filename)) {
warnings.push(
'The "main" field cannot contain font, image, audio, or video files'
);
warnings.push('The "main" field cannot contain font, image, audio, or video files');
}
var ext = path.extname(filename);
if (ext.length >= 2) {
@@ -209,15 +192,10 @@ function getIssues(json) {
files.push(filename);
}
});
Object.keys(ext2files).forEach(function(ext) {
Object.keys(ext2files).forEach(function (ext) {
var files = ext2files[ext];
if (files.length > 1) {
warnings.push(
'The "main" field has to contain only 1 file per filetype; found multiple ' +
ext +
' files: ' +
JSON.stringify(files)
);
warnings.push('The "main" field has to contain only 1 file per filetype; found multiple ' + ext + ' files: ' + JSON.stringify(files));
}
});
}
@@ -256,15 +234,12 @@ function find(folder, files, callback) {
}
if (!files.length) {
err = createError(
'None of ' + possibleJsons.join(', ') + ' were found in ' + folder,
'ENOENT'
);
err = createError('None of ' + possibleJsons.join(', ') + ' were found in ' + folder, 'ENOENT');
return callback(err);
}
file = path.resolve(path.join(folder, files[0]));
fs.exists(file, function(exists) {
fs.exists(file, function (exists) {
if (!exists) {
return find(folder, files.slice(1), callback);
}
@@ -275,7 +250,7 @@ function find(folder, files, callback) {
// If the file is component.json, check it it's a component(1) file
// If it is, we ignore it and keep searching
isComponent(file, function(is) {
isComponent(file, function (is) {
if (is) {
return find(folder, files.slice(1), callback);
}
@@ -294,16 +269,14 @@ function findSync(folder, files) {
}
if (!files.length) {
return createError(
'None of ' + possibleJsons.join(', ') + ' were found in ' + folder,
'ENOENT'
);
return createError('None of ' + possibleJsons.join(', ') + ' were found in ' + folder, 'ENOENT');
}
file = path.resolve(path.join(folder, files[0]));
try {
try{
exists = fs.statSync(file);
} catch (err) {
}
catch (err) {
exists = false;
}
if (exists && exists.isFile()) {

View File

@@ -2,14 +2,10 @@ var extName = require('ext-name');
function isAsset(filename) {
var info = extName(filename);
return (
info &&
info.mime &&
(/^((image)|(audio)|(video)|(font))\//.test(info.mime) ||
/application\/((x[-]font[-])|(font[-]woff(\d?))|(vnd[.]ms[-]fontobject))/.test(
info.mime
))
return info && info.mime && (
/^((image)|(audio)|(video)|(font))\//.test(info.mime) ||
/application\/((x[-]font[-])|(font[-]woff(\d?))|(vnd[.]ms[-]fontobject))/.test(info.mime)
);
}

View File

@@ -3,7 +3,7 @@ var intersect = require('intersect');
// Function to check if a file is a component(1) file
function isComponent(file, callback) {
fs.readFile(file, function(err, contents) {
fs.readFile(file, function (err, contents) {
var json;
var keys;
var common;

View File

@@ -4,7 +4,7 @@
"description": "Read bower.json files with semantics, normalisation, defaults and validation",
"author": "Twitter",
"license": "MIT",
"repository": "https://github.com/bower/bower/tree/master/packages/bower-json",
"repository": "bower/json",
"main": "lib/json",
"engines": {
"node": ">=0.10.0"

View File

@@ -4,142 +4,114 @@ var _s = require('underscore.string');
var bowerJson = require('../lib/json');
var request = require('request');
describe('.find', function() {
it('should find the bower.json file', function(done) {
bowerJson.find(__dirname + '/pkg-bower-json', function(err, file) {
describe('.find', function () {
it('should find the bower.json file', function (done) {
bowerJson.find(__dirname + '/pkg-bower-json', function (err, file) {
if (err) {
return done(err);
}
expect(file).to.equal(
path.resolve(__dirname + '/pkg-bower-json/bower.json')
);
expect(file).to.equal(path.resolve(__dirname + '/pkg-bower-json/bower.json'));
done();
});
});
it('should fallback to the component.json file', function(done) {
bowerJson.find(__dirname + '/pkg-component-json', function(err, file) {
it('should fallback to the component.json file', function (done) {
bowerJson.find(__dirname + '/pkg-component-json', function (err, file) {
if (err) {
return done(err);
}
expect(file).to.equal(
path.resolve(__dirname + '/pkg-component-json/component.json')
);
expect(file).to.equal(path.resolve(__dirname + '/pkg-component-json/component.json'));
done();
});
});
it("should not fallback to the component.json file if it's a component(1) file", function(done) {
bowerJson.find(__dirname + '/pkg-component(1)-json', function(err) {
it('should not fallback to the component.json file if it\'s a component(1) file', function (done) {
bowerJson.find(__dirname + '/pkg-component(1)-json', function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOENT');
expect(err.message).to.equal(
'None of bower.json, component.json, .bower.json were found in ' +
__dirname +
'/pkg-component(1)-json'
);
expect(err.message).to.equal('None of bower.json, component.json, .bower.json were found in ' + __dirname + '/pkg-component(1)-json');
done();
});
});
it('should fallback to the .bower.json file', function(done) {
bowerJson.find(__dirname + '/pkg-dot-bower-json', function(err, file) {
it('should fallback to the .bower.json file', function (done) {
bowerJson.find(__dirname + '/pkg-dot-bower-json', function (err, file) {
if (err) {
return done(err);
}
expect(file).to.equal(
path.resolve(__dirname + '/pkg-dot-bower-json/.bower.json')
);
expect(file).to.equal(path.resolve(__dirname + '/pkg-dot-bower-json/.bower.json'));
done();
});
});
it('should error if no component.json / bower.json / .bower.json is found', function(done) {
bowerJson.find(__dirname, function(err) {
it('should error if no component.json / bower.json / .bower.json is found', function (done) {
bowerJson.find(__dirname, function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOENT');
expect(err.message).to.equal(
'None of bower.json, component.json, .bower.json were found in ' +
__dirname
);
expect(err.message).to.equal('None of bower.json, component.json, .bower.json were found in ' + __dirname);
done();
});
});
});
describe('.findSync', function() {
it('should find the bower.json file', function(done) {
describe('.findSync', function () {
it('should find the bower.json file', function (done) {
var file = bowerJson.findSync(__dirname + '/pkg-bower-json');
expect(file).to.equal(
path.resolve(__dirname + '/pkg-bower-json/bower.json')
);
expect(file).to.equal(path.resolve(__dirname + '/pkg-bower-json/bower.json'));
done();
});
it('should fallback to the component.json file', function(done) {
it('should fallback to the component.json file', function (done) {
var file = bowerJson.findSync(__dirname + '/pkg-component-json');
expect(file).to.equal(
path.resolve(__dirname + '/pkg-component-json/component.json')
);
expect(file).to.equal(path.resolve(__dirname + '/pkg-component-json/component.json'));
done();
});
it('should fallback to the .bower.json file', function(done) {
it('should fallback to the .bower.json file', function (done) {
var file = bowerJson.findSync(__dirname + '/pkg-dot-bower-json');
expect(file).to.equal(
path.resolve(__dirname + '/pkg-dot-bower-json/.bower.json')
);
expect(file).to.equal(path.resolve(__dirname + '/pkg-dot-bower-json/.bower.json'));
done();
});
it('should error if no component.json / bower.json / .bower.json is found', function(done) {
it('should error if no component.json / bower.json / .bower.json is found', function (done) {
var err = bowerJson.findSync(__dirname);
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOENT');
expect(err.message).to.equal(
'None of bower.json, component.json, .bower.json were found in ' +
__dirname
);
expect(err.message).to.equal('None of bower.json, component.json, .bower.json were found in ' + __dirname);
done();
});
});
describe('.read', function() {
it('should give error if file does not exists', function(done) {
bowerJson.read(__dirname + '/willneverexist', function(err) {
describe('.read', function () {
it('should give error if file does not exists', function (done) {
bowerJson.read(__dirname + '/willneverexist', function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOENT');
done();
});
});
it('should give error if when reading an invalid json', function(done) {
bowerJson.read(
__dirname + '/pkg-bower-json-malformed/bower.json',
function(err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('EMALFORMED');
expect(err.file).to.equal(
path.resolve(
__dirname + '/pkg-bower-json-malformed/bower.json'
)
);
done();
}
);
it('should give error if when reading an invalid json', function (done) {
bowerJson.read(__dirname + '/pkg-bower-json-malformed/bower.json', function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.equal('EMALFORMED');
expect(err.file).to.equal(path.resolve(__dirname + '/pkg-bower-json-malformed/bower.json'));
done();
});
});
it('should read the file and give an object', function(done) {
bowerJson.read(__dirname + '/pkg-bower-json/bower.json', function(
err,
json
) {
it('should read the file and give an object', function (done) {
bowerJson.read(__dirname + '/pkg-bower-json/bower.json', function (err, json) {
if (err) {
return done(err);
}
@@ -152,27 +124,20 @@ describe('.read', function() {
});
});
it('should give the json file that was read', function(done) {
bowerJson.read(__dirname + '/pkg-bower-json', function(
err,
json,
file
) {
it('should give the json file that was read', function (done) {
bowerJson.read(__dirname + '/pkg-bower-json', function (err, json, file) {
if (err) {
return done(err);
}
expect(file).to.equal(__dirname + '/pkg-bower-json/bower.json');
done();
});
});
it('should find for a json file if a directory is given', function(done) {
bowerJson.read(__dirname + '/pkg-component-json', function(
err,
json,
file
) {
it('should find for a json file if a directory is given', function (done) {
bowerJson.read(__dirname + '/pkg-component-json', function (err, json, file) {
if (err) {
return done(err);
}
@@ -180,84 +145,60 @@ describe('.read', function() {
expect(json).to.be.an('object');
expect(json.name).to.equal('some-pkg');
expect(json.version).to.equal('0.0.0');
expect(file).to.equal(
path.resolve(__dirname + '/pkg-component-json/component.json')
);
expect(file).to.equal(path.resolve(__dirname + '/pkg-component-json/component.json'));
done();
});
});
it('should validate the returned object unless validate is false', function(done) {
bowerJson.read(
__dirname + '/pkg-bower-json-invalid/bower.json',
function(err) {
expect(err).to.be.an(Error);
expect(err.message).to.contain('name');
expect(err.file).to.equal(
path.resolve(
__dirname + '/pkg-bower-json-invalid/bower.json'
)
);
it('should validate the returned object unless validate is false', function (done) {
bowerJson.read(__dirname + '/pkg-bower-json-invalid/bower.json', function (err) {
expect(err).to.be.an(Error);
expect(err.message).to.contain('name');
expect(err.file).to.equal(path.resolve(__dirname + '/pkg-bower-json-invalid/bower.json'));
bowerJson.read(
__dirname + '/pkg-bower-json-invalid/bower.json',
{ validate: false },
function(err) {
done(err);
}
);
}
);
bowerJson.read(__dirname + '/pkg-bower-json-invalid/bower.json', { validate: false }, function (err) {
done(err);
});
});
});
it('should normalize the returned object if normalize is true', function(done) {
bowerJson.read(__dirname + '/pkg-bower-json/bower.json', function(
err,
json
) {
it('should normalize the returned object if normalize is true', function (done) {
bowerJson.read(__dirname + '/pkg-bower-json/bower.json', function (err, json) {
if (err) {
return done(err);
}
expect(json.main).to.equal('foo.js');
bowerJson.read(
__dirname + '/pkg-bower-json/bower.json',
{ normalize: true },
function(err, json) {
if (err) {
return done(err);
}
expect(json.main).to.eql(['foo.js']);
done();
bowerJson.read(__dirname + '/pkg-bower-json/bower.json', { normalize: true }, function (err, json) {
if (err) {
return done(err);
}
);
expect(json.main).to.eql(['foo.js']);
done();
});
});
});
});
describe('.readSync', function() {
it('should give error if file does not exists', function(done) {
describe('.readSync', function () {
it('should give error if file does not exists', function (done) {
var err = bowerJson.readSync(__dirname + '/willneverexist');
expect(err).to.be.an(Error);
expect(err.code).to.equal('ENOENT');
done();
});
it('should give error if when reading an invalid json', function(done) {
var err = bowerJson.readSync(
__dirname + '/pkg-bower-json-malformed/bower.json'
);
it('should give error if when reading an invalid json', function (done) {
var err = bowerJson.readSync(__dirname + '/pkg-bower-json-malformed/bower.json');
expect(err).to.be.an(Error);
expect(err.code).to.equal('EMALFORMED');
expect(err.file).to.equal(
path.resolve(__dirname + '/pkg-bower-json-malformed/bower.json')
);
expect(err.file).to.equal(path.resolve(__dirname + '/pkg-bower-json-malformed/bower.json'));
done();
});
it('should read the file and give an object', function(done) {
it('should read the file and give an object', function (done) {
var json = bowerJson.readSync(__dirname + '/pkg-bower-json/bower.json');
expect(json).to.be.an('object');
@@ -267,7 +208,7 @@ describe('.readSync', function() {
done();
});
it('should find for a json file if a directory is given', function(done) {
it('should find for a json file if a directory is given', function (done) {
var json = bowerJson.readSync(__dirname + '/pkg-component-json');
expect(json).to.be.an('object');
@@ -276,39 +217,32 @@ describe('.readSync', function() {
done();
});
it('should validate the returned object unless validate is false', function(done) {
var err = bowerJson.readSync(
__dirname + '/pkg-bower-json-invalid/bower.json'
);
it('should validate the returned object unless validate is false', function (done) {
var err = bowerJson.readSync(__dirname + '/pkg-bower-json-invalid/bower.json');
expect(err).to.be.an(Error);
expect(err.message).to.contain('name');
expect(err.file).to.equal(
path.resolve(__dirname + '/pkg-bower-json-invalid/bower.json')
);
expect(err.file).to.equal(path.resolve(__dirname + '/pkg-bower-json-invalid/bower.json'));
err = bowerJson.readSync(
__dirname + '/pkg-bower-json-invalid/bower.json',
{ validate: false }
);
err = bowerJson.readSync(__dirname + '/pkg-bower-json-invalid/bower.json', { validate: false });
expect(err).to.not.be.an(Error);
done();
});
it('should normalize the returned object if normalize is true', function(done) {
var json = bowerJson.readSync(__dirname + '/pkg-bower-json/bower.json');
it('should normalize the returned object if normalize is true', function (done) {
var json = bowerJson.readSync(__dirname + '/pkg-bower-json/bower.json');
expect(json.main).to.equal('foo.js');
json = bowerJson.readSync(__dirname + '/pkg-bower-json/bower.json', {
normalize: true
});
json = bowerJson.readSync(__dirname + '/pkg-bower-json/bower.json', { normalize: true });
expect(json.main).to.eql(['foo.js']);
done();
});
});
describe('.parse', function() {
it('should return the same object, unless clone is true', function() {
describe('.parse', function () {
it('should return the same object, unless clone is true', function () {
var json = { name: 'foo' };
expect(bowerJson.parse(json)).to.equal(json);
@@ -316,17 +250,17 @@ describe('.parse', function() {
expect(bowerJson.parse(json, { clone: true })).to.eql(json);
});
it('should validate the passed object, unless validate is false', function() {
expect(function() {
it('should validate the passed object, unless validate is false', function () {
expect(function () {
bowerJson.parse({});
}).to.throwException(/name/);
expect(function() {
expect(function () {
bowerJson.parse({}, { validate: false });
}).to.not.throwException();
});
it('should not normalize the passed object unless normalize is true', function() {
it('should not normalize the passed object unless normalize is true', function () {
var json = { name: 'foo', main: 'foo.js' };
bowerJson.parse(json);
@@ -337,24 +271,22 @@ describe('.parse', function() {
});
});
describe('.getIssues', function() {
it('should print no errors even for weird package names', function() {
describe('.getIssues', function () {
it('should print no errors even for weird package names', function () {
var json = { name: '@gruNt/my dependency' };
expect(bowerJson.getIssues(json).errors).to.be.empty();
});
it('should validate the name length', function() {
var json = {
name: 'a_123456789_123456789_123456789_123456789_123456789_z'
};
it('should validate the name length', function () {
var json = { name: 'a_123456789_123456789_123456789_123456789_123456789_z' };
expect(bowerJson.getIssues(json).warnings).to.contain(
'The "name" is too long, the limit is 50 characters'
);
});
it('should validate the name is lowercase', function() {
it('should validate the name is lowercase', function () {
var json = { name: 'gruNt' };
expect(bowerJson.getIssues(json).warnings).to.contain(
@@ -362,7 +294,7 @@ describe('.getIssues', function() {
);
});
it('should validate the name starts with lowercase', function() {
it('should validate the name starts with lowercase', function () {
var json = { name: '-runt' };
expect(bowerJson.getIssues(json).warnings).to.contain(
@@ -370,7 +302,7 @@ describe('.getIssues', function() {
);
});
it('should validate the name starts with lowercase', function() {
it('should validate the name starts with lowercase', function () {
var json = { name: '.grunt' };
expect(bowerJson.getIssues(json).warnings).to.contain(
@@ -378,7 +310,7 @@ describe('.getIssues', function() {
);
});
it('should validate the name ends with lowercase', function() {
it('should validate the name ends with lowercase', function () {
var json = { name: 'grun-' };
expect(bowerJson.getIssues(json).warnings).to.contain(
@@ -386,7 +318,7 @@ describe('.getIssues', function() {
);
});
it('should validate the name ends with lowercase', function() {
it('should validate the name ends with lowercase', function () {
var json = { name: 'grun.' };
expect(bowerJson.getIssues(json).warnings).to.contain(
@@ -394,13 +326,13 @@ describe('.getIssues', function() {
);
});
it('should validate the name is valid', function() {
it('should validate the name is valid', function () {
var json = { name: 'gru.n-t' };
expect(bowerJson.getIssues(json).warnings).to.eql([]);
});
it('should validate the description length', function() {
it('should validate the description length', function () {
var json = {
name: 'foo',
description: _s.repeat('æ', 141)
@@ -411,7 +343,7 @@ describe('.getIssues', function() {
);
});
it('should validate the description is valid', function() {
it('should validate the description is valid', function () {
var json = {
name: 'foo',
description: _s.repeat('æ', 140)
@@ -420,7 +352,7 @@ describe('.getIssues', function() {
expect(bowerJson.getIssues(json).warnings).to.eql([]);
});
it('should validate that main does not contain globs', function() {
it('should validate that main does not contain globs', function () {
var json = {
name: 'foo',
main: ['js/*.js']
@@ -431,7 +363,7 @@ describe('.getIssues', function() {
);
});
it('should validate that main does not contain minified files', function() {
it('should validate that main does not contain minified files', function () {
var json = {
name: 'foo',
main: ['foo.min.css']
@@ -442,7 +374,7 @@ describe('.getIssues', function() {
);
});
it('should validate that main does not contain fonts', function() {
it('should validate that main does not contain fonts', function () {
var json = {
name: 'foo',
main: ['foo.woff']
@@ -453,7 +385,7 @@ describe('.getIssues', function() {
);
});
it('should validate that main does not contain images', function() {
it('should validate that main does not contain images', function () {
var json = {
name: 'foo',
main: ['foo.png']
@@ -464,7 +396,7 @@ describe('.getIssues', function() {
);
});
it('should validate that main does not contain multiple files of the same filetype', function() {
it('should validate that main does not contain multiple files of the same filetype', function () {
var json = {
name: 'foo',
main: ['foo.js', 'bar.js']
@@ -476,35 +408,35 @@ describe('.getIssues', function() {
});
});
describe('.validate', function() {
it('should validate the name property', function() {
expect(function() {
describe('.validate', function () {
it('should validate the name property', function () {
expect(function () {
bowerJson.validate({});
}).to.throwException(/name/);
});
it('should validate the type of main', function() {
it('should validate the type of main', function () {
var json = {
name: 'foo',
main: {}
};
expect(function() {
expect(function () {
bowerJson.validate(json);
}).to.throwException();
});
it('should validate the type of items of an Array main', function() {
it('should validate the type of items of an Array main', function () {
var json = {
name: 'foo',
main: [{}]
};
expect(function() {
expect(function () {
bowerJson.validate(json);
}).to.throwException();
});
});
describe('.normalize', function() {
it('should normalize the main property', function() {
describe('.normalize', function () {
it('should normalize the main property', function () {
var json = { name: 'foo', main: 'foo.js' };
bowerJson.normalize(json);
@@ -512,35 +444,34 @@ describe('.normalize', function() {
});
});
describe('packages from bower registry', function() {
describe('packages from bower registry', function () {
var packageList,
packageListUrl = 'http://registry.bower.io/packages';
packageListUrl = 'http://bower.herokuapp.com/packages';
this.timeout(60000);
it('can be downloaded from online source ' + packageListUrl, function(
done
) {
request(
{
url: packageListUrl,
json: true
},
function(error, response, body) {
if (error) {
throw error;
}
it('can be downloaded from online source ' + packageListUrl, function(done) {
request({
url: packageListUrl,
json: true
}, function(error, response, body) {
expect(body).to.be.an('array');
expect(body).to.not.be.empty();
packageList = body;
done();
if(error) {
throw error;
}
);
expect(body).to.be.an('array');
expect(body).to.not.be.empty();
packageList = body;
done();
});
});
it('should validate each listed package', function(done) {
it('should validate each listed package', function (done) {
expect(packageList).to.be.an('array');
var invalidPackageCount = 0;
@@ -548,23 +479,19 @@ describe('packages from bower registry', function() {
packageList.forEach(function(package) {
try {
bowerJson.validate(package);
} catch (e) {
} catch(e) {
invalidPackageCount++;
console.error(
'validation of "' + package.name + '" failed: ' + e.message
);
console.error('validation of "' + package.name + '" failed: ' + e.message);
}
});
if (invalidPackageCount) {
throw new Error(
invalidPackageCount +
'/' +
packageList.length +
' package names do not validate'
);
if(invalidPackageCount) {
throw new Error(invalidPackageCount + '/' + packageList.length + ' package names do not validate');
}
done();
});
});

View File

@@ -10,18 +10,18 @@ function Logger() {
util.inherits(Logger, EventEmitter);
Logger.prototype.intercept = function(fn) {
Logger.prototype.intercept = function (fn) {
this._interceptors.push(fn);
return this;
};
Logger.prototype.emit = function() {
Logger.prototype.emit = function () {
var ret;
var args = slice.call(arguments);
// Run interceptors before
if (args[0] === 'log') {
this._interceptors.forEach(function(interceptor) {
this._interceptors.forEach(function (interceptor) {
interceptor.apply(this, args.slice(1));
});
}
@@ -29,27 +29,27 @@ Logger.prototype.emit = function() {
ret = EventEmitter.prototype.emit.apply(this, args);
// Pipe
this._piped.forEach(function(emitter) {
this._piped.forEach(function (emitter) {
emitter.emit.apply(emitter, args);
});
return ret;
};
Logger.prototype.pipe = function(emitter) {
Logger.prototype.pipe = function (emitter) {
this._piped.push(emitter);
return emitter;
};
Logger.prototype.geminate = function() {
Logger.prototype.geminate = function () {
var logger = new Logger();
logger.pipe(this);
return logger;
};
Logger.prototype.log = function(level, id, message, data) {
Logger.prototype.log = function (level, id, message, data) {
var log = {
level: level,
id: id,
@@ -63,7 +63,7 @@ Logger.prototype.log = function(level, id, message, data) {
return this;
};
Logger.prototype.prompt = function(prompts, callback) {
Logger.prototype.prompt = function (prompts, callback) {
var fn;
var one;
var invalid;
@@ -78,7 +78,7 @@ Logger.prototype.prompt = function(prompts, callback) {
}
// Validate prompt types
invalid = prompts.some(function(prompt) {
invalid = prompts.some(function (prompt) {
return validPrompts.indexOf(prompt.type) === -1;
});
@@ -88,20 +88,20 @@ Logger.prototype.prompt = function(prompts, callback) {
return callback(error);
}
fn = function(answers) {
fn = function (answers) {
// Run callback only once
if (runned) {
return;
}
// Trim answers automatically
Object.keys(answers).forEach(function(key) {
Object.keys(answers).forEach(function (key) {
var value = answers[key];
if (typeof value === 'string') {
answers[key] = value.trim();
} else if (Array.isArray(value)) {
answers[key] = value.map(function(item) {
answers[key] = value.map(function (item) {
if (typeof item === 'string') {
return item.trim();
}
@@ -124,20 +124,25 @@ Logger.prototype.prompt = function(prompts, callback) {
// ------------------
Logger._validPrompts = ['input', 'confirm', 'password', 'checkbox'];
Logger._validPrompts = [
'input',
'confirm',
'password',
'checkbox'
];
Logger.LEVELS = {
error: 5,
conflict: 4,
warn: 3,
action: 2,
info: 1,
debug: 0
'error': 5,
'conflict': 4,
'warn': 3,
'action': 2,
'info': 1,
'debug': 0
};
// Add helpful log methods
Object.keys(Logger.LEVELS).forEach(function(level) {
Logger.prototype[level] = function(id, message, data) {
Object.keys(Logger.LEVELS).forEach(function (level) {
Logger.prototype[level] = function (id, message, data) {
this.log(level, id, message, data);
};
});

View File

@@ -9,7 +9,7 @@
"url": "https://github.com/bower/logger/blob/master/LICENSE"
}
],
"repository": "https://github.com/bower/bower/tree/master/packages/bower-logger",
"repository": "bower/logger",
"main": "lib/Logger",
"engines": {
"node": ">=0.10.0"

View File

@@ -2,55 +2,57 @@ var expect = require('expect.js');
var EventEmitter = require('events').EventEmitter;
var Logger = require('../');
describe('Logger', function() {
describe('Logger', function () {
var logger;
beforeEach(function() {
beforeEach(function () {
logger = new Logger();
});
describe('.constructor', function() {
it('should provide an instance of Logger', function() {
describe('.constructor', function () {
it('should provide an instance of Logger', function () {
expect(logger instanceof Logger).to.be(true);
});
it('should provide an instance of EventEmitter', function() {
it('should provide an instance of EventEmitter', function () {
expect(logger instanceof EventEmitter).to.be(true);
});
it('should have prototype methods', function() {
var methods = ['intercept', 'pipe', 'geminate', 'log'];
it('should have prototype methods', function () {
var methods = [
'intercept', 'pipe', 'geminate', 'log'
];
methods.forEach(function(method) {
methods.forEach(function (method) {
expect(logger).to.have.property(method);
});
});
});
describe('events', function() {
describe('events', function () {
var logData = {
foo: 'bar',
baz: 'string'
};
it('should pass through {}', function(next) {
logger.on('log', function(log) {
it('should pass through {}', function (next) {
logger.on('log', function (log) {
expect(log.data).to.eql({});
next();
});
logger.info();
});
it('should pass through logData', function(next) {
logger.on('log', function(log) {
it('should pass through logData', function (next) {
logger.on('log', function (log) {
expect(log.data).to.eql(logData);
next();
});
logger.info('foo', 'message', logData);
});
it('should emit error event', function(next) {
logger.on('log', function(log) {
it('should emit error event', function (next) {
logger.on('log', function (log) {
expect(log.level).to.eql('error');
expect(log.id).to.eql('foo');
expect(log.message).to.eql('error message');
@@ -60,8 +62,8 @@ describe('Logger', function() {
logger.error('foo', 'error message');
});
it('should emit conflict event', function(next) {
logger.on('log', function(log) {
it('should emit conflict event', function (next) {
logger.on('log', function (log) {
expect(log.level).to.eql('conflict');
expect(log.id).to.eql('foo');
expect(log.message).to.eql('conflict message');
@@ -71,8 +73,8 @@ describe('Logger', function() {
logger.conflict('foo', 'conflict message');
});
it('should emit warn event', function(next) {
logger.on('log', function(log) {
it('should emit warn event', function (next) {
logger.on('log', function (log) {
expect(log.level).to.eql('warn');
expect(log.id).to.eql('foo');
expect(log.message).to.eql('warn message');
@@ -82,8 +84,8 @@ describe('Logger', function() {
logger.warn('foo', 'warn message');
});
it('should emit action event', function(next) {
logger.on('log', function(log) {
it('should emit action event', function (next) {
logger.on('log', function (log) {
expect(log.level).to.eql('action');
expect(log.id).to.eql('foo');
expect(log.message).to.eql('action message');
@@ -93,8 +95,8 @@ describe('Logger', function() {
logger.action('foo', 'action message');
});
it('should emit info event', function(next) {
logger.on('log', function(log) {
it('should emit info event', function (next) {
logger.on('log', function (log) {
expect(log.level).to.eql('info');
expect(log.id).to.eql('foo');
expect(log.message).to.eql('info message');
@@ -104,8 +106,8 @@ describe('Logger', function() {
logger.info('foo', 'info message');
});
it('should emit debug event', function(next) {
logger.on('log', function(log) {
it('should emit debug event', function (next) {
logger.on('log', function (log) {
expect(log.level).to.eql('debug');
expect(log.id).to.eql('foo');
expect(log.message).to.eql('debug message');
@@ -116,14 +118,14 @@ describe('Logger', function() {
});
});
describe('.intercept', function() {
it('should add the function and call it when a log occurs', function(next) {
describe('.intercept', function () {
it('should add the function and call it when a log occurs', function (next) {
var called;
var data = {
some: 'thing'
'some': 'thing'
};
logger.intercept(function(log) {
logger.intercept(function (log) {
called = true;
expect(log).to.eql({
@@ -141,13 +143,13 @@ describe('Logger', function() {
next();
});
it('should call the interceptors by order before emitting the event', function(next) {
it('should call the interceptors by order before emitting the event', function (next) {
var called = [];
logger.intercept(function() {
logger.intercept(function () {
called.push(1);
});
logger.intercept(function() {
logger.intercept(function () {
called.push(2);
});
@@ -157,21 +159,21 @@ describe('Logger', function() {
next();
});
it('should call the interceptors along the chain', function(next) {
it('should call the interceptors along the chain', function (next) {
var called = [];
var childLogger = logger.geminate();
childLogger.intercept(function() {
childLogger.intercept(function () {
called.push(1);
});
logger.intercept(function() {
logger.intercept(function () {
called.push(3);
});
childLogger.on('log', function() {
childLogger.on('log', function () {
called.push(2);
});
logger.on('log', function() {
logger.on('log', function () {
called.push(4);
});
@@ -182,22 +184,22 @@ describe('Logger', function() {
});
});
describe('.pipe', function() {
it('should return the passed emitter', function() {
describe('.pipe', function () {
it('should return the passed emitter', function () {
var otherEmitter = new EventEmitter();
expect(logger.pipe(otherEmitter)).to.equal(otherEmitter);
});
it('should pipe log events to another emitter', function(next) {
it('should pipe log events to another emitter', function (next) {
var otherEmitter = new EventEmitter();
var data = {
some: 'thing'
'some': 'thing'
};
var piped;
logger.pipe(otherEmitter);
otherEmitter.on('log', function(log) {
otherEmitter.on('log', function (log) {
piped = true;
expect(log).to.eql({
level: 'warn',
@@ -214,8 +216,8 @@ describe('Logger', function() {
});
});
describe('.geminate', function() {
it('should return a new logger instance', function() {
describe('.geminate', function () {
it('should return a new logger instance', function () {
var newLogger = logger.geminate();
expect(newLogger).to.be.an(Logger);
@@ -223,14 +225,14 @@ describe('Logger', function() {
expect(newLogger).to.not.be.equal(logger);
});
it('should pipe the new logger events to the original logger', function(next) {
it('should pipe the new logger events to the original logger', function (next) {
var piped = [];
var childLogger = logger.geminate();
var data = {
some: 'thing'
'some': 'thing'
};
childLogger.on('log', function(log) {
childLogger.on('log', function (log) {
piped.push(1);
expect(log).to.eql({
level: 'warn',
@@ -241,7 +243,7 @@ describe('Logger', function() {
expect(log.data).to.equal(data);
});
logger.on('log', function(log) {
logger.on('log', function (log) {
piped.push(2);
expect(log).to.eql({
level: 'warn',
@@ -258,150 +260,130 @@ describe('Logger', function() {
});
});
describe('.prompt', function() {
it('should only allow calling the callback once', function() {
describe('.prompt', function () {
it('should only allow calling the callback once', function () {
var calls = 0;
logger
.once('prompt', function(prompts, callback) {
callback({ prompt: 'bar' });
callback({ prompt: 'foo' });
})
.prompt(
{
type: 'input',
message: 'foo'
},
function() {
calls += 1;
}
);
.once('prompt', function (prompts, callback) {
callback({ prompt: 'bar' });
callback({ prompt: 'foo' });
})
.prompt({
type: 'input',
message: 'foo'
}, function () {
calls += 1;
});
expect(calls).to.equal(1);
});
it('should accept a prompt', function(next) {
it('should accept a prompt', function (next) {
logger
.once('prompt', function(prompts, callback) {
callback({
prompt: 'bar'
});
})
.prompt(
{
type: 'input',
message: 'foo'
},
function(err, answer) {
expect(err).to.not.be.ok();
expect(answer).to.equal('bar');
next();
}
);
.once('prompt', function (prompts, callback) {
callback({
prompt: 'bar'
});
})
.prompt({
type: 'input',
message: 'foo'
}, function (err, answer) {
expect(err).to.not.be.ok();
expect(answer).to.equal('bar');
next();
});
});
it('should accept several prompts', function(next) {
it('should accept several prompts', function (next) {
logger
.once('prompt', function(prompts, callback) {
callback({
foo: 'bar',
foz: 'baz'
});
})
.prompt(
[
{
name: 'foo',
type: 'input',
message: 'foo'
},
{
name: 'foz',
type: 'confirm',
message: 'foz'
}
],
function(err, answer) {
expect(err).to.not.be.ok();
expect(answer.foo).to.equal('bar');
expect(answer.foz).to.equal('baz');
logger
.once('prompt', function(prompts, callback) {
callback({
foo: 'bar'
});
})
.prompt(
[
{
name: 'foo',
type: 'input',
message: 'foo'
}
],
function(err, answer) {
expect(err).to.not.be.ok();
expect(answer.foo).to.equal('bar');
next();
}
);
}
);
});
it('should error on invalid prompt type', function(next) {
logger.prompt(
.once('prompt', function (prompts, callback) {
callback({
foo: 'bar',
foz: 'baz'
});
})
.prompt([
{
type: 'xxx',
name: 'foo',
type: 'input',
message: 'foo'
},
function(err) {
expect(err).to.be.an(Error);
expect(err.code).to.be('ENOTSUP');
next();
{
name: 'foz',
type: 'confirm',
message: 'foz'
}
);
});
], function (err, answer) {
expect(err).to.not.be.ok();
expect(answer.foo).to.equal('bar');
expect(answer.foz).to.equal('baz');
it('should trim the answers', function(next) {
logger
.once('prompt', function(prompts, callback) {
logger
.once('prompt', function (prompts, callback) {
callback({
prompt: ' bar '
foo: 'bar'
});
})
.prompt(
.prompt([
{
name: 'foo',
type: 'input',
message: 'foo'
},
function(err, answer) {
expect(err).to.not.be.ok();
expect(answer).to.equal('bar');
next();
}
);
], function (err, answer) {
expect(err).to.not.be.ok();
expect(answer.foo).to.equal('bar');
next();
});
});
});
it('should trim multiple response answers', function(next) {
logger
.once('prompt', function(prompts, callback) {
callback({
prompt: [' bar ', ' foo', 'baz ']
});
})
.prompt(
{
type: 'checkbox',
message: 'foo'
},
function(err, answer) {
expect(err).to.not.be.ok();
expect(answer).to.eql(['bar', 'foo', 'baz']);
next();
}
);
it('should error on invalid prompt type', function (next) {
logger.prompt({
type: 'xxx',
message: 'foo'
}, function (err) {
expect(err).to.be.an(Error);
expect(err.code).to.be('ENOTSUP');
next();
});
});
it('should trim the answers', function (next) {
logger
.once('prompt', function (prompts, callback) {
callback({
prompt: ' bar '
});
})
.prompt({
type: 'input',
message: 'foo'
}, function (err, answer) {
expect(err).to.not.be.ok();
expect(answer).to.equal('bar');
next();
});
});
it('should trim multiple response answers', function (next) {
logger
.once('prompt', function (prompts, callback) {
callback({
prompt: [' bar ', ' foo', 'baz ']
});
})
.prompt({
type: 'checkbox',
message: 'foo'
}, function (err, answer) {
expect(err).to.not.be.ok();
expect(answer).to.eql(['bar', 'foo', 'baz']);
next();
});
});
});
});

View File

@@ -7,16 +7,12 @@ function RegistryClient(config, logger) {
this._config = config;
if (!this._config.registry) {
throw new Error(
'You need to pass config as read by bower-config module. Registry field is missing.'
);
throw new Error("You need to pass config as read by bower-config module. Registry field is missing.");
}
// Cache defaults to storage registry
if (!Object.prototype.hasOwnProperty.call(this._config, 'cache')) {
this._config.cache = this._config.storage
? this._config.storage.registry
: null;
this._config.cache = this._config.storage ? this._config.storage.registry : null;
}
// Init the cache
@@ -30,23 +26,20 @@ RegistryClient.prototype.list = methods.list;
RegistryClient.prototype.register = methods.register;
RegistryClient.prototype.unregister = methods.unregister;
RegistryClient.prototype.clearCache = function(name, callback) {
RegistryClient.prototype.clearCache = function (name, callback) {
if (typeof name === 'function') {
callback = name;
name = null;
}
async.parallel(
[
this.lookup.clearCache.bind(this, name),
this.search.clearCache.bind(this, name),
this.list.clearCache.bind(this)
],
callback
);
async.parallel([
this.lookup.clearCache.bind(this, name),
this.search.clearCache.bind(this, name),
this.list.clearCache.bind(this)
], callback);
};
RegistryClient.prototype.resetCache = function(name) {
RegistryClient.prototype.resetCache = function (name) {
this.lookup.resetCache.call(this, name);
this.search.resetCache.call(this, name);
this.list.resetCache.call(this);
@@ -54,13 +47,13 @@ RegistryClient.prototype.resetCache = function(name) {
return this;
};
RegistryClient.clearRuntimeCache = function() {
RegistryClient.clearRuntimeCache = function () {
Cache.clearRuntimeCache();
};
// -----------------------------
RegistryClient.prototype._initCache = function() {
RegistryClient.prototype._initCache = function () {
var cache;
var dir = this._config.cache;

View File

@@ -1,11 +1,16 @@
module.exports = function(grunt) {
'use strict';
module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.initConfig({
jshint: {
files: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js'],
files: [
'Gruntfile.js',
'lib/**/*.js',
'test/**/*.js'
],
options: {
jshintrc: '.jshintrc'
}

View File

@@ -20,63 +20,59 @@ function list(callback) {
}
// List packages in series in each registry
async.doUntil(
function(next) {
var remote = url.parse(registry[index]);
var listCache = that._listCache[remote.host];
async.doUntil(function (next) {
var remote = url.parse(registry[index]);
var listCache = that._listCache[remote.host];
// If offline flag is passed, only query the cache
if (that._config.offline) {
return listCache.get('list', function(err, results) {
if (err || !results) {
return next(err);
}
// Add each result
results.forEach(function(result) {
addResult.call(that, data, result);
});
next();
});
}
// Otherwise make a request to always obtain fresh data
doRequest.call(that, index, function(err, results) {
// If offline flag is passed, only query the cache
if (that._config.offline) {
return listCache.get('list', function (err, results) {
if (err || !results) {
return next(err);
}
// Add each result
results.forEach(function(result) {
addResult(data, result);
results.forEach(function (result) {
addResult.call(that, data, result);
});
// Store in cache for future offline usage
listCache.set('list', results, getMaxAge(), next);
next();
});
},
function() {
// Until there's still registries to test
return ++index === total;
},
function(err) {
// Clear runtime cache, keeping the persistent data
// in files for future offline usage
resetCache();
}
// If some of the registry entries failed, error out
if (err) {
return callback(err);
// Otherwise make a request to always obtain fresh data
doRequest.call(that, index, function (err, results) {
if (err || !results) {
return next(err);
}
callback(null, data);
// Add each result
results.forEach(function (result) {
addResult(data, result);
});
// Store in cache for future offline usage
listCache.set('list', results, getMaxAge(), next);
});
}, function () {
// Until there's still registries to test
return ++index === total;
}, function (err) {
// Clear runtime cache, keeping the persistent data
// in files for future offline usage
resetCache();
// If some of the registry entries failed, error out
if (err) {
return callback(err);
}
);
callback(null, data);
});
}
function addResult(accumulated, result) {
var exists = accumulated.some(function(current) {
var exists = accumulated.some(function (current) {
return current.name === result.name;
});
@@ -97,69 +93,35 @@ function doRequest(index, callback) {
headers['User-Agent'] = this._config.userAgent;
}
req = replay(
request.get(
requestUrl,
{
ca: this._config.ca.search[index],
headers: headers,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
json: true
},
function(err, response, body) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(
createError(
'Request to ' +
requestUrl +
' failed: ' +
err.message,
err.code
)
);
}
req = replay(request.get(requestUrl, {
ca: this._config.ca.search[index],
headers: headers,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
json: true
}, function (err, response, body) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(createError('Request to ' + requestUrl + ' failed: ' + err.message, err.code));
}
// Abort if there was an error (range different than 2xx)
if (response.statusCode < 200 || response.statusCode > 299) {
return callback(
createError(
'Request to ' +
requestUrl +
' failed with ' +
response.statusCode,
'EINVRES'
)
);
}
// Abort if there was an error (range different than 2xx)
if (response.statusCode < 200 || response.statusCode > 299) {
return callback(createError('Request to ' + requestUrl + ' failed with ' + response.statusCode, 'EINVRES'));
}
// Validate response body, since we are expecting a JSON object
// If the server returns an invalid JSON, it's still a string
if (typeof body !== 'object') {
return callback(
createError(
'Response of request to ' +
requestUrl +
' is not a valid json',
'EINVRES'
)
);
}
// Validate response body, since we are expecting a JSON object
// If the server returns an invalid JSON, it's still a string
if (typeof body !== 'object') {
return callback(createError('Response of request to ' + requestUrl + ' is not a valid json', 'EINVRES'));
}
callback(null, body);
}
)
);
callback(null, body);
}));
if (this._logger) {
req.on('replay', function(replay) {
msg =
'Request to ' +
requestUrl +
' failed with ' +
replay.error.code +
', ';
req.on('replay', function (replay) {
msg = 'Request to ' + requestUrl + ' failed with ' + replay.error.code + ', ';
msg += 'retrying in ' + (replay.delay / 1000).toFixed(1) + 's';
that._logger.warn('retry', msg);
});
@@ -175,7 +137,7 @@ function initCache() {
this._listCache = this._cache.list || {};
// Generate a cache instance for each registry endpoint
this._config.registry.search.forEach(function(registry) {
this._config.registry.search.forEach(function (registry) {
var cacheDir;
var host = url.parse(registry).host;
@@ -185,11 +147,7 @@ function initCache() {
}
if (this._config.cache) {
cacheDir = path.join(
this._config.cache,
encodeURIComponent(host),
'list'
);
cacheDir = path.join(this._config.cache, encodeURIComponent(host), 'list');
}
this._listCache[host] = new Cache(cacheDir, {
@@ -206,13 +164,9 @@ function clearCache(callback) {
// There's only one key, which is 'list'..
// But we clear everything anyway
async.forEach(
remotes,
function(remote, next) {
listCache[remote].clear(next);
},
callback
);
async.forEach(remotes, function (remote, next) {
listCache[remote].clear(next);
}, callback);
}
function resetCache() {

View File

@@ -20,37 +20,22 @@ function lookup(name, callback) {
// Lookup package in series in each registry
// endpoint until we got the data
async.doUntil(
function(next) {
var remote = url.parse(registry[index]);
var lookupCache = that._lookupCache[remote.host];
async.doUntil(function (next) {
var remote = url.parse(registry[index]);
var lookupCache = that._lookupCache[remote.host];
// If force flag is disabled we check the cache
if (!that._config.force) {
lookupCache.get(name, function(err, value) {
data = value;
// If force flag is disabled we check the cache
if (!that._config.force) {
lookupCache.get(name, function (err, value) {
data = value;
// Don't proceed with making a request if we got an error,
// a value from the cache or if the offline flag is enabled
if (err || data || that._config.offline) {
return next(err);
}
// Don't proceed with making a request if we got an error,
// a value from the cache or if the offline flag is enabled
if (err || data || that._config.offline) {
return next(err);
}
doRequest.call(that, name, index, function(err, entry) {
if (err || !entry) {
return next(err);
}
data = entry;
// Store in cache
lookupCache.set(name, entry, getMaxAge(entry), next);
});
});
// Otherwise, we totally bypass the cache and
// make only the request
} else {
doRequest.call(that, name, index, function(err, entry) {
doRequest.call(that, name, index, function (err, entry) {
if (err || !entry) {
return next(err);
}
@@ -60,30 +45,38 @@ function lookup(name, callback) {
// Store in cache
lookupCache.set(name, entry, getMaxAge(entry), next);
});
}
},
function() {
// Until the data is unknown or there's still registries to test
return !!data || ++index === total;
},
function(err) {
// If some of the registry entries failed, error out
if (err) {
return callback(err);
}
});
// Otherwise, we totally bypass the cache and
// make only the request
} else {
doRequest.call(that, name, index, function (err, entry) {
if (err || !entry) {
return next(err);
}
callback(null, data);
data = entry;
// Store in cache
lookupCache.set(name, entry, getMaxAge(entry), next);
});
}
);
}, function () {
// Until the data is unknown or there's still registries to test
return !!data || ++index === total;
}, function (err) {
// If some of the registry entries failed, error out
if (err) {
return callback(err);
}
callback(null, data);
});
}
function doRequest(name, index, callback) {
var req;
var msg;
var requestUrl =
this._config.registry.search[index] +
'/packages/' +
encodeURIComponent(name);
var requestUrl = this._config.registry.search[index] + '/packages/' + encodeURIComponent(name);
var remote = url.parse(requestUrl);
var headers = {};
var that = this;
@@ -92,81 +85,47 @@ function doRequest(name, index, callback) {
headers['User-Agent'] = this._config.userAgent;
}
req = replay(
request.get(
requestUrl,
{
headers: headers,
ca: this._config.ca.search[index],
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
json: true
},
function(err, response, body) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(
createError(
'Request to ' +
requestUrl +
' failed: ' +
err.message,
err.code
)
);
}
req = replay(request.get(requestUrl, {
headers: headers,
ca: this._config.ca.search[index],
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
json: true
}, function (err, response, body) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(createError('Request to ' + requestUrl + ' failed: ' + err.message, err.code));
}
// If not found, try next
if (response.statusCode === 404) {
return callback();
}
// If not found, try next
if (response.statusCode === 404) {
return callback();
}
// Abort if there was an error (range different than 2xx)
if (response.statusCode < 200 || response.statusCode > 299) {
return callback(
createError(
'Request to ' +
requestUrl +
' failed with ' +
response.statusCode,
'EINVRES'
)
);
}
// Abort if there was an error (range different than 2xx)
if (response.statusCode < 200 || response.statusCode > 299) {
return callback(createError('Request to ' + requestUrl + ' failed with ' + response.statusCode, 'EINVRES'));
}
// Validate response body, since we are expecting a JSON object
// If the server returns an invalid JSON, it's still a string
if (typeof body !== 'object') {
return callback(
createError(
'Response of request to ' +
requestUrl +
' is not a valid json',
'EINVRES'
)
);
}
// Validate response body, since we are expecting a JSON object
// If the server returns an invalid JSON, it's still a string
if (typeof body !== 'object') {
return callback(createError('Response of request to ' + requestUrl + ' is not a valid json', 'EINVRES'));
}
var data;
if (body.url) {
data = {
type: 'alias',
url: body.url
};
}
callback(null, data);
}
)
);
var data;
if (body.url) {
data = {
type: 'alias',
url: body.url
};
}
callback(null, data);
}));
if (this._logger) {
req.on('replay', function(replay) {
msg =
'Request to ' +
requestUrl +
' failed with ' +
replay.error.code +
', ';
req.on('replay', function (replay) {
msg = 'Request to ' + requestUrl + ' failed with ' + replay.error.code + ', ';
msg += 'retrying in ' + (replay.delay / 1000).toFixed(1) + 's';
that._logger.warn('retry', msg);
});
@@ -187,7 +146,7 @@ function initCache() {
this._lookupCache = this._cache.lookup || {};
// Generate a cache instance for each registry endpoint
this._config.registry.search.forEach(function(registry) {
this._config.registry.search.forEach(function (registry) {
var cacheDir;
var host = url.parse(registry).host;
@@ -197,11 +156,7 @@ function initCache() {
}
if (this._config.cache) {
cacheDir = path.join(
this._config.cache,
encodeURIComponent(host),
'lookup'
);
cacheDir = path.join(this._config.cache, encodeURIComponent(host), 'lookup');
}
this._lookupCache[host] = new Cache(cacheDir, {
@@ -222,21 +177,13 @@ function clearCache(name, callback) {
}
if (name) {
async.forEach(
remotes,
function(remote, next) {
lookupCache[remote].del(name, next);
},
callback
);
async.forEach(remotes, function (remote, next) {
lookupCache[remote].del(name, next);
}, callback);
} else {
async.forEach(
remotes,
function(remote, next) {
lookupCache[remote].clear(next);
},
callback
);
async.forEach(remotes, function (remote, next) {
lookupCache[remote].clear(next);
}, callback);
}
}

View File

@@ -16,61 +16,43 @@ function register(name, url, callback) {
requestUrl += '?access_token=' + config.accessToken;
}
request.post(
{
url: requestUrl,
headers: headers,
ca: config.ca.register,
strictSSL: config.strictSsl,
timeout: config.timeout,
json: true,
form: {
name: name,
url: url
}
},
function(err, response) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(
createError(
'Request to ' + requestUrl + ' failed: ' + err.message,
err.code
)
);
}
// Duplicate
if (response.statusCode === 403) {
return callback(createError('Duplicate package', 'EDUPLICATE'));
}
// Invalid format
if (response.statusCode === 400) {
return callback(
createError('Invalid URL format', 'EINVFORMAT')
);
}
// Everything other than 201 is unknown
if (response.statusCode !== 201) {
return callback(
createError(
'Unknown error: ' +
response.statusCode +
' - ' +
response.body,
'EUNKNOWN'
)
);
}
callback(null, {
name: name,
url: url
});
request.post({
url: requestUrl,
headers: headers,
ca: config.ca.register,
strictSSL: config.strictSsl,
timeout: config.timeout,
json: true,
form: {
name: name,
url: url
}
);
}, function (err, response) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(createError('Request to ' + requestUrl + ' failed: ' + err.message, err.code));
}
// Duplicate
if (response.statusCode === 403) {
return callback(createError('Duplicate package', 'EDUPLICATE'));
}
// Invalid format
if (response.statusCode === 400) {
return callback(createError('Invalid URL format', 'EINVFORMAT'));
}
// Everything other than 201 is unknown
if (response.statusCode !== 201) {
return callback(createError('Unknown error: ' + response.statusCode + ' - ' + response.body, 'EUNKNOWN'));
}
callback(null, {
name: name,
url: url
});
});
}
module.exports = register;

View File

@@ -28,63 +28,59 @@ function search(name, callback) {
// Search package in series in each registry,
// merging results together
async.doUntil(
function(next) {
var remote = url.parse(registry[index]);
var searchCache = that._searchCache[remote.host];
async.doUntil(function (next) {
var remote = url.parse(registry[index]);
var searchCache = that._searchCache[remote.host];
// If offline flag is passed, only query the cache
if (that._config.offline) {
return searchCache.get(name, function(err, results) {
if (err || !results || !results.length) {
return next(err);
}
// Add each result
results.forEach(function(result) {
addResult.call(that, data, result);
});
next();
});
}
// Otherwise make a request to always obtain fresh data
doRequest.call(that, name, index, function(err, results) {
// If offline flag is passed, only query the cache
if (that._config.offline) {
return searchCache.get(name, function (err, results) {
if (err || !results || !results.length) {
return next(err);
}
// Add each result
results.forEach(function(result) {
results.forEach(function (result) {
addResult.call(that, data, result);
});
// Store in cache for future offline usage
searchCache.set(name, results, getMaxAge(), next);
next();
});
},
function() {
// Until the data is unknown or there's still registries to test
return ++index === total;
},
function(err) {
// Clear runtime cache, keeping the persistent data
// in files for future offline usage
resetCache();
}
// If some of the registry entries failed, error out
if (err) {
return callback(err);
// Otherwise make a request to always obtain fresh data
doRequest.call(that, name, index, function (err, results) {
if (err || !results || !results.length) {
return next(err);
}
callback(null, data);
// Add each result
results.forEach(function (result) {
addResult.call(that, data, result);
});
// Store in cache for future offline usage
searchCache.set(name, results, getMaxAge(), next);
});
}, function () {
// Until the data is unknown or there's still registries to test
return ++index === total;
}, function (err) {
// Clear runtime cache, keeping the persistent data
// in files for future offline usage
resetCache();
// If some of the registry entries failed, error out
if (err) {
return callback(err);
}
);
callback(null, data);
});
}
function addResult(accumulated, result) {
var exists = accumulated.some(function(current) {
var exists = accumulated.some(function (current) {
return current.name === result.name;
});
@@ -96,10 +92,7 @@ function addResult(accumulated, result) {
function doRequest(name, index, callback) {
var req;
var msg;
var requestUrl =
this._config.registry.search[index] +
'/packages/search/' +
encodeURIComponent(name);
var requestUrl = this._config.registry.search[index] + '/packages/search/' + encodeURIComponent(name);
var remote = url.parse(requestUrl);
var headers = {};
var that = this;
@@ -108,69 +101,35 @@ function doRequest(name, index, callback) {
headers['User-Agent'] = this._config.userAgent;
}
req = replay(
request.get(
requestUrl,
{
headers: headers,
ca: this._config.ca.search[index],
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
json: true
},
function(err, response, body) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(
createError(
'Request to ' +
requestUrl +
' failed: ' +
err.message,
err.code
)
);
}
req = replay(request.get(requestUrl, {
headers: headers,
ca: this._config.ca.search[index],
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
json: true
}, function (err, response, body) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(createError('Request to ' + requestUrl + ' failed: ' + err.message, err.code));
}
// Abort if there was an error (range different than 2xx)
if (response.statusCode < 200 || response.statusCode > 299) {
return callback(
createError(
'Request to ' +
requestUrl +
' failed with ' +
response.statusCode,
'EINVRES'
)
);
}
// Abort if there was an error (range different than 2xx)
if (response.statusCode < 200 || response.statusCode > 299) {
return callback(createError('Request to ' + requestUrl + ' failed with ' + response.statusCode, 'EINVRES'));
}
// Validate response body, since we are expecting a JSON object
// If the server returns an invalid JSON, it's still a string
if (typeof body !== 'object') {
return callback(
createError(
'Response of request to ' +
requestUrl +
' is not a valid json',
'EINVRES'
)
);
}
// Validate response body, since we are expecting a JSON object
// If the server returns an invalid JSON, it's still a string
if (typeof body !== 'object') {
return callback(createError('Response of request to ' + requestUrl + ' is not a valid json', 'EINVRES'));
}
callback(null, body);
}
)
);
callback(null, body);
}));
if (this._logger) {
req.on('replay', function(replay) {
msg =
'Request to ' +
requestUrl +
' failed with ' +
replay.error.code +
', ';
req.on('replay', function (replay) {
msg = 'Request to ' + requestUrl + ' failed with ' + replay.error.code + ', ';
msg += 'retrying in ' + (replay.delay / 1000).toFixed(1) + 's';
that._logger.warn('retry', msg);
});
@@ -186,7 +145,7 @@ function initCache() {
this._searchCache = this._cache.search || {};
// Generate a cache instance for each registry endpoint
this._config.registry.search.forEach(function(registry) {
this._config.registry.search.forEach(function (registry) {
var cacheDir;
var host = url.parse(registry).host;
@@ -196,11 +155,7 @@ function initCache() {
}
if (this._config.cache) {
cacheDir = path.join(
this._config.cache,
encodeURIComponent(host),
'search'
);
cacheDir = path.join(this._config.cache, encodeURIComponent(host), 'search');
}
this._searchCache[host] = new Cache(cacheDir, {
@@ -225,13 +180,9 @@ function clearCache(name, callback) {
// One possible solution would be to read every entry from the cache and
// delete if the package is contained in the search results
// But this is too expensive
async.forEach(
remotes,
function(remote, next) {
searchCache[remote].clear(next);
},
callback
);
async.forEach(remotes, function (remote, next) {
searchCache[remote].clear(next);
}, callback);
}
function resetCache() {

View File

@@ -16,40 +16,32 @@ function unregister(name, callback) {
requestUrl += '?access_token=' + config.accessToken;
}
request.del(
{
url: requestUrl,
headers: headers,
ca: config.ca.register,
strictSSL: config.strictSsl,
timeout: config.timeout
},
function(err, response) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(
createError(
'Request to ' + requestUrl + ' failed: ' + err.message,
err.code
)
);
}
// Forbidden
if (response.statusCode === 403) {
return callback(createError(response.body, 'EFORBIDDEN'));
}
// Everything other than 204 is unknown
if (response.statusCode !== 204) {
return callback(createError(response.body, 'EUNKNOWN'));
}
callback(null, {
name: name
});
request.del({
url: requestUrl,
headers: headers,
ca: config.ca.register,
strictSSL: config.strictSsl,
timeout: config.timeout
}, function (err, response) {
// If there was an internal error (e.g. timeout)
if (err) {
return callback(createError('Request to ' + requestUrl + ' failed: ' + err.message, err.code));
}
);
// Forbidden
if (response.statusCode === 403) {
return callback(createError(response.body, 'EFORBIDDEN'));
}
// Everything other than 204 is unknown
if (response.statusCode !== 204) {
return callback(createError(response.body, 'EUNKNOWN'));
}
callback(null, {
name: name
});
});
}
module.exports = unregister;

View File

@@ -22,7 +22,7 @@ function Cache(dir, options) {
}
}
Cache.prototype.get = function(key, callback) {
Cache.prototype.get = function (key, callback) {
var file;
var json = this._cache.get(key);
@@ -43,38 +43,35 @@ Cache.prototype.get = function(key, callback) {
}
file = this._getFile(key);
fs.readFile(
file,
function(err, contents) {
var json;
fs.readFile(file, function (err, contents) {
var json;
// Check if there was an error reading
// Note that if the file does not exist then
// we don't have its value
if (err) {
return callback(err.code === 'ENOENT' ? null : err);
}
// Check if there was an error reading
// Note that if the file does not exist then
// we don't have its value
if (err) {
return callback(err.code === 'ENOENT' ? null : err);
}
// If there was an error reading the file as json
// simply assume it doesn't exist
try {
json = JSON.parse(contents.toString());
} catch (e) {
return this.del(key, callback); // If so, delete it
}
// If there was an error reading the file as json
// simply assume it doesn't exist
try {
json = JSON.parse(contents.toString());
} catch (e) {
return this.del(key, callback); // If so, delete it
}
// Check if it has expired
if (this._hasExpired(json)) {
return this.del(key, callback);
}
// Check if it has expired
if (this._hasExpired(json)) {
return this.del(key, callback);
}
this._cache.set(key, json);
callback(null, json.value);
}.bind(this)
);
this._cache.set(key, json);
callback(null, json.value);
}.bind(this));
};
Cache.prototype.set = function(key, value, maxAge, callback) {
Cache.prototype.set = function (key, value, maxAge, callback) {
var file;
var entry;
var str;
@@ -105,7 +102,7 @@ Cache.prototype.set = function(key, value, maxAge, callback) {
fs.writeFile(file, str, callback);
};
Cache.prototype.del = function(key, callback) {
Cache.prototype.del = function (key, callback) {
// Delete from memory
this._cache.del(key);
@@ -114,7 +111,7 @@ Cache.prototype.del = function(key, callback) {
return callback(null);
}
fs.unlink(this._getFile(key), function(err) {
fs.unlink(this._getFile(key), function (err) {
if (err && err.code !== 'ENOENT') {
return callback(err);
}
@@ -123,7 +120,7 @@ Cache.prototype.del = function(key, callback) {
});
};
Cache.prototype.clear = function(callback) {
Cache.prototype.clear = function (callback) {
var dir = this._dir;
// Clear in memory cache
@@ -134,39 +131,35 @@ Cache.prototype.clear = function(callback) {
return callback(null);
}
fs.readdir(dir, function(err, files) {
fs.readdir(dir, function (err, files) {
if (err) {
return callback(err);
}
// Delete every file in parallel
async.forEach(
files,
function(file, next) {
fs.unlink(path.join(dir, file), function(err) {
if (err && err.code !== 'ENOENT') {
return next(err);
}
async.forEach(files, function (file, next) {
fs.unlink(path.join(dir, file), function (err) {
if (err && err.code !== 'ENOENT') {
return next(err);
}
next();
});
},
callback
);
next();
});
}, callback);
});
};
Cache.prototype.reset = function() {
Cache.prototype.reset = function () {
this._cache.reset();
};
Cache.clearRuntimeCache = function() {
Cache.clearRuntimeCache = function () {
// Note that _cache refers to the static _cache variable
// that holds other caches per dir!
// Do not confuse it with the instance cache
// Clear cache of each directory
this._cache.forEach(function(cache) {
this._cache.forEach(function (cache) {
cache.reset();
});
@@ -176,7 +169,7 @@ Cache.clearRuntimeCache = function() {
//-------------------------------
Cache.prototype._hasExpired = function(json) {
Cache.prototype._hasExpired = function (json) {
var expires = json.expires;
if (!expires || this._options.useStale) {
@@ -187,19 +180,16 @@ Cache.prototype._hasExpired = function(json) {
return Date.now() > expires;
};
Cache.prototype._getFile = function(key) {
Cache.prototype._getFile = function (key) {
// Append a truncated md5 to the end of the file to solve case issues
// on case insensitive file systems
// See: https://github.com/bower/bower/issues/859
return path.join(
this._dir,
encodeURIComponent(key) + '_' + md5(key).substr(0, 5)
);
return path.join(this._dir, encodeURIComponent(key) + '_' + md5(key).substr(0, 5));
};
Cache._cache = new LRU({
max: 5,
maxAge: 60 * 30 * 1000 // 30 minutes
maxAge: 60 * 30 * 1000 // 30 minutes
});
module.exports = Cache;

View File

@@ -1,10 +1,7 @@
var crypto = require('crypto');
function md5(contents) {
return crypto
.createHash('md5')
.update(contents)
.digest('hex');
return crypto.createHash('md5').update(contents).digest('hex');
}
module.exports = md5;

View File

@@ -4,7 +4,7 @@
"description": "Provides easy interaction with the Bower registry",
"author": "Twitter",
"license": "MIT",
"repository": "https://github.com/bower/bower/tree/master/packages/bower-registry-client",
"repository": "bower/registry-client",
"main": "Client",
"engines": {
"node": ">=0.10.0"

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,21 @@
var index = require('../../lib/index');
var expect = require('expect.js');
describe('index module', function() {
describe('requiring the index module', function() {
it('should expose a lookup method', function() {
describe('index module', function () {
describe('requiring the index module', function () {
it('should expose a lookup method', function () {
expect(index.lookup).to.be.ok;
});
it('should expose a list method', function() {
it('should expose a list method', function () {
expect(index.list).to.be.ok;
});
it('should expose a register method', function() {
it('should expose a register method', function () {
expect(index.register).to.be.ok;
});
it('should expose a search method', function() {
it('should expose a search method', function () {
expect(index.search).to.be.ok;
});
});

View File

@@ -1,23 +1,23 @@
var list = require('../../lib/list');
var expect = require('expect.js');
describe('list module', function() {
describe('requiring the list module', function() {
it('should expose a list method', function() {
describe('list module', function () {
describe('requiring the list module', function () {
it('should expose a list method', function () {
expect(typeof list === 'function').to.be.ok;
});
it('should expose a initCache method', function() {
it('should expose a initCache method', function () {
expect(list.initCache).to.be.ok;
expect(typeof list.initCache === 'function').to.be.ok;
});
it('should expose a clearCache method', function() {
it('should expose a clearCache method', function () {
expect(list.clearCache).to.be.ok;
expect(typeof list.clearCache === 'function').to.be.ok;
});
it('should expose a resetCache method', function() {
it('should expose a resetCache method', function () {
expect(list.resetCache).to.be.ok;
expect(typeof list.resetCache === 'function').to.be.ok;
});

View File

@@ -1,23 +1,23 @@
var lookup = require('../../lib/lookup');
var expect = require('expect.js');
describe('lookup module', function() {
describe('requiring the lookup module', function() {
it('should expose a lookup method', function() {
describe('lookup module', function () {
describe('requiring the lookup module', function () {
it('should expose a lookup method', function () {
expect(typeof lookup === 'function').to.be.ok;
});
it('should expose a initCache method', function() {
it('should expose a initCache method', function () {
expect(lookup.initCache).to.be.ok;
expect(typeof lookup.initCache === 'function').to.be.ok;
});
it('should expose a clearCache method', function() {
it('should expose a clearCache method', function () {
expect(lookup.clearCache).to.be.ok;
expect(typeof lookup.clearCache === 'function').to.be.ok;
});
it('should expose a resetCache method', function() {
it('should expose a resetCache method', function () {
expect(lookup.resetCache).to.be.ok;
expect(typeof lookup.resetCache === 'function').to.be.ok;
});

View File

@@ -1,9 +1,9 @@
var register = require('../../lib/register');
var expect = require('expect.js');
describe('register module', function() {
describe('requiring the register module', function() {
it('should expose a register method', function() {
describe('register module', function () {
describe('requiring the register module', function () {
it('should expose a register method', function () {
expect(typeof register === 'function').to.be.ok;
});
});

View File

@@ -1,23 +1,23 @@
var search = require('../../lib/search');
var expect = require('expect.js');
describe('search module', function() {
describe('requiring the search module', function() {
it('should expose a search method', function() {
describe('search module', function () {
describe('requiring the search module', function () {
it('should expose a search method', function () {
expect(typeof search === 'function').to.be.ok;
});
it('should expose a initCache method', function() {
it('should expose a initCache method', function () {
expect(search.initCache).to.be.ok;
expect(typeof search.initCache === 'function').to.be.ok;
});
it('should expose a clearCache method', function() {
it('should expose a clearCache method', function () {
expect(search.clearCache).to.be.ok;
expect(typeof search.clearCache === 'function').to.be.ok;
});
it('should expose a resetCache method', function() {
it('should expose a resetCache method', function () {
expect(search.resetCache).to.be.ok;
expect(typeof search.resetCache === 'function').to.be.ok;
});

View File

@@ -1,60 +1,48 @@
var Cache = require('../../../lib/util/Cache');
var expect = require('expect.js');
describe('Cache', function() {
beforeEach(function() {
describe('Cache', function () {
beforeEach(function () {
this.cache = new Cache();
});
describe('Constructor', function() {
describe('instantiating cache', function() {
it('should provide an instance of RegistryClient', function() {
describe('Constructor', function () {
describe('instantiating cache', function () {
it('should provide an instance of RegistryClient', function () {
expect(this.cache instanceof Cache).to.be.ok;
});
it('should inherit LRU cache methods', function() {
it('should inherit LRU cache methods', function () {
var self = this,
lruMethods = [
'max',
'lengthCalculator',
'length',
'itemCount',
'forEach',
'keys',
'values',
'reset',
'dump',
'dumpLru',
'set',
'has',
'get',
'peek',
'del'
];
'max', 'lengthCalculator', 'length', 'itemCount', 'forEach',
'keys', 'values', 'reset', 'dump', 'dumpLru', 'set', 'has',
'get', 'peek', 'del'
];
lruMethods.forEach(function(method) {
lruMethods.forEach(function (method) {
expect(self.cache._cache).to.have.property(method);
});
});
});
it('should have a get prototype method', function() {
it('should have a get prototype method', function () {
expect(Cache.prototype).to.have.property('get');
});
it('should have a set prototype method', function() {
it('should have a set prototype method', function () {
expect(Cache.prototype).to.have.property('set');
});
it('should have a del prototype method', function() {
it('should have a del prototype method', function () {
expect(Cache.prototype).to.have.property('del');
});
it('should have a clear prototype method', function() {
it('should have a clear prototype method', function () {
expect(Cache.prototype).to.have.property('clear');
});
it('should have a reset prototype method', function() {
it('should have a reset prototype method', function () {
expect(Cache.prototype).to.have.property('reset');
});
});

View File

@@ -1,28 +1,35 @@
var createError = require('../../../lib/util/createError');
var expect = require('expect.js');
describe('createError', function() {
beforeEach(function() {
describe('createError', function () {
beforeEach(function () {
this.err = createError('message', 500);
});
describe('requiring the createError module', function() {
it('should expose a createError method', function() {
describe('requiring the createError module', function () {
it('should expose a createError method', function () {
expect(typeof createError === 'function').to.be.ok;
});
});
describe('invoking createError', function() {
it('should return a new Error Object', function() {
describe('invoking createError', function () {
it('should return a new Error Object', function () {
expect(typeof createError() === 'object').to.be.ok;
});
it('should return an Error with message', function() {
it('should return an Error with message', function () {
expect(this.err.message).to.eql('message');
});
it('should return an Error with code', function() {
it('should return an Error with code', function () {
expect(this.err.code).to.eql(500);
});
});
});

14
packages/bower/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
/node_modules
/npm-debug.log
/test/assets/package-*/
/test/assets/temp-*/
/test/reports
/test/tmp/
/bower.json
/component.json
/bower_components
/test/sample
!/test/sample/bower.json
/npm-shrinkwrap.json

213
packages/bower/Gruntfile.js Normal file
View File

@@ -0,0 +1,213 @@
'use strict';
var tmp = require('tmp');
var childProcess = require('child_process');
var arraydiff = require('arr-diff');
var fs = require('fs');
var wrench = require('wrench');
var inquirer = require('inquirer');
var path = require('path');
module.exports = function (grunt) {
require('load-grunt-tasks')(grunt);
grunt.initConfig({
eslint: {
options: {
fix: true
},
files: [
'Gruntfile.js',
'bin/*',
'lib/**/*.js',
'test/**/*.js',
'!test/assets/**/*',
'!test/reports/**/*',
'!test/sample/**/*',
'!test/tmp/**/*'
]
},
simplemocha: {
options: {
reporter: 'spec',
timeout: '15000'
},
full: {
src: ['test/test.js']
},
short: {
options: {
reporter: 'dot'
},
src: ['test/test.js']
}
},
exec: {
'assets': {
command: 'node test/packages.js && node test/packages-svn.js'
},
'assets-force': {
command: 'node test/packages.js --force && node test/packages-svn.js --force'
},
'cover': {
command: 'node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- --timeout 30000 -R dot test/test.js'
},
'coveralls': {
command: 'npm run coveralls < test/reports/lcov.info',
exitCodes: [0, 1, 2, 3] // Alow for failure for coverage report
}
},
watch: {
files: ['<%= eslint.files %>'],
tasks: ['eslint', 'simplemocha:short']
}
});
grunt.registerTask('assets', ['exec:assets-force']);
grunt.registerTask('test', ['eslint', 'exec:assets', 'simplemocha:full']);
grunt.registerTask('cover', 'exec:cover');
grunt.registerTask('travis', ['eslint', 'exec:assets', 'exec:cover', 'exec:coveralls']);
grunt.registerTask('default', 'test');
grunt.task.registerTask('publish', 'Perform final checks and publish Bower', function () {
var npmVersion = JSON.parse(childProcess.execSync('npm version --json').toString()).npm.split('.');
var npmMajor = parseInt(npmVersion[0], 10);
var npmMinor = parseInt(npmVersion[1], 10);
var jsonPackage = require('./package');
if (npmMajor !== 3 || npmMinor < 5) {
grunt.log.writeln('You need to use at least npm@3.5 to publish bower.');
grunt.log.writeln('It is because npm 2.x produces too long paths that Windows does not handle.');
grunt.log.writeln('Please upgrade it: npm install -g npm');
process.exit(1);
}
var version = jsonPackage.version;
var changelog = fs.readFileSync('./CHANGELOG.md');
if (changelog.indexOf('## ' + version) === -1) {
grunt.log.writeln('Please add changelog.md entry for this bower version (' + version + ')');
var lastRelease = childProcess.execSync('git tag | tail -1').toString().trim();
grunt.log.writeln('Commits since last release (' + lastRelease + '): \n');
grunt.log.writeln(childProcess.execSync('git log --oneline ' + lastRelease + '..').toString());
process.exit(1);
}
if (childProcess.execSync('git rev-parse --abbrev-ref HEAD').toString().trim() !== 'master') {
grunt.log.writeln('You need to release bower from the "master" branch');
process.exit(1);
}
if (process.env.SKIP_TESTS !== '1') {
grunt.log.writeln('Reinstalling dependencies...');
childProcess.execSync('rm -rf node_modules && npm install', { stdio: [0, 1, 2] });
grunt.log.writeln('Running test suite...');
childProcess.execSync('grunt test', { stdio: [0, 1, 2] });
}
var dir = tmp.dirSync().name;
wrench.copyDirSyncRecursive(__dirname, dir, {
forceDelete: true,
include: function (path) {
return !path.match(/node_modules|\.git|test/);
}
});
grunt.log.writeln('Installing production dependencies...');
childProcess.execSync('npm install --production --silent', { cwd: dir, stdio: [0, 1, 2] });
delete jsonPackage.dependencies;
delete jsonPackage.devDependencies;
delete jsonPackage.scripts;
fs.writeFileSync(path.resolve(dir, 'package.json'), JSON.stringify(jsonPackage, null, ' ') + '\n');
grunt.log.writeln('Moving node_modules to lib directory...');
wrench.copyDirSyncRecursive(path.resolve(dir, 'node_modules'), path.resolve(dir, 'lib', 'node_modules'));
wrench.rmdirSyncRecursive(path.resolve(dir, 'node_modules'));
grunt.log.writeln('Testing bower on sample project...');
childProcess.execSync(
'cd test/sample && rm -rf bower_components && ' + dir + '/bin/bower install --force', { stdio: [0, 1, 2] }
);
var expectedPackages = (
'SHA-1 ace-builds almond angular angular-animate angular-bootstrap angular-charts angular-contenteditable ' +
'angular-deckgrid angular-fullscreen angular-gravatar angular-hotkeys angular-local-storage angular-marked ' +
'angular-moment angular-sanitize angular-touch angular-ui-router angular-ui-sortable ' +
'angulartics asEvented bootstrap coffee-script d3 es6-shim font-awesome howler jquery ' +
'jquery-ui jquery-waypoints js-beautify lodash lz-string marked moment ng-file-upload peerjs ' +
'requirejs restangular slimScroll slimScrollHorizontal venturocket-angular-slider'
).split(' ');
var installedPackages = fs.readdirSync('./test/sample/bower_components');
var installedDiff = arraydiff(expectedPackages, installedPackages);
if (installedDiff.length > 0) {
grunt.log.writeln('ERROR. Some packages were not installed by bower: ');
grunt.log.writeln(installedDiff.join(', '));
process.exit(1);
}
grunt.log.writeln('\nBower production bundle installed in:');
grunt.log.writeln(dir + '\n');
var questions = [
{
type: 'confirm',
name: 'review',
message: 'Did you review all the changes with "git diff"?',
default: false
},
{
type: 'confirm',
name: 'changelog',
message: 'Are you sure the CHANGELOG.md contains all changes?',
default: false
},
{
type: 'confirm',
name: 'tests',
message: 'Are you sure all tests are passing on Travis and Appveyor?',
default: false
},
{
type: 'confirm',
name: 'publish',
message: 'Are you SURE you want to publish ' + jsonPackage.name + '@' + jsonPackage.version + '?',
default: false
}
];
var done = this.async();
inquirer.prompt(questions, function (answers) {
if (!answers.review || !answers.changelog || !answers.tests || !answers.publish) {
grunt.log.writeln('Please publish bower after you fix this issue');
process.exit(1);
}
grunt.log.writeln('\nPlease remember to tag this relese, and add a release on Github!');
grunt.log.writeln('\nAlso, please remember to test published Bower one more time!');
grunt.log.writeln('\nPublishing Bower...');
childProcess.execSync('npm publish --tag beta', { cwd: dir, stdio: [0, 1, 2] });
done();
});
});
};

View File

@@ -1,4 +1,4 @@
Copyright (c) 2013-present Twitter and other contributors
Copyright (c) 2016 Twitter and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@@ -18,8 +18,8 @@ var logger;
var levels = Logger.LEVELS;
options = cli.readOptions({
version: { type: Boolean, shorthand: 'v' },
help: { type: Boolean, shorthand: 'h' },
'version': { type: Boolean, shorthand: 'v' },
'help': { type: Boolean, shorthand: 'h' },
'allow-root': { type: Boolean }
});
@@ -73,12 +73,12 @@ command = command && command.replace(/\./g, ' ');
if (!commandFunc) {
logger = bower.commands.help();
command = 'help';
// If the user requested help, show the command's help
// Do the same if the actual command is a group of other commands (e.g.: cache)
// If the user requested help, show the command's help
// Do the same if the actual command is a group of other commands (e.g.: cache)
} else if (options.help || !commandFunc.line) {
logger = bower.commands.help(command);
command = 'help';
// Call the line method
// Call the line method
} else {
logger = commandFunc.line(process.argv);
@@ -95,47 +95,42 @@ renderer = cli.getRenderer(command, logger.json, bower.config);
function handleLogger(logger, renderer) {
logger
.on('end', function(data) {
if (!bower.config.silent && !bower.config.quiet) {
renderer.end(data);
.on('end', function (data) {
if (!bower.config.silent && !bower.config.quiet) {
renderer.end(data);
}
})
.on('error', function (err) {
if (command !== 'help' && (err.code === 'EREADOPTIONS' || err.code === 'EINVFORMAT')) {
logger = bower.commands.help(command);
renderer = cli.getRenderer('help', logger.json, bower.config);
handleLogger(logger, renderer);
} else {
if (levels.error >= loglevel) {
renderer.error(err);
}
})
.on('error', function(err) {
if (
command !== 'help' &&
(err.code === 'EREADOPTIONS' || err.code === 'EINVFORMAT')
) {
logger = bower.commands.help(command);
renderer = cli.getRenderer('help', logger.json, bower.config);
handleLogger(logger, renderer);
} else {
if (levels.error >= loglevel) {
renderer.error(err);
}
process.exit(1);
}
})
.on('log', function(log) {
if (levels[log.level] >= loglevel) {
renderer.log(log);
}
})
.on('prompt', function(prompt, callback) {
renderer.prompt(prompt).then(function(answer) {
callback(answer);
});
process.exit(1);
}
})
.on('log', function (log) {
if (levels[log.level] >= loglevel) {
renderer.log(log);
}
})
.on('prompt', function (prompt, callback) {
renderer.prompt(prompt)
.then(function (answer) {
callback(answer);
});
});
}
handleLogger(logger, renderer);
// Warn if HOME is not SET
if (!userHome) {
logger.warn(
'no-home',
'HOME environment variable not set. User config will not be loaded.'
);
logger.warn('no-home', 'HOME environment variable not set. User config will not be loaded.');
}
if (bower.config.interactive) {

View File

@@ -22,10 +22,10 @@ function clean(logger, endpoints, options, config) {
// Generate decomposed endpoints and names based on the endpoints
if (endpoints) {
decEndpoints = endpoints.map(function(endpoint) {
decEndpoints = endpoints.map(function (endpoint) {
return endpointParser.decompose(endpoint);
});
names = decEndpoints.map(function(decEndpoint) {
names = decEndpoints.map(function (decEndpoint) {
return decEndpoint.name || decEndpoint.source;
});
}
@@ -33,26 +33,27 @@ function clean(logger, endpoints, options, config) {
return Q.all([
clearPackages(decEndpoints, config, logger),
clearLinks(names, config, logger)
]).spread(function(entries) {
])
.spread(function (entries) {
return entries;
});
}
function clearPackages(decEndpoints, config, logger) {
var repository = new PackageRepository(config, logger);
var repository = new PackageRepository(config, logger);
return repository.list().then(function(entries) {
return repository.list()
.then(function (entries) {
var promises;
// Filter entries according to the specified packages
if (decEndpoints) {
entries = entries.filter(function(entry) {
return !!mout.array.find(decEndpoints, function(decEndpoint) {
entries = entries.filter(function (entry) {
return !!mout.array.find(decEndpoints, function (decEndpoint) {
var entryPkgMeta = entry.pkgMeta;
// Check if name or source match the entry
if (
decEndpoint.name !== entryPkgMeta.name &&
if (decEndpoint.name !== entryPkgMeta.name &&
decEndpoint.source !== entryPkgMeta.name &&
decEndpoint.source !== entryPkgMeta._source
) {
@@ -66,47 +67,36 @@ function clearPackages(decEndpoints, config, logger) {
// If it's a semver target, compare using semver spec
if (semver.validRange(decEndpoint.target)) {
return semver.satisfies(
entryPkgMeta.version,
decEndpoint.target
);
return semver.satisfies(entryPkgMeta.version, decEndpoint.target);
}
// Otherwise, compare against target/release
return (
decEndpoint.target === entryPkgMeta._target ||
decEndpoint.target === entryPkgMeta._release
);
return decEndpoint.target === entryPkgMeta._target ||
decEndpoint.target === entryPkgMeta._release;
});
});
}
promises = entries.map(function(entry) {
return repository.eliminate(entry.pkgMeta).then(function() {
logger.info(
'deleted',
'Cached package ' +
entry.pkgMeta.name +
': ' +
entry.canonicalDir,
{
file: entry.canonicalDir
}
);
promises = entries.map(function (entry) {
return repository.eliminate(entry.pkgMeta)
.then(function () {
logger.info('deleted', 'Cached package ' + entry.pkgMeta.name + ': ' + entry.canonicalDir, {
file: entry.canonicalDir
});
});
});
return Q.all(promises)
.then(function() {
if (!decEndpoints) {
// Ensure that everything is cleaned,
// even invalid packages in the cache
return repository.clear();
}
})
.then(function() {
return entries;
});
.then(function () {
if (!decEndpoints) {
// Ensure that everything is cleaned,
// even invalid packages in the cache
return repository.clear();
}
})
.then(function () {
return entries;
});
});
}
@@ -116,59 +106,59 @@ function clearLinks(names, config, logger) {
// If no names are passed, grab all links
if (!names) {
promise = Q.nfcall(fs.readdir, dir).fail(function(err) {
promise = Q.nfcall(fs.readdir, dir)
.fail(function (err) {
if (err.code === 'ENOENT') {
return [];
}
throw err;
});
// Otherwise use passed ones
// Otherwise use passed ones
} else {
promise = Q.resolve(names);
}
return promise.then(function(names) {
return promise
.then(function (names) {
var promises;
var linksToRemove = [];
// Decide which links to delete
promises = names.map(function(name) {
promises = names.map(function (name) {
var link = path.join(config.storage.links, name);
return Q.nfcall(fs.readlink, link).then(
function(linkTarget) {
// Link exists, check if it points to a folder
// that still exists
return (
Q.nfcall(fs.stat, linkTarget)
.then(function(stat) {
// Target is not a folder..
if (!stat.isDirectory()) {
linksToRemove.push(link);
}
})
// Error occurred reading the link
.fail(function() {
linksToRemove.push(link);
})
);
// Ignore if link does not exist
},
function(err) {
if (err.code !== 'ENOENT') {
return Q.nfcall(fs.readlink, link)
.then(function (linkTarget) {
// Link exists, check if it points to a folder
// that still exists
return Q.nfcall(fs.stat, linkTarget)
.then(function (stat) {
// Target is not a folder..
if (!stat.isDirectory()) {
linksToRemove.push(link);
}
})
// Error occurred reading the link
.fail(function () {
linksToRemove.push(link);
});
// Ignore if link does not exist
}, function (err) {
if (err.code !== 'ENOENT') {
linksToRemove.push(link);
}
);
});
});
return Q.all(promises).then(function() {
return Q.all(promises)
.then(function () {
var promises;
// Remove each link that was declared as invalid
promises = linksToRemove.map(function(link) {
return Q.nfcall(rimraf, link).then(function() {
promises = linksToRemove.map(function (link) {
return Q.nfcall(rimraf, link)
.then(function () {
logger.info('deleted', 'Invalid link: ' + link, {
file: link
});
@@ -182,7 +172,7 @@ function clearLinks(names, config, logger) {
// -------------------
clean.readOptions = function(argv) {
clean.readOptions = function (argv) {
var cli = require('../../util/cli');
var options = cli.readOptions(argv);
var endpoints = options.argv.remain.slice(2);

View File

@@ -13,11 +13,12 @@ function list(logger, packages, options, config) {
packages = null;
}
return repository.list().then(function(entries) {
return repository.list()
.then(function (entries) {
if (packages) {
// Filter entries according to the specified packages
entries = entries.filter(function(entry) {
return !!mout.array.find(packages, function(pkg) {
entries = entries.filter(function (entry) {
return !!mout.array.find(packages, function (pkg) {
return pkg === entry.pkgMeta.name;
});
});
@@ -29,7 +30,7 @@ function list(logger, packages, options, config) {
// -------------------
list.readOptions = function(argv) {
list.readOptions = function (argv) {
var cli = require('../../util/cli');
var options = cli.readOptions(argv);
var packages = options.argv.remain.slice(2);

View File

@@ -7,17 +7,15 @@ function help(logger, name, config) {
var json;
if (name) {
json = path.resolve(
__dirname,
'../templates/json/help-' + name.replace(/\s+/g, '/') + '.json'
);
json = path.resolve(__dirname, '../templates/json/help-' + name.replace(/\s+/g, '/') + '.json');
} else {
json = path.resolve(__dirname, '../templates/json/help.json');
}
return Q.promise(function(resolve) {
return Q.promise(function (resolve) {
fs.exists(json, resolve);
}).then(function(exists) {
})
.then(function (exists) {
if (!exists) {
throw createError('Unknown command: ' + name, 'EUNKNOWNCMD', {
command: name
@@ -30,7 +28,7 @@ function help(logger, name, config) {
// -------------------
help.readOptions = function(argv) {
help.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain.slice(1).join(' ');

View File

@@ -16,7 +16,8 @@ function home(logger, name, config) {
// If no name is specified, read the project json
// If a name is specified, fetch from the package repository
if (!name) {
promise = project.hasJson().then(function(json) {
promise = project.hasJson()
.then(function (json) {
if (!json) {
throw createError('You are not inside a package', 'ENOENT');
}
@@ -25,16 +26,14 @@ function home(logger, name, config) {
});
} else {
decEndpoint = endpointParser.decompose(name);
promise = project
.getPackageRepository()
.fetch(decEndpoint)
.spread(function(canonicalDir, pkgMeta) {
return pkgMeta;
});
promise = project.getPackageRepository().fetch(decEndpoint)
.spread(function (canonicalDir, pkgMeta) {
return pkgMeta;
});
}
// Get homepage and open it
return promise.then(function(pkgMeta) {
return promise.then(function (pkgMeta) {
var homepage = pkgMeta.homepage;
if (!homepage) {
@@ -48,7 +47,7 @@ function home(logger, name, config) {
// -------------------
home.readOptions = function(argv) {
home.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];

View File

@@ -14,7 +14,7 @@ function commandFactory(id) {
var command = require(id);
var commandArgs = [].slice.call(arguments);
return withLogger(function(logger) {
return withLogger(function (logger) {
commandArgs.unshift(logger);
return command.apply(undefined, commandArgs);
@@ -27,7 +27,7 @@ function commandFactory(id) {
commandArgs = command.readOptions(argv);
return withLogger(function(logger) {
return withLogger(function (logger) {
commandArgs.unshift(logger);
return command.apply(undefined, commandArgs);
@@ -37,18 +37,16 @@ function commandFactory(id) {
function withLogger(func) {
var logger = new Logger();
Q.try(func, logger).done(
function() {
config.restore();
var args = [].slice.call(arguments);
args.unshift('end');
logger.emit.apply(logger, args);
},
function(error) {
config.restore();
logger.emit('error', error);
}
);
Q.try(func, logger)
.done(function () {
config.restore();
var args = [].slice.call(arguments);
args.unshift('end');
logger.emit.apply(logger, args);
}, function (error) {
config.restore();
logger.emit('error', error);
});
return logger;
}
@@ -58,10 +56,11 @@ function commandFactory(id) {
return runApi;
}
module.exports = {
cache: {
clean: commandFactory('./cache/clean'),
list: commandFactory('./cache/list')
list: commandFactory('./cache/list'),
},
help: commandFactory('./help'),
home: commandFactory('./home'),

View File

@@ -9,13 +9,6 @@ function info(logger, endpoint, property, config) {
return;
}
// handle @ as version divider
var splitParts = endpoint.split('/');
splitParts[splitParts.length - 1] = splitParts[
splitParts.length - 1
].replace('@', '#');
endpoint = splitParts.join('/');
var repository;
var decEndpoint;
@@ -26,10 +19,9 @@ function info(logger, endpoint, property, config) {
return Q.all([
getPkgMeta(repository, decEndpoint, property),
decEndpoint.target === '*' && !property
? repository.versions(decEndpoint.source)
: null
]).spread(function(pkgMeta, versions) {
decEndpoint.target === '*' && !property ? repository.versions(decEndpoint.source) : null
])
.spread(function (pkgMeta, versions) {
if (versions) {
return {
name: decEndpoint.source,
@@ -43,25 +35,24 @@ function info(logger, endpoint, property, config) {
}
function getPkgMeta(repository, decEndpoint, property) {
return repository
.fetch(decEndpoint)
.spread(function(canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function(value, key) {
return key.charAt(0) !== '_';
});
// Retrieve specific property
if (property) {
pkgMeta = mout.object.get(pkgMeta, property);
}
return pkgMeta;
return repository.fetch(decEndpoint)
.spread(function (canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function (value, key) {
return key.charAt(0) !== '_';
});
// Retrieve specific property
if (property) {
pkgMeta = mout.object.get(pkgMeta, property);
}
return pkgMeta;
});
}
// -------------------
info.readOptions = function(argv) {
info.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var pkg = options.argv.remain[1];

View File

@@ -0,0 +1,313 @@
var mout = require('mout');
var fs = require('../util/fs');
var path = require('path');
var Q = require('q');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var cmd = require('../util/cmd');
var createError = require('../util/createError');
function init(logger, config) {
var project;
config = config || {};
if (!config.cwd) {
config.cwd = process.cwd();
}
config = defaultConfig(config);
// This command requires interactive to be enabled
if (!config.interactive) {
throw createError('Register requires an interactive shell', 'ENOINT', {
details: 'Note that you can manually force an interactive shell with --config.interactive'
});
}
project = new Project(config, logger);
// Start with existing JSON details
return readJson(project, logger)
// Fill in defaults
.then(setDefaults.bind(null, config))
// Now prompt user to make changes
.then(promptUser.bind(null, logger))
// Set ignore based on the response
.spread(setIgnore.bind(null, config))
// Set dependencies based on the response
.spread(setDependencies.bind(null, project))
// All done!
.spread(saveJson.bind(null, project, logger));
}
function readJson(project, logger) {
return project.hasJson()
.then(function (json) {
if (json) {
logger.warn('existing', 'The existing ' + path.basename(json) + ' file will be used and filled in');
}
return project.getJson();
});
}
function saveJson(project, logger, json) {
// Cleanup empty props (null values, empty strings, objects and arrays)
mout.object.forOwn(json, function (value, key) {
if (!validConfigValue(value)) {
delete json[key];
}
});
logger.info('json', 'Generated json', { json: json });
// Confirm the json with the user
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Looks good?',
default: true
})
.then(function (good) {
if (!good) {
return null;
}
// Save json (true forces file creation)
return project.saveJson(true);
});
}
// Test if value is of a type supported by bower.json[0] - Object, Array, String, Boolean - or a Number
// [0]: https://github.com/bower/bower.json-spec
function validConfigValue(val) {
return (
mout.lang.isObject(val) ||
mout.lang.isArray(val) ||
mout.lang.isString(val) ||
mout.lang.isBoolean(val) ||
mout.lang.isNumber(val)
);
}
function setDefaults(config, json) {
var name;
var promise = Q.resolve();
// Name
if (!json.name) {
json.name = path.basename(config.cwd);
}
// Main
if (!json.main) {
// Remove '.js' from the end of the package name if it is there
name = path.basename(json.name, '.js');
if (fs.existsSync(path.join(config.cwd, 'index.js'))) {
json.main = 'index.js';
} else if (fs.existsSync(path.join(config.cwd, name + '.js'))) {
json.main = name + '.js';
}
}
// Homepage
if (!json.homepage) {
// Set as GitHub homepage if it's a GitHub repository
promise = promise.then(function () {
return cmd('git', ['config', '--get', 'remote.origin.url'])
.spread(function (stdout) {
var pair;
stdout = stdout.trim();
if (!stdout) {
return;
}
pair = GitHubResolver.getOrgRepoPair(stdout);
if (pair) {
json.homepage = 'https://github.com/' + pair.org + '/' + pair.repo;
}
})
.fail(function () { });
});
}
if (!json.authors) {
promise = promise.then(function () {
// Get the user name configured in git
return cmd('git', ['config', '--get', '--global', 'user.name'])
.spread(function (stdout) {
var gitEmail;
var gitName = stdout.trim();
// Abort if no name specified
if (!gitName) {
return;
}
// Get the user email configured in git
return cmd('git', ['config', '--get', '--global', 'user.email'])
.spread(function (stdout) {
gitEmail = stdout.trim();
}, function () {})
.then(function () {
json.authors = gitName;
json.authors += gitEmail ? ' <' + gitEmail + '>' : '';
});
}, function () {});
});
}
return promise.then(function () {
return json;
});
}
function promptUser(logger, json) {
var questions = [
{
'name': 'name',
'message': 'name',
'default': json.name,
'type': 'input'
},
{
'name': 'description',
'message': 'description',
'default': json.description,
'type': 'input'
},
{
'name': 'main',
'message': 'main file',
'default': json.main,
'type': 'input'
},
{
'name': 'keywords',
'message': 'keywords',
'default': json.keywords ? json.keywords.toString() : null,
'type': 'input'
},
{
'name': 'authors',
'message': 'authors',
'default': json.authors ? json.authors.toString() : null,
'type': 'input'
},
{
'name': 'license',
'message': 'license',
'default': json.license || 'MIT',
'type': 'input'
},
{
'name': 'homepage',
'message': 'homepage',
'default': json.homepage,
'type': 'input'
},
{
'name': 'dependencies',
'message': 'set currently installed components as dependencies?',
'default': !mout.object.size(json.dependencies) && !mout.object.size(json.devDependencies),
'type': 'confirm'
},
{
'name': 'ignore',
'message': 'add commonly ignored files to ignore list?',
'default': true,
'type': 'confirm'
},
{
'name': 'private',
'message': 'would you like to mark this package as private which prevents it from being accidentally published to the registry?',
'default': !!json.private,
'type': 'confirm'
}
];
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
json.name = answers.name;
json.description = answers.description;
json.main = answers.main;
json.keywords = toArray(answers.keywords);
json.authors = toArray(answers.authors, ',');
json.license = answers.license;
json.homepage = answers.homepage;
json.private = answers.private || null;
return [json, answers];
});
}
function toArray(value, splitter) {
var arr = value.split(splitter || /[\s,]/);
// Trim values
arr = arr.map(function (item) {
return item.trim();
});
// Filter empty values
arr = arr.filter(function (item) {
return !!item;
});
return arr.length ? arr : null;
}
function setIgnore(config, json, answers) {
if (answers.ignore) {
json.ignore = mout.array.combine(json.ignore || [], [
'**/.*',
'node_modules',
'bower_components',
config.directory,
'test',
'tests'
]);
}
return [json, answers];
}
function setDependencies(project, json, answers) {
if (answers.dependencies) {
return project.getTree()
.spread(function (tree, flattened, extraneous) {
if (extraneous.length) {
json.dependencies = {};
// Add extraneous as dependencies
extraneous.forEach(function (extra) {
var jsonEndpoint;
// Skip linked packages
if (extra.linked) {
return;
}
jsonEndpoint = endpointParser.decomposed2json(extra.endpoint);
mout.object.mixIn(json.dependencies, jsonEndpoint);
});
}
return [json, answers];
});
}
return [json, answers];
}
// -------------------
init.readOptions = function (argv) {
return [];
};
module.exports = init;

View File

@@ -15,14 +15,7 @@ function install(logger, endpoints, options, config) {
// Convert endpoints to decomposed endpoints
endpoints = endpoints || [];
decEndpoints = endpoints.map(function(endpoint) {
// handle @ as version divider
var splitParts = endpoint.split('/');
splitParts[splitParts.length - 1] = splitParts[
splitParts.length - 1
].replace('@', '#');
endpoint = splitParts.join('/');
decEndpoints = endpoints.map(function (endpoint) {
return endpointParser.decompose(endpoint);
});
@@ -31,19 +24,16 @@ function install(logger, endpoints, options, config) {
// -------------------
install.readOptions = function(argv) {
install.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(
{
'force-latest': { type: Boolean, shorthand: 'F' },
production: { type: Boolean, shorthand: 'p' },
save: { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' },
'save-exact': { type: Boolean, shorthand: 'E' }
},
argv
);
var options = cli.readOptions({
'force-latest': { type: Boolean, shorthand: 'F'},
'production': { type: Boolean, shorthand: 'p' },
'save': { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' },
'save-exact': { type: Boolean, shorthand: 'E' }
}, argv);
var packages = options.argv.remain.slice(1);

View File

@@ -20,24 +20,23 @@ function linkSelf(logger, config) {
config = defaultConfig(config);
project = new Project(config, logger);
return project.getJson().then(function(json) {
return project.getJson()
.then(function (json) {
var src = config.cwd;
var dst = path.join(config.storage.links, json.name);
// Delete previous link if any
return (
Q.nfcall(rimraf, dst)
// Link globally
.then(function() {
return createLink(src, dst);
})
.then(function() {
return {
src: src,
dst: dst
};
})
);
return Q.nfcall(rimraf, dst)
// Link globally
.then(function () {
return createLink(src, dst);
})
.then(function () {
return {
src: src,
dst: dst
};
});
});
}
@@ -54,29 +53,27 @@ function linkTo(logger, name, localName, config) {
dst = path.join(relativeToBaseDir(config.cwd)(config.directory), localName);
// Delete destination folder if any
return (
Q.nfcall(rimraf, dst)
// Link locally
.then(function() {
return createLink(src, dst);
})
// Install linked package deps
.then(function() {
return project.update([localName]);
})
.then(function(installed) {
return {
src: src,
dst: dst,
installed: installed
};
})
);
return Q.nfcall(rimraf, dst)
// Link locally
.then(function () {
return createLink(src, dst);
})
// Install linked package deps
.then(function () {
return project.update([localName]);
})
.then(function (installed) {
return {
src: src,
dst: dst,
installed: installed
};
});
}
// -------------------
link.readOptions = function(argv) {
link.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];

View File

@@ -11,48 +11,39 @@ function list(logger, options, config) {
options = options || {};
// Make relative option true by default when used with paths
if (options.paths && options.relative == null) {
if (options.paths && options.relative == null) {
options.relative = true;
}
config = defaultConfig(config);
project = new Project(config, logger);
return project.getTree(options).spread(function(tree, flattened) {
return project.getTree(options)
.spread(function (tree, flattened) {
// Relativize paths
// Also normalize paths on windows
project.walkTree(
tree,
function(node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(
config.cwd,
node.canonicalDir
);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
}
},
true
);
// Note that we need to to parse the flattened tree because it might
// contain additional packages
mout.object.forOwn(flattened, function(node) {
project.walkTree(tree, function (node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(
config.cwd,
node.canonicalDir
);
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
}
}, true);
// Note that we need to to parse the flattened tree because it might
// contain additional packages
mout.object.forOwn(flattened, function (node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(config.cwd, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
@@ -70,7 +61,8 @@ function list(logger, options, config) {
}
// Check for new versions
return checkVersions(project, tree, logger).then(function() {
return checkVersions(project, tree, logger)
.then(function () {
return tree;
});
});
@@ -82,40 +74,32 @@ function checkVersions(project, tree, logger) {
var repository = project.getPackageRepository();
// Gather all nodes, ignoring linked nodes
project.walkTree(
tree,
function(node) {
if (!node.linked) {
nodes.push(node);
}
},
true
);
project.walkTree(tree, function (node) {
if (!node.linked) {
nodes.push(node);
}
}, true);
if (nodes.length) {
logger.info(
'check-new',
'Checking for new versions of the project dependencies...'
);
logger.info('check-new', 'Checking for new versions of the project dependencies...');
}
// Check for new versions for each node
promises = nodes.map(function(node) {
promises = nodes.map(function (node) {
var target = node.endpoint.target;
return repository
.versions(node.endpoint.source)
.then(function(versions) {
node.versions = versions;
return repository.versions(node.endpoint.source)
.then(function (versions) {
node.versions = versions;
// Do not check if node's target is not a valid semver one
if (versions.length && semver.validRange(target)) {
node.update = {
target: semver.maxSatisfying(versions, target),
latest: semver.maxSatisfying(versions, '*')
};
}
});
// Do not check if node's target is not a valid semver one
if (versions.length && semver.validRange(target)) {
node.update = {
target: semver.maxSatisfying(versions, target),
latest: semver.maxSatisfying(versions, '*')
};
}
});
});
// Set the versions also for the root node
@@ -127,7 +111,7 @@ function checkVersions(project, tree, logger) {
function paths(flattened) {
var ret = {};
mout.object.forOwn(flattened, function(pkg, name) {
mout.object.forOwn(flattened, function (pkg, name) {
var main;
if (pkg.missing) {
@@ -148,7 +132,7 @@ function paths(flattened) {
}
// Concatenate each main entry with the canonical dir
main = main.map(function(part) {
main = main.map(function (part) {
return normalize(path.join(pkg.canonicalDir, part).trim());
});
@@ -166,16 +150,13 @@ function normalize(src) {
// -------------------
list.readOptions = function(argv) {
list.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(
{
paths: { type: Boolean, shorthand: 'p' },
relative: { type: Boolean, shorthand: 'r' }
},
argv
);
var options = cli.readOptions({
'paths': { type: Boolean, shorthand: 'p' },
'relative': { type: Boolean, shorthand: 'r' }
}, argv);
delete options.argv;

View File

@@ -0,0 +1,125 @@
var Configstore = require('configstore');
var GitHub = require('github');
var Q = require('q');
var createError = require('../util/createError');
var defaultConfig = require('../config');
function login(logger, options, config) {
var configstore = new Configstore('bower-github');
config = defaultConfig(config);
var promise;
options = options || {};
if (options.token) {
promise = Q.resolve({ token: options.token });
} else {
// This command requires interactive to be enabled
if (!config.interactive) {
logger.emit('error', createError('Login requires an interactive shell', 'ENOINT', {
details: 'Note that you can manually force an interactive shell with --config.interactive'
}));
return;
}
var questions = [
{
'name': 'username',
'message': 'Username',
'type': 'input',
'default': configstore.get('username')
},
{
'name': 'password',
'message': 'Password',
'type': 'password'
}
];
var github = new GitHub({
version: '3.0.0'
});
promise = Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
configstore.set('username', answers.username);
github.authenticate({
type: 'basic',
username: answers.username,
password: answers.password
});
return Q.ninvoke(github.authorization, 'create', {
scopes: ['user', 'repo'],
note: 'Bower command line client (' + (new Date()).toISOString() + ')'
});
});
}
return promise.then(function (result) {
configstore.set('accessToken', result.token);
logger.info('EAUTH', 'Logged in as ' + configstore.get('username'), {});
return result;
}, function (error) {
var message;
try {
message = JSON.parse(error.message).message;
} catch (e) {
message = 'Authorization failed';
}
var questions = [
{
'name': 'otpcode',
'message': 'Two-Factor Auth Code',
'type': 'input'
}
];
if (message === 'Must specify two-factor authentication OTP code.') {
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
return Q.ninvoke(github.authorization, 'create', {
scopes: ['user', 'repo'],
note: 'Bower command line client (' + (new Date()).toISOString() + ')',
headers: {
'X-GitHub-OTP': answers.otpcode
}
});
})
.then(function (result) {
configstore.set('accessToken', result.token);
logger.info('EAUTH', 'Logged in as ' + configstore.get('username'), {});
return result;
}, function () {
logger.emit('error', createError(message, 'EAUTH'));
});
} else {
logger.emit('error', createError(message, 'EAUTH'));
}
});
}
// -------------------
login.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
token: { type: String, shorthand: 't' },
}, argv);
delete options.argv;
return [options];
};
module.exports = login;

View File

@@ -12,21 +12,18 @@ function lookup(logger, name, config) {
var repository = new PackageRepository(config, logger);
var registryClient = repository.getRegistryClient();
return Q.nfcall(registryClient.lookup.bind(registryClient), name).then(
function(entry) {
return !entry
? null
: {
name: name,
url: entry.url
};
}
);
return Q.nfcall(registryClient.lookup.bind(registryClient), name)
.then(function (entry) {
return !entry ? null : {
name: name,
url: entry && entry.url
};
});
}
// -------------------
lookup.readOptions = function(argv) {
lookup.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];

View File

@@ -0,0 +1,55 @@
var mout = require('mout');
var Project = require('../core/Project');
var defaultConfig = require('../config');
function prune(logger, options, config) {
var project;
options = options || {};
config = defaultConfig(config);
project = new Project(config, logger);
return clean(project, options);
}
function clean(project, options, removed) {
removed = removed || {};
// Continually call clean until there is no more extraneous
// dependencies to remove
return project.getTree(options)
.spread(function (tree, flattened, extraneous) {
var names = extraneous.map(function (extra) {
return extra.endpoint.name;
});
// Uninstall extraneous
return project.uninstall(names, options)
.then(function (uninstalled) {
// Are we done?
if (!mout.object.size(uninstalled)) {
return removed;
}
// Not yet, recurse!
mout.object.mixIn(removed, uninstalled);
return clean(project, options, removed);
});
});
}
// -------------------
prune.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
'production': { type: Boolean, shorthand: 'p' },
}, argv);
delete options.argv;
return [options];
};
module.exports = prune;

View File

@@ -0,0 +1,88 @@
var Q = require('q');
var chalk = require('chalk');
var PackageRepository = require('../core/PackageRepository');
var createError = require('../util/createError');
var defaultConfig = require('../config');
function register(logger, name, source, config) {
var repository;
var registryClient;
var force;
var url;
var githubSourceRegex = /^\w[\w-]*\/\w[\w-]*$/;
var getGithubUrl = function (source) {
return 'git@github.com:' + source + '.git';
};
config = defaultConfig(config);
force = config.force;
name = (name || '').trim();
source = (source || '').trim();
url = source.match(githubSourceRegex) ? getGithubUrl(source) : source;
// Bypass any cache
config.offline = false;
config.force = true;
return Q.try(function () {
// Verify name and url
if (!name || !url) {
throw createError('Usage: bower register <name> <url>', 'EINVFORMAT');
}
// Attempt to resolve the package referenced by the URL to ensure
// everything is ok before registering
repository = new PackageRepository(config, logger);
return repository.fetch({ name: name, source: url, target: '*' });
})
.spread(function (canonicalDir, pkgMeta) {
if (pkgMeta.private) {
throw createError('The package you are trying to register is marked as private', 'EPRIV');
}
// If non interactive or user forced, bypass confirmation
if (!config.interactive || force) {
return true;
}
// Confirm if the user really wants to register
return Q.nfcall(logger.prompt.bind(logger), {
type: 'confirm',
message: 'Registering a package will make it installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue?',
default: true
});
})
.then(function (result) {
// If user response was negative, abort
if (!result) {
return;
}
// Register
registryClient = repository.getRegistryClient();
logger.action('register', url, {
name: name,
url: url
});
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
});
}
// -------------------
register.readOptions = function (argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain[1];
var url = options.argv.remain[2];
return [name, url];
};
module.exports = register;

Some files were not shown because too many files have changed in this diff Show More