Compare commits

..

98 Commits

Author SHA1 Message Date
Sampo Silvennoinen
4e0c3c1181 CI: add Node.js 18, 20 & 22 (#2630)
* CI workflow: add Node.js 18, 20 & 22

* CI workflow: pin old Node.js versions to older MacOS

MacOS-latest can run Node versions 6 - 14 anymore. Pin them to MacOS-13 to keep the actions running

* CI: update used GH action versions

* CI: allow failure on Windows

All Windows pipelines currently fail for some integration tests. It seems to be a question of running actual git commands inside a Windows based Github action runner. Reverting it to older Windows runner wont help.
2024-10-13 12:45:04 +02:00
Sampo Silvennoinen
a0d4444324 CI workflow: add Node 16 (#2593)
Adds Node 16 to version matrix for tests

Co-authored-by: Adam Stankiewicz <sheerun@sher.pl>
2022-03-14 16:52:35 +01:00
Adam Stankiewicz
d765b2bb88 Bump to 1.8.14 2022-03-14 16:37:01 +01:00
Adam Stankiewicz
ca23b460b3 Run CI only on node 6+ 2022-03-14 16:31:27 +01:00
Eduardo Freitas
7f26c5b856 Fix bug unauthenticated git protocol in GitHubResolver (#2612)
Co-authored-by: Adam Stankiewicz <sheerun@sher.pl>
2022-03-14 16:23:54 +01:00
Adam Stankiewicz
4b5722f2bd Update README.md 2022-03-10 22:30:29 +01:00
Adam Stankiewicz
557c1cd7f1 Fix mode for bin/bower 2021-11-15 04:10:45 +01:00
Adam Stankiewicz
74560b7308 Fix child process execution 2021-11-15 03:58:38 +01:00
Adam Stankiewicz
2905791817 Fix running bower on non-windows 2021-11-15 03:30:58 +01:00
Adam Stankiewicz
dfdda3fda3 Merge remote-tracking branch 'origin/master' 2021-11-15 03:25:18 +01:00
Adam Stankiewicz
fa36814f64 Bump to 1.8.13 2021-11-15 03:23:11 +01:00
Adam Stankiewicz
f19bc34f84 Make sure correct git/svn binary is always used 2021-11-15 03:22:34 +01:00
Ikko Ashimine
eb8659ea32 Fix typo in Manager.js (#2607)
depdendency -> dependency
2021-10-20 14:58:32 +02:00
Ikko Ashimine
537b9b4558 Fix typo in Project.js (#2597)
propery -> property
2021-06-08 10:05:45 +02:00
Adam Stankiewicz
372fefbf1c Finish fixing linting 2021-01-18 17:34:20 +01:00
Adam Stankiewicz
f6ced09f76 Fix linting 2021-01-18 16:48:23 +01:00
Adam Stankiewicz
f18330b248 Bump to 1.8.12 2021-01-18 16:41:52 +01:00
Adam Stankiewicz
a1b5d93f13 Remove wild .npmignore before publishing, #2591 2021-01-18 16:41:32 +01:00
Adam Stankiewicz
74baec8a60 Bump to 1.8.11 2021-01-18 12:00:36 +01:00
Adam Stankiewicz
771c7d0ac8 Fix packaging node_modules 2021-01-18 12:00:15 +01:00
Adam Stankiewicz
50e98031e5 Fix publish script 2021-01-18 11:31:15 +01:00
Adam Stankiewicz
41126ec03e Update build script for 1.8.10 2021-01-18 11:01:39 +01:00
Adam Stankiewicz
bb563b01c2 Bump to 1.8.10 2021-01-14 18:42:22 +01:00
Adam Stankiewicz
c94f3f6422 Fix production packing 2021-01-14 18:42:06 +01:00
Adam Stankiewicz
ab589e46e9 Bump to 1.8.9 2021-01-14 17:21:29 +01:00
Adam Stankiewicz
341c3772f0 Improve publish script 2021-01-11 23:56:45 +01:00
Adam Stankiewicz
9734497faf Add package-lock.json to gitignores (we use yarn.lock) 2021-01-11 19:31:26 +01:00
dependabot[bot]
87cc6a3c6c Bump tar-fs from 1.15.3 to 1.16.2 (#2576)
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 1.15.3 to 1.16.2.
- [Release notes](https://github.com/mafintosh/tar-fs/releases)
- [Commits](https://github.com/mafintosh/tar-fs/compare/v1.15.3...v1.16.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-11 19:18:41 +01:00
dependabot[bot]
2a47ba1a1d Bump ini from 1.3.4 to 1.3.7 (#2589)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.4 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.4...v1.3.7)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-11 19:17:13 +01:00
dependabot[bot]
26212e7a67 Bump handlebars from 4.3.0 to 4.5.3 (#2586)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.3.0 to 4.5.3.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.3.0...v4.5.3)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-11 19:16:10 +01:00
Adam Stankiewicz
7af181f276 Update CONTRIBUTING.md 2020-05-19 10:01:56 +02:00
Adam Stankiewicz
975f6216e5 Update CONTRIBUTING.md 2020-05-19 10:01:23 +02:00
Adam Stankiewicz
d8747d794e Update minimist (#2578) 2020-04-28 14:04:17 +02:00
Adam Stankiewicz
8630604e82 Update mocha (#2577) 2020-04-28 13:19:08 +02:00
Adam Stankiewicz
8f604adf26 Revert "Bump tar-fs from 1.15.3 to 1.16.2 (#2569)"
This reverts commit 3f01228db4.
2020-04-28 12:41:52 +02:00
Satoshi Nakamura
34b4644ca3 Fix typos in changelog (#2574) 2020-04-28 01:11:43 +02:00
dependabot[bot]
3f01228db4 Bump tar-fs from 1.15.3 to 1.16.2 (#2569)
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 1.15.3 to 1.16.2.
- [Release notes](https://github.com/mafintosh/tar-fs/releases)
- [Commits](https://github.com/mafintosh/tar-fs/compare/v1.15.3...v1.16.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-27 22:10:20 +02:00
Adam Stankiewicz
f5dbab0eac Update yarn.lock 2020-04-27 21:31:30 +02:00
Adam Stankiewicz
26031df67c Prevent errors when expanded env variable does not exist, fixes #2573 2020-04-27 21:29:47 +02:00
Adam Stankiewicz
1226186126 Update dependencies and yarn.lock 2020-04-27 18:05:53 +02:00
Adam Stankiewicz
752d6d42d7 Update deep-extend 2020-04-27 18:03:33 +02:00
Adam Stankiewicz
587f08844b Update yarn.lock 2020-04-27 13:57:27 +02:00
Adam Stankiewicz
6afb7b19ef Fix requires in bower-json 2020-04-27 13:55:22 +02:00
Adam Stankiewicz
6798f961f7 Update yarn.lock 2020-04-27 01:58:42 +02:00
Adam Stankiewicz
f1685ec484 Bump bower-json 2020-04-27 01:57:47 +02:00
Adam Stankiewicz
6682e312ac Vendor ext-name (drop meow dependency) (#2572) 2020-04-26 22:51:21 +02:00
Adam Stankiewicz
b123fed19b Remove coveralls and istanbul 2020-04-26 22:21:06 +02:00
Adam Stankiewicz
f6443b675a Update eslint (#2571) 2020-04-26 22:19:22 +02:00
dependabot[bot]
199e46c94a Bump fstream from 1.0.11 to 1.0.12 (#2568)
Bumps [fstream](https://github.com/npm/fstream) from 1.0.11 to 1.0.12.
- [Release notes](https://github.com/npm/fstream/releases)
- [Commits](https://github.com/npm/fstream/compare/v1.0.11...v1.0.12)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-26 20:43:39 +02:00
dependabot[bot]
d3455643d2 Bump is-my-json-valid from 2.16.1 to 2.20.0 (#2570)
Bumps [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid) from 2.16.1 to 2.20.0.
- [Release notes](https://github.com/mafintosh/is-my-json-valid/releases)
- [Commits](https://github.com/mafintosh/is-my-json-valid/compare/v2.16.1...v2.20.0)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-04-26 20:43:28 +02:00
Adam Stankiewicz
95d5a35f70 Restore yarn.lock 2020-04-26 20:20:59 +02:00
Adam Stankiewicz
7c922f577a Pin chmodr 2020-04-26 20:14:34 +02:00
Adam Stankiewicz
7ae3808b44 Fix linting 2020-04-26 19:57:41 +02:00
Adam Stankiewicz
f7b21d38a8 Fix formatting 2020-04-26 19:52:20 +02:00
Adam Stankiewicz
807eada63f Bump bower-config in bower 2020-04-26 19:48:22 +02:00
Adam Stankiewicz
c1abde9269 Bump bower-config 2020-04-26 19:47:20 +02:00
Adam Stankiewicz
f82a541679 Bump bower-config 2020-04-26 19:42:39 +02:00
Adam Stankiewicz
ce59f15cc9 Apply security fix for minimist, closes #2564 2020-04-26 19:40:42 +02:00
Adam Stankiewicz
845995fccd Allow to run bower with sudo by default, closes #2406 2020-04-26 15:58:38 +02:00
Adam Stankiewicz
ebd99ad866 Update support channels 2020-04-26 15:37:03 +02:00
Adam Stankiewicz
26a37942de Update SECURITY.md 2020-04-25 02:41:14 +02:00
Adam Stankiewicz
390dec6d7f Create SECURITY.md 2020-04-25 02:40:23 +02:00
Adam Stankiewicz
234ee9149d Update travis badges and remove old files 2020-04-25 02:23:19 +02:00
Adam Stankiewicz
bcb1911268 Test subpackages, all node versions, and all platforms (#2554) 2020-04-25 02:14:49 +02:00
Adam Stankiewicz
c18aa260f3 Update sponsor badges 2020-04-20 14:05:09 +02:00
Adam Stankiewicz
9d5f7b7f61 Update README 2020-04-19 15:27:13 +02:00
Tim Gates
71661c6460 docs: Fix simple typo, wheter -> whether (#2563) 2020-04-02 16:50:45 +02:00
dependabot[bot]
67590f7728 Bump handlebars from 4.1.2 to 4.3.0 (#2555)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.1.2 to 4.3.0.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.1.2...v4.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-01-15 16:37:53 +01:00
dependabot[bot]
4e68c23c87 Bump stringstream from 0.0.5 to 0.0.6 (#2553)
Bumps [stringstream](https://github.com/mhart/StringStream) from 0.0.5 to 0.0.6.
- [Release notes](https://github.com/mhart/StringStream/releases)
- [Commits](https://github.com/mhart/StringStream/compare/v0.0.5...v0.0.6)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 12:59:40 +01:00
dependabot[bot]
fcc02c4200 Bump handlebars from 4.0.10 to 4.1.2 (#2552)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.0.10 to 4.1.2.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/wycats/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.0.10...v4.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 12:33:10 +01:00
dependabot[bot]
d24e7be811 Bump mime from 1.4.0 to 1.6.0 (#2551)
Bumps [mime](https://github.com/broofa/node-mime) from 1.4.0 to 1.6.0.
- [Release notes](https://github.com/broofa/node-mime/releases)
- [Changelog](https://github.com/broofa/node-mime/blob/v1.6.0/CHANGELOG.md)
- [Commits](https://github.com/broofa/node-mime/compare/v1.4.0...v1.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 11:41:29 +01:00
dependabot[bot]
746fd3a669 Bump sshpk from 1.13.1 to 1.16.1 (#2550)
Bumps [sshpk](https://github.com/joyent/node-sshpk) from 1.13.1 to 1.16.1.
- [Release notes](https://github.com/joyent/node-sshpk/releases)
- [Commits](https://github.com/joyent/node-sshpk/compare/v1.13.1...v1.16.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 09:35:48 +01:00
dependabot[bot]
031c7731a4 Bump extend from 3.0.1 to 3.0.2 (#2549)
Bumps [extend](https://github.com/justmoon/node-extend) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/justmoon/node-extend/releases)
- [Changelog](https://github.com/justmoon/node-extend/blob/master/CHANGELOG.md)
- [Commits](https://github.com/justmoon/node-extend/compare/v3.0.1...v3.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-12-10 09:35:31 +01:00
Adam Stankiewicz
fa62654b9c Test node 10 and 12 on Windows 2019-12-10 09:26:12 +01:00
Adam Stankiewicz
fd2d448b7e Test node 10 and 12 on CI (#2548) 2019-12-10 08:53:36 +01:00
Adam Stankiewicz
997fb2b298 Create FUNDING.yml 2019-11-25 23:18:17 +01:00
Adam Stankiewicz
9c9f332ea0 Add sponsor slots 2019-08-29 17:19:21 +02:00
Adam Stankiewicz
1861fa4355 Add sponsor slots 2019-08-29 17:18:09 +02:00
Adam Stankiewicz
79a539ffb4 Add sponsor slots 2019-06-26 14:30:21 +02:00
Adam Stankiewicz
0608b26364 Add sponsors slots 2019-06-26 14:28:47 +02:00
Adam Stankiewicz
15e5b9709d Add some slots 2019-06-26 14:26:05 +02:00
Adam Stankiewicz
67741b4bfe Bump to 1.8.8 2019-01-23 22:15:30 +01:00
Adam Stankiewicz
45c6bfa86f Fix .tar.gz extract vulnerability 2019-01-23 22:14:48 +01:00
Adam Stankiewicz
4f68fc7daa Update decompress-zip and bump 2019-01-17 23:35:49 +01:00
Adam Stankiewicz
206046b271 Bump to 1.8.6 2019-01-17 14:44:28 +01:00
Adam Stankiewicz
43894f5149 Bump to 1.8.5 2019-01-17 14:19:04 +01:00
Adam Stankiewicz
6390815c5f Update decompress-zip 2019-01-17 13:03:49 +01:00
Adam Stankiewicz
e8b94ecbd0 Mention parcel 2018-06-23 18:06:56 +02:00
Adam Stankiewicz
51feb8f925 Fix release script 2018-04-19 11:07:03 +02:00
Adam Stankiewicz
1c15deadc0 Bump to 1.8.4 2018-03-28 20:43:29 +02:00
Adam Stankiewicz
2aa1f27367 Update publish script 2018-03-28 20:16:27 +02:00
Adam Stankiewicz
275601601a Bump to 1.8.3 2018-03-28 19:52:42 +02:00
Adam Stankiewicz
c22c09546e Disable testing per commit and update changelog 2018-03-28 19:33:16 +02:00
Adam Stankiewicz
6bc778df32 Format with prettier (#2510) 2018-03-28 19:28:43 +02:00
Adam Stankiewicz
451c60ec20 Do not store resolutions if --save is not used, fixes #2344 (#2508) 2018-03-28 18:15:30 +02:00
Adam Stankiewicz
d6a18ae7ee Revert "Update request version in bower-registry-client #2336"
This reverts commit 1e2c27f338.
2018-03-28 18:09:01 +02:00
Adam Stankiewicz
b62faa19a6 Update request version in bower-registry-client #2336 2018-03-28 18:09:01 +02:00
Adam Stankiewicz
50ee729ea2 Allow to disable shorthand resolver (#2507) 2018-03-28 17:58:56 +02:00
188 changed files with 18170 additions and 13669 deletions

View File

@@ -1,46 +0,0 @@
# http://www.appveyor.com/docs/appveyor-yml
# Set build version format here instead of in the admin panel.
version: "{build}"
# Fix line endings in Windows. (runs before repo cloning)
init:
- git config --global core.autocrlf input
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.10"
- nodejs_version: "0.12"
- nodejs_version: "4"
- nodejs_version: "6"
- nodejs_version: "8"
- nodejs_version: "9"
# Finish on first failed build
matrix:
fast_finish: true
# Install node, display versions, install dependencies
install:
- ps: Install-Product node 8
- node --version && npm --version
- git --version && svn --version
- npm install -g yarn grunt
- yarn
- ps: Install-Product node $env:nodejs_version
# Post-install test scripts.
test_script:
- cmd: npm run ci
# Make clone much faster
shallow_clone: true
# Disable Visual Studio build and deploy
build: off
deploy: off
# Cache node modules, and refresh if package.json changes
cache:
- "%LOCALAPPDATA%\\Yarn"

6
.eslintignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules
test/assets
test/reports
test/sample
test/tmp
packages/bower-logger/test

View File

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

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
open_collective: bower

88
.github/workflows/nodejs.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: build
on:
push:
branches:
- master
pull_request:
branches:
- '**'
jobs:
test:
name: Node v${{ matrix.node-version }} on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# https://github.com/actions/setup-node/issues/27
node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x]
os: [ubuntu-latest, macOS-latest, windows-latest]
include:
# Pin deprecated Node.js versions on Mac to specific MacOS,
# as later OS architecture wont support them anymore.
- node-version: 6.x
os: macOS-13
- node-version: 8.x
os: macOS-13
- node-version: 10.x
os: macOS-13
- node-version: 12.x
os: macOS-13
- node-version: 14.x
os: macOS-13
exclude:
# Exclude older Node.js versions from macOS-latest
- node-version: 6.x
os: macOS-latest
- node-version: 8.x
os: macOS-latest
- node-version: 10.x
os: macOS-latest
- node-version: 12.x
os: macOS-latest
- node-version: 14.x
os: macOS-latest
runs-on: ${{ matrix.os }}
steps:
- name: Set git config
shell: bash
run: |
git config --global core.autocrlf false
git config --global core.symlinks true
if: runner.os == 'Windows'
- uses: actions/checkout@v4
- name: install
run: yarn && (cd packages/bower-json && yarn link) && yarn link bower-json
- name: lint
run: npm run lint
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: bower tests
run: npm test
env:
CI: true
continue-on-error: ${{ matrix.os == 'windows-latest' }} # Allow failure on Windows
- name: bower-logger tests
run: (cd packages/bower-logger && npm install && npm test)
env:
CI: true
- name: bower-config tests
run: (cd packages/bower-config && npm install && npm test)
env:
CI: true
- name: bower-endpoint-parser tests
run: (cd packages/bower-endpoint-parser && npm install && npm test)
env:
CI: true
- name: bower-json tests
run: (cd packages/bower-json && npm install && npm test)
env:
CI: true
- name: bower-registry-client tests
run: (cd packages/bower-registry-client && npm install && npm test)
env:
CI: true

2
.gitignore vendored
View File

@@ -1,3 +1,4 @@
!lib/bin
/node_modules
/npm-debug.log
@@ -12,3 +13,4 @@
/test/sample
!/test/sample/bower.json
/npm-shrinkwrap.json
package-lock.json

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
**/node_modules/**
**/test/assets/**
**/bower_components/**
test/sample

View File

@@ -1,43 +0,0 @@
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
install:
- yarn
- nvm install $TEST_NODE_VERSION
- npm install -g grunt
os:
- osx
- linux
matrix:
fast_finish: true
allow_failures:
- os: osx
script:
- nvm use $TEST_NODE_VERSION
- node --version && npm --version && git --version && svn --version | head -n 1
- grunt travis

View File

@@ -1,5 +1,9 @@
# 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)

View File

@@ -1,6 +1,6 @@
# Contributing to Bower
Bower is a large community project with many different developers contributing at all levels to the project. We're **actively** looking for more contributors right now. If you're interested in becoming a Bower maintainer or supporting in any way, please fill the following form: http://goo.gl/forms/P1ndzCNoiG. There is more information about [contributing](https://github.com/bower/bower/wiki/Contributor-Guidelines) in the Wiki.
Bower is a large community project with many different developers contributing at all levels to the project. There is more information about [contributing](https://github.com/bower/bower/wiki/Contributor-Guidelines) in the Wiki.
<a name="bugs"></a>
## 🐛 [Bug reports](https://github.com/bower/bower/wiki/Report-a-Bug)
@@ -16,12 +16,6 @@ Bower is a large community project with many different developers contributing a
* Read [Architecture doc](https://github.com/bower/bower/wiki/Rewrite-architecture)
* Triage, close, fix and resolve [issues](https://github.com/bower/bower/issues)
## Team Meetings
We communicate through a channel on Discord https://discord.gg/0fFM7QF0KpZRh2cY
If you'd like to attend the meetings, please fill the [support form](http://goo.gl/forms/P1ndzCNoiG), and you'll get an invite.
## Using the issue tracker
The issue tracker is the preferred channel for [bug reports](#bugs),
@@ -29,12 +23,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),
(twitter-bower@googlegroups.com), or
[#bower](http://webchat.freenode.net/?channels=bower) on Freenode.
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower), or in serious cases
send an e-mail to team@bower.io
* Please **do not** derail or troll issues. Keep the discussion on topic and
respect the opinions of others.

View File

@@ -1,213 +0,0 @@
'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();
});
});
};

210
README.md
View File

@@ -1,14 +1,10 @@
# Bower - A package manager for the web
[![Build](https://github.com/bower/bower/workflows/build/badge.svg)](https://github.com/bower/bower/actions?query=branch%3Amaster)
[![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!
[![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)
> ..psst! While Bower is maintained, we recommend [yarn](https://yarnpkg.com/) and [webpack](https://webpack.js.org/) or [parcel](https://parceljs.org/) for new front-end projects!
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
@@ -101,9 +97,11 @@ Bower can be configured using JSON in a `.bowerrc` file. Read over available opt
## Support
* [Discord chat](https://discord.gg/0fFM7QF0KpZRh2cY)
You can ask questions on following channels in order:
* [StackOverflow](http://stackoverflow.com/questions/tagged/bower)
* [Mailinglist](http://groups.google.com/group/twitter-bower) - twitter-bower@googlegroups.com
* [Issue Tracker](https://github.com/bower/bower/issues)
* team@bower.io
## Contributing
@@ -125,72 +123,136 @@ git config --global core.autocrlf input
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>
<a href='https://opencollective.com/bower/tiers/sponsors/1/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/1/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/2/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/2/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/3/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/3/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/4/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/4/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/5/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/5/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/6/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/6/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/7/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/7/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/8/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/8/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/9/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/9/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/10/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/10/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/11/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/11/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/12/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/12/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/13/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/13/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/14/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/14/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/15/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/15/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/16/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/16/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/17/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/17/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/18/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/18/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/19/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/19/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/20/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/20/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/21/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/21/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/22/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/22/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/23/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/23/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/24/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/24/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/25/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/25/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/26/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/26/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/27/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/27/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/28/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/28/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/29/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/29/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/30/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/30/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/31/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/31/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/32/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/32/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/33/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/33/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/34/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/34/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/35/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/35/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/36/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/36/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/37/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/37/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/38/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/38/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/39/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/39/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/40/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/40/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/41/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/41/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/42/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/42/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/43/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/43/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/44/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/44/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/45/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/45/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/46/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/46/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/47/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/47/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/48/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/48/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/49/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/49/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/50/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/50/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/51/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/51/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/52/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/52/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/53/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/53/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/54/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/54/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/55/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/55/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/56/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/56/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/57/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/57/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/58/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/58/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/59/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/59/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/60/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/60/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/61/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/61/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/62/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/62/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/63/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/63/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/64/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/64/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/65/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/65/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/66/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/66/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/67/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/67/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/68/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/68/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/69/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/69/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/70/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/70/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/71/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/71/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/72/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/72/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/73/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/73/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/74/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/74/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/75/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/75/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/76/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/76/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/77/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/77/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/78/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/78/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/79/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/79/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/80/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/80/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/81/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/81/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/82/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/82/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/83/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/83/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/84/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/84/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/85/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/85/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/86/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/86/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/87/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/87/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/88/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/88/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/89/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/89/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/90/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/90/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/91/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/91/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/92/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/92/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/93/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/93/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/94/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/94/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/95/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/95/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/96/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/96/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/97/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/97/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/98/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/98/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/99/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/99/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/100/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/100/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/101/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/101/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/102/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/102/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/103/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/103/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/104/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/104/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/105/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/105/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/106/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/106/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/107/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/107/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/108/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/108/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/109/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/109/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/110/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/110/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/111/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/111/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/112/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/112/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/113/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/113/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/114/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/114/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/115/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/115/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/116/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/116/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/117/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/117/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/118/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/118/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/119/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/119/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/120/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/120/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/121/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/121/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/122/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/122/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/123/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/123/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/124/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/124/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/125/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/125/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/126/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/126/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/127/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/127/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/128/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/128/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/129/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/129/avatar.svg'></a>
<a href='https://opencollective.com/bower/tiers/sponsors/130/website' target='_blank'><img src='https://opencollective.com/bower/tiers/sponsors/130/avatar.svg'></a>
## License

3
SECURITY.md Normal file
View File

@@ -0,0 +1,3 @@
# Security Policy
For critical security issues, please send an e-mail to team@bower.io instead of filing issue here.

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,42 +95,47 @@ 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('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('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);
}
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,27 +33,26 @@ 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
) {
@@ -67,36 +66,47 @@ 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;
});
});
}
@@ -106,59 +116,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()) {
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);
}
})
// 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
});
@@ -172,7 +182,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,12 +13,11 @@ 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;
});
});
@@ -30,7 +29,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,15 +7,17 @@ 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
@@ -28,7 +30,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,8 +16,7 @@ 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');
}
@@ -26,14 +25,16 @@ 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) {
@@ -47,7 +48,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,16 +37,18 @@ 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;
}
@@ -56,11 +58,10 @@ 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

@@ -11,10 +11,11 @@ function info(logger, endpoint, property, config) {
// handle @ as version divider
var splitParts = endpoint.split('/');
splitParts[splitParts.length - 1] = splitParts[splitParts.length - 1].replace('@', '#');
splitParts[splitParts.length - 1] = splitParts[
splitParts.length - 1
].replace('@', '#');
endpoint = splitParts.join('/');
var repository;
var decEndpoint;
@@ -25,25 +26,27 @@ 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) {
if (versions) {
return {
name: decEndpoint.source,
versions: versions,
latest: pkgMeta
};
}
decEndpoint.target === '*' && !property
? repository.versions(decEndpoint.source)
: null
]).spread(function(pkgMeta, versions) {
if (versions) {
return {
name: decEndpoint.source,
versions: versions,
latest: pkgMeta
};
}
return pkgMeta;
});
return pkgMeta;
});
}
function getPkgMeta(repository, decEndpoint, property) {
return repository.fetch(decEndpoint)
.spread(function (canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function (value, key) {
return repository
.fetch(decEndpoint)
.spread(function(canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function(value, key) {
return key.charAt(0) !== '_';
});
@@ -58,7 +61,7 @@ function getPkgMeta(repository, decEndpoint, property) {
// -------------------
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

@@ -23,31 +23,38 @@ function init(logger, 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'
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));
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) {
return project.hasJson().then(function(json) {
if (json) {
logger.warn('existing', 'The existing ' + path.basename(json) + ' file will be used and filled in');
logger.warn(
'existing',
'The existing ' +
path.basename(json) +
' file will be used and filled in'
);
}
return project.getJson();
@@ -56,7 +63,7 @@ function readJson(project, logger) {
function saveJson(project, logger, json) {
// Cleanup empty props (null values, empty strings, objects and arrays)
mout.object.forOwn(json, function (value, key) {
mout.object.forOwn(json, function(value, key) {
if (!validConfigValue(value)) {
delete json[key];
}
@@ -69,8 +76,7 @@ function saveJson(project, logger, json) {
type: 'confirm',
message: 'Looks good?',
default: true
})
.then(function (good) {
}).then(function(good) {
if (!good) {
return null;
}
@@ -84,9 +90,9 @@ function saveJson(project, logger, json) {
// [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.isObject(val) ||
mout.lang.isArray(val) ||
mout.lang.isString(val) ||
mout.lang.isBoolean(val) ||
mout.lang.isNumber(val)
);
@@ -116,52 +122,70 @@ function setDefaults(config, json) {
// Homepage
if (!json.homepage) {
// Set as GitHub homepage if it's a GitHub repository
promise = promise.then(function () {
promise = promise.then(function() {
return cmd('git', ['config', '--get', 'remote.origin.url'])
.spread(function (stdout) {
var pair;
.spread(function(stdout) {
var pair;
stdout = stdout.trim();
if (!stdout) {
return;
}
stdout = stdout.trim();
if (!stdout) {
return;
}
pair = GitHubResolver.getOrgRepoPair(stdout);
if (pair) {
json.homepage = 'https://github.com/' + pair.org + '/' + pair.repo;
}
})
.fail(function () { });
pair = GitHubResolver.getOrgRepoPair(stdout);
if (pair) {
json.homepage =
'https://github.com/' + pair.org + '/' + pair.repo;
}
})
.fail(function() {});
});
}
if (!json.authors) {
promise = promise.then(function () {
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();
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;
}
// 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 () {});
// 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 promise.then(function() {
return json;
});
}
@@ -169,69 +193,73 @@ function setDefaults(config, json) {
function promptUser(logger, json) {
var questions = [
{
'name': 'name',
'message': 'name',
'default': json.name,
'type': 'input'
name: 'name',
message: 'name',
default: json.name,
type: 'input'
},
{
'name': 'description',
'message': 'description',
'default': json.description,
'type': 'input'
name: 'description',
message: 'description',
default: json.description,
type: 'input'
},
{
'name': 'main',
'message': 'main file',
'default': json.main,
'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: '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: 'authors',
message: 'authors',
default: json.authors ? json.authors.toString() : null,
type: 'input'
},
{
'name': 'license',
'message': 'license',
'default': json.license || 'MIT',
'type': 'input'
name: 'license',
message: 'license',
default: json.license || 'MIT',
type: 'input'
},
{
'name': 'homepage',
'message': 'homepage',
'default': json.homepage,
'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: '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: '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'
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) {
return Q.nfcall(logger.prompt.bind(logger), questions).then(function(
answers
) {
json.name = answers.name;
json.description = answers.description;
json.main = answers.main;
@@ -249,12 +277,12 @@ function toArray(value, splitter) {
var arr = value.split(splitter || /[\s,]/);
// Trim values
arr = arr.map(function (item) {
arr = arr.map(function(item) {
return item.trim();
});
// Filter empty values
arr = arr.filter(function (item) {
arr = arr.filter(function(item) {
return !!item;
});
@@ -278,13 +306,12 @@ function setIgnore(config, json, answers) {
function setDependencies(project, json, answers) {
if (answers.dependencies) {
return project.getTree()
.spread(function (tree, flattened, extraneous) {
return project.getTree().spread(function(tree, flattened, extraneous) {
if (extraneous.length) {
json.dependencies = {};
// Add extraneous as dependencies
extraneous.forEach(function (extra) {
extraneous.forEach(function(extra) {
var jsonEndpoint;
// Skip linked packages
@@ -292,7 +319,9 @@ function setDependencies(project, json, answers) {
return;
}
jsonEndpoint = endpointParser.decomposed2json(extra.endpoint);
jsonEndpoint = endpointParser.decomposed2json(
extra.endpoint
);
mout.object.mixIn(json.dependencies, jsonEndpoint);
});
}
@@ -306,7 +335,7 @@ function setDependencies(project, json, answers) {
// -------------------
init.readOptions = function (argv) {
init.readOptions = function(argv) {
return [];
};

View File

@@ -15,11 +15,12 @@ function install(logger, endpoints, options, config) {
// Convert endpoints to decomposed endpoints
endpoints = endpoints || [];
decEndpoints = endpoints.map(function (endpoint) {
decEndpoints = endpoints.map(function(endpoint) {
// handle @ as version divider
var splitParts = endpoint.split('/');
splitParts[splitParts.length - 1] = splitParts[splitParts.length - 1].replace('@', '#');
splitParts[splitParts.length - 1] = splitParts[
splitParts.length - 1
].replace('@', '#');
endpoint = splitParts.join('/');
return endpointParser.decompose(endpoint);
@@ -30,16 +31,19 @@ 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,23 +20,24 @@ 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
};
})
);
});
}
@@ -53,27 +54,29 @@ 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,39 +11,48 @@ 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;
}
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);
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) {
mout.object.forOwn(flattened, 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);
@@ -61,8 +70,7 @@ 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;
});
});
@@ -74,32 +82,40 @@ 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
@@ -111,7 +127,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) {
@@ -132,7 +148,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());
});
@@ -150,13 +166,16 @@ 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

@@ -19,24 +19,28 @@ function login(logger, options, config) {
} 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'
}));
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: 'username',
message: 'Username',
type: 'input',
default: configstore.get('username')
},
{
'name': 'password',
'message': 'Password',
'type': 'password'
name: 'password',
message: 'Password',
type: 'password'
}
];
@@ -44,8 +48,9 @@ function login(logger, options, config) {
version: '3.0.0'
});
promise = Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
promise = Q.nfcall(logger.prompt.bind(logger), questions).then(function(
answers
) {
configstore.set('username', answers.username);
github.authenticate({
@@ -56,66 +61,91 @@ function login(logger, options, config) {
return Q.ninvoke(github.authorization, 'create', {
scopes: ['user', 'repo'],
note: 'Bower command line client (' + (new Date()).toISOString() + ')'
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 promise.then(
function(result) {
configstore.set('accessToken', result.token);
logger.info(
'EAUTH',
'Logged in as ' + configstore.get('username'),
{}
);
return result;
}, function (error) {
var message;
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'
try {
message = JSON.parse(error.message).message;
} catch (e) {
message = 'Authorization failed';
}
];
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'), {});
var questions = [
{
name: 'otpcode',
message: 'Two-Factor Auth Code',
type: 'input'
}
];
return result;
}, function () {
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'));
});
} else {
logger.emit('error', createError(message, 'EAUTH'));
}
}
});
);
}
// -------------------
login.readOptions = function (argv) {
login.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
token: { type: String, shorthand: 't' },
}, argv);
var options = cli.readOptions(
{
token: { type: String, shorthand: 't' }
},
argv
);
delete options.argv;

View File

@@ -12,18 +12,21 @@ 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.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

@@ -17,35 +17,40 @@ function clean(project, options, 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;
});
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;
}
// 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);
// Not yet, recurse!
mout.object.mixIn(removed, uninstalled);
return clean(project, options, removed);
});
});
});
}
// -------------------
prune.readOptions = function (argv) {
prune.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
'production': { type: Boolean, shorthand: 'p' },
}, argv);
var options = cli.readOptions(
{
production: { type: Boolean, shorthand: 'p' }
},
argv
);
delete options.argv;

View File

@@ -10,7 +10,7 @@ function register(logger, name, source, config) {
var force;
var url;
var githubSourceRegex = /^\w[\w-]*\/\w[\w-]*$/;
var getGithubUrl = function (source) {
var getGithubUrl = function(source) {
return 'git@github.com:' + source + '.git';
};
@@ -26,10 +26,13 @@ function register(logger, name, source, config) {
config.offline = false;
config.force = true;
return Q.try(function () {
return Q.try(function() {
// Verify name and url
if (!name || !url) {
throw createError('Usage: bower register <name> <url>', 'EINVFORMAT');
throw createError(
'Usage: bower register <name> <url>',
'EINVFORMAT'
);
}
// Attempt to resolve the package referenced by the URL to ensure
@@ -37,45 +40,54 @@ function register(logger, name, source, config) {
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');
}
.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;
}
// 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
// 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
);
});
})
.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) {
register.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);

View File

@@ -27,7 +27,7 @@ function search(logger, name, config) {
// -------------------
search.readOptions = function (argv) {
search.readOptions = function(argv) {
var options = cli.readOptions(argv);
var terms = options.argv.remain.slice(1);

View File

@@ -14,54 +14,62 @@ function uninstall(logger, names, options, config) {
config = defaultConfig(config);
project = new Project(config, logger);
return project.getTree(options)
.spread(function (tree, flattened) {
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 = [];
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));
}
});
// 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);
});
// Clean them!
return clean(project, children, uninstalled);
})
);
});
}
function clean(project, names, removed) {
removed = removed || {};
return project.getTree()
.spread(function (tree, flattened) {
return project.getTree().spread(function(tree, flattened) {
var nodes = [];
var dependantsCounter = {};
// Grab the nodes of each specified name
mout.object.forOwn(flattened, function (node) {
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);
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) {
nodes = nodes.filter(function(node) {
return !dependantsCounter[node.endpoint.name];
});
@@ -71,39 +79,48 @@ function clean(project, names, removed) {
}
// Grab the nodes after filtering
names = nodes.map(function (node) {
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;
return (
project
.uninstall(names)
// Clean out non-shared uninstalled dependencies
.then(function(uninstalled) {
var children;
mout.object.mixIn(removed, uninstalled);
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));
});
// 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);
});
// Recurse!
return clean(project, children, removed);
})
);
});
}
// -------------------
uninstall.readOptions = function (argv) {
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 options = cli.readOptions(
{
save: { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' }
},
argv
);
var names = options.argv.remain.slice(1);

View File

@@ -6,7 +6,6 @@ var PackageRepository = require('../core/PackageRepository');
var createError = require('../util/createError');
function unregister(logger, name, config) {
if (!name) {
return;
}
@@ -28,46 +27,58 @@ function unregister(logger, name, config) {
repository = new PackageRepository(config, logger);
if (!config.accessToken) {
return logger.emit('error',
createError('Use "bower login" with collaborator credentials', 'EFORBIDDEN')
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;
}
.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
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;
});
})
.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) {
unregister.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);

View File

@@ -18,13 +18,16 @@ function update(logger, names, options, config) {
// -------------------
update.readOptions = function (argv) {
update.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
'force-latest': { type: Boolean, shorthand: 'F' },
'production': { type: Boolean, shorthand: 'p' }
}, argv);
var options = cli.readOptions(
{
'force-latest': { type: Boolean, shorthand: 'F' },
production: { type: Boolean, shorthand: 'p' }
},
argv
);
var names = options.argv.remain.slice(1);

View File

@@ -1,9 +1,9 @@
var semver = require('semver');
var which = require('which');
var which = require('../util/which');
var fs = require('../util/fs');
var path = require('path');
var Q = require('q');
var execFile = require('child_process').execFile;
var execFile = require('../util/childProcess').execFile;
var defaultConfig = require('../config');
var createError = require('../util/createError');
@@ -23,137 +23,186 @@ function bump(logger, config, versionArg, message) {
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';
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 (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 });
if (!newVersion) {
throw createError(
'Invalid <version> argument: ' + versionArg,
'EINVALIDVERSION',
{ version: versionArg }
);
}
});
}
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;
});
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) {
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 Q.nfcall(fs.stat, gitDir).then(
function(stat) {
if (stat.isDirectory()) {
return checkGitStatus(cwd);
}
return false;
},
function() {
//Ignore not found .git directory
return false;
}
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;
});
.fail(function(err) {
err.code = 'ENOGIT';
throw err;
})
.then(function(gitPath) {
return Q.nfcall(
execFile,
gitPath,
['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 stdout
.trim()
.split('\n')
.filter(function(line) {
return line.trim() && !line.match(/^\?\? /);
})
.map(function(line) {
return line.trim();
});
}
return checkGit(cwd).then(function (hasGit) {
return checkGit(cwd).then(function(hasGit) {
if (!hasGit) {
throw createError('Version bump currently supports only git repositories', 'ENOTGITREPOSITORY');
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);
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 [];
});
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];
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;
});
return version;
},
function() {
return undefined;
}
);
},
bump: function (cwd, tag, message) {
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});
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) {
version.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions({
'message': { type: String, shorthand: 'm'}
}, argv);
var options = cli.readOptions(
{
message: { type: String, shorthand: 'm' }
},
argv
);
return [options.argv.remain[1], options];
};

View File

@@ -22,26 +22,26 @@ function readCachedConfig(cwd, overwrites) {
// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = (
process.bin === 'bower' &&
tty.isatty(1) &&
!process.env.CI
);
config.interactive =
process.bin === 'bower' && tty.isatty(1) && !process.env.CI;
}
// Merge common CLI options into the config
if (process.bin === 'bower') {
var cli = require('./util/cli');
object.mixIn(config, cli.readOptions({
force: { type: Boolean, shorthand: 'f' },
offline: { type: Boolean, shorthand: 'o' },
verbose: { type: Boolean, shorthand: 'V' },
quiet: { type: Boolean, shorthand: 'q' },
loglevel: { type: String, shorthand: 'l' },
json: { type: Boolean, shorthand: 'j' },
silent: { type: Boolean, shorthand: 's' }
}));
object.mixIn(
config,
cli.readOptions({
force: { type: Boolean, shorthand: 'f' },
offline: { type: Boolean, shorthand: 'o' },
verbose: { type: Boolean, shorthand: 'V' },
quiet: { type: Boolean, shorthand: 'q' },
loglevel: { type: String, shorthand: 'l' },
json: { type: Boolean, shorthand: 'j' },
silent: { type: Boolean, shorthand: 's' }
})
);
}
return config;

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,6 @@ var createError = require('../util/createError');
var RegistryClient = require('bower-registry-client');
function PackageRepository(config, logger) {
var registryOptions;
this._config = config;
@@ -24,7 +23,7 @@ function PackageRepository(config, logger) {
// -----------------
PackageRepository.prototype.fetch = function (decEndpoint) {
PackageRepository.prototype.fetch = function(decEndpoint) {
var logger;
var that = this;
var isTargetable;
@@ -36,132 +35,192 @@ PackageRepository.prototype.fetch = function (decEndpoint) {
// used to fetch
logger = this._logger.geminate();
// Intercept all logs, adding additional information
logger.intercept(function (log) {
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;
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
});
if (!resolver.isCacheable()) {
return that._resolve(resolver, logger);
}
// 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];
// 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);
}
// Otherwise resolve to the newest one
logger.info('new', 'version for ' + resolver.getSource() + '#' + resolver.getTarget());
logger.action('resolve', resolver.getSource() + '#' + resolver.getTarget());
// 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
}
);
}
return that._resolve(resolver, logger);
});
});
})
// If something went wrong, also extend the error
.fail(function (err) {
that._extendLog(err, info);
throw err;
});
// 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) {
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());
}
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));
// Otherwise, fetch remotely
return resolver.constructor.versions(resolver.getSource());
}.bind(this)
);
};
PackageRepository.prototype.eliminate = function (pkgMeta) {
PackageRepository.prototype.eliminate = function(pkgMeta) {
return Q.all([
this._resolveCache.eliminate(pkgMeta),
Q.nfcall(this._registryClient.clearCache.bind(this._registryClient), pkgMeta.name)
Q.nfcall(
this._registryClient.clearCache.bind(this._registryClient),
pkgMeta.name
)
]);
};
PackageRepository.prototype.clear = function () {
PackageRepository.prototype.clear = function() {
return Q.all([
this._resolveCache.clear(),
Q.nfcall(this._registryClient.clearCache.bind(this._registryClient))
]);
};
PackageRepository.prototype.reset = function () {
PackageRepository.prototype.reset = function() {
this._resolveCache.reset();
this._registryClient.resetCache();
};
PackageRepository.prototype.list = function () {
PackageRepository.prototype.list = function() {
return this._resolveCache.list();
};
PackageRepository.prototype.getRegistryClient = function () {
PackageRepository.prototype.getRegistryClient = function() {
return this._registryClient;
};
PackageRepository.prototype.getResolveCache = function () {
PackageRepository.prototype.getResolveCache = function() {
return this._resolveCache;
};
PackageRepository.clearRuntimeCache = function () {
PackageRepository.clearRuntimeCache = function() {
ResolveCache.clearRuntimeCache();
RegistryClient.clearRuntimeCache();
resolverFactory.clearRuntimeCache();
@@ -169,41 +228,59 @@ PackageRepository.clearRuntimeCache = function () {
// ---------------------
PackageRepository.prototype._getResolver = function (decEndpoint, logger) {
PackageRepository.prototype._getResolver = function(decEndpoint, logger) {
logger = logger || this._logger;
// Get the appropriate resolver
return resolverFactory(decEndpoint, { config: this._config, logger: logger }, this._registryClient);
return resolverFactory(
decEndpoint,
{ config: this._config, logger: logger },
this._registryClient
);
};
PackageRepository.prototype._resolve = function (resolver, logger) {
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 (
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();
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()];
});
logger.info(
'resolved',
resolver.getSource() +
(pkgMeta._release ? '#' + pkgMeta._release : '')
);
return [dir, pkgMeta, resolver.constructor.isTargetable()];
})
);
};
PackageRepository.prototype._extendLog = function (log, info) {
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']);
log.data.endpoint = mout.object.pick(info.decEndpoint, [
'name',
'source',
'target'
]);
}
// Store the resolver info in each log

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ function ResolveCache(config) {
if (!this._cache) {
this._cache = new LRU({
max: 100,
maxAge: 60 * 5 * 1000 // 5 minutes
maxAge: 60 * 5 * 1000 // 5 minutes
});
this.constructor._cache.set(this._dir, this._cache);
}
@@ -40,7 +40,7 @@ function ResolveCache(config) {
// -----------------
ResolveCache.prototype.retrieve = function (source, target) {
ResolveCache.prototype.retrieve = function(source, target) {
var sourceId = md5(source);
var dir = path.join(this._dir, sourceId);
var that = this;
@@ -48,56 +48,57 @@ ResolveCache.prototype.retrieve = function (source, target) {
target = target || '*';
return this._getVersions(sourceId)
.spread(function (versions) {
var suitable;
.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 target is a semver, find a suitable version
if (semver.validRange(target)) {
suitable = semver.maxSatisfying(versions, target, true);
if (suitable) {
return suitable;
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';
// 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;
// Otherwise check if there's an exact match
return mout.array.find(versions, function (version) {
return version === target;
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);
});
}
);
});
})
.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) {
ResolveCache.prototype.store = function(canonicalDir, pkgMeta) {
var sourceId;
var release;
var dir;
@@ -108,70 +109,82 @@ ResolveCache.prototype.store = function (canonicalDir, pkgMeta) {
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');
.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;
}
// 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;
}
// 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);
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);
});
}).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;
});
})
.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) {
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 () {
return Q.nfcall(rimraf, dir).then(function() {
var versions = that._cache.get(sourceId) || [];
mout.array.remove(versions, release);
@@ -182,8 +195,7 @@ ResolveCache.prototype.eliminate = function (pkgMeta) {
if (!versions.length) {
that._cache.del(sourceId);
return that._getVersions(sourceId)
.spread(function (versions) {
return that._getVersions(sourceId).spread(function(versions) {
if (!versions.length) {
// Do not keep in-memory cache if it's completely
// empty
@@ -196,125 +208,144 @@ ResolveCache.prototype.eliminate = function (pkgMeta) {
});
};
ResolveCache.prototype.clear = function () {
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));
.then(
function() {
return Q.nfcall(fs.mkdir, this._dir);
}.bind(this)
)
.then(
function() {
this._cache.reset();
}.bind(this)
);
};
ResolveCache.prototype.reset = function () {
ResolveCache.prototype.reset = function() {
this._cache.reset();
return this;
};
ResolveCache.prototype.versions = function (source) {
ResolveCache.prototype.versions = function(source) {
var sourceId = md5(source);
return this._getVersions(sourceId)
.spread(function (versions) {
return versions.filter(function (version) {
return this._getVersions(sourceId).spread(function(versions) {
return versions.filter(function(version) {
return semver.valid(version);
});
});
};
ResolveCache.prototype.list = function () {
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);
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;
}
);
});
}, 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.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.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 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);
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 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 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);
});
});
// Sort by target
return pkgMeta1._target.localeCompare(pkgMeta2._target);
});
})
);
};
// ------------------------
ResolveCache.clearRuntimeCache = function () {
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) {
this._cache.forEach(function(cache) {
cache.reset();
});
@@ -324,8 +355,10 @@ ResolveCache.clearRuntimeCache = function () {
// ------------------------
ResolveCache.prototype._getPkgRelease = function (pkgMeta) {
var release = pkgMeta.version || (pkgMeta._target === '*' ? '_wildcard' : pkgMeta._target);
ResolveCache.prototype._getPkgRelease = function(pkgMeta) {
var release =
pkgMeta.version ||
(pkgMeta._target === '*' ? '_wildcard' : pkgMeta._target);
// Encode some dangerous chars such as / and \
release = encodeURIComponent(release);
@@ -333,16 +366,15 @@ ResolveCache.prototype._getPkgRelease = function (pkgMeta) {
return release;
};
ResolveCache.prototype._readPkgMeta = function (dir) {
ResolveCache.prototype._readPkgMeta = function(dir) {
var filename = path.join(dir, '.bower.json');
return readJson(filename)
.spread(function (json) {
return readJson(filename).spread(function(json) {
return json;
});
};
ResolveCache.prototype._getVersions = function (sourceId) {
ResolveCache.prototype._getVersions = function(sourceId) {
var dir;
var versions = this._cache.get(sourceId);
var that = this;
@@ -352,29 +384,31 @@ ResolveCache.prototype._getVersions = function (sourceId) {
}
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 = [];
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;
});
throw err;
}
);
};
ResolveCache.prototype._sortVersions = function (versions) {
ResolveCache.prototype._sortVersions = function(versions) {
// Sort DESC
versions.sort(function (version1, version2) {
versions.sort(function(version1, version2) {
var validSemver1 = semver.valid(version1);
var validSemver2 = semver.valid(version2);
@@ -400,7 +434,7 @@ ResolveCache.prototype._sortVersions = function (versions) {
ResolveCache._cache = new LRU({
max: 5,
maxAge: 60 * 30 * 1000 // 30 minutes
maxAge: 60 * 30 * 1000 // 30 minutes
});
module.exports = ResolveCache;

View File

@@ -13,9 +13,15 @@ function createInstance(decEndpoint, options, registryClient) {
options.version = require('../version');
return getConstructor(decEndpoint, options, registryClient)
.spread(function (ConcreteResolver, decEndpoint) {
return new ConcreteResolver(decEndpoint, options.config, options.logger);
return getConstructor(decEndpoint, options, registryClient).spread(function(
ConcreteResolver,
decEndpoint
) {
return new ConcreteResolver(
decEndpoint,
options.config,
options.logger
);
});
}
@@ -29,8 +35,8 @@ function getConstructor(decEndpoint, options, registryClient) {
var promise = Q.resolve();
var addResolver = function (resolverFactory) {
promise = promise.then(function (result) {
var addResolver = function(resolverFactory) {
promise = promise.then(function(result) {
if (result === undefined) {
return resolverFactory(decEndpoint, options);
} else {
@@ -43,7 +49,7 @@ function getConstructor(decEndpoint, options, registryClient) {
//
// It requires each resolver defined in config.resolvers and calls
// its "match" to check if given resolves supports given decEndpoint
addResolver(function () {
addResolver(function() {
var selectedResolver;
var resolverNames;
@@ -55,28 +61,32 @@ function getConstructor(decEndpoint, options, registryClient) {
resolverNames = [];
}
var resolverPromises = resolverNames.map(function (resolverName) {
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')
throw createError(
'Bower resolver not found: ' + resolverName,
'ENORESOLVER'
);
}
resolver = pluginResolverFactory(require(resolverPath), options);
resolver = pluginResolverFactory(
require(resolverPath),
options
);
}
return function () {
return function() {
if (selectedResolver === undefined) {
var match = resolver.match.bind(resolver);
return Q.fcall(match, source).then(function (result) {
return Q.fcall(match, source).then(function(result) {
if (result) {
return selectedResolver = resolver;
return (selectedResolver = resolver);
}
});
} else {
@@ -85,26 +95,39 @@ function getConstructor(decEndpoint, options, registryClient) {
};
});
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];
}
});
}
});
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)) {
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
@@ -117,65 +140,74 @@ function getConstructor(decEndpoint, options, registryClient) {
});
// SVN case: svn, svn+ssh, svn+http, svn+https, svn+file
addResolver(function () {
addResolver(function() {
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
return [resolvers.Svn, decEndpoint];
}
});
// URL case
addResolver(function () {
addResolver(function() {
if (/^https?:\/\//i.exec(source)) {
return [resolvers.Url, decEndpoint];
}
});
// If source is ./ or ../ or an absolute path
addResolver(function () {
addResolver(function() {
var absolutePath = path.resolve(config.cwd, source);
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(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;
return (
Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
.then(function(stats) {
decEndpoint.source = absolutePath;
if (stats.isDirectory()) {
return Q.resolve([resolvers.GitFs, decEndpoint]);
}
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;
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]);
}
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;
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]);
});
});
return Q.resolve([resolvers.Fs, decEndpoint]);
});
})
);
}
});
// Check if is a shorthand and expand it
addResolver(function () {
addResolver(function() {
// Check if the shorthandResolver is falsy
if (!config.shorthandResolver) {
return;
}
// Skip ssh and/or URL with auth
if (/[:@]/.test(source)) {
return;
@@ -184,26 +216,34 @@ function getConstructor(decEndpoint, options, registryClient) {
// 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]
});
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 () {
addResolver(function() {
if (!registryClient) {
return;
}
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
.then(function (entry) {
return Q.nfcall(
registryClient.lookup.bind(registryClient),
source
).then(function(entry) {
if (!entry) {
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
throw createError(
'Package ' + source + ' not found',
'ENOTFOUND'
);
}
decEndpoint.registry = true;
@@ -218,15 +258,18 @@ function getConstructor(decEndpoint, options, registryClient) {
});
});
addResolver(function () {
throw createError('Could not find appropriate resolver for ' + source, 'ENORESOLVER');
addResolver(function() {
throw createError(
'Could not find appropriate resolver for ' + source,
'ENORESOLVER'
);
});
return promise;
}
function clearRuntimeCache() {
mout.object.values(resolvers).forEach(function (ConcreteResolver) {
mout.object.values(resolvers).forEach(function(ConcreteResolver) {
ConcreteResolver.clearRuntimeCache();
});
}

View File

@@ -17,13 +17,19 @@ function FsResolver(decEndpoint, config, logger) {
// If target was specified, simply reject the promise
if (this._target !== '*') {
throw createError('File system sources can\'t resolve targets', 'ENORESTARGET');
throw createError(
"File system sources can't resolve targets",
'ENORESTARGET'
);
}
// If the name was guessed
if (this._guessedName) {
// Remove extension
this._name = this._name.substr(0, this._name.length - path.extname(this._name).length);
this._name = this._name.substr(
0,
this._name.length - path.extname(this._name).length
);
}
}
@@ -32,7 +38,7 @@ mout.object.mixIn(FsResolver, Resolver);
// -----------------
FsResolver.isTargetable = function () {
FsResolver.isTargetable = function() {
return false;
};
@@ -42,19 +48,18 @@ FsResolver.isTargetable = function () {
// TODO: There's room for improvement by using streams if the source
// is an archive file, by piping read stream to the zip extractor
// This will likely increase the complexity of code but might worth it
FsResolver.prototype._resolve = function () {
FsResolver.prototype._resolve = function() {
return this._copy()
.then(this._extract.bind(this))
.then(this._rename.bind(this));
.then(this._extract.bind(this))
.then(this._rename.bind(this));
};
// -----------------
FsResolver.prototype._copy = function () {
FsResolver.prototype._copy = function() {
var that = this;
return Q.nfcall(fs.stat, this._source)
.then(function (stat) {
return Q.nfcall(fs.stat, this._source).then(function(stat) {
var dst;
var copyOpts;
var promise;
@@ -68,22 +73,24 @@ FsResolver.prototype._copy = function () {
// Read the bower.json inside the folder, so that we
// copy only the necessary files if it has ignore specified
promise = that._readJson(that._source)
.then(function (json) {
copyOpts.ignore = json.ignore;
return copy.copyDir(that._source, dst, copyOpts);
})
.then(function () {
// Resolve to null because it's a dir
return;
});
// Else it's a file
promise = that
._readJson(that._source)
.then(function(json) {
copyOpts.ignore = json.ignore;
return copy.copyDir(that._source, dst, copyOpts);
})
.then(function() {
// Resolve to null because it's a dir
return;
});
// Else it's a file
} else {
dst = path.join(that._tempDir, path.basename(that._source));
promise = copy.copyFile(that._source, dst, copyOpts)
.then(function () {
return dst;
});
promise = copy
.copyFile(that._source, dst, copyOpts)
.then(function() {
return dst;
});
}
that._logger.action('copy', that._source, {
@@ -95,7 +102,7 @@ FsResolver.prototype._copy = function () {
});
};
FsResolver.prototype._extract = function (file) {
FsResolver.prototype._extract = function(file) {
if (!file || !extract.canExtract(file)) {
return Q.resolve();
}
@@ -108,30 +115,34 @@ FsResolver.prototype._extract = function (file) {
return extract(file, this._tempDir);
};
FsResolver.prototype._rename = function () {
return Q.nfcall(fs.readdir, this._tempDir)
.then(function (files) {
var file;
var oldPath;
var newPath;
FsResolver.prototype._rename = function() {
return Q.nfcall(fs.readdir, this._tempDir).then(
function(files) {
var file;
var oldPath;
var newPath;
// Remove any OS specific files from the files array
// before checking its length
files = files.filter(junk.isnt);
// Remove any OS specific files from the files array
// before checking its length
files = files.filter(junk.isnt);
// Only rename if there's only one file and it's not the json
if (files.length === 1 && !/^(bower|component)\.json$/.test(files[0])) {
file = files[0];
this._singleFile = 'index' + path.extname(file);
oldPath = path.join(this._tempDir, file);
newPath = path.join(this._tempDir, this._singleFile);
// Only rename if there's only one file and it's not the json
if (
files.length === 1 &&
!/^(bower|component)\.json$/.test(files[0])
) {
file = files[0];
this._singleFile = 'index' + path.extname(file);
oldPath = path.join(this._tempDir, file);
newPath = path.join(this._tempDir, this._singleFile);
return Q.nfcall(fs.rename, oldPath, newPath);
}
}.bind(this));
return Q.nfcall(fs.rename, oldPath, newPath);
}
}.bind(this)
);
};
FsResolver.prototype._savePkgMeta = function (meta) {
FsResolver.prototype._savePkgMeta = function(meta) {
// Store main if is a single file
if (this._singleFile) {
meta.main = this._singleFile;

View File

@@ -19,31 +19,52 @@ mout.object.mixIn(GitFsResolver, GitResolver);
// -----------------
// Override the checkout function to work with the local copy
GitFsResolver.prototype._checkout = function () {
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
});
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 }));
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 () {
GitFsResolver.prototype._copy = function() {
return copy.copyDir(this._source, this._tempDir);
};
// -----------------
// Grab refs locally
GitFsResolver.refs = function (source) {
GitFsResolver.refs = function(source) {
var value;
// TODO: Normalize source because of the various available protocols?
@@ -52,20 +73,24 @@ GitFsResolver.refs = function (source) {
return Q.resolve(value);
}
value = cmd('git', ['show-ref', '--tags', '--heads'], { cwd : source })
.spread(function (stdout) {
var refs;
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
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);
// Update the refs with the actual refs
this._cache.refs.set(source, refs);
return refs;
}.bind(this));
return refs;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value

View File

@@ -29,13 +29,11 @@ function GitHubResolver(decEndpoint, config, logger) {
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://');
}
// Use https:// rather than git:// (PR #2611)
this._source = this._source.replace('git://', 'https://');
// Enable shallow clones for GitHub repos
this._shallowClone = function () {
this._shallowClone = function() {
return Q.resolve(true);
};
}
@@ -45,10 +43,20 @@ mout.object.mixIn(GitHubResolver, GitRemoteResolver);
// -----------------
GitHubResolver.prototype._checkout = function () {
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 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 = {};
@@ -70,54 +78,88 @@ GitHubResolver.prototype._checkout = function () {
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(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
});
// 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 (
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
)
);
})
);
// 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));
});
return that
._cleanTempDir()
.then(GitRemoteResolver.prototype._checkout.bind(that));
}
);
};
GitHubResolver.prototype._savePkgMeta = function (meta) {
GitHubResolver.prototype._savePkgMeta = function(meta) {
// Set homepage if not defined
if (!meta.homepage) {
meta.homepage = 'https://github.com/' + this._org + '/' + this._repo;
@@ -128,10 +170,12 @@ GitHubResolver.prototype._savePkgMeta = function (meta) {
// ----------------
GitHubResolver.getOrgRepoPair = function (url) {
GitHubResolver.getOrgRepoPair = function(url) {
var match;
match = url.match(/(?:@|:\/\/)github.com[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?\/?$/i);
match = url.match(
/(?:@|:\/\/)github.com[:\/]([^\/\s]+?)\/([^\/\s]+?)(?:\.git)?\/?$/i
);
if (!match) {
return null;
}

View File

@@ -25,7 +25,7 @@ function GitRemoteResolver(decEndpoint, config, logger) {
} else {
this._remote = url.parse(this._source);
}
this._host = this._remote.host;
// Verify whether the server supports shallow cloning
@@ -37,33 +37,37 @@ mout.object.mixIn(GitRemoteResolver, GitResolver);
// -----------------
GitRemoteResolver.prototype._checkout = function () {
GitRemoteResolver.prototype._checkout = function() {
var promise;
var timer;
var reporter;
var that = this;
var resolution = this._resolution;
this._logger.action('checkout', resolution.tag || resolution.branch || resolution.commit, {
resolution: resolution,
to: this._tempDir
});
this._logger.action(
'checkout',
resolution.tag || resolution.branch || resolution.commit,
{
resolution: resolution,
to: this._tempDir
}
);
// If resolution is a commit, we need to clone the entire repo and check it out
// Because a commit is not a named ref, there's no better solution
if (resolution.type === 'commit') {
promise = this._slowClone(resolution);
// Otherwise we are checking out a named ref so we can optimize it
// Otherwise we are checking out a named ref so we can optimize it
} else {
promise = this._fastClone(resolution);
}
// Throttle the progress reporter to 1 time each sec
reporter = mout.fn.throttle(function (data) {
reporter = mout.fn.throttle(function(data) {
var lines;
lines = data.split(/[\r\n]+/);
lines.forEach(function (line) {
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?
@@ -73,94 +77,125 @@ GitRemoteResolver.prototype._checkout = function () {
}, 1000);
// Start reporting progress after a few seconds
timer = setTimeout(function () {
timer = setTimeout(function() {
promise.progress(reporter);
}, 8000);
return promise
// Add additional proxy information to the error if necessary
.fail(function (err) {
that._suggestProxyWorkaround(err);
throw err;
})
// Clear timer at the end
.fin(function () {
clearTimeout(timer);
reporter.cancel();
});
return (
promise
// Add additional proxy information to the error if necessary
.fail(function(err) {
that._suggestProxyWorkaround(err);
throw err;
})
// Clear timer at the end
.fin(function() {
clearTimeout(timer);
reporter.cancel();
})
);
};
GitRemoteResolver.prototype._findResolution = function (target) {
GitRemoteResolver.prototype._findResolution = function(target) {
var that = this;
// Override this function to include a meaningful message related to proxies
// if necessary
return GitResolver.prototype._findResolution.call(this, target)
.fail(function (err) {
that._suggestProxyWorkaround(err);
throw err;
});
return GitResolver.prototype._findResolution
.call(this, target)
.fail(function(err) {
that._suggestProxyWorkaround(err);
throw err;
});
};
// ------------------------------
GitRemoteResolver.prototype._slowClone = function (resolution) {
return cmd('git', ['clone', this._source, this._tempDir, '--progress'])
.then(cmd.bind(cmd, 'git', ['checkout', resolution.commit], { cwd: this._tempDir }));
GitRemoteResolver.prototype._slowClone = function(resolution) {
return cmd('git', [
'clone',
this._source,
this._tempDir,
'--progress'
]).then(
cmd.bind(cmd, 'git', ['checkout', resolution.commit], {
cwd: this._tempDir
})
);
};
GitRemoteResolver.prototype._fastClone = function (resolution) {
GitRemoteResolver.prototype._fastClone = function(resolution) {
var branch,
args,
that = this;
branch = resolution.tag || resolution.branch;
args = ['clone', this._source, '-b', branch, '--progress', '.'];
args = ['clone', this._source, '-b', branch, '--progress', '.'];
return this._shallowClone().then(function (shallowCloningSupported) {
return this._shallowClone().then(function(shallowCloningSupported) {
// If the host does not support shallow clones, we don't use --depth=1
if (shallowCloningSupported && !GitRemoteResolver._noShallow.get(that._host)) {
if (
shallowCloningSupported &&
!GitRemoteResolver._noShallow.get(that._host)
) {
args.push('--depth', 1);
}
return cmd('git', args, { cwd: that._tempDir })
.spread(function (stdout, stderr) {
// Only after 1.7.10 --branch accepts tags
// Detect those cases and inform the user to update git otherwise it's
// a lot slower than newer versions
if (!/branch .+? not found/i.test(stderr)) {
return;
}
return cmd('git', args, { cwd: that._tempDir }).spread(
function(stdout, stderr) {
// Only after 1.7.10 --branch accepts tags
// Detect those cases and inform the user to update git otherwise it's
// a lot slower than newer versions
if (!/branch .+? not found/i.test(stderr)) {
return;
}
that._logger.warn('old-git', 'It seems you are using an old version of git, it will be slower and propitious to errors!');
return cmd('git', ['checkout', resolution.commit], { cwd: that._tempDir });
}, function (err) {
// Some git servers do not support shallow clones
// When that happens, we mark this host and try again
if (!GitRemoteResolver._noShallow.has(that._source) &&
err.details &&
/(rpc failed|shallow|--depth)/i.test(err.details)
that._logger.warn(
'old-git',
'It seems you are using an old version of git, it will be slower and propitious to errors!'
);
return cmd('git', ['checkout', resolution.commit], {
cwd: that._tempDir
});
},
function(err) {
// Some git servers do not support shallow clones
// When that happens, we mark this host and try again
if (
!GitRemoteResolver._noShallow.has(that._source) &&
err.details &&
/(rpc failed|shallow|--depth)/i.test(err.details)
) {
GitRemoteResolver._noShallow.set(that._host, true);
return that._fastClone(resolution);
}
GitRemoteResolver._noShallow.set(that._host, true);
return that._fastClone(resolution);
}
throw err;
});
throw err;
}
);
});
};
GitRemoteResolver.prototype._suggestProxyWorkaround = function (err) {
if ((this._config.proxy || this._config.httpsProxy) &&
GitRemoteResolver.prototype._suggestProxyWorkaround = function(err) {
if (
(this._config.proxy || this._config.httpsProxy) &&
mout.string.startsWith(this._source, 'git://') &&
err.code === 'ECMDERR' && err.details
err.code === 'ECMDERR' &&
err.details
) {
err.details = err.details.trim();
err.details += '\n\nWhen under a proxy, you must configure git to use https:// instead of git://.';
err.details += '\nYou can configure it for every endpoint or for this specific host as follows:';
err.details +=
'\n\nWhen under a proxy, you must configure git to use https:// instead of git://.';
err.details +=
'\nYou can configure it for every endpoint or for this specific host as follows:';
err.details += '\ngit config --global url."https://".insteadOf git://';
err.details += '\ngit config --global url."https://' + this._host + '".insteadOf git://' + this._host;
err.details += 'Ignore this suggestion if you already have this configured.';
err.details +=
'\ngit config --global url."https://' +
this._host +
'".insteadOf git://' +
this._host;
err.details +=
'Ignore this suggestion if you already have this configured.';
}
};
@@ -182,7 +217,7 @@ GitRemoteResolver.prototype._suggestProxyWorkaround = function (err) {
// negotiation that needs to take place.
//
// The above should cover most cases, including BitBucket.
GitRemoteResolver.prototype._supportsShallowCloning = function () {
GitRemoteResolver.prototype._supportsShallowCloning = function() {
var value = true;
// Verify that the remote could be parsed and that a protocol is set
@@ -191,8 +226,11 @@ GitRemoteResolver.prototype._supportsShallowCloning = function () {
return Q.resolve(false);
}
if (!this._host || !this._config.shallowCloneHosts || this._config.shallowCloneHosts.indexOf(this._host) === -1) {
if (
!this._host ||
!this._config.shallowCloneHosts ||
this._config.shallowCloneHosts.indexOf(this._host) === -1
) {
return Q.resolve(false);
}
@@ -200,34 +238,43 @@ GitRemoteResolver.prototype._supportsShallowCloning = function () {
// HTTP or HTTPS, not for Git or SSH.
// Also check for hosts that have been checked in a previous request and have been found to support
// shallow cloning.
if (mout.string.startsWith(this._remote.protocol, 'http')
&& !GitRemoteResolver._canShallow.get(this._host)) {
if (
mout.string.startsWith(this._remote.protocol, 'http') &&
!GitRemoteResolver._canShallow.get(this._host)
) {
// Provide GIT_CURL_VERBOSE=2 environment variable to capture curl output.
// Calling ls-remote includes a call to the git-upload-pack service, which returns the content type in the response.
var processEnv = mout.object.merge(process.env, { 'GIT_CURL_VERBOSE': '2' });
var processEnv = mout.object.merge(process.env, {
GIT_CURL_VERBOSE: '2'
});
value = cmd('git', ['ls-remote', '--heads', this._source], {
env: processEnv
})
.spread(function (stdout, stderr) {
// Check stderr for content-type, ignore stdout
var isSmartServer;
}).spread(
function(stdout, stderr) {
// Check stderr for content-type, ignore stdout
var isSmartServer;
// If the content type is 'x-git', then the server supports shallow cloning
isSmartServer = mout.string.contains(stderr,
'Content-Type: application/x-git-upload-pack-advertisement');
// If the content type is 'x-git', then the server supports shallow cloning
isSmartServer = mout.string.contains(
stderr,
'Content-Type: application/x-git-upload-pack-advertisement'
);
this._logger.debug('detect-smart-git', 'Smart Git host detected: ' + isSmartServer);
this._logger.debug(
'detect-smart-git',
'Smart Git host detected: ' + isSmartServer
);
if (isSmartServer) {
// Cache this host
GitRemoteResolver._canShallow.set(this._host, true);
}
if (isSmartServer) {
// Cache this host
GitRemoteResolver._canShallow.set(this._host, true);
}
return isSmartServer;
}.bind(this));
}
else {
return isSmartServer;
}.bind(this)
);
} else {
// One of the following cases:
// * A non-HTTP/HTTPS protocol
// * A host that has been checked before and that supports shallow cloning
@@ -240,7 +287,7 @@ GitRemoteResolver.prototype._supportsShallowCloning = function () {
// ------------------------------
// Grab refs remotely
GitRemoteResolver.refs = function (source) {
GitRemoteResolver.refs = function(source) {
var value;
// TODO: Normalize source because of the various available protocols?
@@ -250,20 +297,22 @@ GitRemoteResolver.refs = function (source) {
}
// Store the promise in the refs object
value = cmd('git', ['ls-remote', '--tags', '--heads', source])
.spread(function (stdout) {
var refs;
value = cmd('git', ['ls-remote', '--tags', '--heads', 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
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);
// Update the refs with the actual refs
this._cache.refs.set(source, refs);
return refs;
}.bind(this));
return refs;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value

View File

@@ -3,7 +3,7 @@ var path = require('path');
var Q = require('q');
var rimraf = require('../../util/rimraf');
var mkdirp = require('mkdirp');
var which = require('which');
var which = require('../../util/which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
@@ -51,18 +51,20 @@ mout.object.mixIn(GitResolver, Resolver);
// -----------------
GitResolver.prototype._hasNew = function (pkgMeta) {
GitResolver.prototype._hasNew = function(pkgMeta) {
var oldResolution = pkgMeta._resolution || {};
return this._findResolution()
.then(function (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)) {
if (
resolution.type === 'version' &&
semver.neq(resolution.tag, oldResolution.tag)
) {
return true;
}
@@ -71,35 +73,37 @@ GitResolver.prototype._hasNew = function (pkgMeta) {
});
};
GitResolver.prototype._resolve = function () {
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();
});
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 () {
GitResolver.prototype._checkout = function() {
throw new Error('_checkout not implemented');
};
GitResolver.refs = function (source) {
GitResolver.refs = function(source) {
throw new Error('refs not implemented');
};
// -----------------
GitResolver.prototype._findResolution = function (target) {
GitResolver.prototype._findResolution = function(target) {
var err;
var self = this.constructor;
var that = this;
@@ -108,18 +112,15 @@ GitResolver.prototype._findResolution = function (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)) {
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;
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
@@ -127,98 +128,143 @@ GitResolver.prototype._findResolution = function (target) {
return that._findResolution('master');
}
versionsArr = versions.map(function (obj) { return obj.version; });
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 };
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) {
]).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] };
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] };
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(', ')
});
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'
);
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]
});
}
that._resolution = { type: 'commit', commit: target };
return that._resolution;
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;
}
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 () {
GitResolver.prototype._cleanup = function() {
var gitFolder = path.join(this._tempDir, '.git');
return Q.nfcall(rimraf, gitFolder);
};
GitResolver.prototype._savePkgMeta = function (meta) {
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
});
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
@@ -232,9 +278,10 @@ GitResolver.prototype._savePkgMeta = function (meta) {
// 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);
meta._release =
version ||
this._resolution.tag ||
this._resolution.commit.substr(0, 10);
// Save resolution to be used in hasNew later
meta._resolution = this._resolution;
@@ -244,51 +291,56 @@ GitResolver.prototype._savePkgMeta = function (meta) {
// ------------------------------
GitResolver.versions = function (source, extra) {
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);
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;
});
}
// 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));
return versions;
}.bind(this)
);
}
value = this.tags(source)
.then(function (tags) {
var tag;
var version;
var versions = [];
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] });
// 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);
});
// 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));
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
@@ -297,30 +349,31 @@ GitResolver.versions = function (source, extra) {
return value;
};
GitResolver.tags = function (source) {
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 = {};
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+)/);
// 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];
}
});
if (match && !mout.string.endsWith(match[2], '^{}')) {
tags[match[2]] = match[1];
}
});
this._cache.tags.set(source, tags);
this._cache.tags.set(source, tags);
return tags;
}.bind(this));
return tags;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
@@ -329,32 +382,33 @@ GitResolver.tags = function (source) {
return value;
};
GitResolver.branches = function (source) {
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 = {};
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+)/);
// 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];
}
});
if (match) {
branches[match[2]] = match[1];
}
});
this._cache.branches.set(source, branches);
this._cache.branches.set(source, branches);
return branches;
}.bind(this));
return branches;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
@@ -363,9 +417,9 @@ GitResolver.branches = function (source) {
return value;
};
GitResolver.clearRuntimeCache = function () {
GitResolver.clearRuntimeCache = function() {
// Reset cache for branches, tags, etc
mout.object.forOwn(GitResolver._cache, function (lru) {
mout.object.forOwn(GitResolver._cache, function(lru) {
lru.reset();
});
};

View File

@@ -24,27 +24,27 @@ function Resolver(decEndpoint, config, logger) {
// -----------------
Resolver.prototype.getSource = function () {
Resolver.prototype.getSource = function() {
return this._source;
};
Resolver.prototype.getName = function () {
Resolver.prototype.getName = function() {
return this._name;
};
Resolver.prototype.getTarget = function () {
Resolver.prototype.getTarget = function() {
return this._target;
};
Resolver.prototype.getTempDir = function () {
Resolver.prototype.getTempDir = function() {
return this._tempDir;
};
Resolver.prototype.getPkgMeta = function () {
Resolver.prototype.getPkgMeta = function() {
return this._pkgMeta;
};
Resolver.prototype.hasNew = function (pkgMeta) {
Resolver.prototype.hasNew = function(pkgMeta) {
var promise;
var that = this;
@@ -58,12 +58,12 @@ Resolver.prototype.hasNew = function (pkgMeta) {
// Avoid reading the package meta if already given
promise = this._hasNew(pkgMeta);
return promise.fin(function () {
return promise.fin(function() {
that._working = false;
});
};
Resolver.prototype.resolve = function () {
Resolver.prototype.resolve = function() {
var that = this;
// If already working, error out
@@ -74,89 +74,104 @@ Resolver.prototype.resolve = function () {
this._working = true;
// Create temporary dir
return this._createTempDir()
// Resolve self
.then(this._resolve.bind(this))
// Read json, generating the package meta
.then(this._readJson.bind(this, null))
// Apply and save package meta
.then(function (meta) {
return that._applyPkgMeta(meta)
.then(that._savePkgMeta.bind(that, meta));
})
.then(function () {
// Resolve with the folder
return that._tempDir;
}, function (err) {
// If something went wrong, unset the temporary dir
that._tempDir = null;
throw err;
})
.fin(function () {
that._working = false;
});
return (
this._createTempDir()
// Resolve self
.then(this._resolve.bind(this))
// Read json, generating the package meta
.then(this._readJson.bind(this, null))
// Apply and save package meta
.then(function(meta) {
return that
._applyPkgMeta(meta)
.then(that._savePkgMeta.bind(that, meta));
})
.then(
function() {
// Resolve with the folder
return that._tempDir;
},
function(err) {
// If something went wrong, unset the temporary dir
that._tempDir = null;
throw err;
}
)
.fin(function() {
that._working = false;
})
);
};
Resolver.prototype.isCacheable = function () {
Resolver.prototype.isCacheable = function() {
// Bypass cache for local dependencies
if (this._source &&
if (
this._source &&
/^(?:file:[\/\\]{2}|[A-Z]:)?\.?\.?[\/\\]/.test(this._source)
) {
return false;
}
// We don't want to cache moving targets like branches
if (this._pkgMeta &&
if (
this._pkgMeta &&
this._pkgMeta._resolution &&
this._pkgMeta._resolution.type === 'branch') {
this._pkgMeta._resolution.type === 'branch'
) {
return false;
}
return true;
};
// -----------------
// Abstract functions that must be implemented by concrete resolvers
Resolver.prototype._resolve = function () {
Resolver.prototype._resolve = function() {
throw new Error('_resolve not implemented');
};
// Abstract functions that can be re-implemented by concrete resolvers
// as necessary
Resolver.prototype._hasNew = function (pkgMeta) {
Resolver.prototype._hasNew = function(pkgMeta) {
return Q.resolve(true);
};
Resolver.isTargetable = function () {
Resolver.isTargetable = function() {
return true;
};
Resolver.versions = function (source) {
Resolver.versions = function(source) {
return Q.resolve([]);
};
Resolver.clearRuntimeCache = function () {};
Resolver.clearRuntimeCache = function() {};
// -----------------
Resolver.prototype._createTempDir = function () {
Resolver.prototype._createTempDir = function() {
return Q.nfcall(mkdirp, this._config.tmp)
.then(function () {
return Q.nfcall(tmp.dir, {
template: path.join(this._config.tmp, md5(this._name) + '-' + process.pid + '-XXXXXX'),
mode: 0777 & ~process.umask(),
unsafeCleanup: true
});
}.bind(this))
.then(function (dir) {
// nfcall may return multiple callback arguments as an array
return this._tempDir = Array.isArray(dir) ? dir[0] : dir;
}.bind(this));
.then(
function() {
return Q.nfcall(tmp.dir, {
template: path.join(
this._config.tmp,
md5(this._name) + '-' + process.pid + '-XXXXXX'
),
mode: 0777 & ~process.umask(),
unsafeCleanup: true
});
}.bind(this)
)
.then(
function(dir) {
// nfcall may return multiple callback arguments as an array
return (this._tempDir = Array.isArray(dir) ? dir[0] : dir);
}.bind(this)
);
};
Resolver.prototype._cleanTempDir = function () {
Resolver.prototype._cleanTempDir = function() {
var tempDir = this._tempDir;
if (!tempDir) {
@@ -165,32 +180,37 @@ Resolver.prototype._cleanTempDir = function () {
// Delete and create folder
return Q.nfcall(rimraf, tempDir)
.then(function () {
return Q.nfcall(mkdirp, tempDir, 0777 & ~process.umask());
})
.then(function () {
return tempDir;
});
.then(function() {
return Q.nfcall(mkdirp, tempDir, 0777 & ~process.umask());
})
.then(function() {
return tempDir;
});
};
Resolver.prototype._readJson = function (dir) {
Resolver.prototype._readJson = function(dir) {
var that = this;
dir = dir || this._tempDir;
return readJson(dir, {
assume: { name: this._name },
logger: that._logger
})
.spread(function (json, deprecated) {
}).spread(function(json, deprecated) {
if (deprecated) {
that._logger.warn('deprecated', 'Package ' + that._name + ' is using the deprecated ' + deprecated);
that._logger.warn(
'deprecated',
'Package ' +
that._name +
' is using the deprecated ' +
deprecated
);
}
return json;
});
};
Resolver.prototype._applyPkgMeta = function (meta) {
Resolver.prototype._applyPkgMeta = function(meta) {
// 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._guessedName) {
@@ -204,13 +224,12 @@ Resolver.prototype._applyPkgMeta = function (meta) {
}
// Otherwise remove them from the temp dir
return removeIgnores(this._tempDir, meta)
.then(function () {
return removeIgnores(this._tempDir, meta).then(function() {
return meta;
});
};
Resolver.prototype._savePkgMeta = function (meta) {
Resolver.prototype._savePkgMeta = function(meta) {
var that = this;
var contents;
@@ -221,9 +240,12 @@ Resolver.prototype._savePkgMeta = function (meta) {
// Stringify contents
contents = JSON.stringify(meta, null, 2);
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
.then(function () {
return that._pkgMeta = meta;
return Q.nfcall(
fs.writeFile,
path.join(this._tempDir, '.bower.json'),
contents
).then(function() {
return (that._pkgMeta = meta);
});
};

View File

@@ -1,6 +1,6 @@
var util = require('util');
var Q = require('q');
var which = require('which');
var which = require('../../util/which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
@@ -31,27 +31,29 @@ mout.object.mixIn(SvnResolver, Resolver);
// -----------------
SvnResolver.getSource = function (source) {
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
.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) {
SvnResolver.prototype._hasNew = function(pkgMeta) {
var oldResolution = pkgMeta._resolution || {};
return this._findResolution()
.then(function (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)) {
if (
resolution.type === 'version' &&
semver.neq(resolution.tag, oldResolution.tag)
) {
return true;
}
@@ -60,18 +62,17 @@ SvnResolver.prototype._hasNew = function (pkgMeta) {
});
};
SvnResolver.prototype._resolve = function () {
SvnResolver.prototype._resolve = function() {
var that = this;
return this._findResolution()
.then(function () {
return this._findResolution().then(function() {
return that._export();
});
};
// -----------------
SvnResolver.prototype._export = function () {
SvnResolver.prototype._export = function() {
var promise;
var timer;
var reporter;
@@ -80,27 +81,56 @@ SvnResolver.prototype._export = function () {
this.source = SvnResolver.getSource(this._source);
this._logger.action('export', resolution.tag || resolution.branch || resolution.commit, {
resolution: resolution,
to: this._tempDir
});
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]);
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]);
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]);
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]);
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) {
reporter = mout.fn.throttle(function(data) {
var lines;
lines = data.split(/[\r\n]+/);
lines.forEach(function (line) {
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?
@@ -110,25 +140,27 @@ SvnResolver.prototype._export = function () {
}, 1000);
// Start reporting progress after a few seconds
timer = setTimeout(function () {
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();
});
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) {
SvnResolver.prototype._findResolution = function(target) {
var err;
var self = this.constructor;
var that = this;
@@ -139,7 +171,7 @@ SvnResolver.prototype._findResolution = function (target) {
// 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)) {
if (/^r\d+/.test(target)) {
target = target.split('r');
this._resolution = { type: 'commit', commit: target[1] };
@@ -148,13 +180,12 @@ SvnResolver.prototype._findResolution = function (target) {
// Target is a range/version
if (semver.validRange(target)) {
return self.versions(this._source, true)
.then(function (versions) {
var versionsArr,
version,
index;
return self.versions(this._source, true).then(function(versions) {
var versionsArr, version, index;
versionsArr = versions.map(function (obj) { return obj.version; });
versionsArr = versions.map(function(obj) {
return obj.version;
});
// If there are no tags and target is *,
// fallback to the latest commit on trunk
@@ -162,80 +193,124 @@ SvnResolver.prototype._findResolution = function (target) {
return that._findResolution('trunk');
}
versionsArr = versions.map(function (obj) { return obj.version; });
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 };
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) {
]).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] };
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] };
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(', ')
});
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] };
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;
}
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) {
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
});
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
@@ -249,9 +324,7 @@ SvnResolver.prototype._savePkgMeta = function (meta) {
// 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;
meta._release = version || this._resolution.tag || this._resolution.commit;
// Save resolution to be used in hasNew later
meta._resolution = this._resolution;
@@ -261,52 +334,58 @@ SvnResolver.prototype._savePkgMeta = function (meta) {
// ------------------------------
SvnResolver.versions = function (source, extra) {
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);
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;
});
}
// 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));
return versions;
}.bind(this)
);
}
value = this.tags(source)
.then(function (tags) {
var tag;
var version;
var versions = [];
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] });
// 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);
});
// Sort them by DESC order
versions.sort(function(a, b) {
return semver.rcompare(a.version, b.version);
});
this._cache.versions.set(source, versions);
this._cache.versions.set(source, versions);
// Call the function again to keep it DRY
return this.versions(source, extra);
}.bind(this));
// 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
@@ -315,7 +394,7 @@ SvnResolver.versions = function (source, extra) {
return value;
};
SvnResolver.tags = function (source) {
SvnResolver.tags = function(source) {
source = SvnResolver.getSource(source);
var value = this._cache.tags.get(source);
@@ -324,14 +403,19 @@ SvnResolver.tags = function (source) {
return Q.resolve(value);
}
value = cmd('svn', ['list', source + '/tags', '--verbose', '--non-interactive'])
.spread(function (stout) {
var tags = SvnResolver.parseSubversionListOutput(stout.toString());
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));
this._cache.tags.set(source, tags);
return tags;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
@@ -340,7 +424,7 @@ SvnResolver.tags = function (source) {
return value;
};
SvnResolver.branches = function (source) {
SvnResolver.branches = function(source) {
source = SvnResolver.getSource(source);
var value = this._cache.branches.get(source);
@@ -349,17 +433,24 @@ SvnResolver.branches = function (source) {
return Q.resolve(value);
}
value = cmd('svn', ['list', source + '/branches', '--verbose', '--non-interactive'])
.spread(function (stout) {
var branches = SvnResolver.parseSubversionListOutput(stout.toString());
value = cmd('svn', [
'list',
source + '/branches',
'--verbose',
'--non-interactive'
]).spread(
function(stout) {
var branches = SvnResolver.parseSubversionListOutput(
stout.toString()
);
// trunk is a branch!
branches.trunk = '*';
// trunk is a branch!
branches.trunk = '*';
this._cache.branches.set(source, branches);
return branches;
}.bind(this));
this._cache.branches.set(source, branches);
return branches;
}.bind(this)
);
// Store the promise to be reused until it resolves
// to a specific value
@@ -368,15 +459,12 @@ SvnResolver.branches = function (source) {
return value;
};
SvnResolver.parseSubversionListOutput = function (stout) {
SvnResolver.parseSubversionListOutput = function(stout) {
var entries = {};
var lines = stout
.trim()
.split(/[\r\n]+/);
var lines = stout.trim().split(/[\r\n]+/);
// For each line in the refs, match only the branches
lines.forEach(function (line) {
lines.forEach(function(line) {
var match = line.match(/\s+([0-9]+)\s.+\s([\w.$-]+)\//i);
if (match && match[2] !== '.') {
@@ -387,9 +475,9 @@ SvnResolver.parseSubversionListOutput = function (stout) {
return entries;
};
SvnResolver.clearRuntimeCache = function () {
SvnResolver.clearRuntimeCache = function() {
// Reset cache for branches, tags, etc
mout.object.forOwn(SvnResolver._cache, function (lru) {
mout.object.forOwn(SvnResolver._cache, function(lru) {
lru.reset();
});
};

View File

@@ -16,7 +16,7 @@ function UrlResolver(decEndpoint, config, logger) {
// If target was specified, error out
if (this._target !== '*') {
throw createError('URL sources can\'t resolve targets', 'ENORESTARGET');
throw createError("URL sources can't resolve targets", 'ENORESTARGET');
}
// If the name was guessed
@@ -24,7 +24,10 @@ function UrlResolver(decEndpoint, config, logger) {
// Remove the ?xxx part
this._name = this._name.replace(/\?.*$/, '');
// Remove extension
this._name = this._name.substr(0, this._name.length - path.extname(this._name).length);
this._name = this._name.substr(
0,
this._name.length - path.extname(this._name).length
);
}
this._remote = url.parse(this._source);
@@ -35,11 +38,11 @@ mout.object.mixIn(UrlResolver, Resolver);
// -----------------
UrlResolver.isTargetable = function () {
UrlResolver.isTargetable = function() {
return false;
};
UrlResolver.prototype._hasNew = function (pkgMeta) {
UrlResolver.prototype._hasNew = function(pkgMeta) {
var oldCacheHeaders = pkgMeta._cacheHeaders || {};
var reqHeaders = {};
@@ -54,61 +57,71 @@ UrlResolver.prototype._hasNew = function (pkgMeta) {
}
// Make an HEAD request to the source
return Q.nfcall(request.head, this._source, {
ca: this._config.ca.default,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders
})
// Compare new headers with the old ones
.spread(function (response) {
var cacheHeaders;
return (
Q.nfcall(request.head, this._source, {
ca: this._config.ca.default,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders
})
// Compare new headers with the old ones
.spread(
function(response) {
var cacheHeaders;
// If the server responded with 303 then the resource
// still has the same ETag
if (response.statusCode === 304) {
return false;
}
// If the server responded with 303 then the resource
// still has the same ETag
if (response.statusCode === 304) {
return false;
}
// If status code is not in the 2xx range,
// then just resolve to true
if (response.statusCode < 200 || response.statusCode >= 300) {
return true;
}
// If status code is not in the 2xx range,
// then just resolve to true
if (
response.statusCode < 200 ||
response.statusCode >= 300
) {
return true;
}
// Fallback to comparing cache headers
cacheHeaders = this._collectCacheHeaders(response);
return !mout.object.equals(oldCacheHeaders, cacheHeaders);
}.bind(this), function () {
// Assume new contents if the request failed
// Note that we do not retry the request using the "request-replay" module
// because it would take too long
return true;
});
// Fallback to comparing cache headers
cacheHeaders = this._collectCacheHeaders(response);
return !mout.object.equals(oldCacheHeaders, cacheHeaders);
}.bind(this),
function() {
// Assume new contents if the request failed
// Note that we do not retry the request using the "request-replay" module
// because it would take too long
return true;
}
)
);
};
// TODO: There's room for improvement by using streams if the URL
// is an archive file, by piping read stream to the zip extractor
// This will likely increase the complexity of code but might worth it
UrlResolver.prototype._resolve = function () {
UrlResolver.prototype._resolve = function() {
// Download
return this._download()
// Parse headers
.spread(this._parseHeaders.bind(this))
// Extract file
.spread(this._extract.bind(this))
// Rename file to index
.then(this._rename.bind(this));
return (
this._download()
// Parse headers
.spread(this._parseHeaders.bind(this))
// Extract file
.spread(this._extract.bind(this))
// Rename file to index
.then(this._rename.bind(this))
);
};
// -----------------
UrlResolver.prototype._parseSourceURL = function (_url) {
UrlResolver.prototype._parseSourceURL = function(_url) {
return url.parse(path.basename(_url)).pathname;
};
UrlResolver.prototype._download = function () {
UrlResolver.prototype._download = function() {
var fileName = this._parseSourceURL(this._source);
if (!fileName) {
@@ -136,32 +149,43 @@ UrlResolver.prototype._download = function () {
timeout: this._config.timeout,
headers: reqHeaders
})
.progress(function (state) {
var msg;
.progress(function(state) {
var msg;
// Retry?
if (state.retry) {
msg = 'Download of ' + that._source + ' failed' + (state.error.code ? ' 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);
}
// Retry?
if (state.retry) {
msg =
'Download of ' +
that._source +
' failed' +
(state.error.code ? ' 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 (response) {
that._response = response;
return [file, response];
});
// 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(response) {
that._response = response;
return [file, response];
});
};
UrlResolver.prototype._parseHeaders = function (file, response) {
UrlResolver.prototype._parseHeaders = function(file, response) {
var disposition;
var newFile;
var match;
@@ -196,20 +220,19 @@ UrlResolver.prototype._parseHeaders = function (file, response) {
newFile = path.join(this._tempDir, newFile);
return Q.nfcall(fs.rename, file, newFile)
.then(function () {
return Q.nfcall(fs.rename, file, newFile).then(function() {
return [newFile, response];
});
};
UrlResolver.prototype._extract = function (file, response) {
UrlResolver.prototype._extract = function(file, response) {
var mimeType = response.headers['content-type'];
if (mimeType) {
// Clean everything after ; and trim the end result
mimeType = mimeType.split(';')[0].trim();
// Some servers add quotes around the content-type, so we trim that also
mimeType = mout.string.trim(mimeType, ['"', '\'']);
mimeType = mout.string.trim(mimeType, ['"', "'"]);
}
if (!extract.canExtract(file, mimeType)) {
@@ -226,36 +249,42 @@ UrlResolver.prototype._extract = function (file, response) {
});
};
UrlResolver.prototype._rename = function () {
return Q.nfcall(fs.readdir, this._tempDir)
.then(function (files) {
var file;
var oldPath;
var newPath;
UrlResolver.prototype._rename = function() {
return Q.nfcall(fs.readdir, this._tempDir).then(
function(files) {
var file;
var oldPath;
var newPath;
// Remove any OS specific files from the files array
// before checking its length
files = files.filter(junk.isnt);
// Remove any OS specific files from the files array
// before checking its length
files = files.filter(junk.isnt);
// Only rename if there's only one file and it's not the json
if (files.length === 1 && !/^(component|bower)\.json$/.test(files[0])) {
file = files[0];
this._singleFile = 'index' + path.extname(file);
oldPath = path.join(this._tempDir, file);
newPath = path.join(this._tempDir, this._singleFile);
// Only rename if there's only one file and it's not the json
if (
files.length === 1 &&
!/^(component|bower)\.json$/.test(files[0])
) {
file = files[0];
this._singleFile = 'index' + path.extname(file);
oldPath = path.join(this._tempDir, file);
newPath = path.join(this._tempDir, this._singleFile);
return Q.nfcall(fs.rename, oldPath, newPath);
}
}.bind(this));
return Q.nfcall(fs.rename, oldPath, newPath);
}
}.bind(this)
);
};
UrlResolver.prototype._savePkgMeta = function (meta) {
UrlResolver.prototype._savePkgMeta = function(meta) {
// Store collected headers in the package meta
meta._cacheHeaders = this._collectCacheHeaders(this._response);
// Store ETAG under _release
if (meta._cacheHeaders.ETag) {
meta._release = 'e-tag:' + mout.string.trim(meta._cacheHeaders.ETag.substr(0, 10), '"');
meta._release =
'e-tag:' +
mout.string.trim(meta._cacheHeaders.ETag.substr(0, 10), '"');
}
// Store main if is a single file
@@ -266,11 +295,11 @@ UrlResolver.prototype._savePkgMeta = function (meta) {
return Resolver.prototype._savePkgMeta.call(this, meta);
};
UrlResolver.prototype._collectCacheHeaders = function (res) {
UrlResolver.prototype._collectCacheHeaders = function(res) {
var headers = {};
// Collect cache headers
this.constructor._cacheHeaders.forEach(function (name) {
this.constructor._cacheHeaders.forEach(function(name) {
var value = res.headers[name.toLowerCase()];
if (value != null) {

View File

@@ -12,7 +12,12 @@ function pluginResolverFactory(resolverFactory, bower) {
bower = bower || {};
if (typeof resolverFactory !== 'function') {
throw createError('Resolver has "' + typeof resolverFactory + '" type instead of "function" type.', 'ERESOLERAPI');
throw createError(
'Resolver has "' +
typeof resolverFactory +
'" type instead of "function" type.',
'ERESOLERAPI'
);
}
var resolver = resolverFactory(bower);
@@ -20,7 +25,9 @@ function pluginResolverFactory(resolverFactory, bower) {
function maxSatisfyingVersion(versions, target) {
var versionsArr, index;
versionsArr = versions.map(function (obj) { return obj.version; });
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 *
@@ -36,7 +43,7 @@ function pluginResolverFactory(resolverFactory, bower) {
}
// @private
PluginResolver.prototype.getEndpoint = function () {
PluginResolver.prototype.getEndpoint = function() {
return object.merge(this._decEndpoint, {
name: this.getName(),
source: this.getSource(),
@@ -44,15 +51,15 @@ function pluginResolverFactory(resolverFactory, bower) {
});
};
PluginResolver.prototype.getSource = function () {
PluginResolver.prototype.getSource = function() {
return this._decEndpoint.source;
};
PluginResolver.prototype.getTarget = function () {
PluginResolver.prototype.getTarget = function() {
return this._decEndpoint.target || '*';
};
PluginResolver.prototype.getName = function () {
PluginResolver.prototype.getName = function() {
if (!this._decEndpoint.name && typeof resolver.getName === 'function') {
return resolver.getName.call(resolver, this.getSource());
} else if (!this._decEndpoint.name) {
@@ -62,7 +69,7 @@ function pluginResolverFactory(resolverFactory, bower) {
}
};
PluginResolver.prototype.getPkgMeta = function () {
PluginResolver.prototype.getPkgMeta = function() {
return this._pkgMeta;
};
@@ -70,32 +77,32 @@ function pluginResolverFactory(resolverFactory, bower) {
// Plugin Resolver is always considered potentially cacheable
// The "resolve" method decides whether to use cached or fetch new version.
PluginResolver.prototype.isCacheable = function () {
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) {
PluginResolver.prototype.hasNew = function(pkgMeta) {
if (this.hasNewPromise) {
return this.hasNewPromise;
}
this._pkgMeta = pkgMeta;
return this.hasNewPromise = this.resolve().then(function (result) {
return (this.hasNewPromise = this.resolve().then(function(result) {
return result !== undefined;
});
}));
};
PluginResolver.prototype.resolve = function () {
PluginResolver.prototype.resolve = function() {
if (this.resolvePromise) {
return this.resolvePromise;
}
var that = this;
return this.resolvePromise = Q.fcall(function () {
return (this.resolvePromise = Q.fcall(function() {
var target = that.getTarget();
// It means that we can accept ranges as targets
@@ -103,15 +110,19 @@ function pluginResolverFactory(resolverFactory, bower) {
that._release = target;
if (semver.validRange(target)) {
return Q.fcall(resolver.releases.bind(resolver), that.getSource())
.then(function (result) {
return Q.fcall(
resolver.releases.bind(resolver),
that.getSource()
).then(function(result) {
if (!result) {
throw createError('Resolver did not provide releases of package.');
throw createError(
'Resolver did not provide releases of package.'
);
}
var releases = that._releases = result;
var releases = (that._releases = result);
var versions = releases.filter(function (target) {
var versions = releases.filter(function(target) {
return semver.clean(target.version);
});
@@ -119,91 +130,122 @@ function pluginResolverFactory(resolverFactory, bower) {
if (maxRelease) {
that._version = maxRelease.version;
that._release = that._decEndpoint.target = maxRelease.target;
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(', ')
});
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 + ')'));
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.');
.then(function() {
// We pass old _resolution (if hasNew has been called before contents).
// So resolver can decide whether use cached version of contents new one.
if (typeof resolver.fetch !== 'function') {
throw createError(
'Resolver does not implement the "fetch" method.'
);
}
that._tempDir = result.tempPath;
var cached = {};
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;
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) {
PluginResolver.prototype._readJson = function(dir) {
var that = this;
return readJson(dir, {
assume: { name: that.getName() },
logger: bower.logger
})
.spread(function (json, deprecated) {
}).spread(function(json, deprecated) {
if (deprecated) {
bower.logger.warn('deprecated', 'Package ' + that.getName() + ' is using the deprecated ' + deprecated);
bower.logger.warn(
'deprecated',
'Package ' +
that.getName() +
' is using the deprecated ' +
deprecated
);
}
return json;
});
};
PluginResolver.prototype._applyPkgMeta = function (meta, result) {
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) {
@@ -212,17 +254,21 @@ function pluginResolverFactory(resolverFactory, bower) {
// 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) {
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 removeIgnores(this._tempDir, meta).then(function() {
return meta;
});
};
PluginResolver.prototype._savePkgMeta = function (meta, result) {
PluginResolver.prototype._savePkgMeta = function(meta, result) {
var that = this;
meta._source = that.getSource();
@@ -245,30 +291,37 @@ function pluginResolverFactory(resolverFactory, bower) {
// 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;
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) {
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.');
throw createError(
'Resolver did not provide releases of package.'
);
}
var releases = this._releases = result;
var releases = (this._releases = result);
var versions = releases.map(function (version) {
var versions = releases.map(function(version) {
return semver.clean(version.version);
});
versions = versions.filter(function (version) {
versions = versions.filter(function(version) {
return version;
});
versions.sort(function (a, b) {
versions.sort(function(a, b) {
return semver.rcompare(a, b);
});
@@ -276,39 +329,50 @@ function pluginResolverFactory(resolverFactory, bower) {
});
};
PluginResolver.isTargetable = function () {
PluginResolver.isTargetable = function() {
// If resolver doesn't define versions function, it's not targetable..
return typeof resolver.releases === 'function';
};
PluginResolver.clearRuntimeCache = function () {
PluginResolver.clearRuntimeCache = function() {
resolver = resolverFactory(bower);
};
PluginResolver.match = function (source) {
PluginResolver.match = function(source) {
if (typeof resolver.match !== 'function') {
throw createError('Resolver is missing "match" method.', 'ERESOLVERAPI');
throw createError(
'Resolver is missing "match" method.',
'ERESOLVERAPI'
);
}
var match = resolver.match.bind(resolver);
return Q.fcall(match, source).then(function (result) {
return Q.fcall(match, source).then(function(result) {
if (typeof result !== 'boolean') {
throw createError('Resolver\'s "match" method should return a boolean', 'ERESOLVERAPI');
throw createError(
'Resolver\'s "match" method should return a boolean',
'ERESOLVERAPI'
);
}
return result;
});
};
PluginResolver.locate = function (source) {
PluginResolver.locate = function(source) {
if (typeof resolver.locate !== 'function') {
return source;
}
return Q.fcall(resolver.locate.bind(resolver), source).then(function (result) {
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');
throw createError(
'Resolver\'s "locate" method should return a string',
'ERESOLVERAPI'
);
}
return result;
@@ -318,6 +382,4 @@ function pluginResolverFactory(resolverFactory, bower) {
return PluginResolver;
}
module.exports = pluginResolverFactory;

View File

@@ -3,28 +3,36 @@ var cmd = require('../util/cmd');
var Q = require('q');
var shellquote = require('shell-quote');
var orderByDependencies = function (packages, installed, json) {
var orderByDependencies = function(packages, installed, json) {
var ordered = [];
installed = mout.object.keys(installed);
var depsSatisfied = function (packageName) {
return mout.array.difference(mout.object.keys(packages[packageName].dependencies), installed, ordered).length === 0;
var depsSatisfied = function(packageName) {
return (
mout.array.difference(
mout.object.keys(packages[packageName].dependencies),
installed,
ordered
).length === 0
);
};
var depsFromBowerJson = json && json.dependencies ? mout.object.keys(json.dependencies) : [];
var depsFromBowerJson =
json && json.dependencies ? mout.object.keys(json.dependencies) : [];
var packageNames = mout.object.keys(packages);
//get the list of the packages that are specified in bower.json in that order
//its nice to maintain that order for users
var desiredOrder = mout.array.intersection(depsFromBowerJson, packageNames);
//then add to the end any remaining packages that werent in bower.json
desiredOrder = desiredOrder.concat(mout.array.difference(packageNames, desiredOrder));
desiredOrder = desiredOrder.concat(
mout.array.difference(packageNames, desiredOrder)
);
//the desired order isn't necessarily a correct dependency specific order
//so we ensure that below
var resolvedOne = true;
while (resolvedOne) {
resolvedOne = false;
for (var i = 0; i < desiredOrder.length; i++) {
@@ -43,17 +51,16 @@ var orderByDependencies = function (packages, installed, json) {
//so lets just jam those names on the end
ordered = ordered.concat(desiredOrder);
}
}
return ordered;
};
var run = function (cmdString, action, logger, config) {
var run = function(cmdString, action, logger, config) {
logger.action(action, cmdString);
//pass env + BOWER_PID so callees can identify a preinstall+postinstall from the same bower instance
var env = mout.object.mixIn({ 'BOWER_PID': process.pid }, process.env);
var env = mout.object.mixIn({ BOWER_PID: process.pid }, process.env);
var args = shellquote.parse(cmdString, env);
var cmdName = args[0];
mout.array.remove(args, cmdName); //no rest() in mout
@@ -65,8 +72,8 @@ var run = function (cmdString, action, logger, config) {
var promise = cmd(cmdName, args, options);
promise.progress(function (progress) {
progress.split('\n').forEach(function (line) {
promise.progress(function(progress) {
progress.split('\n').forEach(function(line) {
if (line) {
logger.action(action, line);
}
@@ -76,14 +83,32 @@ var run = function (cmdString, action, logger, config) {
return promise;
};
var hook = function (action, ordered, config, logger, packages, installed, json) {
if (mout.object.keys(packages).length === 0 || !config.scripts || !config.scripts[action]) {
var hook = function(
action,
ordered,
config,
logger,
packages,
installed,
json
) {
if (
mout.object.keys(packages).length === 0 ||
!config.scripts ||
!config.scripts[action]
) {
return Q();
}
var orderedPackages = ordered ? orderByDependencies(packages, installed, json) : mout.object.keys(packages);
var orderedPackages = ordered
? orderByDependencies(packages, installed, json)
: mout.object.keys(packages);
var placeholder = new RegExp('%', 'g');
var cmdString = mout.string.replace(config.scripts[action], placeholder, orderedPackages.join(' '));
var cmdString = mout.string.replace(
config.scripts[action],
placeholder,
orderedPackages.join(' ')
);
return run(cmdString, action, logger, config);
};

View File

@@ -7,7 +7,7 @@ function JsonRenderer() {
this._nrLogs = 0;
}
JsonRenderer.prototype.end = function (data) {
JsonRenderer.prototype.end = function(data) {
if (this._nrLogs) {
process.stderr.write(']\n');
}
@@ -17,7 +17,7 @@ JsonRenderer.prototype.end = function (data) {
}
};
JsonRenderer.prototype.error = function (err) {
JsonRenderer.prototype.error = function(err) {
var message = err.message;
var stack;
@@ -32,13 +32,13 @@ JsonRenderer.prototype.error = function (err) {
// Stack
stack = err.fstream_stack || err.stack || 'N/A';
err.stacktrace = (Array.isArray(stack) ? stack.join('\n') : stack);
err.stacktrace = Array.isArray(stack) ? stack.join('\n') : stack;
this.log(err);
this.end();
};
JsonRenderer.prototype.log = function (log) {
JsonRenderer.prototype.log = function(log) {
if (!this._nrLogs) {
process.stderr.write('[');
} else {
@@ -49,12 +49,12 @@ JsonRenderer.prototype.log = function (log) {
this._nrLogs++;
};
JsonRenderer.prototype.prompt = function (prompts) {
JsonRenderer.prototype.prompt = function(prompts) {
var promise = Q.resolve();
var answers = {};
var that = this;
prompts.forEach(function (prompt) {
prompts.forEach(function(prompt) {
var opts;
var funcName;
@@ -63,65 +63,68 @@ JsonRenderer.prototype.prompt = function (prompts) {
// Prompt
opts = {
silent: true, // To not mess with JSON output
trim: false, // To allow " " to not assume the default value
default: prompt.default == null ? '' : prompt.default, // If default is null, make it '' so that it does not retry
validator: !prompt.validate ? null : function (value) {
var ret = prompt.validate(value);
silent: true, // To not mess with JSON output
trim: false, // To allow " " to not assume the default value
default: prompt.default == null ? '' : prompt.default, // If default is null, make it '' so that it does not retry
validator: !prompt.validate
? null
: function(value) {
var ret = prompt.validate(value);
if (typeof ret === 'string') {
throw ret;
}
if (typeof ret === 'string') {
throw ret;
}
return value;
},
return value;
}
};
// For now only "input", "confirm" and "password" are supported
switch (prompt.type) {
case 'input':
funcName = 'prompt';
break;
case 'confirm':
case 'password':
funcName = prompt.type;
break;
case 'checkbox':
funcName = 'prompt';
break;
default:
promise = promise.then(function () {
throw createError('Unknown prompt type', 'ENOTSUP');
});
return;
case 'input':
funcName = 'prompt';
break;
case 'confirm':
case 'password':
funcName = prompt.type;
break;
case 'checkbox':
funcName = 'prompt';
break;
default:
promise = promise.then(function() {
throw createError('Unknown prompt type', 'ENOTSUP');
});
return;
}
promise = promise.then(function () {
promise = promise.then(function() {
// Log
prompt.level = 'prompt';
that.log(prompt);
return Q.nfcall(promptly[funcName], '', opts)
.then(function (answer) {
return Q.nfcall(promptly[funcName], '', opts).then(function(
answer
) {
answers[prompt.name] = answer;
});
});
if (prompt.type === 'checkbox') {
promise = promise.then(function () {
promise = promise.then(function() {
answers[prompt.name] = answers[prompt.name].split(',');
});
}
});
return promise.then(function () {
return promise.then(function() {
return answers;
});
};
// -------------------------
JsonRenderer.prototype._stringify = function (log) {
JsonRenderer.prototype._stringify = function(log) {
// To json
var str = JSON.stringify(log, null, ' ');
// Remove colors in case some log has colors..

View File

@@ -11,9 +11,9 @@ var template = require('../util/template');
function StandardRenderer(command, config) {
this._sizes = {
id: 13, // Id max chars
id: 13, // Id max chars
label: 20, // Label max chars
sumup: 5 // Amount to sum when the label exceeds
sumup: 5 // Amount to sum when the label exceeds
};
this._colors = {
warn: chalk.yellow,
@@ -32,7 +32,7 @@ function StandardRenderer(command, config) {
this._compact = process.stdout.columns < 120;
}
var exitOnPipeError = function (err) {
var exitOnPipeError = function(err) {
if (err.code === 'EPIPE') {
process.exit(0);
}
@@ -43,7 +43,7 @@ function StandardRenderer(command, config) {
process.stderr.on('error', exitOnPipeError);
}
StandardRenderer.prototype.end = function (data) {
StandardRenderer.prototype.end = function(data) {
var method = '_' + mout.string.camelCase(this._command);
if (this[method]) {
@@ -51,7 +51,7 @@ StandardRenderer.prototype.end = function (data) {
}
};
StandardRenderer.prototype.error = function (err) {
StandardRenderer.prototype.error = function(err) {
var str;
var stack;
@@ -60,12 +60,19 @@ StandardRenderer.prototype.error = function (err) {
err.id = err.code || 'error';
err.level = 'error';
str = this._prefix(err) + ' ' + err.message.replace(/\r?\n/g, ' ').trim() + '\n';
str =
this._prefix(err) +
' ' +
err.message.replace(/\r?\n/g, ' ').trim() +
'\n';
this._write(process.stderr, 'bower ' + str);
// Check if additional details were provided
if (err.details) {
str = chalk.yellow('\nAdditional error details:\n') + err.details.trim() + '\n';
str =
chalk.yellow('\nAdditional error details:\n') +
err.details.trim() +
'\n';
this._write(process.stderr, str);
}
@@ -83,12 +90,18 @@ StandardRenderer.prototype.error = function (err) {
// Print bower version, node version and system info.
this._write(process.stderr, chalk.yellow('\nSystem info:\n'));
this._write(process.stderr, 'Bower version: ' + version + '\n');
this._write(process.stderr, 'Node version: ' + process.versions.node + '\n');
this._write(process.stderr, 'OS: ' + os.type() + ' ' + os.release() + ' ' + os.arch() + '\n');
this._write(
process.stderr,
'Node version: ' + process.versions.node + '\n'
);
this._write(
process.stderr,
'OS: ' + os.type() + ' ' + os.release() + ' ' + os.arch() + '\n'
);
}
};
StandardRenderer.prototype.log = function (log) {
StandardRenderer.prototype.log = function(log) {
var method = '_' + mout.string.camelCase(log.id) + 'Log';
this._guessOrigin(log);
@@ -101,12 +114,12 @@ StandardRenderer.prototype.log = function (log) {
}
};
StandardRenderer.prototype.prompt = function (prompts) {
StandardRenderer.prototype.prompt = function(prompts) {
var deferred;
// Strip colors from the prompt if color is disabled
if (!this._config.color) {
prompts.forEach(function (prompt) {
prompts.forEach(function(prompt) {
prompt.message = chalk.stripColor(prompt.message);
});
}
@@ -121,7 +134,7 @@ StandardRenderer.prototype.prompt = function (prompts) {
// -------------------------
StandardRenderer.prototype._help = function (data) {
StandardRenderer.prototype._help = function(data) {
var str;
var that = this;
var specific;
@@ -136,60 +149,68 @@ StandardRenderer.prototype._help = function (data) {
if (template.exists(specific)) {
str = template.render(specific, data);
} else {
str = template.render('std/help-generic.std', data);
str = template.render('std/help-generic.std', data);
}
that._write(process.stdout, str);
}
};
StandardRenderer.prototype._install = function (packages) {
StandardRenderer.prototype._install = function(packages) {
var str = '';
mout.object.forOwn(packages, function (pkg) {
var cliTree;
mout.object.forOwn(
packages,
function(pkg) {
var cliTree;
// List only 1 level deep dependencies
mout.object.forOwn(pkg.dependencies, function (dependency) {
dependency.dependencies = {};
});
// Make canonical dir relative
pkg.canonicalDir = path.relative(this._config.cwd, pkg.canonicalDir);
// Signal as root
pkg.root = true;
// List only 1 level deep dependencies
mout.object.forOwn(pkg.dependencies, function(dependency) {
dependency.dependencies = {};
});
// Make canonical dir relative
pkg.canonicalDir = path.relative(
this._config.cwd,
pkg.canonicalDir
);
// Signal as root
pkg.root = true;
cliTree = this._tree2archy(pkg);
str += '\n' + archy(cliTree);
}, this);
cliTree = this._tree2archy(pkg);
str += '\n' + archy(cliTree);
},
this
);
if (str) {
this._write(process.stdout, str);
}
};
StandardRenderer.prototype._update = function (packages) {
StandardRenderer.prototype._update = function(packages) {
this._install(packages);
};
StandardRenderer.prototype._list = function (tree) {
StandardRenderer.prototype._list = function(tree) {
var cliTree;
if (tree.pkgMeta) {
tree.root = true;
cliTree = archy(this._tree2archy(tree));
} else {
cliTree = stringifyObject(tree, { indent: ' ' }).replace(/[{}]/g, '') + '\n';
cliTree =
stringifyObject(tree, { indent: ' ' }).replace(/[{}]/g, '') + '\n';
}
this._write(process.stdout, cliTree);
};
StandardRenderer.prototype._search = function (results) {
StandardRenderer.prototype._search = function(results) {
var str = template.render('std/search-results.std', results);
this._write(process.stdout, str);
};
StandardRenderer.prototype._info = function (data) {
StandardRenderer.prototype._info = function(data) {
var str = '';
var pkgMeta = data;
var includeVersions = false;
@@ -212,7 +233,7 @@ StandardRenderer.prototype._info = function (data) {
data.numPreReleases = 0;
// If output isn't verbose, hide prereleases
if (!this._config.verbose) {
data.versions = mout.array.filter(data.versions, function (version) {
data.versions = mout.array.filter(data.versions, function(version) {
version = semverUtils.parse(version);
if (!version.release && !version.build) {
return true;
@@ -227,13 +248,13 @@ StandardRenderer.prototype._info = function (data) {
this._write(process.stdout, str);
};
StandardRenderer.prototype._lookup = function (data) {
StandardRenderer.prototype._lookup = function(data) {
var str = template.render('std/lookup.std', data);
this._write(process.stdout, str);
};
StandardRenderer.prototype._link = function (data) {
StandardRenderer.prototype._link = function(data) {
this._sizes.id = 4;
this.log({
@@ -248,7 +269,7 @@ StandardRenderer.prototype._link = function (data) {
}
};
StandardRenderer.prototype._register = function (data) {
StandardRenderer.prototype._register = function(data) {
var str;
// If no data passed, it means the user aborted
@@ -260,17 +281,20 @@ StandardRenderer.prototype._register = function (data) {
this._write(process.stdout, str);
};
StandardRenderer.prototype._cacheList = function (entries) {
entries.forEach(function (entry) {
StandardRenderer.prototype._cacheList = function(entries) {
entries.forEach(function(entry) {
var pkgMeta = entry.pkgMeta;
var version = pkgMeta.version || pkgMeta._target;
this._write(process.stdout, pkgMeta.name + '=' + pkgMeta._source + '#' + version + '\n');
this._write(
process.stdout,
pkgMeta.name + '=' + pkgMeta._source + '#' + version + '\n'
);
}, this);
};
// -------------------------
StandardRenderer.prototype._genericLog = function (log) {
StandardRenderer.prototype._genericLog = function(log) {
var stream;
var str;
@@ -284,7 +308,7 @@ StandardRenderer.prototype._genericLog = function (log) {
this._write(stream, 'bower ' + str);
};
StandardRenderer.prototype._checkoutLog = function (log) {
StandardRenderer.prototype._checkoutLog = function(log) {
if (this._compact) {
log.message = log.origin.split('#')[0] + '#' + log.message;
}
@@ -292,7 +316,7 @@ StandardRenderer.prototype._checkoutLog = function (log) {
this._genericLog(log);
};
StandardRenderer.prototype._progressLog = function (log) {
StandardRenderer.prototype._progressLog = function(log) {
if (this._compact) {
log.message = log.origin + ' ' + log.message;
}
@@ -300,7 +324,7 @@ StandardRenderer.prototype._progressLog = function (log) {
this._genericLog(log);
};
StandardRenderer.prototype._extractLog = function (log) {
StandardRenderer.prototype._extractLog = function(log) {
if (this._compact) {
log.message = log.origin + ' ' + log.message;
}
@@ -308,19 +332,23 @@ StandardRenderer.prototype._extractLog = function (log) {
this._genericLog(log);
};
StandardRenderer.prototype._incompatibleLog = function (log) {
StandardRenderer.prototype._incompatibleLog = function(log) {
var str;
var templatePath;
// Generate dependants string for each pick
log.data.picks.forEach(function (pick) {
pick.dependants = pick.dependants.map(function (dependant) {
var release = dependant.pkgMeta._release;
return dependant.endpoint.name + (release ? '#' + release : '');
}).join(', ');
log.data.picks.forEach(function(pick) {
pick.dependants = pick.dependants
.map(function(dependant) {
var release = dependant.pkgMeta._release;
return dependant.endpoint.name + (release ? '#' + release : '');
})
.join(', ');
});
templatePath = log.data.suitable ? 'std/conflict-resolved.std' : 'std/conflict.std';
templatePath = log.data.suitable
? 'std/conflict-resolved.std'
: 'std/conflict.std';
str = template.render(templatePath, log.data);
this._write(process.stdout, '\n');
@@ -328,15 +356,18 @@ StandardRenderer.prototype._incompatibleLog = function (log) {
this._write(process.stdout, '\n');
};
StandardRenderer.prototype._solvedLog = function (log) {
StandardRenderer.prototype._solvedLog = function(log) {
this._incompatibleLog(log);
};
StandardRenderer.prototype._jsonLog = function (log) {
this._write(process.stdout, '\n' + this._highlightJson(log.data.json) + '\n\n');
StandardRenderer.prototype._jsonLog = function(log) {
this._write(
process.stdout,
'\n' + this._highlightJson(log.data.json) + '\n\n'
);
};
StandardRenderer.prototype._cachedEntryLog = function (log) {
StandardRenderer.prototype._cachedEntryLog = function(log) {
if (this._compact) {
log.message = log.origin;
}
@@ -346,7 +377,7 @@ StandardRenderer.prototype._cachedEntryLog = function (log) {
// -------------------------
StandardRenderer.prototype._guessOrigin = function (log) {
StandardRenderer.prototype._guessOrigin = function(log) {
var data = log.data;
if (!data) {
@@ -354,7 +385,8 @@ StandardRenderer.prototype._guessOrigin = function (log) {
}
if (data.endpoint) {
log.origin = data.endpoint.name || (data.registry && data.endpoint.source);
log.origin =
data.endpoint.name || (data.registry && data.endpoint.source);
// Resort to using the resolver name for unnamed endpoints
if (!log.origin && data.resolver) {
@@ -373,7 +405,7 @@ StandardRenderer.prototype._guessOrigin = function (log) {
}
};
StandardRenderer.prototype._prefix = function (log) {
StandardRenderer.prototype._prefix = function(log) {
var label;
var length;
var nrSpaces;
@@ -405,7 +437,7 @@ StandardRenderer.prototype._prefix = function (log) {
return chalk.green(label) + mout.string.repeat(' ', nrSpaces) + idColor(id);
};
StandardRenderer.prototype._write = function (stream, str) {
StandardRenderer.prototype._write = function(stream, str) {
if (!this._config.color) {
str = chalk.stripColor(str);
}
@@ -413,18 +445,18 @@ StandardRenderer.prototype._write = function (stream, str) {
stream.write(str);
};
StandardRenderer.prototype._highlightJson = function (json) {
StandardRenderer.prototype._highlightJson = function(json) {
var cardinal = require('cardinal');
return cardinal.highlight(stringifyObject(json, { indent: ' ' }), {
theme: {
String: {
_default: function (str) {
_default: function(str) {
return chalk.cyan(str);
}
},
Identifier: {
_default: function (str) {
_default: function(str) {
return chalk.green(str);
}
}
@@ -433,9 +465,11 @@ StandardRenderer.prototype._highlightJson = function (json) {
});
};
StandardRenderer.prototype._tree2archy = function (node) {
StandardRenderer.prototype._tree2archy = function(node) {
var dependencies = mout.object.values(node.dependencies);
var version = !node.missing ? node.pkgMeta._release || node.pkgMeta.version : null;
var version = !node.missing
? node.pkgMeta._release || node.pkgMeta.version
: null;
var label = node.endpoint.name + (version ? '#' + version : '');
var update;
@@ -458,7 +492,8 @@ StandardRenderer.prototype._tree2archy = function (node) {
}
if (node.incompatible) {
label += chalk.yellow(' incompatible') + ' with ' + node.endpoint.target;
label +=
chalk.yellow(' incompatible') + ' with ' + node.endpoint.target;
} else if (node.extraneous) {
label += chalk.green(' extraneous');
}
@@ -472,7 +507,7 @@ StandardRenderer.prototype._tree2archy = function (node) {
}
if (node.update.latest !== node.update.target) {
update += (update ? ', ' : '');
update += update ? ', ' : '';
update += 'latest is ' + node.update.latest;
}
@@ -500,7 +535,7 @@ StandardRenderer._wideCommands = [
'register'
];
StandardRenderer._idMappings = {
'mutual': 'conflict',
mutual: 'conflict',
'cached-entry': 'cached'
};

View File

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

View File

@@ -1,9 +1,9 @@
var mout = require('mout');
var leadLinesRegExp = /^\r?\n/;
var multipleLinesRegExp = /\r?\n(\r?\n)+/mg;
var multipleLinesRegExp = /\r?\n(\r?\n)+/gm;
function condense(Handlebars) {
Handlebars.registerHelper('condense', function (context) {
Handlebars.registerHelper('condense', function(context) {
var str = context.fn(this);
// Remove multiple lines

View File

@@ -1,7 +1,7 @@
var mout = require('mout');
function indent(Handlebars) {
Handlebars.registerHelper('indent', function (context) {
Handlebars.registerHelper('indent', function(context) {
var hash = context.hash;
var indentStr = mout.string.repeat(' ', parseInt(hash.level, 10));

View File

@@ -1,7 +1,7 @@
var mout = require('mout');
function rpad(Handlebars) {
Handlebars.registerHelper('rpad', function (context) {
Handlebars.registerHelper('rpad', function(context) {
var hash = context.hash;
var minLength = parseInt(hash.minLength, 10);
var chr = hash.char;

View File

@@ -1,5 +1,5 @@
function sum(Handlebars) {
Handlebars.registerHelper('sum', function (val1, val2) {
Handlebars.registerHelper('sum', function(val1, val2) {
return val1 + val2;
});
}

View File

@@ -5,7 +5,7 @@ function expandNames(obj, prefix, stack) {
prefix = prefix || '';
stack = stack || [];
mout.object.forOwn(obj, function (value, name) {
mout.object.forOwn(obj, function(value, name) {
name = prefix + name;
stack.push(name);
@@ -18,7 +18,7 @@ function expandNames(obj, prefix, stack) {
return stack;
}
module.exports = function (commands) {
module.exports = function(commands) {
var abbreviations = abbrev(expandNames(commands));
abbreviations.i = 'install';

21
lib/util/childProcess.js Normal file
View File

@@ -0,0 +1,21 @@
var childProcess = require('child_process');
var which = require('./which');
function execFile(cmd, args, opt, cb) {
try {
cmd = which.sync(cmd);
} catch (e) {
cb(e);
}
return childProcess.execFile(cmd, args, opt, cb);
}
function spawn(cmd, args, opt) {
cmd = which.sync(cmd);
return childProcess.spawn(cmd, args, opt);
}
module.exports = {
execFile: execFile,
spawn: spawn
};

View File

@@ -15,10 +15,10 @@ function readOptions(options, argv) {
options = options || {};
}
types = mout.object.map(options, function (option) {
types = mout.object.map(options, function(option) {
return option.type;
});
mout.object.forOwn(options, function (option, name) {
mout.object.forOwn(options, function(option, name) {
shorthands[option.shorthand] = '--' + name;
});
@@ -26,7 +26,7 @@ function readOptions(options, argv) {
// Filter only the specified options because nopt parses every --
// Also make them camel case
mout.object.forOwn(noptOptions, function (value, key) {
mout.object.forOwn(noptOptions, function(value, key) {
if (options[key]) {
parsedOptions[mout.string.camelCase(key)] = value;
}

View File

@@ -1,8 +1,8 @@
var cp = require('child_process');
var cp = require('./childProcess');
var path = require('path');
var Q = require('q');
var mout = require('mout');
var which = require('which');
var which = require('./which');
var PThrottler = require('p-throttler');
var createError = require('./createError');
@@ -37,17 +37,17 @@ function getWindowsCommand(command) {
try {
fullCommand = which.sync(command);
} catch (err) {
return winWhichCache[command] = command;
return (winWhichCache[command] = command);
}
extension = path.extname(fullCommand).toLowerCase();
// Does it need to be converted?
if (winBatchExtensions.indexOf(extension) === -1) {
return winWhichCache[command] = command;
return (winWhichCache[command] = command);
}
return winWhichCache[command] = fullCommand;
return (winWhichCache[command] = fullCommand);
}
// Executes a shell command, buffering the stdout and stderr
@@ -67,25 +67,25 @@ function executeCmd(command, args, options) {
// Buffer output, reporting progress
process = cp.spawn(command, args, options);
process.stdout.on('data', function (data) {
process.stdout.on('data', function(data) {
data = data.toString();
deferred.notify(data);
stdout += data;
});
process.stderr.on('data', function (data) {
process.stderr.on('data', function(data) {
data = data.toString();
deferred.notify(data);
stderr += data;
});
// If there is an error spawning the command, reject the promise
process.on('error', function (error) {
process.on('error', function(error) {
return deferred.reject(error);
});
// Listen to the close event instead of exit
// They are similar but close ensures that streams are flushed
process.on('close', function (code) {
process.on('close', function(code) {
var fullCommand;
var error;
@@ -99,10 +99,19 @@ function executeCmd(command, args, options) {
fullCommand += args.length ? ' ' + args.join(' ') : '';
// Build the error instance
error = createError('Failed to execute "' + fullCommand + '", exit code of #' + code + '\n' + stderr, 'ECMDERR', {
details: stderr,
exitCode: code
});
error = createError(
'Failed to execute "' +
fullCommand +
'", exit code of #' +
code +
'\n' +
stderr,
'ECMDERR',
{
details: stderr,
exitCode: code
}
);
return deferred.reject(error);
}

View File

@@ -26,18 +26,17 @@ function copy(reader, writer) {
deferred = Q.defer();
reader
.on('error', deferred.reject)
// Pipe to writer
.pipe(fstream.Writer(writer))
.on('error', deferred.reject)
.on('close', deferred.resolve);
.on('error', deferred.reject)
// Pipe to writer
.pipe(fstream.Writer(writer))
.on('error', deferred.reject)
.on('close', deferred.resolve);
return deferred.promise;
}
function copyMode(src, dst) {
return Q.nfcall(fs.stat, src)
.then(function (stat) {
return Q.nfcall(fs.stat, src).then(function(stat) {
return Q.nfcall(fs.chmod, dst, stat.mode);
});
}
@@ -68,14 +67,17 @@ function copyFile(src, dst, opts) {
opts = parseOptions(opts);
promise = copy({
path: src,
type: 'File'
}, {
path: dst,
mode: opts.mode,
type: 'File'
});
promise = copy(
{
path: src,
type: 'File'
},
{
path: dst,
mode: opts.mode,
type: 'File'
}
);
if (opts.copyMode) {
promise = promise.then(copyMode.bind(copyMode, src, dst));
@@ -93,15 +95,18 @@ function copyDir(src, dst, opts) {
opts = parseOptions(opts);
promise = copy({
path: src,
type: 'Directory',
ignore: opts.ignore
}, {
path: dst,
mode: opts.mode,
type: 'Directory'
});
promise = copy(
{
path: src,
type: 'Directory',
ignore: opts.ignore
},
{
path: dst,
mode: opts.mode,
type: 'Directory'
}
);
if (opts.copyMode) {
promise = promise.then(copyMode.bind(copyMode, src, dst));

View File

@@ -10,41 +10,55 @@ 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'
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');
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;
}
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).'
// 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

@@ -20,50 +20,53 @@ function download(url, file, options) {
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 || {});
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 () {
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];
.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);
}
// 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;
// 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);
}
});
// 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;
@@ -75,68 +78,75 @@ function fetch(url, file, options) {
var contentLength;
var bytesDownloaded = 0;
var reject = function (error) {
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']);
.on('response', function(response) {
contentLength = Number(response.headers['content-length']);
var status = response.statusCode;
var status = response.statusCode;
if (status < 200 || status >= 300) {
return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
}
if (status < 200 || status >= 300) {
return deferred.reject(
createError('Status code of ' + status, 'EHTTP')
);
}
var writeStream = createWriteStream(file);
var errored = false;
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) {
// Change error listener so it cleans up writeStream before exiting
req.removeListener('error', reject);
req.on('error', function(error) {
errored = true;
destroy(req);
deferred.resolve(response);
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'
)
);
}
});
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;
}

View File

@@ -38,13 +38,13 @@ 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
});
.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;
}
@@ -54,22 +54,25 @@ function extractTar(archive, dst) {
var stream = fs.createReadStream(archive);
var reject = function (error) {
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);
});
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;
}
@@ -79,24 +82,27 @@ function extractTarGz(archive, dst) {
var stream = fs.createReadStream(archive);
var reject = function (error) {
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);
});
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;
}
@@ -106,25 +112,26 @@ function extractGz(archive, dst) {
var stream = fs.createReadStream(archive);
var reject = function (error) {
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);
});
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 isSymlink(_, entry) {
return entry.type === 'symlink';
}
function filterSymlinks(entry) {
@@ -136,7 +143,7 @@ function getExtractor(archive) {
// This ensures that upper-cased extensions work
archive = archive.toLowerCase();
var type = mout.array.find(extractorTypes, function (type) {
var type = mout.array.find(extractorTypes, function(type) {
return mout.string.endsWith(archive, type);
});
@@ -144,8 +151,7 @@ function getExtractor(archive) {
}
function isSingleDir(dir) {
return Q.nfcall(fs.readdir, dir)
.then(function (files) {
return Q.nfcall(fs.readdir, dir).then(function(files) {
var singleDir;
// Remove any OS specific files from the files array
@@ -158,8 +164,7 @@ function isSingleDir(dir) {
singleDir = path.join(dir, files[0]);
return Q.nfcall(fs.stat, singleDir)
.then(function (stat) {
return Q.nfcall(fs.stat, singleDir).then(function(stat) {
return stat.isDirectory() ? singleDir : false;
});
});
@@ -167,19 +172,19 @@ function isSingleDir(dir) {
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);
.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.nfcall(fs.rename, src, dst);
});
return Q.all(promises);
})
.then(function() {
return Q.nfcall(fs.rmdir, srcDir);
});
return Q.all(promises);
})
.then(function () {
return Q.nfcall(fs.rmdir, srcDir);
});
}
// -----------------------------
@@ -209,57 +214,64 @@ function extract(src, dst, opts) {
// If extractor is null, then the archive type is unknown
if (!extractor) {
return Q.reject(createError('File ' + src + ' is not a known archive', 'ENOTARCHIVE'));
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(tempDir) {
// nfcall may return multiple callback arguments as an array
return Array.isArray(tempDir) ? tempDir[0] : tempDir;
})
.then(function (singleDir) {
if (singleDir && !opts.keepStructure) {
return moveDirectory(singleDir, dst);
} else {
return moveDirectory(tempDir, dst);
}
});
.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'
);
}
// Resolve promise to the dst dir
return promise.then(function () {
return dst;
// 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;

View File

@@ -5,14 +5,14 @@ var readdirSync = fs.readdirSync.bind(fs);
module.exports = fs;
module.exports.readdir = function (dir, callback) {
fs.stat(dir, function (err, stats) {
module.exports.readdir = function(dir, callback) {
fs.stat(dir, function(err, stats) {
if (err) return callback(err);
if (stats.isDirectory()) {
return readdir(dir, callback);
} else {
var error = new Error('ENOTDIR, not a directory \'' + dir + '\'');
var error = new Error("ENOTDIR, not a directory '" + dir + "'");
error.code = 'ENOTDIR';
error.path = dir;
error.errono = -20;
@@ -21,7 +21,7 @@ module.exports.readdir = function (dir, callback) {
});
};
module.exports.readdirSync = function (dir) {
module.exports.readdirSync = function(dir) {
var stats = fs.statSync(dir);
if (stats.isDirectory()) {

View File

@@ -13,41 +13,43 @@ function readJson(file, options) {
options = options || {};
// Read
return Q.nfcall(bowerJson.read, file, options)
.spread(function (json, jsonFile) {
var deprecated;
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);
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);
});
}
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;
}
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

@@ -2,7 +2,7 @@ var path = require('path');
var isPathAbsolute = require('./isPathAbsolute');
function relativeToBaseDir(baseDir) {
return function (filePath) {
return function(filePath) {
if (isPathAbsolute(filePath)) {
return path.resolve(filePath);
} else {

View File

@@ -14,7 +14,7 @@ function removeIgnores(dir, meta) {
// Don't ignore main files
nonIgnored = nonIgnored.concat(meta.main || []);
nonIgnored = nonIgnored.map(function (file) {
nonIgnored = nonIgnored.map(function(file) {
return path.join(dir, file);
});
@@ -27,7 +27,7 @@ function removeIgnores(dir, meta) {
// Monkey patch applyIgnores such that we get hold of all ignored files
applyIgnores = reader.applyIgnores;
reader.applyIgnores = function (entry) {
reader.applyIgnores = function(entry) {
var ret = applyIgnores.apply(this, arguments);
if (!ret) {
@@ -38,28 +38,27 @@ function removeIgnores(dir, meta) {
};
reader
.on('child', function (entry) {
nonIgnored.push(entry.path);
})
.on('error', deferred.reject)
.on('end', function () {
var promises;
.on('child', function(entry) {
nonIgnored.push(entry.path);
})
.on('error', deferred.reject)
.on('end', function() {
var promises;
// Ensure that we are not ignoring files that should not be ignored!
ignored = mout.array.unique(ignored);
ignored = ignored.filter(function (file) {
return nonIgnored.indexOf(file) === -1;
// Ensure that we are not ignoring files that should not be ignored!
ignored = mout.array.unique(ignored);
ignored = ignored.filter(function(file) {
return nonIgnored.indexOf(file) === -1;
});
// Delete all the ignored files
promises = ignored.map(function(file) {
return Q.nfcall(rimraf, file);
});
return Q.all(promises).then(deferred.resolve, deferred.reject);
});
// Delete all the ignored files
promises = ignored.map(function (file) {
return Q.nfcall(rimraf, file);
});
return Q.all(promises)
.then(deferred.resolve, deferred.reject);
});
return deferred.promise;
}

View File

@@ -4,9 +4,9 @@ var resolve = require('resolve');
function startsWith(string, searchString, position) {
position = position || 0;
return string.substr(position, searchString.length) === searchString;
};
}
module.exports = function (id, options) {
module.exports = function(id, options) {
var resolvedPath;
var cwd = (options || {}).cwd || process.cwd();
@@ -19,4 +19,4 @@ module.exports = function (id, options) {
}
return resolvedPath;
}
};

View File

@@ -2,15 +2,15 @@ var rimraf = require('rimraf');
var chmodr = require('chmodr');
var fs = require('./fs');
module.exports = function (dir, callback) {
var checkAndRetry = function (e) {
fs.lstat(dir, function (err, stats) {
module.exports = function(dir, callback) {
var checkAndRetry = function(e) {
fs.lstat(dir, function(err, stats) {
if (err) {
if (err.code === 'ENOENT') return callback();
return callback(e);
}
chmodr(dir, 0777, function (err) {
chmodr(dir, 0777, function(err) {
if (err) return callback(e);
rimraf(dir, callback);
});
@@ -24,8 +24,8 @@ module.exports = function (dir, callback) {
}
};
module.exports.sync = function (dir) {
var checkAndRetry = function () {
module.exports.sync = function(dir) {
var checkAndRetry = function() {
try {
fs.lstatSync(dir);
chmodr.sync(dir, 0777);

View File

@@ -1,4 +1,3 @@
'use strict';
var isRoot = require('is-root');
var createError = require('./createError');
@@ -12,7 +11,8 @@ function rootCheck(options, config) {
return;
}
errorMsg = 'Since bower is a user command, there is no need to execute it with \
errorMsg =
'Since bower is a user command, there is no need to execute it with \
superuser permissions.\nIf you\'re having permission errors when using bower without \
sudo, please spend a few minutes learning more about how your system should work and \
make any necessary repairs.\n\n\
@@ -23,8 +23,11 @@ You can however run a command with sudo using "--allow-root" option';
if (isRoot()) {
var cli = require('./cli');
renderer = cli.getRenderer('', false, config);
renderer.error(createError('Cannot be run with sudo', 'ESUDO', { details : errorMsg }));
process.exit(1);
renderer.error(
createError('Please do not run with sudo', 'ESUDO', {
details: errorMsg
})
);
}
}

View File

@@ -6,13 +6,13 @@ function maxSatisfying(versions, range, strictMatch) {
var filteredVersions;
// Filter only valid versions, since semver.maxSatisfying() throws an error
versions = versions.filter(function (version) {
versions = versions.filter(function(version) {
return semver.valid(version);
});
// Exact version & range match
if (semver.valid(range)) {
version = mout.array.find(versions, function (version) {
version = mout.array.find(versions, function(version) {
return version === range;
});
if (version) {
@@ -25,7 +25,7 @@ function maxSatisfying(versions, range, strictMatch) {
// When strict match is enabled give priority to non-pre-releases
// We do this by filtering every pre-release version
if (strictMatch) {
filteredVersions = versions.map(function (version) {
filteredVersions = versions.map(function(version) {
return !isPreRelease(version) ? version : null;
});
@@ -57,7 +57,10 @@ function clean(version) {
}
// Keep builds!
return parsed.version + (parsed.build.length ? '+' + parsed.build.join('.') : '');
return (
parsed.version +
(parsed.build.length ? '+' + parsed.build.join('.') : '')
);
}
function isPreRelease(version) {

View File

@@ -8,7 +8,7 @@ var templatesDir = path.resolve(__dirname, '../templates');
var cache = {};
// Register helpers
mout.object.forOwn(helpers, function (register) {
mout.object.forOwn(helpers, function(register) {
register(Handlebars);
});

View File

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

View File

@@ -5,20 +5,18 @@ 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];
}
.then(function(lstat) {
if (!lstat.isSymbolicLink()) {
return [false];
}
return Q.nfcall(fs.stat, file)
.then(function (stat) {
return [stat];
return Q.nfcall(fs.stat, file).then(function(stat) {
return [stat];
});
})
.fail(function(err) {
return [false, err];
});
})
.fail(function (err) {
return [false, err];
});
}
module.exports = validLink;

51
lib/util/which.js Normal file
View File

@@ -0,0 +1,51 @@
var join = require('path').join;
var execFileSync = require('child_process').execFileSync;
var cache = {};
var originalWhich = require('which');
var isWin = process.platform === 'win32';
function which(name, opt, cb) {
if (typeof opt === 'function') {
cb = opt;
opt = {};
}
if (isWin) {
var result = whichSync(name);
if (result) {
cb(null, result);
} else {
cb(new Error('Could not find ' + name + ' in PATH'));
}
} else {
originalWhich(name, opt, cb);
}
}
function whichSync(name, opt) {
if (name in cache) {
return cache[name];
}
if (isWin) {
var WHERE_PATH = join(process.env.WINDIR, 'System32', 'where.exe');
var stdout = execFileSync(WHERE_PATH, ['$PATH:' + name], {
stdio: ['pipe', 'pipe', 'ignore']
}).toString();
var matches = stdout.split('\r\n');
if (matches.length === 0) {
throw new Error('Could not find ' + name + ' in PATH');
}
var result = matches[0].trim();
cache[name] = result;
return result;
}
var result = originalWhich.sync(name, opt);
cache[name] = result;
return result;
}
which.sync = whichSync;
module.exports = which;

View File

@@ -1,6 +1,7 @@
{
"private": true,
"name": "bower",
"version": "1.8.0",
"version": "1.8.14",
"description": "The browser package manager",
"author": "Twitter",
"license": "MIT",
@@ -17,16 +18,11 @@
"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.1",
"cardinal": "0.4.4",
"chalk": "^1.0.0",
"chmodr": "^1.0.2",
"chmodr": "1.0.2",
"configstore": "^2.0.0",
"decompress-zip": "^0.2.1",
"decompress-zip": "^0.2.2",
"destroy": "^1.0.3",
"findup-sync": "^0.3.0",
"fs-write-stream-atomic": "1.0.8",
@@ -35,7 +31,7 @@
"github": "^0.2.3",
"glob": "^4.3.2",
"graceful-fs": "^4.1.3",
"handlebars": "^4.0.5",
"handlebars": "^4.5.3",
"inquirer": "0.10.0",
"is-root": "^1.0.0",
"junk": "^1.0.0",
@@ -49,7 +45,7 @@
"p-throttler": "0.1.1",
"promptly": "0.2.0",
"q": "^1.1.2",
"request": "2.79.0",
"request": "2.67.0",
"request-progress": "0.3.1",
"requireg": "^0.1.5",
"resolve": "^1.1.7",
@@ -68,33 +64,46 @@
"devDependencies": {
"arr-diff": "^2.0.0",
"chai": "^3.5.0",
"coveralls": "^2.11.9",
"eslint": "^4.18.2",
"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",
"load-grunt-tasks": "^3.5.0",
"mocha": "^2.5.3",
"lint-staged": "^9.5.0",
"mocha": "^3.5.3",
"multiline": "^1.0.2",
"nock": "^9.2.3",
"nock": "^11.7.0",
"nock-legacy": "npm:nock@9.2.3",
"node-uuid": "^1.4.7",
"prettier": "^1.19.1",
"proxyquire": "^1.7.9",
"spawn-sync": "1.0.15",
"wrench": "^1.5.8"
},
"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"
"lint": "eslint .",
"test": "node test/packages.js && node test/packages-svn.js && mocha --timeout 15000 --reporter spec",
"prepublishOnly": "in-publish && echo 'You need to use \"node publish.js\" 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"
]
},
"files": [
"bin",
"lib"
],
"resolutions": {
"deep-extend": "0.5.1",
"minimist": "0.2.1"
},
"workspaces": [
"packages/bower-config",
"packages/bower-endpoint-parser",
"packages/bower-json",
"packages/bower-logger",
"packages/bower-registry-client"
]
}

View File

@@ -1,61 +0,0 @@
{
"predef": [
"console",
"describe",
"it",
"after",
"afterEach",
"before",
"beforeEach"
],
"indent": 4,
"node": true,
"devel": true,
"bitwise": false,
"curly": false,
"eqeqeq": true,
"forin": false,
"immed": true,
"latedef": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": true,
"plusplus": false,
"regexp": false,
"undef": true,
"unused": true,
"quotmark": "single",
"strict": false,
"trailing": true,
"asi": false,
"boss": true,
"debug": false,
"eqnull": true,
"es5": false,
"esnext": false,
"evil": false,
"expr": false,
"funcscope": false,
"globalstrict": false,
"iterator": false,
"lastsemic": false,
"laxbreak": true,
"laxcomma": false,
"loopfunc": true,
"multistr": false,
"onecase": true,
"regexdash": false,
"scripturl": false,
"smarttabs": false,
"shadow": false,
"sub": false,
"supernew": true,
"validthis": false,
"nomen": false,
"white": true
}

View File

@@ -1,9 +0,0 @@
sudo: false
language: node_js
node_js:
- '5'
- '4'
- '0.12'
- '0.10'
script:
- grunt travis

View File

@@ -1,5 +1,13 @@
# Changelog
## 1.4.2
- Prevent errors when expanded env variable does not exist
## 1.4.2
- Update minimist to 0.2.1 to fix security issue
## 1.4.0
- Change default shorthand resolver from git:// to https://

View File

@@ -1,53 +0,0 @@
'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',
timeout: '10000'
},
full: {
src: ['test/test.js']
},
short: {
options: {
reporter: 'dot'
},
src: ['test/test.js']
}
},
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'
},
coveralls: {
command: 'node node_modules/.bin/coveralls < test/reports/lcov.info'
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'simplemocha:short']
}
});
grunt.registerTask('test', ['jshint', 'simplemocha:full']);
grunt.registerTask('cover', 'exec:cover');
grunt.registerTask('travis', ['jshint', 'exec:cover', 'exec:coveralls']);
grunt.registerTask('default', 'test');
};

View File

@@ -1,4 +1,4 @@
# bower-config [![Build Status](https://secure.travis-ci.org/bower/config.png?branch=master)](http://travis-ci.org/bower/config)[![Coverage Status](https://coveralls.io/repos/bower/config/badge.svg?branch=master&service=github)](https://coveralls.io/github/bower/config?branch=master)
# bower-config
> The Bower config (`.bowerrc`) reader and writer.

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,10 +44,14 @@ 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) {
@@ -68,15 +72,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();
};
@@ -86,13 +90,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,43 +2,44 @@ 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://registry.bower.io',
'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,31 @@ 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) {
function doEnvReplaceStr(f) {
// Un-tildify
var untildify = require('untildify');
f = untildify(f);
// Un-tildify
var untildify = require('untildify');
f = untildify(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]) {
return '${' + 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];
});
return process.env[name];
});
}
function envReplace(config) {
var envReplaced = {};
if ( lang.isArray(config) ) {
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,22 +55,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) ) {
} 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;
}
});
@@ -92,7 +87,8 @@ 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://registry.bower.io';
config.registry = {
default: config.registry.default,

View File

@@ -4,8 +4,16 @@ 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
@@ -30,7 +38,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;
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;
}
}
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 (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.http_proxy !== undefined) {
process.env.http_proxy = this.restoreFrom.http_proxy;
} else {
delete process.env.http_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 (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.https_proxy !== undefined) {
process.env.https_proxy = this.restoreFrom.https_proxy;
} else {
delete process.env.https_proxy;
}
}
};
module.exports = EnvProxy;

View File

@@ -1,6 +1,6 @@
var path = require('path');
var fs = require('graceful-fs');
var optimist = require('optimist');
var optimist = require('../vendor/optimist');
var osenv = require('osenv');
var object = require('mout/object');
var string = require('mout/string');
@@ -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,10 +122,9 @@ 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);
}
@@ -137,7 +136,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

@@ -0,0 +1,358 @@
var path = require('path');
var minimist = require('minimist');
var wordwrap = require('wordwrap');
/* Hack an instance of Argv with process.argv into Argv
so people can do
require('optimist')(['--beeble=1','-z','zizzle']).argv
to parse a list of args and
require('optimist').argv
to get a parsed version of process.argv.
*/
var inst = Argv(process.argv.slice(2));
Object.keys(inst).forEach(function(key) {
Argv[key] =
typeof inst[key] == 'function' ? inst[key].bind(inst) : inst[key];
});
var exports = (module.exports = Argv);
function Argv(processArgs, cwd) {
var self = {};
if (!cwd) cwd = process.cwd();
self.$0 = process.argv
.slice(0, 2)
.map(function(x) {
var b = rebase(cwd, x);
return x.match(/^\//) && b.length < x.length ? b : x;
})
.join(' ');
if (process.env._ != undefined && process.argv[1] == process.env._) {
self.$0 = process.env._.replace(
path.dirname(process.execPath) + '/',
''
);
}
var options = {
boolean: [],
string: [],
alias: {},
default: []
};
self.boolean = function(bools) {
options.boolean.push.apply(options.boolean, [].concat(bools));
return self;
};
self.string = function(strings) {
options.string.push.apply(options.string, [].concat(strings));
return self;
};
self.default = function(key, value) {
if (typeof key === 'object') {
Object.keys(key).forEach(function(k) {
self.default(k, key[k]);
});
} else {
options.default[key] = value;
}
return self;
};
self.alias = function(x, y) {
if (typeof x === 'object') {
Object.keys(x).forEach(function(key) {
self.alias(key, x[key]);
});
} else {
options.alias[x] = (options.alias[x] || []).concat(y);
}
return self;
};
var demanded = {};
self.demand = function(keys) {
if (typeof keys == 'number') {
if (!demanded._) demanded._ = 0;
demanded._ += keys;
} else if (Array.isArray(keys)) {
keys.forEach(function(key) {
self.demand(key);
});
} else {
demanded[keys] = true;
}
return self;
};
var usage;
self.usage = function(msg, opts) {
if (!opts && typeof msg === 'object') {
opts = msg;
msg = null;
}
usage = msg;
if (opts) self.options(opts);
return self;
};
function fail(msg) {
self.showHelp();
if (msg) console.error(msg);
process.exit(1);
}
var checks = [];
self.check = function(f) {
checks.push(f);
return self;
};
var descriptions = {};
self.describe = function(key, desc) {
if (typeof key === 'object') {
Object.keys(key).forEach(function(k) {
self.describe(k, key[k]);
});
} else {
descriptions[key] = desc;
}
return self;
};
self.parse = function(args) {
return parseArgs(args);
};
self.option = self.options = function(key, opt) {
if (typeof key === 'object') {
Object.keys(key).forEach(function(k) {
self.options(k, key[k]);
});
} else {
if (opt.alias) self.alias(key, opt.alias);
if (opt.demand) self.demand(key);
if (typeof opt.default !== 'undefined') {
self.default(key, opt.default);
}
if (opt.boolean || opt.type === 'boolean') {
self.boolean(key);
}
if (opt.string || opt.type === 'string') {
self.string(key);
}
var desc = opt.describe || opt.description || opt.desc;
if (desc) {
self.describe(key, desc);
}
}
return self;
};
var wrap = null;
self.wrap = function(cols) {
wrap = cols;
return self;
};
self.showHelp = function(fn) {
if (!fn) fn = console.error;
fn(self.help());
};
self.help = function() {
var keys = Object.keys(
Object.keys(descriptions)
.concat(Object.keys(demanded))
.concat(Object.keys(options.default))
.reduce(function(acc, key) {
if (key !== '_') acc[key] = true;
return acc;
}, {})
);
var help = keys.length ? ['Options:'] : [];
if (usage) {
help.unshift(usage.replace(/\$0/g, self.$0), '');
}
var switches = keys.reduce(function(acc, key) {
acc[key] = [key]
.concat(options.alias[key] || [])
.map(function(sw) {
return (sw.length > 1 ? '--' : '-') + sw;
})
.join(', ');
return acc;
}, {});
var switchlen = longest(
Object.keys(switches).map(function(s) {
return switches[s] || '';
})
);
var desclen = longest(
Object.keys(descriptions).map(function(d) {
return descriptions[d] || '';
})
);
keys.forEach(function(key) {
var kswitch = switches[key];
var desc = descriptions[key] || '';
if (wrap) {
desc = wordwrap(switchlen + 4, wrap)(desc).slice(switchlen + 4);
}
var spadding = new Array(
Math.max(switchlen - kswitch.length + 3, 0)
).join(' ');
var dpadding = new Array(
Math.max(desclen - desc.length + 1, 0)
).join(' ');
var type = null;
if (options.boolean[key]) type = '[boolean]';
if (options.string[key]) type = '[string]';
if (!wrap && dpadding.length > 0) {
desc += dpadding;
}
var prelude = ' ' + kswitch + spadding;
var extra = [
type,
demanded[key] ? '[required]' : null,
options.default[key] !== undefined
? '[default: ' + JSON.stringify(options.default[key]) + ']'
: null
]
.filter(Boolean)
.join(' ');
var body = [desc, extra].filter(Boolean).join(' ');
if (wrap) {
var dlines = desc.split('\n');
var dlen =
dlines.slice(-1)[0].length +
(dlines.length === 1 ? prelude.length : 0);
body =
desc +
(dlen + extra.length > wrap - 2
? '\n' +
new Array(wrap - extra.length + 1).join(' ') +
extra
: new Array(wrap - extra.length - dlen + 1).join(' ') +
extra);
}
help.push(prelude + body);
});
help.push('');
return help.join('\n');
};
Object.defineProperty(self, 'argv', {
get: function() {
return parseArgs(processArgs);
},
enumerable: true
});
function parseArgs(args) {
var argv = minimist(args, options);
argv.$0 = self.$0;
if (demanded._ && argv._.length < demanded._) {
fail(
'Not enough non-option arguments: got ' +
argv._.length +
', need at least ' +
demanded._
);
}
var missing = [];
Object.keys(demanded).forEach(function(key) {
if (!argv[key]) missing.push(key);
});
if (missing.length) {
fail('Missing required arguments: ' + missing.join(', '));
}
checks.forEach(function(f) {
try {
if (f(argv) === false) {
fail('Argument check failed: ' + f.toString());
}
} catch (err) {
fail(err);
}
});
return argv;
}
function longest(xs) {
return Math.max.apply(
null,
xs.map(function(x) {
return x.length;
})
);
}
return self;
}
// rebase an absolute path to a relative one with respect to a base directory
// exported for tests
exports.rebase = rebase;
function rebase(base, dir) {
var ds = path
.normalize(dir)
.split('/')
.slice(1);
var bs = path
.normalize(base)
.split('/')
.slice(1);
for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
ds.splice(0, i);
bs.splice(0, i);
var p = path
.normalize(
bs
.map(function() {
return '..';
})
.concat(ds)
.join('/')
)
.replace(/\/$/, '')
.replace(/^$/, '.');
return p.match(/^[.\/]/) ? p : './' + p;
}

View File

@@ -1,6 +1,6 @@
{
"name": "bower-config",
"version": "1.4.0",
"version": "1.4.3",
"description": "The Bower config reader and writer.",
"author": "Twitter",
"license": "MIT",
@@ -12,32 +12,23 @@
},
"dependencies": {
"graceful-fs": "^4.1.3",
"minimist": "^0.2.1",
"mout": "^1.0.0",
"optimist": "^0.6.1",
"osenv": "^0.1.3",
"untildify": "^2.1.0"
"untildify": "^2.1.0",
"wordwrap": "^0.0.3"
},
"devDependencies": {
"coveralls": "^2.11.4",
"expect.js": "^0.3.1",
"glob": "^4.5.3",
"grunt": "^0.4.5",
"grunt-cli": "^0.1.13",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-coveralls": "^1.0.0",
"grunt-exec": "^0.4.6",
"grunt-simple-mocha": "^0.4.0",
"istanbul": "^0.4.1",
"load-grunt-tasks": "^2.0.0",
"mkdirp": "^0.5.0",
"mocha": "~1.12.0",
"mocha": "^3.5.3",
"node-uuid": "^1.4.3",
"q": "^1.2.0",
"rimraf": "^2.3.2"
},
"scripts": {
"test": "grunt test"
"test": "mocha test"
},
"files": [
"lib"

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env node
var path = require('path');
var bowerConfig = require('..');
var config = bowerConfig.read(path.join(__dirname + '/assets/env-variables'), {
foo: 'bar'
});
console.log(config);

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,51 +76,56 @@ 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);
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);
});
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,66 +1,64 @@
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://registry.bower.io',
search: ['https://registry.bower.io'],
register: 'https://registry.bower.io',
publish: 'https://registry.bower.io'
});
});
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://registry.bower.io',
search: ['https://foobar'],
register: 'https://registry.bower.io',
publish: 'https://registry.bower.io'
});
});
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';
@@ -68,24 +66,22 @@ 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://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
});
});
@@ -96,41 +92,49 @@ 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'));
var config = require('../lib/Config').read(
path.resolve('test/assets/custom-ca')
);
['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 + ']');
@@ -138,23 +142,26 @@ 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;
@@ -163,40 +170,58 @@ 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');
assert.equal(process.env.HTTPS_PROXY, 'http://HTTPS_PROXY');
assert.equal(process.env.NO_PROXY, 'google.com');
assert.equal(process.env.http_proxy, undefined);
assert.equal(process.env.https_proxy, undefined);
assert.equal(process.env.no_proxy, undefined);
// On windows env is case insensitive
if (process.platform === 'win32') {
assert.equal(process.env.http_proxy, 'http://HTTP_PROXY');
assert.equal(process.env.https_proxy, 'http://HTTPS_PROXY');
assert.equal(process.env.no_proxy, 'google.com');
} else {
assert.equal(process.env.http_proxy, undefined);
assert.equal(process.env.https_proxy, undefined);
assert.equal(process.env.no_proxy, undefined);
}
});
it('restores env variables', function () {
process.env.HTTP_PROXY = 'a';
process.env.HTTPS_PROXY = 'b';
process.env.NO_PROXY = 'c';
it('restores env variables', function() {
process.env.http_proxy = 'd';
process.env.https_proxy = 'e';
process.env.no_proxy = 'f';
process.env.HTTP_PROXY = 'a';
process.env.HTTPS_PROXY = 'b';
process.env.NO_PROXY = 'c';
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');
assert.equal(process.env.HTTPS_PROXY, 'b');
assert.equal(process.env.NO_PROXY, 'c');
assert.equal(process.env.http_proxy, 'd');
assert.equal(process.env.https_proxy, 'e');
assert.equal(process.env.no_proxy, 'f');
// On windows precedence for restoring is for capital case
if (process.platform === 'win32') {
assert.equal(process.env.http_proxy, 'a');
assert.equal(process.env.https_proxy, 'b');
assert.equal(process.env.no_proxy, 'c');
} else {
assert.equal(process.env.http_proxy, 'd');
assert.equal(process.env.https_proxy, 'e');
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);
@@ -208,7 +233,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'
});
@@ -220,28 +245,33 @@ 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(path.resolve('/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.register
);
});
});

View File

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

View File

@@ -29,7 +29,6 @@ describe('rc', function() {
'.bowerrc/foo': {
key: 'bar'
}
});
it('correctly reads .bowerrc files', function() {
@@ -60,22 +59,21 @@ 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

@@ -1,61 +0,0 @@
{
"predef": [
"console",
"describe",
"it",
"after",
"afterEach",
"before",
"beforeEach"
],
"indent": 4,
"node": true,
"devel": true,
"bitwise": false,
"curly": false,
"eqeqeq": true,
"forin": false,
"immed": true,
"latedef": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": true,
"plusplus": false,
"regexp": false,
"undef": true,
"unused": true,
"quotmark": "single",
"strict": false,
"trailing": true,
"camelcase": true,
"asi": false,
"boss": true,
"debug": false,
"eqnull": true,
"esnext": false,
"evil": false,
"expr": true,
"funcscope": false,
"globalstrict": false,
"iterator": false,
"lastsemic": false,
"laxbreak": true,
"laxcomma": false,
"loopfunc": true,
"multistr": false,
"onecase": true,
"regexdash": false,
"scripturl": false,
"smarttabs": false,
"shadow": false,
"sub": false,
"supernew": true,
"validthis": false,
"nomen": false,
"white": true
}

View File

@@ -1,4 +0,0 @@
language: node_js
node_js:
- "0.10"
- "0.8"

View File

@@ -1,4 +1,4 @@
# endpoint-parser [![Build Status](https://secure.travis-ci.org/bower/endpoint-parser.png?branch=master)](http://travis-ci.org/bower/endpoint-parser)
# bower-endpoint-parser
Little module that helps with endpoints parsing.

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

@@ -16,7 +16,7 @@
},
"devDependencies": {
"expect.js": "~0.2.0",
"mocha": "~1.12.0",
"mocha": "^3.5.3",
"mout": "~0.9.0"
},
"scripts": {

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