Compare commits

..

2 Commits

Author SHA1 Message Date
Paul Irish
b62740be69 add tracking ID 2013-10-03 19:41:41 +01:00
Pascal Hartig
51dfa159b3 Basic usage tracking with insight
The user is prompted with a question whether he would like to provide usage data
on the first start, when run in interactive mode. The current implementation
tracks calls to `install`, `info`, `search` and `uninstall` when run with at
least one parameter.

Fixes #260
2013-10-03 19:40:51 +01:00
261 changed files with 7503 additions and 31292 deletions

View File

@@ -13,6 +13,3 @@ trim_trailing_whitespace = false
[**.std]
insert_final_newline = false
[{package,bower}.json]
indent_size = 2

View File

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

View File

@@ -1,51 +0,0 @@
{
"env": {
"node": true,
"mocha": true
},
"rules": {
"no-bitwise": 0,
"curly": 0,
"eqeqeq": 0,
"guard-for-in": 0,
"no-use-before-define": 0,
"no-caller": 2,
"no-new": 2,
"no-plusplus": 0,
"no-undef": 2,
"no-unused-vars": 0,
"strict": 0,
"semi": 0,
"comma-spacing": 2,
"quote-props": [2, "as-needed"],
"quotes": [2, "single", "avoid-escape"],
"no-cond-assign": [ 2, "except-parens" ],
"no-debugger": 2,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"no-fallthrough": 2,
"no-ex-assign": 2,
"no-eq-null": 0,
"no-eval": 0,
"no-unused-expressions": 0,
"block-scoped-var": 0,
"no-iterator": 0,
"no-loop-func": 2,
"no-script-url": 0,
"no-shadow": 0,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-invalid-this": 0,
"space-before-blocks": [2, "always"],
"space-before-function-paren": [2, "never"],
"space-infix-ops": 2,
"keyword-spacing": 2,
"new-parens": 2,
"no-multiple-empty-lines": [2, { max: 2}],
"eol-last": 2,
"no-trailing-spaces": 2
}
}

3
.github/FUNDING.yml vendored
View File

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

View File

@@ -1,44 +0,0 @@
<!--
If you are reporting a new issue, make sure that we do not have any duplicates.
You can ensure this by searching the issue list for this repository.
You are welcome to open issues to discuss important general topics concerning Bower.
However for support questions, please consider using http://stackoverflow.com or
asking for help in our Discord channel: https://discordapp.com/invite/0fFM7QF0KpZaDeN9
# BUG REPORT
Use the commands below to provide key information to reproduce:
You do NOT have to include this information if this is a FEATURE REQUEST OR DISCUSSION
For more information about reporting bugs, see:
https://github.com/bower/bower/wiki/Report-a-Bug
-->
**Output of `bower -v && npm -v && node -v`:**
```
(paste your output here)
```
**Additional environment details (proxy, private registry, etc.):**
**Steps to reproduce the issue:**
1.
2.
3.
**Describe the results you received:**
**Describe the results you expected:**
**Additional information:**

View File

@@ -1,88 +0,0 @@
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

10
.gitignore vendored
View File

@@ -1,16 +1,12 @@
!lib/bin
/node_modules
/npm-debug.log
/test/assets/temp
/test/assets/temp2
/test/assets/temp-resolve-cache
/test/assets/package-*/
/test/assets/temp-*/
/test/reports
/test/tmp/
/bower.json
/component.json
/bower_components
/test/sample
!/test/sample/bower.json
/npm-shrinkwrap.json
package-lock.json

62
.jshintrc Normal file
View File

@@ -0,0 +1,62 @@
{
"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": "vars",
"quotmark": "single",
"strict": false,
"trailing": true,
"camelcase": 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,4 +0,0 @@
**/node_modules/**
**/test/assets/**
**/bower_components/**
test/sample

6
.travis.yml Normal file
View File

@@ -0,0 +1,6 @@
language: node_js
node_js:
- "0.10"
- "0.8"
before_script:
- npm install grunt-cli -g

View File

@@ -1,343 +1,5 @@
# Changelog
## Newer releases
Please see: https://github.com/bower/bower/releases
## 1.8.0 - 2016-11-07
- Download tar archives from GitHub when possible (#2263)
- Change default shorthand resolver for github from `git://` to `https://`
- Fix ssl handling by not setting GIT_SSL_NO_VERIFY=false (#2361)
- Allow for removing components with url instead of name (#2368)
- Show in warning message location of malformed bower.json (#2357)
- Improve handling of non-semver versions in git resolver (#2316)
- Fix handling of cached releases pluginResolverFactory (#2356)
- Allow to type the entire version when conflict occured (#2243)
- Allow `owner/reponame` shorthand for registering components (#2248)
- Allow single-char repo names and package names (#2249)
- Make `bower version` no longer honor `version` in bower.json (#2232)
- Add `postinstall` hook (#2252)
- Allow for `@` instead of `#` for `install` and `info` commands (#2322)
- Upgrade all bundled modules
## 1.7.9 - 2016-04-05
- Show warnings for invalid bower.json fields
- Update bower-json
- Less strict validation on package name (allow spaces, slashes, and "@")
## 1.7.8 - 2016-04-04
- Don't ask for git credentials in non-interactive session, fixes #956 #1009
- Prevent swallowing exceptions with programmatic api, fixes #2187
- Update graceful-fs to 4.x in all dependences, fixes nodejs/node#5213
- Resolve pluggable resolvers using cwd and fallback to global modules, fixes #1919
- Upgrade handlebars to 4.0.5, closes #2195
- Replace all % chatacters in defined scripts, instead of only first one, fixes #2174
- Update opn package to fix issues with "bower open" command on Windows
- Update bower-config
- Do not interpolate environment variables in script hooks, fixes bower/config#47
- Update bower-json
- Validate package name more strictly and allow only latin letters, dots, dashes and underscores
- Add support for "save" and "save-exact" in .bowerrc, #2161
## 1.7.7 - 2016-01-27
Revert locations of all files while still packaging `node_modules`.
It's because people are depending on internals of bower, like
`bower/lib/renderers/StandardRenderer`. We want to preserve this
implicit contract, but we discourage it. The only official way
to use bower programmatically is through `require('bower')`.
## 1.7.6 - 2016-01-27
- Revert location of "bin/bower" as developers are using it directly ([#2157](https://github.com/bower/bower/issues/2157))
Note: Correctly, you should use an alias created in `npm bin --global`.
## 1.7.5 - 2016-01-26
- Remove analytics from Bower, fixes ([#2150](https://github.com/bower/bower/pull/2150))
- Default to ^ operator on `bower install --save` ([#2145](https://github.com/bower/bower/pull/2145))
- Support absolute path in .bowerrc directory option ([#2130](https://github.com/bower/bower/pull/2130))
- Display user's name upon `bower login` command ([#2133](https://github.com/bower/bower/pull/2133))
- Decompress gzip files ([#2092](https://github.com/bower/bower/pull/2092))
- Prevent name clashes in package extraction ([#2102](https://github.com/bower/bower/pull/2102))
- When strictSsl is false, set GIT_SSL_NO_VERIFY=true ([#2129](https://github.com/bower/bower/issues/2129))
- Distribute bower with npm@3 for better Windows support ([#2146](https://github.com/bower/bower/issues/2146))
- Update request to 2.67.0 and fs-write-stream-atomic to 1.0.8
- Documentation improvements
## 1.7.4 - 2016-01-21
Unpublished because of issue with npm distribution:
https://github.com/npm/npm/issues/11227
## 1.7.3 - 2016-01-20
Unpublished because of issue with npm distribution:
https://github.com/npm/npm/issues/11227
## 1.7.2 - 2015-12-31
- Lock "fs-write-stream-atomic" to 1.0.5
## 1.7.1 - 2015-12-11
- Rollback "Add `bower update --save` functionality", it causes issues and needs more testing
- Fix backward-compatibility of `bower search --json` ([#2066](https://github.com/bower/bower/issues/2066))
- Ignore prerelease versions from `bower info` output
- Update update-notifier to 0.6.0
- Better formatting of help messages (https://github.com/bower/bower/commit/de3e1089da80f47ea3667c5ab80d301cddfd8c3e)
- Add help menu for update `--save` and `update --save-dev` (https://github.com/bower/bower/commit/612aaa88eb4d4b268b2d8665c338ac086af3a5b0)
## 1.7.0 - 2015-12-07
- Add `bower update --save` functionality ([#2035](https://github.com/bower/bower/issues/2035))
- `bower search` shows help message when no package name is specified ([#2066](https://github.com/bower/bower/issues/2066))
- Update only those packages that are explicitly requested by the user. Related Issues
- [#256](https://github.com/bower/bower/issues/256)
- [#924](https://github.com/bower/bower/issues/924)
- [#1770](https://github.com/bower/bower/issues/1770)
- Allow for @ in username for SVN on windows ([#1650](https://github.com/bower/bower/issues/1650))
- Update bower config
- Loads the .bowerrc file from the cwd specified on the command line
- Allow the use of environment variables in .bowerrc ([#41](https://github.com/bower/config/issues/41))
- Allow for array notation in ENV variables ([#44](https://github.com/bower/config/issues/44))
## 1.6.9 - 2015-12-04
- Change git version of fs-write-stream-atomic back to npm version ([#2079](https://github.com/bower/bower/issues/2079))
## 1.6.8 - 2015-11-27
- Use fs-write-stream-atomic for downloads
- Improved downloader that properly cleans after itself
- Fix shallow host detection ([#2040](https://github.com/bower/bower/pull/2040))
- Upgrade to ([bower-config#1.2.3](https://github.com/bower/config/releases/tag/1.2.3))
- Properly restore env variables if they are undefined at the beginning
- Properly handle `default` setting for config.ca
- Display proper error if .bowerrc is a directory instead of file
## 1.6.7 - 2015-11-26
- Bundless all the dependencies again
## 1.6.6 - 2015-11-25
- Fixes regression with the published npm version
## 1.6.5 - 2015-10-24
- Updates to tests and documentation
- Fixes passing options when requesting downloads
## 1.6.4 - 2015-10-24
- Fix ignoring dependencies on multiple install run ([#1970](https://github.com/bower/bower/pull/1970))
- Use --non-interactive when running svn client ([#1969](https://github.com/bower/bower/pull/1969))
- Fix downloading of URLs ending with slash ([#1956](https://github.com/bower/bower/pull/1956))
- Add user-agent field for downloads by Bower ([#1960](https://github.com/bower/bower/pull/1960))
## 1.6.3 - 2015-10-16
Fixes regression issues introduced with 1.6.2, specifically:
- Allow for bower_components to be a symlink
- Allow setting custom registry in .bowerrc
## 1.6.2 - 2015-10-15
Fix dependency issues of 1.6.1. First published release of 1.6.x.
## 1.6.1 - 2015-10-15
Fix dependency issues of 1.6.0. Reverted release.
## 1.6.0 - 2015-10-15
- Shrinkwrap all dependencies and add them to bundledDependencies ([#1948](https://github.com/bower/bower/pull/1948))
- Allow for ignoring of child dependencies ([#1394](https://github.com/bower/bower/pull/1394))
- Allow passing `--config.resolvers` through CLI ([#1922](https://github.com/bower/bower/pull/1922))
- Use defaults values from package.json if it exists (bower init) ([#1731](https://github.com/bower/bower/issues/1731))
- Properly use cerificates set in .bowerrc ([#1869](https://github.com/bower/bower/pull/1869))
- Include package name when version conflict occurs ([#1917](https://github.com/bower/bower/pull/1917))
- Add timeout for permission check ([yeoman/insight#35](https://github.com/yeoman/insight/pull/35))
- Close file-handles when possible. Prevents all sorts of permission issues on Windows ([0bb1536](https://github.com/bower/bower/commit/0bb1536c9972e13f3be06bea9a8619632966c664))
- Prevent ENOENT error on Windows when in VM environment ([isaacs/chmodr#8](https://github.com/isaacs/chmodr/pull/8))
Reverted release.
## 1.5.4 - 2015-11-24
- [fix] Lock lru-cache dependency to 2.7.0
## 1.5.3 - 2015-09-24
- Revert auto sorting of bower dependencies, fixes ([#1897](https://github.com/bower/bower/issues/1897))
- Fix --save-exact feature for github endpoints, fixes ([#1925](https://github.com/bower/bower/issues/1925))
- Fix `bower init` to support private flag again ([#1819](https://github.com/bower/bower/pull/1819))
- Bump insight dependency to support prompt timeout ([#1102](https://github.com/bower/bower/issues/1102))
## 1.5.2 - 2015-08-25
- Revert update semver version from 2.x to 5.x, fixes ([#1896](https://github.com/bower/bower/issues/1896))
- Make bower commands work from subdirectories, fixes ([#1893](https://github.com/bower/bower/issues/1893))
- Put auto shallow cloning for git behind a flag, fixes ([#1764](https://github.com/bower/bower/issues/1764))
## 1.5.1 - 2015-08-24
- If cwd provided explicitly, force using it, fixes #1866
## 1.5.0 - 2015-08-24
- Pluggable Resolvers! http://bower.io/docs/pluggable-resolvers/
- Update semver version from 2.x to 5.x ([#1852](https://github.com/bower/bower/issues/1852))
- Auto-sort dependencies alphabetically ([#1381](https://github.com/bower/bower/issues/1381))
- Make bower commands work from subdirectories ([#1866](https://github.com/bower/bower/issues/1866))
- No longer prefer installing bower as global module ([#1865](https://github.com/bower/bower/issues/1865))
## 1.4.2 - 2015-11-24
- [fix] Lock lru-cache dependency to 2.7.0
## 1.4.1 - 2015-04-01
- [fix] Reading .bowerrc upwards directory tree ([#1763](https://github.com/bower/bower/issues/1763))
- [fix] Update bower-registry-client so it uses the same bower-config as bower
## 1.4.0 - 2015-03-30
- Add login and unregister commands ([#1719](https://github.com/bower/bower/issues/1719))
- Automatically detecting smart Git hosts ([#1628](https://github.com/bower/bower/issues/1628))
- [bower/config#23] Allow npm config variables ([#1711](https://github.com/bower/bower/issues/1711))
- [bower/config#24] Merge .bowerrc files upwards directory tree ([#1689](https://github.com/bower/bower/issues/1689))
- Better homedir detection (514eb8f)
- Add --save-exact flag ([#1654](https://github.com/bower/bower/issues/1654))
- Ensure extracted files are readable (tar-fs) ([#1548](https://github.com/bower/bower/issues/1548))
- The version command in the programmatic API now returns the new version ([#1755](https://github.com/bower/bower/issues/1755))
- Some minor fixes: #1639, #1620, #1576, #1557, 962a565, a464f5a
- Improved Windows support (AppVeyor CI, tests actually passing on Windows)
- OSX testing enabled on TravisCI
It also includes improved test coverage (~60% -> ~85%) and many refactors.
## 1.3.12 - 2014-09-28
- [stability] Fix versions for unstable dependencies ([#1532](https://github.com/bower/bower/pull/1532))
- [fix] Update tar-fs to support old tar format ([#1537](https://github.com/bower/bower/issues/1537))
- [fix] Make analytics work again ([#1529](https://github.com/bower/bower/pull/1529))
- [fix] Always disable analytics for non-interactive mode ([#1529](https://github.com/bower/bower/pull/1529))
- [fix] Bower init can create private packages again ([#1522](https://github.com/bower/bower/issues/1522))
- [fix] Show again missing newline for bower search output ([#1538](https://github.com/bower/bower/issues/1538))
## 1.3.11 - 2014-09-17
- [fix] Restore install missing dependencies on update ([1519](https://github.com/bower/bower/pull/1519))
## 1.3.10 - 2014-09-13
- [fix] Back down concurrency from 50 to 5 ([#1483](https://github.com/bower/bower/pull/1483))
- [fix] Read .bowerrc from specified cwd ([#1301](https://github.com/bower/bower/pull/1301))
- [fix] Disable shallow clones except those from GitHub ([#1393](https://github.com/bower/bower/pull/1393))
- [fix] Expose bower version ([#1478](https://github.com/bower/bower/pull/1478))
- [fix] Bump dependencies, including "request" ([#1467](https://github.com/bower/bower/pull/1467))
- [fix] Prevent an error when piping bower output to head ([#1508](https://github.com/bower/bower/pull/1508))
- [fix] Disable removing unnecessary resolutions ([#1061](https://github.com/bower/bower/pull/1061))
- [fix] Display the output of hooks again ([#1484](https://github.com/bower/bower/issues/1484))
- [fix] analytics: true in .bowerrc prevents user prompt ([#1470](https://github.com/bower/bower/pull/1470))
- [perf] Use `tar-fs` instead of `tar` for faster TAR extraction ([#1490](https://github.com/bower/bower/pull/1490))
## 1.3.9 - 2014-08-06
- [fix] Handle `tmp` sometimes returning an array ([#1434](https://github.com/bower/bower/pull/1434))
## 1.3.8 - 2014-7-11
- [fix] Lock down `tmp` package dep ([#1403](https://github.com/bower/bower/pull/1403), [#1407](https://github.com/bower/bower/pull/1407))
## 1.3.7 - 2014-07-04
- [fix] callstack error when processing installed packages with circular dependencies ([#1349](https://github.com/bower/bower/issues/1349))
- [fix] Prevent bower list --paths` failing with TypeError ([#1383](https://github.com/bower/bower/issues/1383))
- "bower install" fails if there's no bower.json in current directory ([#922](https://github.com/bower/bower/issues/922))
## 1.3.6 - 2014-07-02
- [fix] Make --force always re-run installation ([#931](https://github.com/bower/bower/issues/931))
- [fix] Disable caching for local resources ([#1356](https://github.com/bower/bower/issues/1356))
- [fix] Emit errors instead throwing them when using bower.commands API ([#1297](https://github.com/bower/bower/issues/1297))
- [fix] Main files and bower.json are never ignored ([#547](https://github.com/bower/bower/issues/547))
- [fix] Check if pkgMeta is undefined during uninstall command ([#1329](https://github.com/bower/bower/issues/1329))
- [fix] Make custom tmp dir and ignores play well with each other ([#1299](https://github.com/bower/bower/issues/1299))
- Warn users when installing package with missing properties ([#694](https://github.com/bower/bower/issues/694))
## 1.3.5 - 2014-06-06
- Search compatible versions in fetching packages ([#1147](https://github.com/bower/bower/issues/1147))
## 1.3.4 - 2014-06-02
- Resolve a situation in which the install process gets into an infinite loop ([#1169](https://github.com/bower/bower/issues/1169))
- Improved CLI output for conflicts ([#1284](https://github.com/bower/bower/issues/1284))
- Changed `bower version` to mirror the tag format of `npm version` ([#1278](https://github.com/bower/bower/issues/1278))
- Allow short commit SHAs to be used ([#990](https://github.com/bower/bower/issues/990))
## 1.3.3 - 2014-04-24
- Do not cache moving targets like branches ([#1242](https://github.com/bower/bower/issues/1242))
- Suppress output if --quiet option is specified ([#1124](https://github.com/bower/bower/pull/1124))
- Use "svn export" for efficiency ([#1224](https://github.com/bower/bower/pull/1224))
- Prevent loading insights and analytics on CI ([#1221](https://github.com/bower/bower/issues/1221))
- Make "bower list" respect custom components directory ([#1237](https://github.com/bower/bower/issues/1237))
- Improve non-interactive loading performance 2x ([#1238](https://github.com/bower/bower/issues/1238))
- Load commands only on demand, improving performance ([#1232](https://github.com/bower/bower/pull/1232))
## 1.3.2 - 2014-04-05
- Added yui moduleType [PR #1129](https://github.com/bower/bower/pull/1129)
- Fixes for concurrency issues [PR #1211](https://github.com/bower/bower/pull/1211)
- `link` now installs package dependencies [PR #891](https://github.com/bower/bower/pull/891)
- Improved conflict installation message [Commit](https://github.com/bower/bower/commit/bea533acf87903d4b411bfbaa7df93f852ef46a3)
- Add --production switch to "prune" command [PR #1168](https://github.com/bower/bower/pull/1168)
## 1.3.1 - 2014-03-10
- No longer ask for permission to gather analytics when running on in a CI environment.
## 1.3.0 - 2014-03-10
- **Removed support for node 0.8.** It may still work but we will no longer fix bugs for older versions of node.
- Add **Bower Insight** for opt-in analytics integration to help improve tool and gain insight on community trends
- Old overview of [Insight](https://github.com/yeoman/yeoman/wiki/Insight), [Issue #260](https://github.com/bower/bower/issues/260)
- Reporting to GA. Public Dashboard is in progress.
- [Turn off interactive mode](https://github.com/bower/bower/issues/1162) if you run Bower in a CI environment
- Add `moduleType` property to bower init ([#934](https://github.com/bower/bower/pull/934))
- Fix prune command to log only after cleanup is completed ([#1023](https://github.com/bower/bower/issues/1023))
- Fix git resolver to ignore pre-release versions ([#1017](https://github.com/bower/bower/issues/1017))
- Fix shorthand flag for `save` option on `uninstall` command ([#1031](https://github.com/bower/bower/pull/1031))
- Add `bower version` command ([#961](https://github.com/bower/bower/pull/961))
- Add .bowerrc option to use `--save` by default when using `bower install` command ([#1074](https://github.com/bower/bower/pull/1074))
- Fix git resolver caching ([#1083](https://github.com/bower/bower/issues/1083))
- Fix reading versions from cache directory ([#1076](https://github.com/bower/bower/pull/1076))
- Add svn support ([#1055](https://github.com/bower/bower/pull/1055))
- Allow circular dependencies to be installed ([#1104](https://github.com/bower/bower/pull/1104))
- Add scripts/hooks support ([#718](https://github.com/bower/bower/pull/718))
_NOTE_: It's advisable that users use `--config.interactive=false` on automated scripts.
## 1.2.8 - 2013-12-02
- Fix absolute paths ending with / not going through the FsResolver, ([#898](https://github.com/bower/bower/issues/898))
- Allow query string parameters in package URLs
- Swapped 'unzip' module for 'decompress-zip', and some other small unzipping fixes([#873](https://github.com/bower/bower/issues/873), [#896](https://github.com/bower/bower/issues/896))
- Allow the root-check to be overridden when calling bower programmatically.
- Fixed some bugs relating to packages with a very large dependency tree
- Fix a bug caused by a recent change to semver
## 1.2.7 - 2013-09-29
@@ -417,7 +79,7 @@ _NOTE_: It's advisable that users run `bower cache clean`.
- Ignore `component.json` if it looks like a component(1) file ([#556](https://github.com/bower/bower/issues/556))
- Fix multi-user usage on bower when it creates temporary directories to hold some files
- Fix prompting causing an invalid JSON output when running commands with `--json`
- When running Bower commands programmatically, prompting is now disabled by default (see the updated programmatic [usage](https://github.com/bower/bower#programmatic-api) for more info)
- When running Bower commands programmatically, prompting is now disabled by default (see the updated progammatic [usage](https://github.com/bower/bower#programmatic-api) for more info)
- Other minor improvements and fixes
Fix for `#788` requires installed components to be re-installed.
@@ -479,7 +141,7 @@ Fix for `#788` requires installed components to be re-installed.
- Fix `Bower` not working when calling `.bat`/`.cmd` commands on Windows; it affected people using `Git portable` ([#626](https://github.com/bower/bower/issues/626))
- Fix `bower list --paths` not resolving all files to absolute paths when the `main` property contained multiple files ([660](https://github.com/bower/bower/issues/660))
- Fix `Bower` renaming `bower.json` and `component.json` files to `index.json` when it was the only file in the folder ([#674](https://github.com/bower/bower/issues/674))
- Ignore symlinks when copying/extracting since they are not portable, specially across different hard-drives ([#665](https://github.com/bower/bower/issues/665))
- Ignore symlinks when copying/extracting since they are not portable, specially accross different hard-drives ([#665](https://github.com/bower/bower/issues/665))
- Local file/dir endpoints are now exclusively referenced by an absolute path or relative path starting with `.` ([#666](https://github.com/bower/bower/issues/666))
- Linked packages `bower.json` files are now parsed, making `bower list` account linked packages dependencies ([#659](https://github.com/bower/bower/issues/659))
- Bower now fails to run with sudo unless `--allow-root` is passed ([#498](https://github.com/bower/bower/issues/498))
@@ -492,7 +154,7 @@ Fix for `#788` requires installed components to be re-installed.
## 1.0.0 - 2013-07-23
Total rewrite of bower.
The list bellow highlights the most important stuff.
The list bellow highlights the most important stuff.
For a complete list of changes that this rewrite and release brings please read: https://github.com/bower/bower/wiki/Rewrite-state
@@ -515,7 +177,7 @@ Non-backwards compatible changes:
- `--map` and `--sources` from the list command were removed, use `--json` instead
- Programmatic usage changed, specially the commands interface
Users upgrading from `bower-canary` and `bower@~0.x.x` should do a `bower cache clean`.
Users upgrading from `bower-canary` and `bower@~0.x.x` should do a `bower cache clean`.
Additionally you may remove the `~/.bower` folder manually since it's no longer used.
On Windows the folder is located in `AppData/bower`.
@@ -643,7 +305,7 @@ _NOTE_: The `components` folder will still be used if already created, making it
## 0.6.1 - 2012-11-22
- Fix uninstall when the project component.json has no deps saved ([#153](https://github.com/bower/bower/issues/153))
- Fix uncaught errors when using file writer (they are now caught and reported)
- Fix uncaught errors when using file writter (they are now caught and reported)
- Fix temporary directories not being deleted when an exception occurs ([#153](https://github.com/bower/bower/issues/140))
## 0.6.0 - 2012-11-21

View File

@@ -1,20 +1,13 @@
# Contributing to Bower
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.
Please take a moment to review this document in order to make the contribution
process easy and effective for everyone involved.
<a name="bugs"></a>
## 🐛 [Bug reports](https://github.com/bower/bower/wiki/Report-a-Bug)
Following these guidelines helps to communicate that you respect the time of
the developers managing and developing this open source project. In return,
they should reciprocate that respect in addressing your issue, assessing
changes, and helping you finalize your pull requests.
## Casual Involvement
* Improve the bower.io site ([tickets](https://github.com/bower/bower.github.io/issues))
* Comment on issues and drive to resolution
## High-impact Involvement
* Maintaining the bower client.
* Read [Architecture doc](https://github.com/bower/bower/wiki/Rewrite-architecture)
* Triage, close, fix and resolve [issues](https://github.com/bower/bower/issues)
## Using the issue tracker
@@ -23,12 +16,57 @@ 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), or in serious cases
send an e-mail to team@bower.io
[Stack Overflow](http://stackoverflow.com/questions/tagged/bower), our
[Mailing List](http://groups.google.com/group/twitter-bower)
(twitter-bower@googlegroups.com), or
[#bower](http://webchat.freenode.net/?channels=bower) on Freenode.
* Please **do not** derail or troll issues. Keep the discussion on topic and
respect the opinions of others.
<a name="bugs"></a>
## Bug reports
A bug is a _demonstrable problem_ that is caused by the code in the repository.
Good bug reports are extremely helpful - thank you!
Guidelines for bug reports:
1. **Use the GitHub issue search** &mdash; check if the issue has already been
reported.
2. **Check if the issue has been fixed** &mdash; try to reproduce it using the
latest `master` or development branch in the repository.
3. **Isolate the problem** &mdash; ideally create a [reduced test
case](http://css-tricks.com/6263-reduced-test-cases/).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report. What is
your environment? What steps will reproduce the issue? What OS experiences the
problem? What would you expect to be the outcome? All these details will help
people to fix any potential bugs.
Example:
> Short and descriptive example bug report title
>
> A summary of the issue and the browser/OS environment in which it occurs. If
> suitable, include the steps required to reproduce the bug.
>
> 1. This is the first step
> 2. This is the second step
> 3. Further steps, etc.
>
> `<url>` - a link to the reduced test case
>
> Any other information you want to share that is relevant to the issue being
> reported. This might include the lines of code that you have identified as
> causing the bug, and potential solutions (and your opinions on their
> merits).
<a name="features"></a>
## Feature requests
@@ -112,8 +150,6 @@ included in the project:
force push to your remote feature branch. You may also be asked to squash
commits.
10. If you are asked to squash your commits, then please use `git rebase -i master`. It will ask you to pick your commits - pick the major commits and squash the rest.
**IMPORTANT**: By submitting a patch, you agree to license your work under the
same license as that used by the project.

48
Gruntfile.js Normal file
View File

@@ -0,0 +1,48 @@
module.exports = function (grunt) {
grunt.initConfig({
jshint: {
options: {
jshintrc: '.jshintrc'
},
files: ['Gruntfile.js', 'bin/*', 'lib/**/*.js', 'test/**/*.js', '!test/assets/**/*', '!test/reports/**/*']
},
simplemocha: {
options: {
reporter: 'spec',
timeout: '5000'
},
full: { src: ['test/test.js'] },
short: {
options: {
reporter: 'dot'
},
src: ['test/test.js']
}
},
exec: {
assets: {
command: 'node test/packages.js'
},
'assets-force': {
command: 'node test/packages.js --force'
},
cover: {
command: 'node node_modules/istanbul/lib/cli.js cover --dir ./test/reports node_modules/mocha/bin/_mocha -- -R dot test/test.js'
}
},
watch: {
files: ['<%= jshint.files %>'],
tasks: ['jshint', 'simplemocha:short']
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-exec');
grunt.registerTask('assets', ['exec:assets-force']);
grunt.registerTask('test', ['jshint', 'exec:assets', 'simplemocha:full']);
grunt.registerTask('cover', 'exec:cover');
grunt.registerTask('default', 'test');
};

View File

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

493
README.md
View File

@@ -1,262 +1,357 @@
# Bower - A package manager for the web
# BOWER [![Build Status](https://secure.travis-ci.org/bower/bower.png?branch=master)](http://travis-ci.org/bower/bower)
[![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)
Bower is a package manager for the web. It offers a generic, unopinionated
solution to the problem of **front-end package management**, while exposing the
package dependency model via an API that can be consumed by a more opinionated
build stack. There are no system wide dependencies, no dependencies are shared
between different apps, and the dependency tree is flat.
> ..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!
Bower runs over Git, and is package-agnostic. A packaged component can be made
up of any type of asset, and use any type of transport (e.g., AMD, CommonJS,
etc.).
<img align="right" height="300" src="http://bower.io/img/bower-logo.png">
[View all packages available through Bower's registry](http://sindresorhus.com/bower-components/).
---
Bower offers a generic, unopinionated solution to the problem of **front-end package management**, while exposing the package dependency model via an API that can be consumed by a more opinionated build stack. There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat.
## Installing Bower
Bower runs over Git, and is package-agnostic. A packaged component can be made up of any type of asset, and use any type of transport (e.g., AMD, CommonJS, etc.).
Bower depends on [Node](http://nodejs.org/) and [npm](http://npmjs.org/). It's
installed globally using npm:
**View complete docs on [bower.io](http://bower.io)**
[View all packages available through Bower's registry](http://bower.io/search/).
## Install
```sh
$ npm install -g bower
```
npm install -g bower
```
Bower depends on [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/). Also make sure that [git](http://git-scm.com/) is installed as some bower
Also make sure that [git](http://git-scm.com/) is installed as some bower
packages require it to be fetched and installed.
## Usage
See complete command line reference at [bower.io/docs/api/](http://bower.io/docs/api/)
Much more information is available via `bower help` once it's installed. This
is just enough to get you started.
#### Warning
On `prezto` or `oh-my-zsh`, do not forget to `alias bower='noglob bower'` or `bower install jquery\#1.9.1`
#### Running commands with sudo
Bower is a user command, there is no need to execute it with superuser permissions.
However, if you still want to run commands with sudo, use `--allow-root` option.
### Installing packages and dependencies
```sh
# install dependencies listed in bower.json
$ bower install
Bower offers several ways to install packages:
# install a package and add it to bower.json
$ bower install <package> --save
# install specific version of a package and add it to bower.json
$ bower install <package>#<version> --save
```
# Using the dependencies listed in the current directory's bower.json
bower install
# Using a local or remote package
bower install <package>
# Using a specific version of a package
bower install <package>#<version>
# Using a different name and a specific version of a package
bower install <name>=<package>#<version>
```
Where `<package>` can be any one of the following:
* A name that maps to a package registered with Bower, e.g, `jquery`. ‡
* A remote Git endpoint, e.g., `git://github.com/someone/some-package.git`. Can be
public or private. ‡
* A local endpoint, i.e., a folder that's a Git repository. ‡
* A shorthand endpoint, e.g., `someone/some-package` (defaults to GitHub). ‡
* A URL to a file, including `zip` and `tar` files. Its contents will be
extracted.
‡ These types of `<package>` might have versions available. You can specify a
[semver](http://semver.org/) compatible version to fetch a specific release, and lock the
package to that version. You can also use ranges to specify a range of versions.
All package contents are installed in the `bower_components` directory by default.
You should **never** directly modify the contents of this directory.
Using `bower list` will show all the packages that are installed locally.
**N.B.** If you aren't authoring a package that is intended to be consumed by
others (e.g., you're building a web app), you should always check installed
packages into source control.
### Finding packages
To search for packages registered with Bower:
```
bower search [<name>]
```
Using just `bower search` will list all packages in the registry.
### Using packages
We discourage using bower components statically for performance and security reasons (if component has an `upload.php` file that is not ignored, that can be easily exploited to do malicious stuff).
The easiest approach is to use Bower statically, just reference the package's
installed components manually using a `script` tag:
The best approach is to process components installed by bower with build tool (like [Grunt](http://gruntjs.com/) or [gulp](http://gulpjs.com/)), and serve them concatenated or using a module loader (like [RequireJS](http://requirejs.org/)).
```html
<script src="/bower_components/jquery/index.js"></script>
```
For more complex projects, you'll probably want to concatenate your scripts or
use a module loader. Bower is just a package manager, but there are plenty of
other tools -- such as [Sprockets](https://github.com/sstephenson/sprockets)
and [RequireJS](http://requirejs.org/) -- that will help you do this.
### Registering packages
To register a new package:
* There **must** be a valid manifest JSON in the current working directory.
* Your package should use [semver](http://semver.org/) Git tags.
* Your package **must** be available at a Git endpoint (e.g., GitHub); remember
to push your Git tags!
Then use the following command:
```
bower register <my-package-name> <git-endpoint>
```
The Bower registry does not have authentication or user management at this point
in time. It's on a first come, first served basis. Think of it like a URL
shortener. Now anyone can run `bower install <my-package-name>`, and get your
library installed.
There is no direct way to unregister a package yet. For now, you can [request a
package be unregistered](https://github.com/bower/bower/issues/120).
### Uninstalling packages
To uninstall a locally installed package:
```sh
$ bower uninstall <package-name>
```
bower uninstall <package-name>
```
### prezto and oh-my-zsh users
On `prezto` or `oh-my-zsh`, do not forget to `alias bower='noglob bower'` or `bower install jquery\#1.9.1`
## Configuration
### Never run Bower with sudo
Bower can be configured using JSON in a `.bowerrc` file.
Bower is a user command; there is no need to execute it with superuser permissions.
The current spec can be read
[here](https://docs.google.com/document/d/1APq7oA9tNao1UYWyOm8dKqlRP2blVkROYLZ2fLIjtWc/edit#heading=h.4pzytc1f9j8k)
in the `Configuration` section.
### Windows users
## Defining a package
You must create a `bower.json` in your project's root, and specify all of its
dependencies. This is similar to Node's `package.json`, or Ruby's `Gemfile`,
and is useful for locking down a project's dependencies.
*NOTE:* In versions of Bower before 0.9.0 the package metadata file was called
`component.json` rather than `bower.json`. This has changed to avoid a name
clash with another tool. You can still use `component.json` for now but it is
deprecated and the automatic fallback is likely to be removed in an upcoming
release.
You can interactively create a `bower.json` with the following command:
```
bower init
```
The `bower.json` defines several options:
* `name` (required): The name of your package.
* `version`: A semantic version number (see [semver](http://semver.org/)).
* `main` [string|array]: The primary endpoints of your package.
* `ignore` [array]: An array of paths not needed in production that you want
Bower to ignore when installing your package.
* `dependencies` [hash]: Packages your package depends upon in production.
* `devDependencies` [hash]: Development dependencies.
* `private` [boolean]: Set to true if you want to keep the package private and
do not want to register the package in future.
```json
{
"name": "my-project",
"version": "1.0.0",
"main": "path/to/main.css",
"ignore": [
".jshintrc",
"**/*.txt"
],
"dependencies": {
"<name>": "<version>",
"<name>": "<folder>",
"<name>": "<package>"
},
"devDependencies": {
"<test-framework-name>": "<version>"
}
}
```
## Consuming a package
Bower also makes available a source mapping. This can be used by build tools to
easily consume Bower packages.
If you pass the `--paths` option to Bower's `list` command, you will get a
simple path-to-name mapping:
```json
{
"backbone": "bower_components/backbone/index.js",
"jquery": "bower_components/jquery/index.js",
"underscore": "bower_components/underscore/index.js"
}
```
Alternatively, every command supports the `--json` option that makes bower
output JSON. Command result is outputted to `stdout` and error/logs to
`stderr`.
## Programmatic API
Bower provides a powerful, programmatic API. All commands can be accessed
through the `bower.commands` object.
```js
var bower = require('bower');
bower.commands
.install(['jquery'], { save: true }, { /* custom config */ })
.on('end', function (installed) {
console.log(installed);
});
bower.commands
.search('jquery', {})
.on('end', function (results) {
console.log(results);
});
```
Commands emit four types of events: `log`, `prompt`, `end`, `error`.
* `log` is emitted to report the state/progress of the command.
* `prompt` is emitted whenever the user needs to be prompted.
* `error` will only be emitted if something goes wrong.
* `end` is emitted when the command successfully ends.
For a better of idea how this works, you may want to check out [our bin
file](https://github.com/bower/bower/blob/master/bin/bower).
When using bower programmatically, prompting is disabled by default. Though you can enable it when calling commands with `interactive: true` in the config.
This requires you to listen for the `prompt` event and handle the prompting yourself. The easiest way is to use the [inquirer](https://npmjs.org/package/inquirer) npm module like so:
```js
var inquirer = require('inquirer');
bower.commands
.install(['jquery'], { save: true }, { interactive: true })
// ..
.on('prompt', function (prompts, callback) {
inquirer.prompt(prompts, callback);
});
```
## Completion (experimental)
_NOTE_: Completion is still not implemented for the 1.0.0 release
Bower now has an experimental `completion` command that is based on, and works
similarly to the [npm completion](https://npmjs.org/doc/completion.html). It is
not available for Windows users.
This command will output a Bash / ZSH script to put into your `~/.bashrc`,
`~/.bash_profile`, or `~/.zshrc` file.
```
bower completion >> ~/.bash_profile
```
## A note for Windows users
To use Bower on Windows, you must install
[Git for Windows](http://git-for-windows.github.io/) correctly. Be sure to check the
options shown below:
[msysgit](http://code.google.com/p/msysgit/) correctly. Be sure to check the
option shown below:
<img src="https://cloud.githubusercontent.com/assets/10702007/10532690/d2e8991a-7386-11e5-9a57-613c7f92e84e.png" width="534" height="418" alt="Git for Windows" />
<img src="https://cloud.githubusercontent.com/assets/10702007/10532694/dbe8857a-7386-11e5-9bd0-367e97644403.png" width="534" height="418" alt="Git for Windows" />
![msysgit](http://f.cl.ly/items/2V2O3i1p3R2F1r2v0a12/mysgit.png)
Note that if you use TortoiseGit and if Bower keeps asking for your SSH
password, you should add the following environment variable: `GIT_SSH -
C:\Program Files\TortoiseGit\bin\TortoisePlink.exe`. Adjust the `TortoisePlink`
path if needed.
### Ubuntu users
To use Bower on Ubuntu, you might need to link `nodejs` executable to `node`:
## Contact
```
sudo ln -s /usr/bin/nodejs /usr/bin/node
```
## Configuration
Bower can be configured using JSON in a `.bowerrc` file. Read over available options at [bower.io/docs/config](http://bower.io/docs/config).
## Support
You can ask questions on following channels in order:
Have a question?
* [StackOverflow](http://stackoverflow.com/questions/tagged/bower)
* [Issue Tracker](https://github.com/bower/bower/issues)
* team@bower.io
* [Mailinglist](http://groups.google.com/group/twitter-bower) - twitter-bower@googlegroups.com
* [\#bower](http://webchat.freenode.net/?channels=bower) on Freenode
## Contributing
We welcome [contributions](https://github.com/bower/bower/graphs/contributors) of all kinds from anyone. Please take a moment to review the [guidelines for contributing](CONTRIBUTING.md).
## Contributing to this project
* [Bug reports](https://github.com/bower/bower/wiki/Report-a-Bug)
Anyone and everyone is welcome to contribute. Please take a moment to
review the [guidelines for contributing](CONTRIBUTING.md).
* [Bug reports](CONTRIBUTING.md#bugs)
* [Feature requests](CONTRIBUTING.md#features)
* [Pull requests](CONTRIBUTING.md#pull-requests)
Note that on Windows for tests to pass you need to configure Git before cloning:
## Authors
```
git config --global core.autocrlf input
```
* [@fat](https://github.com/fat)
* [@maccman](https://github.com/maccman)
* [@satazor](https://github.com/satazor)
Thanks for assistance and contributions:
## Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/bower#backer)]
<a href='https://opencollective.com/bower/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>
[@addyosmani](https://github.com/addyosmani),
[@angus-c](https://github.com/angus-c),
[@borismus](https://github.com/borismus),
[@carsonmcdonald](https://github.com/carsonmcdonald),
[@chriseppstein](https://github.com/chriseppstein),
[@danwrong](https://github.com/danwrong),
[@davidmaxwaterman](https://github.com/davidmaxwaterman),
[@desandro](https://github.com/desandro),
[@hemanth](https://github.com/hemanth),
[@isaacs](https://github.com/isaacs),
[@josh](https://github.com/josh),
[@jrburke](https://github.com/jrburke),
[@marcelombc](https://github.com/marcelombc),
[@marcooliveira](https://github.com/marcooliveira),
[@mklabs](https://github.com/mklabs),
[@MrDHat](https://github.com/MrDHat),
[@necolas](https://github.com/necolas),
[@paulirish](https://github.com/paulirish),
[@richo](https://github.com/richo),
[@rvagg](https://github.com/rvagg),
[@sindresorhus](https://github.com/sindresorhus),
[@SlexAxton](https://github.com/SlexAxton),
[@sstephenson](https://github.com/sstephenson),
[@svnlto](https://github.com/svnlto),
[@tomdale](https://github.com/tomdale),
[@uzquiano](https://github.com/uzquiano),
[@visionmedia](https://github.com/visionmedia),
[@wagenet](https://github.com/wagenet),
[@wibblymat](https://github.com/wibblymat),
[@wycats](https://github.com/wycats)
## License
Copyright (c) 2012-present Twitter and [other contributors](https://github.com/bower/bower/graphs/contributors)
Copyright 2012 Twitter, Inc.
Licensed under the MIT License

View File

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

142
bin/bower
View File

@@ -1,3 +1,143 @@
#!/usr/bin/env node
require('../lib/bin/bower');
process.bin = process.title = 'bower';
var path = require('path');
var mout = require('mout');
var updateNotifier = require('update-notifier');
var Logger = require('bower-logger');
var osenv = require('osenv');
var bower = require('../lib');
var pkg = require(path.join(__dirname, '..', 'package.json'));
var cli = require('../lib/util/cli');
var rootCheck = require('../lib/util/rootCheck');
var analytics = require('../lib/util/analytics');
// --------
var options;
var renderer;
var loglevel;
var command;
var commandFunc;
var logger;
var notifier;
var levels = Logger.LEVELS;
options = cli.readOptions({
version: { type: Boolean, shorthand: 'v' },
help: { type: Boolean, shorthand: 'h' },
'allow-root': { type: Boolean }
});
// Handle print of version
if (options.version) {
process.stdout.write(pkg.version + '\n');
process.exit();
}
// Root check
rootCheck(options, bower.config);
// Set loglevel
if (bower.config.silent) {
loglevel = levels.error;
} else if (bower.config.verbose) {
loglevel = -Infinity;
} else if (bower.config.quiet) {
loglevel = levels.warn;
} else {
loglevel = levels[bower.config.loglevel] || levels.info;
}
// Get the command to execute
while (options.argv.remain.length) {
command = options.argv.remain.join(' ');
// Alias lookup
if (bower.abbreviations[command]) {
command = bower.abbreviations[command].replace(/\s/g, '.');
break;
}
command = command.replace(/s/g, '.');
// Direct lookup
if (mout.object.has(bower.commands, command)) {
break;
}
options.argv.remain.pop();
}
// Ask for Insights on first run.
analytics.setup().then(function () {
// Execute the command
commandFunc = command && mout.object.get(bower.commands, command);
command = command && command.replace(/\./g, ' ');
// If no command was specified, show bower help
// Do the same if the command is unknown
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)
} else if (options.help || !commandFunc.line) {
logger = bower.commands.help(command);
command = 'help';
// Call the line method
} else {
logger = commandFunc.line(process.argv);
// If the method failed to interpret the process arguments
// show the command help
if (!logger) {
logger = bower.commands.help(command);
command = 'help';
}
}
// Get the renderer and configure it with the executed command
renderer = cli.getRenderer(command, logger.json, bower.config);
logger
.on('end', function (data) {
if (!bower.config.silent) {
renderer.end(data);
}
})
.on('error', function (err) {
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);
});
});
// Warn if HOME is not SET
if (!osenv.home()) {
logger.warn('no-home', 'HOME not set, user configuration will not be loaded');
}
// Check for newer version of Bower
notifier = updateNotifier({
packageName: pkg.name,
packageVersion: pkg.version
});
if (notifier.update && levels.info >= loglevel) {
renderer.updateNotice(notifier.update);
}
});

View File

@@ -1,150 +0,0 @@
process.bin = process.title = 'bower';
var Q = require('q');
var mout = require('mout');
var Logger = require('bower-logger');
var userHome = require('user-home');
var bower = require('../');
var version = require('../version');
var cli = require('../util/cli');
var rootCheck = require('../util/rootCheck');
var options;
var renderer;
var loglevel;
var command;
var commandFunc;
var logger;
var levels = Logger.LEVELS;
options = cli.readOptions({
version: { type: Boolean, shorthand: 'v' },
help: { type: Boolean, shorthand: 'h' },
'allow-root': { type: Boolean }
});
// Handle print of version
if (options.version) {
process.stdout.write(version + '\n');
process.exit();
}
// Root check
rootCheck(options, bower.config);
// Set loglevel
if (bower.config.silent) {
loglevel = levels.error;
} else if (bower.config.verbose) {
loglevel = -Infinity;
Q.longStackSupport = true;
} else if (bower.config.quiet) {
loglevel = levels.warn;
} else {
loglevel = levels[bower.config.loglevel] || levels.info;
}
// Get the command to execute
while (options.argv.remain.length) {
command = options.argv.remain.join(' ');
// Alias lookup
if (bower.abbreviations[command]) {
command = bower.abbreviations[command].replace(/\s/g, '.');
break;
}
command = command.replace(/\s/g, '.');
// Direct lookup
if (mout.object.has(bower.commands, command)) {
break;
}
options.argv.remain.pop();
}
// Execute the command
commandFunc = command && mout.object.get(bower.commands, command);
command = command && command.replace(/\./g, ' ');
// If no command was specified, show bower help
// Do the same if the command is unknown
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)
} else if (options.help || !commandFunc.line) {
logger = bower.commands.help(command);
command = 'help';
// Call the line method
} else {
logger = commandFunc.line(process.argv);
// If the method failed to interpret the process arguments
// show the command help
if (!logger) {
logger = bower.commands.help(command);
command = 'help';
}
}
// Get the renderer and configure it with the executed command
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);
}
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.'
);
}
if (bower.config.interactive) {
var updateNotifier = require('update-notifier');
// Check for newer version of Bower
var notifier = updateNotifier({ pkg: { name: 'bower', version: version } });
if (notifier.update && levels.info >= loglevel) {
notifier.notify();
}
}

View File

@@ -1,19 +1,22 @@
var fs = require('../../util/fs');
var fs = require('graceful-fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var rimraf = require('../../util/rimraf');
var rimraf = require('rimraf');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../../core/PackageRepository');
var semver = require('../../util/semver');
var cli = require('../../util/cli');
var defaultConfig = require('../../config');
function clean(logger, endpoints, options, config) {
function clean(endpoints, options, config) {
var logger = new Logger();
var decEndpoints;
var names;
options = options || {};
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
// If endpoints is an empty array, null them
if (endpoints && !endpoints.length) {
@@ -22,37 +25,46 @@ 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;
});
}
return Q.all([
Q.all([
clearPackages(decEndpoints, config, logger),
clearLinks(names, config, logger)
]).spread(function(entries) {
clearLinks(names, config, logger),
!names ? clearCompletion(config, logger) : null
])
.spread(function (entries) {
return entries;
})
.done(function (entries) {
logger.emit('end', entries);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
function clearPackages(decEndpoints, config, logger) {
var repository = new PackageRepository(config, logger);
var repository = new PackageRepository(config, logger);
return repository.list().then(function(entries) {
return repository.list()
.then(function (entries) {
var promises;
// Filter entries according to the specified packages
if (decEndpoints) {
entries = entries.filter(function(entry) {
return !!mout.array.find(decEndpoints, function(decEndpoint) {
entries = entries.filter(function (entry) {
return !!mout.array.find(decEndpoints, function (decEndpoint) {
var entryPkgMeta = entry.pkgMeta;
// Check if name or source match the entry
if (
decEndpoint.name !== entryPkgMeta.name &&
if (decEndpoint.name !== entryPkgMeta.name &&
decEndpoint.source !== entryPkgMeta.name &&
decEndpoint.source !== entryPkgMeta._source
) {
@@ -66,47 +78,36 @@ function clearPackages(decEndpoints, config, logger) {
// If it's a semver target, compare using semver spec
if (semver.validRange(decEndpoint.target)) {
return semver.satisfies(
entryPkgMeta.version,
decEndpoint.target
);
return semver.satisfies(entryPkgMeta.version, decEndpoint.target);
}
// Otherwise, compare against target/release
return (
decEndpoint.target === entryPkgMeta._target ||
decEndpoint.target === entryPkgMeta._release
);
return decEndpoint.target === entryPkgMeta._target ||
decEndpoint.target === entryPkgMeta._release;
});
});
}
promises = entries.map(function(entry) {
return repository.eliminate(entry.pkgMeta).then(function() {
logger.info(
'deleted',
'Cached package ' +
entry.pkgMeta.name +
': ' +
entry.canonicalDir,
{
file: entry.canonicalDir
}
);
promises = entries.map(function (entry) {
return repository.eliminate(entry.pkgMeta)
.then(function () {
logger.info('deleted', 'Cached package ' + entry.pkgMeta.name + ': ' + entry.canonicalDir, {
file: entry.canonicalDir
});
});
});
return Q.all(promises)
.then(function() {
if (!decEndpoints) {
// Ensure that everything is cleaned,
// even invalid packages in the cache
return repository.clear();
}
})
.then(function() {
return entries;
});
.then(function () {
if (!decEndpoints) {
// Ensure that everything is cleaned,
// even invalid packages in the cache
return repository.clear();
}
})
.then(function () {
return entries;
});
});
}
@@ -116,59 +117,59 @@ function clearLinks(names, config, logger) {
// If no names are passed, grab all links
if (!names) {
promise = Q.nfcall(fs.readdir, dir).fail(function(err) {
promise = Q.nfcall(fs.readdir, dir)
.fail(function (err) {
if (err.code === 'ENOENT') {
return [];
}
throw err;
});
// Otherwise use passed ones
// Otherwise use passed ones
} else {
promise = Q.resolve(names);
}
return promise.then(function(names) {
return promise
.then(function (names) {
var promises;
var linksToRemove = [];
// Decide which links to delete
promises = names.map(function(name) {
promises = names.map(function (name) {
var link = path.join(config.storage.links, name);
return Q.nfcall(fs.readlink, link).then(
function(linkTarget) {
// Link exists, check if it points to a folder
// that still exists
return (
Q.nfcall(fs.stat, linkTarget)
.then(function(stat) {
// Target is not a folder..
if (!stat.isDirectory()) {
linksToRemove.push(link);
}
})
// Error occurred reading the link
.fail(function() {
linksToRemove.push(link);
})
);
// Ignore if link does not exist
},
function(err) {
if (err.code !== 'ENOENT') {
return Q.nfcall(fs.readlink, link)
.then(function (linkTarget) {
// Link exists, check if it points to a folder
// that still exists
return Q.nfcall(fs.stat, linkTarget)
.then(function (stat) {
// Target is not a folder..
if (!stat.isDirectory()) {
linksToRemove.push(link);
}
})
// Error occurred reading the link
.fail(function () {
linksToRemove.push(link);
});
// Ignore if link does not exist
}, function (err) {
if (err.code !== 'ENOENT') {
linksToRemove.push(link);
}
);
});
});
return Q.all(promises).then(function() {
return Q.all(promises)
.then(function () {
var promises;
// Remove each link that was declared as invalid
promises = linksToRemove.map(function(link) {
return Q.nfcall(rimraf, link).then(function() {
promises = linksToRemove.map(function (link) {
return Q.nfcall(rimraf, link)
.then(function () {
logger.info('deleted', 'Invalid link: ' + link, {
file: link
});
@@ -180,16 +181,37 @@ function clearLinks(names, config, logger) {
});
}
function clearCompletion(config, logger) {
var dir = config.storage.completion;
return Q.nfcall(fs.stat, dir)
.then(function () {
return Q.nfcall(rimraf, dir)
.then(function () {
logger.info('deleted', 'Completion cache', {
file: dir
});
});
}, function (error) {
if (error.code !== 'ENOENT') {
throw error;
}
});
}
// -------------------
clean.readOptions = function(argv) {
var cli = require('../../util/cli');
var options = cli.readOptions(argv);
var endpoints = options.argv.remain.slice(2);
clean.line = function (argv) {
var options = clean.options(argv);
return clean(options.argv.remain.slice(2), options);
};
delete options.argv;
clean.options = function (argv) {
return cli.readOptions(argv);
};
return [endpoints, options];
clean.completion = function () {
// TODO:
};
module.exports = clean;

4
lib/commands/cache/index.js vendored Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
clean: require('./clean'),
list: require('./list')
};

View File

@@ -1,11 +1,14 @@
var mout = require('mout');
var Logger = require('bower-logger');
var PackageRepository = require('../../core/PackageRepository');
var cli = require('../../util/cli');
var defaultConfig = require('../../config');
function list(logger, packages, options, config) {
function list(packages, options, config) {
var repository;
var logger = new Logger();
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
repository = new PackageRepository(config, logger);
// If packages is an empty array, null them
@@ -13,30 +16,41 @@ function list(logger, packages, options, config) {
packages = null;
}
return repository.list().then(function(entries) {
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;
});
});
}
return entries;
})
.done(function (entries) {
logger.emit('end', entries);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
list.readOptions = function(argv) {
var cli = require('../../util/cli');
var options = cli.readOptions(argv);
var packages = options.argv.remain.slice(2);
list.line = function (argv) {
var options = list.options(argv);
return list(options.argv.remain.slice(2), options);
};
delete options.argv;
list.options = function (argv) {
return cli.readOptions(argv);
};
return [packages, options];
list.completion = function () {
// TODO:
};
module.exports = list;

View File

@@ -0,0 +1,31 @@
var Logger = require('bower-logger');
var cli = require('../util/cli');
function completion(config) {
var logger = new Logger();
process.nextTick(function () {
logger.emit('end');
});
return logger;
}
// -------------------
completion.line = function (argv) {
var options = completion.options(argv);
var name = options.argv.remain[1];
return completion(name);
};
completion.options = function (argv) {
return cli.readOptions(argv);
};
completion.completion = function () {
// TODO:
};
module.exports = completion;

View File

@@ -1,41 +1,52 @@
var Q = require('q');
var path = require('path');
var fs = require('../util/fs');
var fs = require('graceful-fs');
var Logger = require('bower-logger');
var cli = require('../util/cli');
var createError = require('../util/createError');
function help(logger, name, config) {
function help(name) {
var json;
var logger = new Logger();
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');
json = path.resolve(__dirname, '../../templates/json/help.json');
}
return Q.promise(function(resolve) {
fs.exists(json, resolve);
}).then(function(exists) {
fs.exists(json, function (exists) {
if (!exists) {
throw createError('Unknown command: ' + name, 'EUNKNOWNCMD', {
return logger.emit('error', createError('Unknown command: ' + name, 'EUNKOWNCMD', {
command: name
});
}));
}
return require(json);
try {
json = require(json);
} catch (error) {
return logger.emit('error', error);
}
logger.emit('end', json);
});
return logger;
}
// -------------------
help.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var name = options.argv.remain.slice(1).join(' ');
help.line = function (argv) {
var options = help.options(argv);
return [name];
return help(options.argv.remain.slice(1).join(' '));
};
help.options = function (argv) {
return cli.readOptions(argv);
};
help.completion = function () {
// TODO
};
module.exports = help;

View File

@@ -1,22 +1,27 @@
var mout = require('mout');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var open = require('opn');
var open = require('open');
var endpointParser = require('bower-endpoint-parser');
var cli = require('../util/cli');
var createError = require('../util/createError');
var defaultConfig = require('../config');
function home(logger, name, config) {
function home(name, config) {
var project;
var promise;
var decEndpoint;
var logger = new Logger();
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
// Get the package meta
// If no name is specified, read the project json
// If a name is specified, fetch from the package repository
if (!name) {
promise = project.hasJson().then(function(json) {
promise = project.hasJson()
.then(function (json) {
if (!json) {
throw createError('You are not inside a package', 'ENOENT');
}
@@ -25,35 +30,47 @@ 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) {
promise.then(function (pkgMeta) {
var homepage = pkgMeta.homepage;
if (!homepage) {
throw createError('No homepage set for ' + pkgMeta.name, 'ENOHOME');
}
open(homepage, { wait: false });
open(homepage);
return homepage;
})
.done(function (homepage) {
logger.emit('end', homepage);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
home.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
home.line = function (argv) {
var options = home.options(argv);
var name = options.argv.remain[1];
return [name];
return home(name);
};
home.options = function (argv) {
return cli.readOptions(argv);
};
home.completion = function () {
// TODO:
};
module.exports = home;

View File

@@ -1,82 +1,17 @@
var Q = require('q');
var Logger = require('bower-logger');
var config = require('../config');
/**
* Require commands only when called.
*
* Running `commandFactory(id)` is equivalent to `require(id)`. Both calls return
* a command function. The difference is that `cmd = commandFactory()` and `cmd()`
* return as soon as possible and load and execute the command asynchronously.
*/
function commandFactory(id) {
function runApi() {
var command = require(id);
var commandArgs = [].slice.call(arguments);
return withLogger(function(logger) {
commandArgs.unshift(logger);
return command.apply(undefined, commandArgs);
});
}
function runFromArgv(argv) {
var commandArgs;
var command = require(id);
commandArgs = command.readOptions(argv);
return withLogger(function(logger) {
commandArgs.unshift(logger);
return command.apply(undefined, commandArgs);
});
}
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);
}
);
return logger;
}
runApi.line = runFromArgv;
return runApi;
}
module.exports = {
cache: {
clean: commandFactory('./cache/clean'),
list: commandFactory('./cache/list')
},
help: commandFactory('./help'),
home: commandFactory('./home'),
info: commandFactory('./info'),
init: commandFactory('./init'),
install: commandFactory('./install'),
link: commandFactory('./link'),
list: commandFactory('./list'),
login: commandFactory('./login'),
lookup: commandFactory('./lookup'),
prune: commandFactory('./prune'),
register: commandFactory('./register'),
search: commandFactory('./search'),
update: commandFactory('./update'),
uninstall: commandFactory('./uninstall'),
unregister: commandFactory('./unregister'),
version: commandFactory('./version')
cache: require('./cache'),
completion: require('./completion'),
help: require('./help'),
home: require('./home'),
info: require('./info'),
init: require('./init'),
install: require('./install'),
link: require('./link'),
list: require('./list'),
lookup: require('./lookup'),
prune: require('./prune'),
register: require('./register'),
search: require('./search'),
update: require('./update'),
uninstall: require('./uninstall')
};

View File

@@ -1,35 +1,30 @@
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var PackageRepository = require('../core/PackageRepository');
var cli = require('../util/cli');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function info(logger, endpoint, property, config) {
if (!endpoint) {
return;
}
// handle @ as version divider
var splitParts = endpoint.split('/');
splitParts[splitParts.length - 1] = splitParts[
splitParts.length - 1
].replace('@', '#');
endpoint = splitParts.join('/');
function info(endpoint, property, config) {
var repository;
var decEndpoint;
var tracker;
var logger = new Logger();
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
repository = new PackageRepository(config, logger);
tracker = new Tracker(config);
decEndpoint = endpointParser.decompose(endpoint);
tracker.trackDecomposedEndpoints('info', [decEndpoint]);
return Q.all([
Q.all([
getPkgMeta(repository, decEndpoint, property),
decEndpoint.target === '*' && !property
? repository.versions(decEndpoint.source)
: null
]).spread(function(pkgMeta, versions) {
decEndpoint.target === '*' && !property ? repository.versions(decEndpoint.source) : null
])
.spread(function (pkgMeta, versions) {
if (versions) {
return {
name: decEndpoint.source,
@@ -39,35 +34,52 @@ function info(logger, endpoint, property, config) {
}
return pkgMeta;
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
function getPkgMeta(repository, decEndpoint, property) {
return repository
.fetch(decEndpoint)
.spread(function(canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function(value, key) {
return key.charAt(0) !== '_';
});
// Retrieve specific property
if (property) {
pkgMeta = mout.object.get(pkgMeta, property);
}
return pkgMeta;
return repository.fetch(decEndpoint)
.spread(function (canonicalDir, pkgMeta) {
pkgMeta = mout.object.filter(pkgMeta, function (value, key) {
return key.charAt(0) !== '_';
});
// Retrieve specific property
if (property) {
pkgMeta = mout.object.get(pkgMeta, property);
}
return pkgMeta;
});
}
// -------------------
info.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
info.line = function (argv) {
var options = info.options(argv);
var pkg = options.argv.remain[1];
var property = options.argv.remain[2];
return [pkg, property];
if (!pkg) {
return null;
}
return info(pkg, property);
};
info.options = function (argv) {
return cli.readOptions(argv);
};
info.completion = function () {
// TODO:
};
module.exports = info;

View File

@@ -1,60 +1,61 @@
var mout = require('mout');
var fs = require('../util/fs');
var fs = require('graceful-fs');
var path = require('path');
var Q = require('q');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
var GitFsResolver = require('../core/resolvers/GitFsResolver');
var cli = require('../util/cli');
var cmd = require('../util/cmd');
var createError = require('../util/createError');
function init(logger, config) {
function init(config) {
var project;
var logger = new Logger();
config = config || {};
if (!config.cwd) {
config.cwd = process.cwd();
}
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
// 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'
process.nextTick(function () {
logger.emit('error', createError('Register requires an interactive shell', 'ENOINT', {
details: 'Note that you can manually force an interactive shell with --config.interactive'
}));
});
return logger;
}
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))
);
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))
.done(function (json) {
logger.emit('end', json);
}, function (error) {
logger.emit('error', error);
});
return 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();
@@ -63,8 +64,8 @@ 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) {
if (!validConfigValue(value)) {
mout.object.forOwn(json, function (value, key) {
if (value == null || mout.lang.isEmpty(value)) {
delete json[key];
}
});
@@ -76,7 +77,8 @@ function saveJson(project, logger, json) {
type: 'confirm',
message: 'Looks good?',
default: true
}).then(function(good) {
})
.then(function (good) {
if (!good) {
return null;
}
@@ -86,18 +88,6 @@ function saveJson(project, logger, json) {
});
}
// Test if value is of a type supported by bower.json[0] - Object, Array, String, Boolean - or a Number
// [0]: https://github.com/bower/bower.json-spec
function validConfigValue(val) {
return (
mout.lang.isObject(val) ||
mout.lang.isArray(val) ||
mout.lang.isString(val) ||
mout.lang.isBoolean(val) ||
mout.lang.isNumber(val)
);
}
function setDefaults(config, json) {
var name;
var promise = Q.resolve();
@@ -107,6 +97,19 @@ function setDefaults(config, json) {
json.name = path.basename(config.cwd);
}
// Version
if (!json.version) {
// Assume latest semver tag if it's a git repo
promise = promise.then(function () {
return GitFsResolver.versions(config.cwd)
.then(function (versions) {
json.version = versions[0] || '0.0.0';
}, function () {
json.version = '0.0.0';
});
});
}
// Main
if (!json.main) {
// Remove '.js' from the end of the package name if it is there
@@ -122,70 +125,52 @@ 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;
});
}
@@ -193,74 +178,77 @@ 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': 'version',
'message': 'version',
'default': json.version,
'type': 'input'
},
{
name: 'main',
message: 'main file',
default: json.main,
type: 'input'
'name': 'description',
'message': 'description',
'default': json.description,
'type': 'input'
},
{
name: 'keywords',
message: 'keywords',
default: json.keywords ? json.keywords.toString() : null,
type: 'input'
'name': 'main',
'message': 'main file',
'default': json.main,
'type': 'input'
},
{
name: 'authors',
message: 'authors',
default: json.authors ? json.authors.toString() : null,
type: 'input'
'name': 'keywords',
'message': 'keywords',
'default': json.keywords ? json.keywords.toString() : null,
'type': 'input'
},
{
name: 'license',
message: 'license',
default: json.license || 'MIT',
type: 'input'
'name': 'authors',
'message': 'authors',
'default': json.authors ? json.authors.toString() : null,
'type': 'input'
},
{
name: 'homepage',
message: 'homepage',
default: json.homepage,
type: 'input'
'name': 'license',
'message': 'license',
'default': json.license || 'MIT',
'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': 'homepage',
'message': 'homepage',
'default': json.homepage,
'type': 'input'
},
{
name: 'ignore',
message: 'add commonly ignored files to ignore list?',
default: true,
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: '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': 'ignore',
'message': 'add commonly ignored files to ignore list?',
'default': true,
'type': 'confirm'
},
{
'name': 'private',
'message': 'would you like to mark this package as private which prevents it from being accidentally published to the registry?',
'default': !!json.private,
'type': 'confirm'
}
];
return Q.nfcall(logger.prompt.bind(logger), questions).then(function(
answers
) {
return Q.nfcall(logger.prompt.bind(logger), questions)
.then(function (answers) {
json.name = answers.name;
json.version = answers.version;
json.description = answers.description;
json.main = answers.main;
json.keywords = toArray(answers.keywords);
@@ -277,12 +265,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;
});
@@ -306,12 +294,13 @@ 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
@@ -319,9 +308,7 @@ function setDependencies(project, json, answers) {
return;
}
jsonEndpoint = endpointParser.decomposed2json(
extra.endpoint
);
jsonEndpoint = endpointParser.decomposed2json(extra.endpoint);
mout.object.mixIn(json.dependencies, jsonEndpoint);
});
}
@@ -335,8 +322,16 @@ function setDependencies(project, json, answers) {
// -------------------
init.readOptions = function(argv) {
return [];
init.line = function () {
return init();
};
init.options = function (argv) {
return cli.readOptions(argv);
};
init.completion = function () {
// TODO:
};
module.exports = init;

View File

@@ -1,55 +1,58 @@
var mout = require('mout');
var Logger = require('bower-logger');
var endpointParser = require('bower-endpoint-parser');
var Project = require('../core/Project');
var cli = require('../util/cli');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function install(logger, endpoints, options, config) {
function install(endpoints, options, config) {
var project;
var decEndpoints;
var tracker;
var logger = new Logger();
options = options || {};
config = defaultConfig(config);
if (options.save === undefined) {
options.save = config.defaultSave;
}
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
tracker = new Tracker(config);
// Convert endpoints to decomposed endpoints
endpoints = endpoints || [];
decEndpoints = endpoints.map(function(endpoint) {
// handle @ as version divider
var splitParts = endpoint.split('/');
splitParts[splitParts.length - 1] = splitParts[
splitParts.length - 1
].replace('@', '#');
endpoint = splitParts.join('/');
decEndpoints = endpoints.map(function (endpoint) {
return endpointParser.decompose(endpoint);
});
tracker.trackDecomposedEndpoints('install', decEndpoints);
return project.install(decEndpoints, options, config);
project.install(decEndpoints, options)
.done(function (installed) {
tracker.trackPackages('installed', installed);
logger.emit('end', installed);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
install.readOptions = function(argv) {
var cli = require('../util/cli');
install.line = function (argv) {
var options = install.options(argv);
return install(options.argv.remain.slice(1), options);
};
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
);
install.options = function (argv) {
return cli.readOptions({
'force-latest': { type: Boolean, shorthand: 'F'},
'production': { type: Boolean, shorthand: 'p' },
'save': { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' }
}, argv);
};
var packages = options.argv.remain.slice(1);
delete options.argv;
return [packages, options];
install.completion = function () {
// TODO:
};
module.exports = install;

View File

@@ -1,88 +1,104 @@
var path = require('path');
var rimraf = require('../util/rimraf');
var rimraf = require('rimraf');
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var createLink = require('../util/createLink');
var cli = require('../util/cli');
var defaultConfig = require('../config');
var relativeToBaseDir = require('../util/relativeToBaseDir');
function link(logger, name, localName, config) {
if (name) {
return linkTo(logger, name, localName, config);
} else {
return linkSelf(logger, config);
}
}
function linkSelf(logger, config) {
function linkSelf(config) {
var project;
var logger = new Logger();
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
return project.getJson().then(function(json) {
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
};
});
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
function linkTo(logger, name, localName, config) {
function linkTo(name, localName, config) {
var src;
var dst;
var project;
var logger = new Logger();
config = defaultConfig(config);
project = new Project(config, logger);
config = mout.object.deepFillIn(config || {}, defaultConfig);
localName = localName || name;
src = path.join(config.storage.links, name);
dst = path.join(relativeToBaseDir(config.cwd)(config.directory), localName);
dst = path.join(process.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
};
})
);
Q.nfcall(rimraf, dst)
// Link locally
.then(function () {
return createLink(src, dst);
})
.then(function () {
return {
src: src,
dst: dst
};
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
link.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
var link = {
linkTo: linkTo,
linkSelf: linkSelf
};
link.line = function (argv) {
var options = link.options(argv);
var name = options.argv.remain[1];
var localName = options.argv.remain[2];
return [name, localName];
if (name) {
return linkTo(name, localName);
}
return linkSelf();
};
link.options = function (argv) {
return cli.readOptions(argv);
};
link.completion = function () {
// TODO:
};
module.exports = link;

View File

@@ -1,58 +1,54 @@
var path = require('path');
var mout = require('mout');
var Q = require('q');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var semver = require('../util/semver');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function list(logger, options, config) {
function list(options, config) {
var project;
var logger = new Logger();
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);
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
return project.getTree(options).spread(function(tree, flattened) {
project.getTree()
.spread(function (tree, flattened) {
var baseDir = path.dirname(path.join(config.cwd, config.directory));
// Relativize paths
// Also normalize paths on windows
project.walkTree(
tree,
function(node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(
config.cwd,
node.canonicalDir
);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
}
},
true
);
// Note that we need to to parse the flattened tree because it might
// contain additional packages
mout.object.forOwn(flattened, function(node) {
project.walkTree(tree, function (node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(
config.cwd,
node.canonicalDir
);
node.canonicalDir = path.relative(baseDir, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
}
}, true);
// Note that we need to to parse the flattened tree because it might
// contain additional packages
mout.object.forOwn(flattened, function (node) {
if (node.missing) {
return;
}
if (options.relative) {
node.canonicalDir = path.relative(baseDir, node.canonicalDir);
}
if (options.paths) {
node.canonicalDir = normalize(node.canonicalDir);
@@ -70,10 +66,20 @@ 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;
});
})
.done(function (value) {
logger.emit('end', value);
}, function (error) {
logger.emit('error', error);
});
logger.json = !!options.paths;
return logger;
}
function checkVersions(project, tree, logger) {
@@ -81,41 +87,31 @@ function checkVersions(project, tree, logger) {
var nodes = [];
var repository = project.getPackageRepository();
// Gather all nodes, ignoring linked nodes
project.walkTree(
tree,
function(node) {
if (!node.linked) {
nodes.push(node);
}
},
true
);
// Gather all nodes
project.walkTree(tree, function (node) {
nodes.push(node);
}, true);
if (nodes.length) {
logger.info(
'check-new',
'Checking for new versions of the project dependencies...'
);
logger.info('check-new', 'Checking for new versions of the project dependencies..');
}
// Check for new versions for each node
promises = nodes.map(function(node) {
promises = nodes.map(function (node) {
var target = node.endpoint.target;
return repository
.versions(node.endpoint.source)
.then(function(versions) {
node.versions = versions;
return repository.versions(node.endpoint.source)
.then(function (versions) {
node.versions = versions;
// Do not check if node's target is not a valid semver one
if (versions.length && semver.validRange(target)) {
node.update = {
target: semver.maxSatisfying(versions, target),
latest: semver.maxSatisfying(versions, '*')
};
}
});
// Do not check if node's target is not a valid semver one
if (versions.length && semver.validRange(target)) {
node.update = {
target: semver.maxSatisfying(versions, target),
latest: semver.maxSatisfying(versions, '*')
};
}
});
});
// Set the versions also for the root node
@@ -127,7 +123,7 @@ function checkVersions(project, tree, logger) {
function paths(flattened) {
var ret = {};
mout.object.forOwn(flattened, function(pkg, name) {
mout.object.forOwn(flattened, function (pkg, name) {
var main;
if (pkg.missing) {
@@ -148,7 +144,7 @@ function paths(flattened) {
}
// Concatenate each main entry with the canonical dir
main = main.map(function(part) {
main = main.map(function (part) {
return normalize(path.join(pkg.canonicalDir, part).trim());
});
@@ -166,20 +162,20 @@ function normalize(src) {
// -------------------
list.readOptions = function(argv) {
var cli = require('../util/cli');
list.line = function (argv) {
var options = list.options(argv);
return list(options);
};
var options = cli.readOptions(
{
paths: { type: Boolean, shorthand: 'p' },
relative: { type: Boolean, shorthand: 'r' }
},
argv
);
list.options = function (argv) {
return cli.readOptions({
'paths': { type: Boolean, shorthand: 'p' },
'relative': { type: Boolean, shorthand: 'r' }
}, argv);
};
delete options.argv;
return [options];
list.completion = function () {
// TODO:
};
module.exports = list;

View File

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

View File

@@ -1,37 +1,56 @@
var mout = require('mout');
var Q = require('q');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('bower-logger');
var RegistryClient = require('bower-registry-client');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function lookup(logger, name, config) {
if (!name) {
return new Q(null);
}
function lookup(name, config) {
var registryClient;
var logger = new Logger();
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
config.cache = config.storage.registry;
var repository = new PackageRepository(config, logger);
var registryClient = repository.getRegistryClient();
registryClient = new RegistryClient(config, logger);
return Q.nfcall(registryClient.lookup.bind(registryClient), name).then(
function(entry) {
return !entry
? null
: {
name: name,
url: entry.url
};
}
);
Q.nfcall(registryClient.lookup.bind(registryClient), name)
.then(function (entry) {
// TODO: Handle entry.type.. for now it's only 'alias'
// When we got published packages, this needs to be adjusted
return !entry ? null : {
name: name,
url: entry && entry.url
};
})
.done(function (result) {
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
lookup.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
lookup.line = function (argv) {
var options = lookup.options(argv);
var name = options.argv.remain[1];
return [name];
if (!name) {
return null;
}
return lookup(name);
};
lookup.options = function (argv) {
return cli.readOptions(argv);
};
lookup.completion = function () {
// TODO:
};
module.exports = lookup;

View File

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

View File

@@ -1,51 +1,59 @@
var mout = require('mout');
var Q = require('q');
var chalk = require('chalk');
var PackageRepository = require('../core/PackageRepository');
var Logger = require('bower-logger');
var Config = require('bower-config');
var Tracker = require('../util/analytics').Tracker;
var cli = require('../util/cli');
var createError = require('../util/createError');
var defaultConfig = require('../config');
var GitHubResolver = require('../core/resolvers/GitHubResolver');
function register(logger, name, source, config) {
function register(name, url, config) {
var repository;
var registryClient;
var tracker;
var logger = new Logger();
var force;
var url;
var githubSourceRegex = /^\w[\w-]*\/\w[\w-]*$/;
var getGithubUrl = function(source) {
return 'git@github.com:' + source + '.git';
};
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
force = config.force;
name = (name || '').trim();
source = (source || '').trim();
url = source.match(githubSourceRegex) ? getGithubUrl(source) : source;
tracker = new Tracker(config);
// Bypass any cache
config.offline = false;
config.force = true;
return Q.try(function() {
// Verify name and url
if (!name || !url) {
throw createError(
'Usage: bower register <name> <url>',
'EINVFORMAT'
);
// Trim name
name = name.trim();
process.nextTick(function () {
// Verify name
// TODO: Verify with the new spec regexp?
if (!name) {
return logger.emit('error', createError('Please type a name', 'EINVNAME'));
}
// The public registry only allows git:// endpoints
// As such, we attempt to convert URLs as necessary
if (config.registry.register === Config.DEFAULT_REGISTRY) {
url = convertUrl(url, logger);
if (!mout.string.startsWith(url, 'git://')) {
return logger.emit('error', createError('The registry only accepts URLs starting with git://', 'EINVFORMAT'));
}
}
tracker.track('register');
// Attempt to resolve the package referenced by the URL to ensure
// everything is ok before registering
repository = new PackageRepository(config, logger);
return repository.fetch({ name: name, source: url, target: '*' });
})
.spread(function(canonicalDir, pkgMeta) {
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'
);
throw createError('The package you are trying to register is marked as private', 'EPRIV');
}
// If non interactive or user forced, bypass confirmation
@@ -56,14 +64,12 @@ function register(logger, name, source, config) {
// 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?',
message: 'Registering a package will make it installable via the registry (' +
chalk.cyan.underline(config.registry.register) + '), continue?',
default: true
});
})
.then(function(result) {
.then(function (result) {
// If user response was negative, abort
if (!result) {
return;
@@ -77,24 +83,55 @@ function register(logger, name, source, config) {
url: url
});
return Q.nfcall(
registryClient.register.bind(registryClient),
name,
url
);
return Q.nfcall(registryClient.register.bind(registryClient), name, url);
})
.done(function (result) {
tracker.track('registered');
logger.emit('end', result);
}, function (error) {
logger.emit('error', error);
});
});
return logger;
}
function convertUrl(url, logger) {
var pair;
var newUrl;
if (!mout.string.startsWith(url, 'git://')) {
// Convert GitHub ssh & https to git://
pair = GitHubResolver.getOrgRepoPair(url);
if (pair) {
newUrl = 'git://github.com/' + pair.org + '/' + pair.repo + '.git';
logger.warn('convert', 'Converted ' + url + ' to ' + newUrl);
}
}
return newUrl || url;
}
// -------------------
register.readOptions = function(argv) {
var cli = require('../util/cli');
var options = cli.readOptions(argv);
register.line = function (argv) {
var options = register.options(argv);
var name = options.argv.remain[1];
var url = options.argv.remain[2];
return [name, url];
if (!name || !url) {
return null;
}
return register(name, url);
};
register.options = function (argv) {
return cli.readOptions(argv);
};
register.completion = function () {
// TODO:
};
module.exports = register;

View File

@@ -1,39 +1,55 @@
var mout = require('mout');
var Q = require('q');
var PackageRepository = require('../core/PackageRepository');
var defaultConfig = require('../config');
var Logger = require('bower-logger');
var RegistryClient = require('bower-registry-client');
var cli = require('../util/cli');
var createError = require('../util/createError');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function search(logger, name, config) {
function search(name, config) {
var registryClient;
var promise;
var tracker;
var logger = new Logger();
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
config.cache = config.storage.registry;
var repository = new PackageRepository(config, logger);
var registryClient = repository.getRegistryClient();
registryClient = new RegistryClient(config, logger);
tracker = new Tracker(config);
if (name) {
return Q.nfcall(registryClient.search.bind(registryClient), name);
// If no name was specified, list all packages
if (!name) {
promise = Q.nfcall(registryClient.list.bind(registryClient));
// Otherwise search it
} else {
// List all packages when in interactive mode + json enabled, and
// always when in non-interactive mode
if (config.interactive && !config.json) {
throw createError('no parameter to bower search', 'EREADOPTIONS');
}
return Q.nfcall(registryClient.list.bind(registryClient));
promise = Q.nfcall(registryClient.search.bind(registryClient), name);
}
promise
.done(function (results) {
tracker.track('searched', name);
logger.emit('end', results);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
search.readOptions = function(argv) {
var options = cli.readOptions(argv);
var terms = options.argv.remain.slice(1);
search.line = function (argv) {
var options = search.options(argv);
return search(options.argv.remain.slice(1).join(' '), options);
};
var name = terms.join(' ');
search.options = function (argv) {
return cli.readOptions(argv);
};
return [name];
search.completion = function () {
// TODO:
};
module.exports = search;

View File

@@ -1,76 +1,70 @@
var mout = require('mout');
var Logger = require('bower-logger');
var Q = require('q');
var Project = require('../core/Project');
var cli = require('../util/cli');
var Tracker = require('../util/analytics').Tracker;
var defaultConfig = require('../config');
function uninstall(logger, names, options, config) {
if (!names.length) {
return new Q();
}
function uninstall(names, options, config) {
var project;
var tracker;
var logger = new Logger();
options = options || {};
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
tracker = new Tracker(config);
return project.getTree(options).spread(function(tree, flattened) {
tracker.trackNames('uninstall', names);
project.getTree()
.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);
});
})
.done(function (uninstalled) {
logger.emit('end', uninstalled);
tracker.trackNames('uninstalled', Object.keys(uninstalled));
}, function (error) {
logger.emit('error', error);
});
return logger;
}
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
);
// Filter out those that have no dependants
nodes = nodes.filter(function(node) {
return !dependantsCounter[node.endpoint.name];
// Filter out those that have dependants
nodes = nodes.filter(function (node) {
return !node.nrDependants;
});
// Are we done?
@@ -79,54 +73,52 @@ 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) {
var cli = require('../util/cli');
var options = cli.readOptions(
{
save: { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' }
},
argv
);
uninstall.line = function (argv) {
var options = uninstall.options(argv);
var names = options.argv.remain.slice(1);
delete options.argv;
if (!names.length) {
return null;
}
return [names, options];
return uninstall(names, options);
};
uninstall.options = function (argv) {
return cli.readOptions({
'save': { type: Boolean, shorthand: 'S' },
'save-dev': { type: Boolean, shorthand: 'D' }
}, argv);
};
uninstall.completion = function () {
// TODO:
};
module.exports = uninstall;

View File

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

View File

@@ -1,11 +1,15 @@
var mout = require('mout');
var Logger = require('bower-logger');
var Project = require('../core/Project');
var cli = require('../util/cli');
var defaultConfig = require('../config');
function update(logger, names, options, config) {
function update(names, options, config) {
var project;
var logger = new Logger();
options = options || {};
config = defaultConfig(config);
config = mout.object.deepFillIn(config || {}, defaultConfig);
project = new Project(config, logger);
// If names is an empty array, null them
@@ -13,27 +17,32 @@ function update(logger, names, options, config) {
names = null;
}
return project.update(names, options);
project.update(names, options)
.done(function (installed) {
logger.emit('end', installed);
}, function (error) {
logger.emit('error', error);
});
return logger;
}
// -------------------
update.readOptions = function(argv) {
var cli = require('../util/cli');
update.line = function (argv) {
var options = update.options(argv);
return update(options.argv.remain.slice(1), options);
};
var options = cli.readOptions(
{
'force-latest': { type: Boolean, shorthand: 'F' },
production: { type: Boolean, shorthand: 'p' }
},
argv
);
update.options = function (argv) {
return cli.readOptions({
'force-latest': { type: Boolean, shorthand: 'F' },
'production': { type: Boolean, shorthand: 'p' }
}, argv);
};
var names = options.argv.remain.slice(1);
delete options.argv;
return [names, options];
update.completion = function () {
// TODO:
};
module.exports = update;

View File

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

View File

@@ -1,63 +1,32 @@
var tty = require('tty');
var object = require('mout').object;
var bowerConfig = require('bower-config');
var Configstore = require('configstore');
var mout = require('mout');
var config = require('bower-config').read();
var cli = require('./util/cli');
var current;
// Delete the json attribute because it is no longer supported
// and conflicts with --json
delete config.json;
function defaultConfig(config) {
config = config || {};
return readCachedConfig(config.cwd || process.cwd(), config);
// If interactive is auto (null), guess its value
if (config.interactive == null) {
config.interactive = process.bin === 'bower' && tty.isatty(1);
}
function readCachedConfig(cwd, overwrites) {
current = bowerConfig.create(cwd).load(overwrites);
var config = current.toObject();
var configstore = new Configstore('bower-github').all;
object.mixIn(config, configstore);
// If interactive is auto (null), guess its value
if (config.interactive == null) {
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' }
})
);
}
return config;
// If `analytics` hasn't been explicitly set, we disable
// it when ran programatically.
if (config.analytics == null) {
config.analytics = config.interactive;
}
function restoreConfig() {
if (current) {
current.restore();
}
}
// Merge common CLI options into the config
mout.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' }
}));
function resetCache() {
restoreConfig();
current = undefined;
}
module.exports = defaultConfig;
module.exports.restore = restoreConfig;
module.exports.reset = resetCache;
module.exports = config;

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
var mout = require('mout');
var Q = require('q');
var RegistryClient = require('bower-registry-client');
var ResolveCache = require('./ResolveCache');
var resolverFactory = require('./resolverFactory');
var createError = require('../util/createError');
var RegistryClient = require('bower-registry-client');
function PackageRepository(config, logger) {
var registryOptions;
@@ -14,7 +14,6 @@ function PackageRepository(config, logger) {
// Instantiate the registry
registryOptions = mout.object.deepMixIn({}, this._config);
registryOptions.cache = this._config.storage.registry;
this._registryClient = new RegistryClient(registryOptions, logger);
// Instantiate the resolve cache
@@ -23,7 +22,7 @@ function PackageRepository(config, logger) {
// -----------------
PackageRepository.prototype.fetch = function(decEndpoint) {
PackageRepository.prototype.fetch = function (decEndpoint) {
var logger;
var that = this;
var isTargetable;
@@ -35,192 +34,129 @@ 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;
// Get the appropriate resolver
return resolverFactory(decEndpoint, this._config, logger, this._registryClient)
// 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
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 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, we have to resolve it
logger.info('not-cached', resolver.getSource() + (resolver.getTarget() ? '#' + 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
}
);
}
// Otherwise, we have to resolve it
logger.info(
'not-cached',
resolver.getSource() +
(resolver.getTarget()
? '#' + resolver.getTarget()
: '')
);
logger.action(
'resolve',
resolver.getSource() +
'#' +
resolver.getTarget()
);
return that._resolve(resolver, logger);
}
info.canonicalDir = canonicalDir;
info.pkgMeta = pkgMeta;
logger.info(
'cached',
resolver.getSource() +
(pkgMeta._release
? '#' + pkgMeta._release
: '')
);
// If offline flag is used, use directly the cached one
if (that._config.offline) {
return [canonicalDir, pkgMeta, isTargetable];
}
// Otherwise check for new contents
logger.action(
'validate',
(pkgMeta._release
? pkgMeta._release + ' against '
: '') +
resolver.getSource() +
(resolver.getTarget()
? '#' + resolver.getTarget()
: '')
);
return resolver
.hasNew(pkgMeta)
.then(function(hasNew) {
// If there are no new contents, resolve to
// the cached one
if (!hasNew) {
return [
canonicalDir,
pkgMeta,
isTargetable
];
}
// Otherwise resolve to the newest one
logger.info(
'new',
'version for ' +
resolver.getSource() +
'#' +
resolver.getTarget()
);
logger.action(
'resolve',
resolver.getSource() +
'#' +
resolver.getTarget()
);
return that._resolve(resolver, logger);
});
})
);
})
// If something went wrong, also extend the error
.fail(function(err) {
that._extendLog(err, info);
throw err;
})
);
};
PackageRepository.prototype.versions = function(source) {
// Resolve the source using the factory because the
// source can actually be a registry name
return this._getResolver({ source: source }).then(
function(resolver) {
// If offline, resolve using the cached versions
if (this._config.offline) {
return this._resolveCache.versions(resolver.getSource());
return that._resolve(resolver, logger);
}
// Otherwise, fetch remotely
return resolver.constructor.versions(resolver.getSource());
}.bind(this)
);
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(canonicalDir, 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.eliminate = function(pkgMeta) {
PackageRepository.prototype.versions = function (source) {
// Resolve the source using the factory because the
// source can actually be a registry name
return resolverFactory.getConstructor(source, this._config, this._registryClient)
.spread(function (ConcreteResolver, source) {
// If offline, resolve using the cached versions
if (this._config.offline) {
return this._resolveCache.versions(source);
}
// Otherwise, fetch remotely
return ConcreteResolver.versions(source);
}.bind(this));
};
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();
@@ -228,59 +164,28 @@ PackageRepository.clearRuntimeCache = function() {
// ---------------------
PackageRepository.prototype._getResolver = function(decEndpoint, logger) {
logger = logger || this._logger;
// Get the appropriate resolver
return resolverFactory(
decEndpoint,
{ config: this._config, logger: logger },
this._registryClient
);
};
PackageRepository.prototype._resolve = function(resolver, logger) {
var that = this;
PackageRepository.prototype._resolve = function (resolver, logger) {
// 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) {
return this._resolveCache.store(canonicalDir, resolver.getPkgMeta());
}.bind(this))
// 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()];
}.bind(this));
};
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

@@ -1,15 +1,14 @@
var fs = require('../util/fs');
var fs = require('graceful-fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var mkdirp = require('mkdirp');
var rimraf = require('../util/rimraf');
var rimraf = require('rimraf');
var LRU = require('lru-cache');
var lockFile = require('lockfile');
var md5 = require('md5-hex');
var semver = require('../util/semver');
var readJson = require('../util/readJson');
var copy = require('../util/copy');
var md5 = require('../util/md5');
function ResolveCache(config) {
// TODO: Make some config entries, such as:
@@ -19,9 +18,6 @@ function ResolveCache(config) {
// - etc..
this._config = config;
this._dir = this._config.storage.packages;
this._lockDir = this._config.storage.packages;
mkdirp.sync(this._lockDir);
// Cache is stored/retrieved statically to ensure singularity
// among instances
@@ -29,7 +25,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 +36,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,143 +44,124 @@ 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';
});
}
// Otherwise check if there's an exact match
return mout.array.find(versions, function(version) {
return version === target;
// If target is '*' check if there's a cached '_wildcard'
if (target === '*') {
return mout.array.find(versions, function (version) {
return version === '_wildcard';
});
})
.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);
});
}
);
// Otherwise check if there's an exact match
return mout.array.find(versions, function (version) {
return version === target;
});
})
.then(function (version) {
var canonicalDir;
if (!version) {
return [];
}
// Resolve with canonical dir and package meta
canonicalDir = path.join(dir, 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;
var pkgLock;
var promise;
var that = this;
promise = pkgMeta ? Q.resolve(pkgMeta) : this._readPkgMeta(canonicalDir);
return promise
.then(function(pkgMeta) {
sourceId = md5(pkgMeta._source);
release = that._getPkgRelease(pkgMeta);
dir = path.join(that._dir, sourceId, release);
pkgLock = path.join(
that._lockDir,
sourceId + '-' + release + '.lock'
);
.then(function (pkgMeta) {
sourceId = md5(pkgMeta._source);
release = that._getPkgRelease(pkgMeta);
dir = path.join(that._dir, sourceId, release);
// Check if destination directory exists to prevent issuing lock at all times
return Q.nfcall(fs.stat, dir)
.fail(function(err) {
var lockParams = { wait: 250, retries: 25, stale: 60000 };
return Q.nfcall(lockFile.lock, pkgLock, lockParams)
.then(function() {
// Ensure other process didn't start copying files before lock was created
return Q.nfcall(fs.stat, dir).fail(function(err) {
// If stat fails, it is expected to return ENOENT
if (err.code !== 'ENOENT') {
throw err;
}
// Create missing directory and copy files there
return Q.nfcall(mkdirp, path.dirname(dir)).then(
function() {
return Q.nfcall(
fs.rename,
canonicalDir,
dir
).fail(function(err) {
// If error is EXDEV it means that we are trying to rename
// across different drives, so we copy and remove it instead
if (err.code !== 'EXDEV') {
throw err;
}
return copy.copyDir(
canonicalDir,
dir
);
});
}
);
});
})
.finally(function() {
lockFile.unlockSync(pkgLock);
});
})
.finally(function() {
// Ensure no tmp dir is left on disk.
return Q.nfcall(rimraf, canonicalDir);
});
})
.then(function() {
var versions = that._cache.get(sourceId);
// Add it to the in memory cache
// and sort the versions afterwards
if (versions && versions.indexOf(release) === -1) {
versions.push(release);
that._sortVersions(versions);
// Check if directory exists
return Q.nfcall(fs.stat, dir)
.then(function () {
// If it does exists, remove it
return Q.nfcall(rimraf, dir);
}, function (err) {
// If directory does not exists, ensure its basename
// is created
if (err.code === 'ENOENT') {
return Q.nfcall(mkdirp, path.dirname(dir));
}
// Resolve with the final location
return dir;
throw err;
})
// Move the canonical to sourceId/target
.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)
.then(function () {
return Q.nfcall(rimraf, canonicalDir);
});
});
});
})
.then(function () {
var versions = that._cache.get(sourceId);
// Add it to the in memory cache
// and sort the versions afterwards
if (versions && versions.indexOf(release) === -1) {
versions.push(release);
that._sortVersions(versions);
}
// Resolve with the final location
return dir;
});
};
ResolveCache.prototype.eliminate = function(pkgMeta) {
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);
@@ -195,7 +172,8 @@ 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
@@ -208,144 +186,125 @@ 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);
});
},
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.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);
}
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);
throw err;
});
});
return Q.nfcall(rimraf, dir);
}
);
});
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);
})
// 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.nfcall(rimraf, dir);
});
});
return entries.sort(function(entry1, entry2) {
var pkgMeta1 = entry1.pkgMeta;
var pkgMeta2 = entry2.pkgMeta;
var comp = pkgMeta1.name.localeCompare(pkgMeta2.name);
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;
});
// Sort by name
if (comp) {
return comp;
}
return entries.sort(function (entry1, entry2) {
var pkgMeta1 = entry1.pkgMeta;
var pkgMeta2 = entry2.pkgMeta;
var comp = pkgMeta1.name.localeCompare(pkgMeta2.name);
// 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 name
if (comp) {
return comp;
}
// Sort by target
return pkgMeta1._target.localeCompare(pkgMeta2._target);
});
})
);
// Sort by version
if (pkgMeta1.version && pkgMeta2.version) {
return semver.compare(pkgMeta1.version, pkgMeta2.version);
}
if (pkgMeta1.version) {
return -1;
}
if (pkgMeta2.version) {
return 1;
}
// Sort by target
return pkgMeta1._target.localeCompare(pkgMeta2._target);
});
});
};
// ------------------------
ResolveCache.clearRuntimeCache = function() {
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();
});
@@ -355,10 +314,8 @@ 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);
@@ -366,15 +323,16 @@ 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;
@@ -384,31 +342,28 @@ 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);
return Q.nfcall(fs.readdir, dir)
.then(function (versions) {
// Sort and cache in memory
that._sortVersions(versions);
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];
},
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);
@@ -434,7 +389,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

@@ -1,275 +1,150 @@
var Q = require('q');
var fs = require('../util/fs');
var fs = require('graceful-fs');
var path = require('path');
var mout = require('mout');
var resolvers = require('./resolvers');
var createError = require('../util/createError');
var resolve = require('../util/resolve');
var pluginResolverFactory = require('./resolvers/pluginResolverFactory');
function createInstance(decEndpoint, config, logger, registryClient) {
return getConstructor(decEndpoint.source, config, registryClient)
.spread(function (ConcreteResolver, source, fromRegistry) {
var decEndpointCopy = mout.object.pick(decEndpoint, ['name', 'target']);
function createInstance(decEndpoint, options, registryClient) {
decEndpoint = mout.object.pick(decEndpoint, ['name', 'target', 'source']);
decEndpointCopy.source = source;
options.version = require('../version');
// Signal if it was fetched from the registry
if (fromRegistry) {
decEndpoint.registry = true;
// If no name was specified, assume the name from the registry
if (!decEndpointCopy.name) {
decEndpointCopy.name = decEndpoint.name = decEndpoint.source;
}
}
return getConstructor(decEndpoint, options, registryClient).spread(function(
ConcreteResolver,
decEndpoint
) {
return new ConcreteResolver(
decEndpoint,
options.config,
options.logger
);
return new ConcreteResolver(decEndpointCopy, config, logger);
});
}
function getConstructor(decEndpoint, options, registryClient) {
var source = decEndpoint.source;
var config = options.config;
// Below we try a series of async tests to guess the type of resolver to use
// If a step was unable to guess the resolver, it returns undefined
// If a step can guess the resolver, it returns with constructor of resolver
var promise = Q.resolve();
var addResolver = function(resolverFactory) {
promise = promise.then(function(result) {
if (result === undefined) {
return resolverFactory(decEndpoint, options);
} else {
return result;
}
});
};
// Plugin resolvers.
//
// It requires each resolver defined in config.resolvers and calls
// its "match" to check if given resolves supports given decEndpoint
addResolver(function() {
var selectedResolver;
var resolverNames;
if (Array.isArray(config.resolvers)) {
resolverNames = config.resolvers;
} else if (!!config.resolvers) {
resolverNames = config.resolvers.split(',');
} else {
resolverNames = [];
}
var resolverPromises = resolverNames.map(function(resolverName) {
var resolver = resolvers[resolverName];
if (resolver === undefined) {
var resolverPath = resolve(resolverName, { cwd: config.cwd });
if (resolverPath === undefined) {
throw createError(
'Bower resolver not found: ' + resolverName,
'ENORESOLVER'
);
}
resolver = pluginResolverFactory(
require(resolverPath),
options
);
}
return function() {
if (selectedResolver === undefined) {
var match = resolver.match.bind(resolver);
return Q.fcall(match, source).then(function(result) {
if (result) {
return (selectedResolver = resolver);
}
});
} else {
return selectedResolver;
}
};
});
return resolverPromises
.reduce(Q.when, new Q(undefined))
.then(function(resolver) {
if (resolver) {
return Q.fcall(
resolver.locate.bind(resolver),
decEndpoint.source
).then(function(result) {
if (result && result !== decEndpoint.source) {
decEndpoint.source = result;
decEndpoint.registry = true;
return getConstructor(
decEndpoint,
options,
registryClient
);
} else {
return [resolver, decEndpoint];
}
});
}
});
});
function getConstructor(source, config, registryClient) {
var absolutePath,
promise;
// Git case: git git+ssh, git+http, git+https
// .git at the end (probably ssh shorthand)
// git@ at the start
addResolver(function() {
if (
/^git(\+(ssh|https?))?:\/\//i.test(source) ||
/\.git\/?$/i.test(source) ||
/^git@/i.test(source)
) {
decEndpoint.source = source.replace(/^git\+/, '');
if (/^git(\+(ssh|https?))?:\/\//i.test(source) || /\.git\/?$/i.test(source) || /^git@/i.test(source)) {
source = source.replace(/^git\+/, '');
return Q.fcall(function () {
// If it's a GitHub repository, return the specialized resolver
if (resolvers.GitHub.getOrgRepoPair(source)) {
return [resolvers.GitHub, decEndpoint];
return [resolvers.GitHub, source];
}
return [resolvers.GitRemote, decEndpoint];
}
});
// SVN case: svn, svn+ssh, svn+http, svn+https, svn+file
addResolver(function() {
if (/^svn(\+(ssh|https?|file))?:\/\//i.test(source)) {
return [resolvers.Svn, decEndpoint];
}
});
return [resolvers.GitRemote, source];
});
}
// URL case
addResolver(function() {
if (/^https?:\/\//i.exec(source)) {
return [resolvers.Url, decEndpoint];
}
});
if (/^https?:\/\//i.exec(source)) {
return Q.fcall(function () {
return [resolvers.Url, source];
});
}
// Below we try a series of async tests to guess the type of resolver to use
// If a step was unable to guess the resolver, it throws an error
// If a step was able to guess the resolver, it resolves with a function
// That function returns a promise that will resolve with the concrete type
// If source is ./ or ../ or an absolute path
absolutePath = path.resolve(config.cwd, source);
addResolver(function() {
var absolutePath = path.resolve(config.cwd, source);
if (/^\.\.?[\/\\]/.test(source) || /^~\//.test(source) || path.normalize(source) === absolutePath) {
promise = Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
.then(function (stats) {
if (stats.isDirectory()) {
return function () {
return Q.resolve([resolvers.GitFs, absolutePath]);
};
}
if (
/^\.\.?[\/\\]/.test(source) ||
/^~\//.test(source) ||
path.normalize(source).replace(/[\/\\]+$/, '') === absolutePath
) {
return (
Q.nfcall(fs.stat, path.join(absolutePath, '.git'))
.then(function(stats) {
decEndpoint.source = absolutePath;
if (stats.isDirectory()) {
return Q.resolve([resolvers.GitFs, decEndpoint]);
}
throw new Error('Not a Git repository');
})
// If not, check if source is a valid Subversion repository
.fail(function() {
return Q.nfcall(
fs.stat,
path.join(absolutePath, '.svn')
).then(function(stats) {
decEndpoint.source = absolutePath;
if (stats.isDirectory()) {
return Q.resolve([resolvers.Svn, decEndpoint]);
}
throw new Error('Not a Subversion repository');
});
})
// If not, check if source is a valid file/folder
.fail(function() {
return Q.nfcall(fs.stat, absolutePath).then(function() {
decEndpoint.source = absolutePath;
return Q.resolve([resolvers.Fs, decEndpoint]);
});
})
);
}
});
throw new Error('Not a Git repository');
})
// If not, check if source is a valid file/folder
.fail(function () {
return Q.nfcall(fs.stat, absolutePath)
.then(function () {
return function () {
return Q.resolve([resolvers.Fs, absolutePath]);
};
});
});
} else {
promise = Q.reject(new Error('Not an absolute or relative file'));
}
return promise
// Check if is a shorthand and expand it
addResolver(function() {
// Check if the shorthandResolver is falsy
if (!config.shorthandResolver) {
return;
}
.fail(function (err) {
var parts;
// Skip ssh and/or URL with auth
if (/[:@]/.test(source)) {
return;
throw err;
}
// Ensure exactly only one "/"
var parts = source.split('/');
parts = source.split('/');
if (parts.length === 2) {
decEndpoint.source = mout.string.interpolate(
config.shorthandResolver,
{
shorthand: source,
owner: parts[0],
package: parts[1]
}
);
source = mout.string.interpolate(config.shorthandResolver, {
shorthand: source,
owner: parts[0],
package: parts[1]
});
return getConstructor(decEndpoint, options, registryClient);
return function () {
return getConstructor(source, config, registryClient);
};
}
});
throw err;
})
// As last resort, we try the registry
addResolver(function() {
.fail(function (err) {
if (!registryClient) {
return;
throw err;
}
return Q.nfcall(
registryClient.lookup.bind(registryClient),
source
).then(function(entry) {
if (!entry) {
throw createError(
'Package ' + source + ' not found',
'ENOTFOUND'
);
}
return function () {
return Q.nfcall(registryClient.lookup.bind(registryClient), source)
.then(function (entry) {
if (!entry) {
throw createError('Package ' + source + ' not found', 'ENOTFOUND');
}
decEndpoint.registry = true;
// TODO: Handle entry.type.. for now it's only 'alias'
// When we got published packages, this needs to be adjusted
source = entry.url;
if (!decEndpoint.name) {
decEndpoint.name = decEndpoint.source;
}
decEndpoint.source = entry.url;
return getConstructor(decEndpoint, options);
});
return getConstructor(source, config, registryClient)
.spread(function (ConcreteResolver, source) {
return [ConcreteResolver, source, true];
});
});
};
})
// If we got the function, simply call and return
.then(function (func) {
return func();
// Finally throw a meaningful error
}, 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

@@ -1,5 +1,5 @@
var util = require('util');
var fs = require('../../util/fs');
var fs = require('graceful-fs');
var path = require('path');
var mout = require('mout');
var Q = require('q');
@@ -17,19 +17,7 @@ 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'
);
}
// If the name was guessed
if (this._guessedName) {
// Remove extension
this._name = this._name.substr(
0,
this._name.length - path.extname(this._name).length
);
throw createError('File system sources can\'t resolve targets', 'ENORESTARGET');
}
}
@@ -38,7 +26,7 @@ mout.object.mixIn(FsResolver, Resolver);
// -----------------
FsResolver.isTargetable = function() {
FsResolver.isTargetable = function () {
return false;
};
@@ -48,18 +36,19 @@ 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;
@@ -73,24 +62,22 @@ 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, {
@@ -102,7 +89,7 @@ FsResolver.prototype._copy = function() {
});
};
FsResolver.prototype._extract = function(file) {
FsResolver.prototype._extract = function (file) {
if (!file || !extract.canExtract(file)) {
return Q.resolve();
}
@@ -115,34 +102,30 @@ 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,52 +19,31 @@ 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?
@@ -73,28 +52,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
this._cache.refs.set(source, value);
this._cache.refs.set(source);
return value;
};

View File

@@ -1,7 +1,6 @@
var util = require('util');
var path = require('path');
var mout = require('mout');
var Q = require('q');
var GitRemoteResolver = require('./GitRemoteResolver');
var download = require('../../util/download');
var extract = require('../../util/extract');
@@ -29,13 +28,13 @@ function GitHubResolver(decEndpoint, config, logger) {
this._source += '.git';
}
// Use https:// rather than git:// (PR #2611)
this._source = this._source.replace('git://', 'https://');
// Check if it's public
this._public = mout.string.startsWith(this._source, 'git://');
// Enable shallow clones for GitHub repos
this._shallowClone = function() {
return Q.resolve(true);
};
// Use https:// rather than git:// if on a proxy
if (this._config.proxy || this._config.httpsProxy) {
this._source = this._source.replace('git://', 'https://');
}
}
util.inherits(GitHubResolver, GitRemoteResolver);
@@ -43,21 +42,15 @@ mout.object.mixIn(GitHubResolver, GitRemoteResolver);
// -----------------
GitHubResolver.prototype._checkout = function() {
var msg;
var name =
this._resolution.tag ||
this._resolution.branch ||
this._resolution.commit;
var tarballUrl =
'https://github.com/' +
this._org +
'/' +
this._repo +
'/archive/' +
name +
'.tar.gz';
GitHubResolver.prototype._checkout = function () {
// Only fully works with public repositories and tags
// Could work with https/ssh protocol but not with 100% certainty
if (!this._public || !this._resolution.tag) {
return GitRemoteResolver.prototype._checkout.call(this);
}
var msg;
var tarballUrl = 'https://github.com/' + this._org + '/' + this._repo + '/archive/' + this._resolution.tag + '.tar.gz';
var file = path.join(this._tempDir, 'archive.tar.gz');
var reqHeaders = {};
var that = this;
@@ -73,93 +66,60 @@ GitHubResolver.prototype._checkout = function() {
// Download tarball
return download(tarballUrl, file, {
ca: this._config.ca.default,
proxy: this._config.httpsProxy,
strictSSL: this._config.strictSsl,
timeout: this._config.timeout,
headers: reqHeaders
})
.progress(function(state) {
// Retry?
if (state.retry) {
msg =
'Download of ' +
tarballUrl +
' failed with ' +
state.error.code +
', ';
msg += 'retrying in ' + (state.delay / 1000).toFixed(1) + 's';
that._logger.debug('error', state.error.message, {
error: state.error
});
return that._logger.warn('retry', msg);
}
.progress(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;
@@ -170,12 +130,10 @@ 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

@@ -19,17 +19,12 @@ function GitRemoteResolver(decEndpoint, config, logger) {
this._name = this._name.slice(0, -4);
}
// Get the remote of this source
// Get the host of this source
if (!/:\/\//.test(this._source)) {
this._remote = url.parse('ssh://' + this._source);
this._host = url.parse('ssh://' + this._source).host;
} else {
this._remote = url.parse(this._source);
this._host = url.parse(this._source).host;
}
this._host = this._remote.host;
// Verify whether the server supports shallow cloning
this._shallowClone = this._supportsShallowCloning;
}
util.inherits(GitRemoteResolver, GitResolver);
@@ -37,37 +32,33 @@ 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?
@@ -77,217 +68,99 @@ 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) {
// If the host does not support shallow clones, we don't use --depth=1
if (
shallowCloningSupported &&
!GitRemoteResolver._noShallow.get(that._host)
) {
args.push('--depth', 1);
// If the host does not support shallow clones, we don't use --depth=1
if (!GitRemoteResolver._noShallow.get(this._host)) {
args.push('--depth', 1);
}
return cmd('git', args, { cwd: this._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)/i.test(err.details)
) {
GitRemoteResolver._noShallow.set(that._host, true);
return that._fastClone(resolution);
}
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);
}
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.';
}
};
// Verifies whether the server supports shallow cloning.
// This is done according to the rules found in the following links:
// * https://github.com/dimitri/el-get/pull/1921/files
// * http://stackoverflow.com/questions/9270488/is-it-possible-to-detect-whether-a-http-git-remote-is-smart-or-dumb
//
// Summary of the rules:
// * Protocols like ssh or git always support shallow cloning
// * HTTP-based protocols can be verified by sending a HEAD or GET request to the URI (appended to the URL of the Git repo):
// /info/refs?service=git-upload-pack
// * If the server responds with a 'Content-Type' header of 'application/x-git-upload-pack-advertisement',
// the server supports shallow cloning ("smart server")
// * If the server responds with a different content type, the server does not support shallow cloning ("dumb server")
// * Instead of doing the HEAD or GET request using an HTTP client, we're letting Git and Curl do the heavy lifting.
// Calling Git with the GIT_CURL_VERBOSE=2 env variable will provide the Git and Curl output, which includes
// the content type. This has the advantage that Git will take care of using stored credentials and any additional
// negotiation that needs to take place.
//
// The above should cover most cases, including BitBucket.
GitRemoteResolver.prototype._supportsShallowCloning = function() {
var value = true;
// Verify that the remote could be parsed and that a protocol is set
// This case is unlikely, but let's still cover it.
if (this._remote == null || this._remote.protocol == null) {
return Q.resolve(false);
}
if (
!this._host ||
!this._config.shallowCloneHosts ||
this._config.shallowCloneHosts.indexOf(this._host) === -1
) {
return Q.resolve(false);
}
// Check for protocol - the remote check for hosts supporting shallow cloning is only required for
// 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)
) {
// 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'
});
value = cmd('git', ['ls-remote', '--heads', this._source], {
env: processEnv
}).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'
);
this._logger.debug(
'detect-smart-git',
'Smart Git host detected: ' + isSmartServer
);
if (isSmartServer) {
// Cache this host
GitRemoteResolver._canShallow.set(this._host, true);
}
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
return Q.resolve(true);
}
return value;
};
// ------------------------------
// Grab refs remotely
GitRemoteResolver.refs = function(source) {
GitRemoteResolver.refs = function (source) {
var value;
// TODO: Normalize source because of the various available protocols?
@@ -297,26 +170,24 @@ 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
this._cache.refs.set(source, value);
this._cache.refs.set(source);
return value;
};
@@ -324,7 +195,4 @@ GitRemoteResolver.refs = function(source) {
// Store hosts that do not support shallow clones here
GitRemoteResolver._noShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
// Store hosts that support shallow clones here
GitRemoteResolver._canShallow = new LRU({ max: 50, maxAge: 5 * 60 * 1000 });
module.exports = GitRemoteResolver;

View File

@@ -1,14 +1,16 @@
var util = require('util');
var path = require('path');
var Q = require('q');
var rimraf = require('../../util/rimraf');
var chmodr = require('chmodr');
var rimraf = require('rimraf');
var mkdirp = require('mkdirp');
var which = require('../../util/which');
var which = require('which');
var LRU = require('lru-cache');
var mout = require('mout');
var Resolver = require('./Resolver');
var semver = require('../../util/semver');
var createError = require('../../util/createError');
var defaultConfig = require('../../config');
var hasGit;
@@ -20,25 +22,13 @@ try {
hasGit = false;
}
// Set template dir to the empty directory so that user templates are not run
// This environment variable is not multiple config aware but it's not documented
// anyway
mkdirp.sync(defaultConfig.storage.empty);
process.env.GIT_TEMPLATE_DIR = defaultConfig.storage.empty;
function GitResolver(decEndpoint, config, logger) {
// Set template dir to the empty directory so that user templates are not run
// This environment variable is not multiple config aware but it's not documented
// anyway
mkdirp.sync(config.storage.empty);
process.env.GIT_TEMPLATE_DIR = config.storage.empty;
if (!config.strictSsl) {
process.env.GIT_SSL_NO_VERIFY = 'true';
}
if (!config.interactive) {
process.env.GIT_TERMINAL_PROMPT = '0';
if (!process.env.SSH_ASKPASS) {
process.env.SSH_ASKPASS = 'echo';
}
}
Resolver.call(this, decEndpoint, config, logger);
if (!hasGit) {
@@ -51,20 +41,18 @@ mout.object.mixIn(GitResolver, Resolver);
// -----------------
GitResolver.prototype._hasNew = function(pkgMeta) {
GitResolver.prototype._hasNew = function (canonicalDir, 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;
}
@@ -73,37 +61,35 @@ 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;
@@ -112,15 +98,20 @@ 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;
versionsArr = versions.map(function (obj) { return obj.version; });
// If there are no tags and target is *,
// fallback to the latest commit on master
@@ -128,143 +119,102 @@ 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: ' + versions.map(function (version) { return version.version; }).join(', ')
});
});
});
}
// Otherwise, target is either a tag or a branch
return Q.all([self.branches(that._source), self.tags(that._source)]).spread(
function(branches, tags) {
// Use hasOwn because a branch/tag could have a name like "hasOwnProperty"
if (mout.object.hasOwn(tags, target)) {
return (that._resolution = {
type: 'tag',
tag: target,
commit: tags[target]
});
}
if (mout.object.hasOwn(branches, target)) {
return (that._resolution = {
type: 'branch',
branch: target,
commit: branches[target]
});
}
if (/^[a-f0-9]{4,40}$/.test(target)) {
if (target.length < 12) {
that._logger.warn(
'short-sha',
'Consider using longer commit SHA to avoid conflicts'
);
}
that._resolution = { type: 'commit', commit: target };
return that._resolution;
}
branches = Object.keys(branches);
tags = Object.keys(tags);
err = createError(
'Tag/branch ' + target + ' does not exist',
'ENORESTARGET'
);
err.details = !tags.length
? 'No tags found in ' + that._source
: 'Available tags: ' + tags.join(', ');
err.details += '\n';
err.details += !branches.length
? 'No branches found in ' + that._source
: 'Available branches: ' + branches.join(', ');
throw err;
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('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);
// Remove the .git folder
// Note that on windows, we need to chmod to 0777 before due to a bug in git
// See: https://github.com/isaacs/rimraf/issues/19
if (process.platform === 'win32') {
return Q.nfcall(chmodr, gitFolder, 0777)
.then(function () {
return Q.nfcall(rimraf, gitFolder);
}, function (err) {
// If .git does not exist, chmodr returns ENOENT
// so, we ignore that error code
if (err.code !== 'ENOENT') {
throw err;
}
});
} else {
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.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
@@ -278,10 +228,9 @@ 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;
@@ -291,56 +240,51 @@ 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;
});
}
return versions;
}.bind(this)
);
}
value = this.tags(source).then(
function(tags) {
var tag;
var version;
var versions = [];
// For each tag
for (tag in tags) {
version = semver.clean(tag);
if (version) {
versions.push({
version: version,
tag: tag,
commit: tags[tag]
});
}
// If no extra information was requested,
// resolve simply with the versions
if (!extra) {
versions = versions.map(function (version) {
return version.version;
});
}
// Sort them by DESC order
versions.sort(function(a, b) {
return semver.rcompare(a.version, b.version);
});
return versions;
}.bind(this));
}
this._cache.versions.set(source, 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] });
}
}
// 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));
// 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
@@ -349,31 +293,32 @@ 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+)/);
var tag;
if (match && !mout.string.endsWith(match[2], '^{}')) {
tags[match[2]] = match[1];
}
});
if (match && !mout.string.endsWith(match[2], '^{}')) {
tag = 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
@@ -382,33 +327,32 @@ 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
@@ -417,9 +361,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

@@ -1,13 +1,12 @@
var fs = require('../../util/fs');
var fs = require('graceful-fs');
var path = require('path');
var Q = require('q');
var tmp = require('tmp');
var mkdirp = require('mkdirp');
var rimraf = require('../../util/rimraf');
var rimraf = require('rimraf');
var readJson = require('../../util/readJson');
var createError = require('../../util/createError');
var removeIgnores = require('../../util/removeIgnores');
var md5 = require('md5-hex');
tmp.setGracefulCleanup();
@@ -24,28 +23,29 @@ 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 (canonicalDir, pkgMeta) {
var promise;
var metaFile;
var that = this;
// If already working, error out
@@ -56,14 +56,31 @@ Resolver.prototype.hasNew = function(pkgMeta) {
this._working = true;
// Avoid reading the package meta if already given
promise = this._hasNew(pkgMeta);
if (pkgMeta) {
promise = this._hasNew(canonicalDir, pkgMeta);
// Otherwise call _hasNew with both the package meta and the canonical dir
} else {
metaFile = path.join(canonicalDir, '.bower.json');
return promise.fin(function() {
promise = readJson(metaFile)
.spread(function (pkgMeta) {
return that._hasNew(canonicalDir, pkgMeta);
}, function (err) {
that._logger.debug('read-json', 'Failed to read ' + metaFile, {
filename: metaFile,
error: err
});
return true; // Simply resolve to true if there was an error reading the file
});
}
return promise.fin(function () {
that._working = false;
});
};
Resolver.prototype.resolve = function() {
Resolver.prototype.resolve = function () {
var that = this;
// If already working, error out
@@ -74,104 +91,70 @@ 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;
})
);
};
Resolver.prototype.isCacheable = function() {
// Bypass cache for local dependencies
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 &&
this._pkgMeta._resolution &&
this._pkgMeta._resolution.type === 'branch'
) {
return false;
}
return true;
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;
});
};
// -----------------
// 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 (canonicalDir, 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, this._name + '-' + process.pid + '-XXXXXX'),
mode: 0777 & ~process.umask(),
unsafeCleanup: true
});
}.bind(this))
.then(function (dir) {
this._tempDir = dir;
return dir;
}.bind(this));
};
Resolver.prototype._cleanTempDir = function() {
Resolver.prototype._cleanTempDir = function () {
var tempDir = this._tempDir;
if (!tempDir) {
@@ -180,37 +163,31 @@ 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) {
assume: { name: this._name }
})
.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) {
@@ -224,13 +201,13 @@ Resolver.prototype._applyPkgMeta = function(meta) {
}
// Otherwise remove them from the temp dir
return removeIgnores(this._tempDir, meta).then(function() {
return removeIgnores(this._tempDir, meta.ignore)
.then(function () {
return meta;
});
};
Resolver.prototype._savePkgMeta = function(meta) {
var that = this;
Resolver.prototype._savePkgMeta = function (meta) {
var contents;
// Store original source & target
@@ -240,13 +217,10 @@ 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 this._pkgMeta = meta;
}.bind(this));
};
module.exports = Resolver;

View File

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

View File

@@ -1,6 +1,6 @@
var util = require('util');
var path = require('path');
var fs = require('../../util/fs');
var fs = require('graceful-fs');
var url = require('url');
var request = require('request');
var Q = require('q');
@@ -12,22 +12,21 @@ var extract = require('../../util/extract');
var createError = require('../../util/createError');
function UrlResolver(decEndpoint, config, logger) {
var pos;
Resolver.call(this, 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
// If the name was guessed, remove the ? part
if (this._guessedName) {
// 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
);
pos = this._name.indexOf('?');
if (pos !== -1) {
this._name = path.basename(this._name.substr(0, pos));
}
}
this._remote = url.parse(this._source);
@@ -38,11 +37,11 @@ mout.object.mixIn(UrlResolver, Resolver);
// -----------------
UrlResolver.isTargetable = function() {
UrlResolver.isTargetable = function () {
return false;
};
UrlResolver.prototype._hasNew = function(pkgMeta) {
UrlResolver.prototype._hasNew = function (canonicalDir, pkgMeta) {
var oldCacheHeaders = pkgMeta._cacheHeaders || {};
var reqHeaders = {};
@@ -57,79 +56,58 @@ 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, {
proxy: this._remote.protocol === 'https:' ? this._config.httpsProxy : this._config.proxy,
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) {
return url.parse(path.basename(_url)).pathname;
};
UrlResolver.prototype._download = function() {
var fileName = this._parseSourceURL(this._source);
if (!fileName) {
this._source = this._source.replace(/\/(?=\?|#)/, '');
fileName = this._parseSourceURL(this._source);
}
var file = path.join(this._tempDir, fileName);
UrlResolver.prototype._download = function () {
var file = path.join(this._tempDir, path.basename(this._source));
var reqHeaders = {};
var that = this;
@@ -144,48 +122,37 @@ UrlResolver.prototype._download = function() {
// Download the file
return download(this._source, file, {
ca: this._config.ca.default,
proxy: this._remote.protocol === 'https:' ? this._config.httpsProxy : this._config.proxy,
strictSSL: this._config.strictSsl,
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;
@@ -220,19 +187,18 @@ 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, ['"', "'"]);
}
if (!extract.canExtract(file, mimeType)) {
@@ -249,42 +215,36 @@ 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
@@ -295,11 +255,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

@@ -2,7 +2,6 @@ module.exports = {
GitFs: require('./GitFsResolver'),
GitRemote: require('./GitRemoteResolver'),
GitHub: require('./GitHubResolver'),
Svn: require('./SvnResolver'),
Fs: require('./FsResolver'),
Url: require('./UrlResolver')
};

View File

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

View File

@@ -1,122 +0,0 @@
var mout = require('mout');
var cmd = require('../util/cmd');
var Q = require('q');
var shellquote = require('shell-quote');
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 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)
);
//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++) {
var packageName = desiredOrder[i];
if (depsSatisfied(packageName)) {
ordered.push(packageName);
mout.array.remove(desiredOrder, packageName);
//as soon as we resolve a package start the loop again
resolvedOne = true;
break;
}
}
if (!resolvedOne && desiredOrder.length > 0) {
//if we're here then some package(s) doesn't have all its deps satisified
//so lets just jam those names on the end
ordered = ordered.concat(desiredOrder);
}
}
return ordered;
};
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 args = shellquote.parse(cmdString, env);
var cmdName = args[0];
mout.array.remove(args, cmdName); //no rest() in mout
var options = {
cwd: config.cwd,
env: env
};
var promise = cmd(cmdName, args, options);
promise.progress(function(progress) {
progress.split('\n').forEach(function(line) {
if (line) {
logger.action(action, line);
}
});
});
return promise;
};
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 placeholder = new RegExp('%', 'g');
var cmdString = mout.string.replace(
config.scripts[action],
placeholder,
orderedPackages.join(' ')
);
return run(cmdString, action, logger, config);
};
module.exports = {
preuninstall: mout.function.partial(hook, 'preuninstall', false),
postuninstall: mout.function.partial(hook, 'postuninstall', false),
preinstall: mout.function.partial(hook, 'preinstall', true),
postinstall: mout.function.partial(hook, 'postinstall', true),
//only exposed for test
_orderByDependencies: orderByDependencies
};

View File

@@ -1,19 +1,40 @@
var abbrev = require('abbrev');
var mout = require('mout');
var commands = require('./commands');
var version = require('./version');
var abbreviations = require('./util/abbreviations')(commands);
var PackageRepository = require('./core/PackageRepository');
var abbreviations = abbrev(expandNames(commands));
abbreviations.i = 'install';
abbreviations.rm = 'uninstall';
abbreviations.ls = 'list';
function expandNames(obj, prefix, stack) {
prefix = prefix || '';
stack = stack || [];
mout.object.forOwn(obj, function (value, name) {
name = prefix + name;
stack.push(name);
if (typeof value === 'object' && !value.line) {
expandNames(value, name + ' ', stack);
}
});
return stack;
}
function clearRuntimeCache() {
// Note that in edge cases, some architecture components instance's
// in-memory cache might be skipped.
// If that's a problem, you should create and fresh instances instead.
var PackageRepository = require('./core/PackageRepository');
PackageRepository.clearRuntimeCache();
}
module.exports = {
version: version,
commands: commands,
config: require('./config')(),
config: require('./config'),
abbreviations: abbreviations,
reset: clearRuntimeCache
};

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;
@@ -31,14 +31,16 @@ JsonRenderer.prototype.error = function(err) {
err.message = message;
// Stack
/*jshint camelcase:false*/
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);
/*jshint camelcase:true*/
this.log(err);
this.end();
};
JsonRenderer.prototype.log = function(log) {
JsonRenderer.prototype.log = function (log) {
if (!this._nrLogs) {
process.stderr.write('[');
} else {
@@ -49,12 +51,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,68 +65,58 @@ 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;
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() {
answers[prompt.name] = answers[prompt.name].split(',');
});
}
});
return promise.then(function() {
return promise.then(function () {
return answers;
});
};
JsonRenderer.prototype.updateNotice = function () {};
// -------------------------
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

@@ -1,19 +1,20 @@
var cardinal = require('cardinal');
var chalk = require('chalk');
var path = require('path');
var mout = require('mout');
var archy = require('archy');
var Q = require('q');
var inquirer = require('inquirer');
var stringifyObject = require('stringify-object');
var os = require('os');
var semverUtils = require('semver-utils');
var version = require('../version');
var pkg = require(path.join(__dirname, '../..', 'package.json'));
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,
@@ -24,26 +25,16 @@ function StandardRenderer(command, config) {
};
this._command = command;
this._config = config || {};
this._config = config;
if (this.constructor._wideCommands.indexOf(command) === -1) {
this._compact = true;
} else {
this._compact = process.stdout.columns < 120;
}
var exitOnPipeError = function(err) {
if (err.code === 'EPIPE') {
process.exit(0);
}
};
// It happens when piping command to "head" util
process.stdout.on('error', exitOnPipeError);
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 +42,7 @@ StandardRenderer.prototype.end = function(data) {
}
};
StandardRenderer.prototype.error = function(err) {
StandardRenderer.prototype.error = function (err) {
var str;
var stack;
@@ -60,48 +51,37 @@ 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);
}
// Print trace if verbose, the error has no code
// or if the error is a node error
if (this._config.verbose || !err.code || err.errno) {
/*jshint camelcase:false*/
stack = err.fstream_stack || err.stack || 'N/A';
str = chalk.yellow('\nStack trace:\n');
str += (Array.isArray(stack) ? stack.join('\n') : stack) + '\n';
str += chalk.yellow('\nConsole trace:\n');
/*jshint camelcase:true*/
this._write(process.stderr, str);
this._write(process.stderr, new Error().stack);
console.trace();
// 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, 'Bower version: ' + pkg.version + '\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);
@@ -114,27 +94,31 @@ 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);
});
}
// Prompt
deferred = Q.defer();
var inquirer = require('inquirer');
inquirer.prompt(prompts, deferred.resolve);
return deferred.promise;
};
StandardRenderer.prototype.updateNotice = function (data) {
var str = template.render('std/update-notice.std', data);
this._write(process.stderr, str);
};
// -------------------------
StandardRenderer.prototype._help = function(data) {
StandardRenderer.prototype._help = function (data) {
var str;
var that = this;
var specific;
@@ -149,68 +133,56 @@ 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';
}
tree.root = true;
cliTree = this._tree2archy(tree);
this._write(process.stdout, cliTree);
this._write(process.stdout, archy(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;
@@ -229,32 +201,19 @@ StandardRenderer.prototype._info = function(data) {
// Render the versions at the end
if (includeVersions) {
data.hidePreReleases = false;
data.numPreReleases = 0;
// If output isn't verbose, hide prereleases
if (!this._config.verbose) {
data.versions = mout.array.filter(data.versions, function(version) {
version = semverUtils.parse(version);
if (!version.release && !version.build) {
return true;
}
data.numPreReleases++;
});
data.hidePreReleases = !!data.numPreReleases;
}
str += '\n' + template.render('std/info.std', 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({
@@ -262,14 +221,9 @@ StandardRenderer.prototype._link = function(data) {
level: 'info',
message: data.dst + ' > ' + data.src
});
// Print also a tree of the installed packages
if (data.installed) {
this._install(data.installed);
}
};
StandardRenderer.prototype._register = function(data) {
StandardRenderer.prototype._register = function (data) {
var str;
// If no data passed, it means the user aborted
@@ -281,20 +235,17 @@ 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;
@@ -308,7 +259,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;
}
@@ -316,7 +267,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;
}
@@ -324,7 +275,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;
}
@@ -332,23 +283,19 @@ 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');
@@ -356,18 +303,15 @@ 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;
}
@@ -377,7 +321,7 @@ StandardRenderer.prototype._cachedEntryLog = function(log) {
// -------------------------
StandardRenderer.prototype._guessOrigin = function(log) {
StandardRenderer.prototype._guessOrigin = function (log) {
var data = log.data;
if (!data) {
@@ -385,8 +329,7 @@ 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) {
@@ -405,7 +348,7 @@ StandardRenderer.prototype._guessOrigin = function(log) {
}
};
StandardRenderer.prototype._prefix = function(log) {
StandardRenderer.prototype._prefix = function (log) {
var label;
var length;
var nrSpaces;
@@ -437,7 +380,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);
}
@@ -445,31 +388,23 @@ StandardRenderer.prototype._write = function(stream, str) {
stream.write(str);
};
StandardRenderer.prototype._highlightJson = function(json) {
var cardinal = require('cardinal');
StandardRenderer.prototype._highlightJson = function (json) {
return cardinal.highlight(stringifyObject(json, { indent: ' ' }), {
theme: {
String: {
_default: function(str) {
return chalk.cyan(str);
}
_default: chalk.cyan
},
Identifier: {
_default: function(str) {
return chalk.green(str);
}
_default: chalk.green
}
},
json: true
});
};
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;
@@ -479,7 +414,7 @@ StandardRenderer.prototype._tree2archy = function(node) {
// State labels
if (node.missing) {
label += chalk.red(' not installed');
label += chalk.red(' missing');
return label;
}
@@ -492,8 +427,7 @@ 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');
}
@@ -507,7 +441,7 @@ StandardRenderer.prototype._tree2archy = function(node) {
}
if (node.update.latest !== node.update.target) {
update += update ? ', ' : '';
update += (update ? ', ' : '');
update += 'latest is ' + node.update.latest;
}
@@ -529,13 +463,12 @@ StandardRenderer.prototype._tree2archy = function(node) {
StandardRenderer._wideCommands = [
'install',
'update',
'link',
'info',
'home',
'register'
];
StandardRenderer._idMappings = {
mutual: 'conflict',
'mutual': 'conflict',
'cached-entry': 'cached'
};

View File

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

View File

@@ -1,12 +0,0 @@
var mout = require('mout');
function rpad(Handlebars) {
Handlebars.registerHelper('rpad', function(context) {
var hash = context.hash;
var minLength = parseInt(hash.minLength, 10);
var chr = hash.char;
return mout.string.rpad(context.fn(this), minLength, chr);
});
}
module.exports = rpad;

View File

@@ -1,19 +0,0 @@
{
"command": "login",
"description": "Authenticate with GitHub and store credentials to be used later.",
"usage": [
"login [<options>]"
],
"options": [
{
"shorthand": "-h",
"flag": "--help",
"description": "Show this help message"
},
{
"shorthand": "-t",
"flag": "--token",
"description": "Pass an existing GitHub auth token rather than prompting for username and password."
}
]
}

View File

@@ -1,19 +0,0 @@
{
"command": "unregister",
"description": "Unregisters a package.",
"usage": [
"unregister <name> [<options>]"
],
"options": [
{
"shorthand": "-f",
"flag": "--force",
"description": "Bypasses confirmation. Bower login is still needed."
},
{
"shorthand": "-h",
"flag": "--help",
"description": "Show this help message"
}
]
}

View File

@@ -1,14 +0,0 @@
{
"command": "version",
"description": "Creates an empty version commit and tag, and fail if the repo is not clean.\n\nThe <version> argument should be a valid semver string, or one of following:\nbuild, patch, minor, major.\n\nIf supplied with --message (shorthand: -m) config option, bower will use it\nas a commit message when creating a version commit. If the message config\ncontains %s then that will be replaced with the resulting version number.\n\nFor example:\n\n bower version patch -m \"Upgrade to %s for reasons\"",
"usage": [
"version [<version> | major | minor | patch]"
],
"options": [
{
"shorthand": "-m",
"flag": "--message",
"description": "Custom git commit and tag message"
}
]
}

View File

@@ -1,9 +0,0 @@
{{#yellow}}Please note that,{{/yellow}}
{{#condense}}
{{#each picks}}
{{#if dependants}}{{#green}}{{dependants}}{{/green}}{{else}}none{{/if}} depends on {{#cyan}}{{endpoint.name}}#{{endpoint.target}}{{/cyan}}{{#if pkgMeta._release}} which resolved to {{#green}}{{endpoint.name}}#{{pkgMeta._release}}{{/green}}{{/if}}
{{/each}}
{{/condense}}
Resort to using {{#cyan}}{{suitable.endpoint.name}}#{{resolution}}{{/cyan}} which resolved to {{#green}}{{suitable.endpoint.name}}#{{suitable.pkgMeta._release}}{{/green}}
Code incompatibilities may occur.

View File

@@ -1,30 +0,0 @@
var abbrev = require('abbrev');
var mout = require('mout');
function expandNames(obj, prefix, stack) {
prefix = prefix || '';
stack = stack || [];
mout.object.forOwn(obj, function(value, name) {
name = prefix + name;
stack.push(name);
if (typeof value === 'object' && !value.line) {
expandNames(value, name + ' ', stack);
}
});
return stack;
}
module.exports = function(commands) {
var abbreviations = abbrev(expandNames(commands));
abbreviations.i = 'install';
abbreviations.rm = 'uninstall';
abbreviations.unlink = 'uninstall';
abbreviations.ls = 'list';
return abbreviations;
};

61
lib/util/analytics.js Normal file
View File

@@ -0,0 +1,61 @@
var Q = require('q');
var Insight = require('insight');
var mout = require('mout');
var config = require('../config');
var pkg = require('../../package.json');
var analytics = module.exports;
var insight;
// Initializes the application-wide insight singleton and asks for the
// permission on the CLI during the first run.
analytics.setup = function setup() {
var deferred = Q.defer();
insight = new Insight({
trackingCode: 'UA-43531210-1',
packageName: pkg.name,
packageVersion: pkg.version
});
// Display the ask prompt only if it hasn't been answered before
// and the current session is interactive.
if (insight.optOut === undefined && config.interactive) {
insight.askPermission(null, deferred.resolve);
} else {
deferred.resolve();
}
return deferred.promise;
};
var Tracker = analytics.Tracker = function Tracker(config) {
if (!config.analytics) {
this.track = function noop() {};
}
};
Tracker.prototype.track = function track() {
if (!insight) {
throw new Error('You must call analytics.setup() prior to tracking.');
}
insight.track.apply(insight, arguments);
};
Tracker.prototype.trackDecomposedEndpoints = function trackDecomposedEndpoints(command, endpoints) {
endpoints.forEach(function (endpoint) {
this.track(command, endpoint.source, endpoint.target);
}.bind(this));
};
Tracker.prototype.trackPackages = function trackPackages(command, packages) {
mout.object.forOwn(packages, function (package) {
var meta = package.pkgMeta;
this.track(command, meta.name, meta.version);
}.bind(this));
};
Tracker.prototype.trackNames = function trackNames(command, names) {
names.forEach(function (name) {
this.track(command, name);
}.bind(this));
};

View File

@@ -1,21 +0,0 @@
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,20 +1,10 @@
var cp = require('./childProcess');
var cp = require('child_process');
var path = require('path');
var Q = require('q');
var mout = require('mout');
var which = require('./which');
var PThrottler = require('p-throttler');
var which = require('which');
var createError = require('./createError');
// The concurrency limit here is kind of magic. You don't really gain a lot from
// having a large number of commands spawned at once, so it isn't super
// important for this number to be large. Reports have shown that much more than 5
// or 10 cause issues for corporate networks, private repos or situations where
// internet bandwidth is limited. We're running with a concurrency of 5 until
// 1.4.X is released, at which time we'll move to what was discussed in #1262
// https://github.com/bower/bower/pull/1262
var throttler = new PThrottler(5);
var winBatchExtensions;
var winWhichCache;
var isWin = process.platform === 'win32';
@@ -37,24 +27,24 @@ 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
// If an error occurs, a meaningful error is generated
// Returns a promise that gets fulfilled if the command succeeds
// or rejected if it fails
function executeCmd(command, args, options) {
function cmd(command, args, options) {
var process;
var stderr = '';
var stdout = '';
@@ -67,25 +57,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,19 +89,10 @@ 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, 'ECMDERR', {
details: stderr,
exitCode: code
});
return deferred.reject(error);
}
@@ -122,8 +103,4 @@ function executeCmd(command, args, options) {
return deferred.promise;
}
function cmd(command, args, options) {
return throttler.enqueue(executeCmd.bind(null, command, args, options));
}
module.exports = cmd;

View File

@@ -1,6 +1,6 @@
var fstream = require('fstream');
var fstreamIgnore = require('fstream-ignore');
var fs = require('./fs');
var fs = require('graceful-fs');
var Q = require('q');
function copy(reader, writer) {
@@ -26,17 +26,18 @@ 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);
});
}
@@ -67,17 +68,14 @@ 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));
@@ -95,18 +93,15 @@ 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

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

View File

@@ -3,150 +3,106 @@ var request = require('request');
var Q = require('q');
var mout = require('mout');
var retry = require('retry');
var fs = require('graceful-fs');
var createError = require('./createError');
var createWriteStream = require('fs-write-stream-atomic');
var destroy = require('destroy');
var errorCodes = [
'EADDRINFO',
'ETIMEDOUT',
'ECONNRESET',
'ESOCKETTIMEDOUT',
'ENOTFOUND'
'ESOCKETTIMEDOUT'
];
function download(url, file, options) {
var operation;
var response;
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
}, options || {});
// Retry on network errors
operation = retry.operation(options);
operation.attempt(function () {
var req;
var writeStream;
var contentLength;
var bytesDownloaded = 0;
operation.attempt(function() {
Q.fcall(fetch, url, file, options)
.then(function(response) {
deferred.resolve(response);
})
.progress(function(status) {
deferred.notify(status);
})
.fail(function(error) {
// Save timeout before retrying to report
var timeout = operation._timeouts[0];
// Reject if error is not a network error
if (errorCodes.indexOf(error.code) === -1) {
return deferred.reject(error);
}
// Next attempt will start reporting download progress immediately
progressDelay = 0;
// This will schedule next retry or return false
if (operation.retry(error)) {
deferred.notify({
retry: true,
delay: timeout,
error: error
});
} else {
deferred.reject(error);
}
});
});
return deferred.promise;
}
function fetch(url, file, options) {
var deferred = Q.defer();
var contentLength;
var bytesDownloaded = 0;
var reject = function(error) {
deferred.reject(error);
};
var req = progress(request(url, options), {
delay: options.progressDelay
})
.on('response', function(response) {
contentLength = Number(response.headers['content-length']);
var status = response.statusCode;
req = progress(request(url, options), {
delay: progressDelay
})
.on('response', function (res) {
var status = res.statusCode;
if (status < 200 || status >= 300) {
return deferred.reject(
createError('Status code of ' + status, 'EHTTP')
);
return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
}
var writeStream = createWriteStream(file);
var errored = false;
// Change error listener so it cleans up writeStream before exiting
req.removeListener('error', reject);
req.on('error', function(error) {
errored = true;
destroy(req);
destroy(writeStream);
// Wait for writeStream to cleanup after itself...
// TODO: Maybe there's a better way?
setTimeout(function() {
deferred.reject(error);
}, 50);
});
writeStream.on('finish', function() {
if (!errored) {
destroy(req);
deferred.resolve(response);
}
});
req.pipe(writeStream);
response = res;
contentLength = Number(res.headers['content-length']);
})
.on('data', function(data) {
.on('data', function (data) {
bytesDownloaded += data.length;
})
.on('progress', function(state) {
.on('progress', function (state) {
deferred.notify(state);
})
.on('error', reject)
.on('end', function() {
.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.emit('error', createError('Transfer closed with ' + (contentLength - bytesDownloaded) + ' bytes remaining to read', 'EINCOMPLETE'));
}
})
.on('error', function (error) {
var timeout = operation._timeouts[0];
// Reject if error is not a network error
if (errorCodes.indexOf(error.code) === -1) {
return deferred.reject(error);
}
// Next attempt will start reporting download progress immediately
progressDelay = 0;
// Check if there are more retries
if (operation.retry(error)) {
// Ensure that there are no more events from this request
req.removeAllListeners();
req.on('error', function () {});
// Ensure that there are no more events from the write stream
writeStream.removeAllListeners();
writeStream.on('error', function () {});
return deferred.notify({
retry: true,
delay: timeout,
error: error
});
}
// No more retries, reject!
deferred.reject(error);
});
// Pipe read stream to write stream
writeStream = req
.pipe(fs.createWriteStream(file))
.on('error', deferred.reject)
.on('close', function () {
deferred.resolve(response);
});
});
return deferred.promise;
}

View File

@@ -1,15 +1,12 @@
var path = require('path');
var fs = require('./fs');
var fs = require('graceful-fs');
var zlib = require('zlib');
var DecompressZip = require('decompress-zip');
var tar = require('tar-fs');
var unzip = require('unzip');
var tar = require('tar');
var Q = require('q');
var mout = require('mout');
var junk = require('junk');
var createError = require('./createError');
var createWriteStream = require('fs-write-stream-atomic');
var destroy = require('destroy');
var tmp = require('tmp');
// This forces the default chunk size to something small in an attempt
// to avoid issue #314
@@ -25,8 +22,6 @@ extractors = {
'.tgz': extractTarGz,
'.gz': extractGz,
'application/zip': extractZip,
'application/x-zip': extractZip,
'application/x-zip-compressed': extractZip,
'application/x-tar': extractTar,
'application/x-tgz': extractTarGz,
'application/x-gzip': extractGz
@@ -37,14 +32,15 @@ extractorTypes = Object.keys(extractors);
function extractZip(archive, dst) {
var deferred = Q.defer();
new DecompressZip(archive)
.on('error', deferred.reject)
.on('extract', deferred.resolve.bind(deferred, dst))
.extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
});
fs.createReadStream(archive)
.on('error', deferred.reject)
.pipe(unzip.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
@@ -52,27 +48,15 @@ function extractZip(archive, dst) {
function extractTar(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(
tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
})
)
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
fs.createReadStream(archive)
.on('error', deferred.reject)
.pipe(tar.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
@@ -80,29 +64,17 @@ function extractTar(archive, dst) {
function extractTarGz(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', reject)
.pipe(
tar.extract(dst, {
ignore: isSymlink, // Filter symlink files
dmode: 0555, // Ensure dirs are readable
fmode: 0444 // Ensure files are readable
})
)
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
fs.createReadStream(archive)
.on('error', deferred.reject)
.pipe(zlib.createGunzip())
.on('error', deferred.reject)
.pipe(tar.Extract({
path: dst,
follow: false, // Do not follow symlinks (#699)
filter: filterSymlinks // Filter symlink files
}))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
@@ -110,30 +82,17 @@ function extractTarGz(archive, dst) {
function extractGz(archive, dst) {
var deferred = Q.defer();
var stream = fs.createReadStream(archive);
var reject = function(error) {
destroy(stream);
deferred.reject(error);
};
stream
.on('error', reject)
.pipe(zlib.createGunzip())
.on('error', reject)
.pipe(createWriteStream(dst))
.on('error', reject)
.on('finish', function(result) {
destroy(stream);
deferred.resolve(dst);
});
fs.createReadStream(archive)
.on('error', deferred.reject)
.pipe(zlib.createGunzip())
.on('error', deferred.reject)
.pipe(fs.createWriteStream(dst))
.on('error', deferred.reject)
.on('close', deferred.resolve.bind(deferred, dst));
return deferred.promise;
}
function isSymlink(_, entry) {
return entry.type === 'symlink';
}
function filterSymlinks(entry) {
return entry.type !== 'SymbolicLink';
}
@@ -143,7 +102,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);
});
@@ -151,7 +110,8 @@ 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
@@ -164,27 +124,32 @@ 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;
});
});
}
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);
function moveSingleDirContents(dir) {
var destDir = path.dirname(dir);
return Q.nfcall(fs.rename, src, dst);
});
return Q.nfcall(fs.readdir, dir)
.then(function (files) {
var promises;
return Q.all(promises);
})
.then(function() {
return Q.nfcall(fs.rmdir, srcDir);
promises = files.map(function (file) {
var src = path.join(dir, file);
var dst = path.join(destDir, file);
return Q.nfcall(fs.rename, src, dst);
});
return Q.all(promises);
})
.then(function () {
return Q.nfcall(fs.rmdir, dir);
});
}
// -----------------------------
@@ -214,64 +179,48 @@ 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'
);
}
// 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);
});
// Extract archive
return extractor(src, dst);
});
// Remove archive
if (!opts.keepArchive) {
promise = promise.then(function() {
return Q.nfcall(fs.unlink, src);
});
}
// TODO: There's an issue here if the src and dst are the same and
// The zip name is the same as some of the zip file contents
// Maybe create a temp directory inside dst, unzip it there,
// unlink zip and then move contents
// 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;
});
// Remove archive
if (!opts.keepArchive) {
promise = promise
.then(function () {
return Q.nfcall(fs.unlink, src);
});
}
// Move contents if a single directory was extracted
if (!opts.keepStructure) {
promise = promise
.then(function () {
return isSingleDir(dst);
})
.then(function (singleDir) {
return singleDir ? moveSingleDirContents(singleDir) : null;
});
}
// Resolve promise to the dst dir
return promise.then(function () {
return dst;
});
}
module.exports = extract;

View File

@@ -1,34 +0,0 @@
var fs = require('graceful-fs');
var readdir = fs.readdir.bind(fs);
var readdirSync = fs.readdirSync.bind(fs);
module.exports = fs;
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 + "'");
error.code = 'ENOTDIR';
error.path = dir;
error.errono = -20;
return callback(error);
}
});
};
module.exports.readdirSync = function(dir) {
var stats = fs.statSync(dir);
if (stats.isDirectory()) {
return readdirSync(dir);
} else {
var error = new Error();
error.code = 'ENOTDIR';
throw error;
}
};

View File

@@ -1,5 +0,0 @@
function isPathAbsolute(filePath) {
return filePath.charAt(0) === '/';
}
module.exports = isPathAbsolute;

7
lib/util/md5.js Normal file
View File

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

View File

@@ -13,43 +13,31 @@ 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);
}
issues.warnings.forEach(function(warning) {
options.logger.warn('invalid-meta', warning);
});
}
jsonFile = path.basename(jsonFile);
deprecated = jsonFile === 'component.json' ? jsonFile : false;
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;
return [json, deprecated, false];
}, function (err) {
// No json file was found, assume one
if (err.code === 'ENOENT' && options.assume) {
return [bowerJson.parse(options.assume, options), false, true];
}
);
err.details = err.message;
if (err.file) {
err.message = 'Failed to read ' + err.file;
err.data = { filename: err.file };
} else {
err.message = 'Failed to read json from ' + file;
}
throw err;
});
}
module.exports = readJson;

View File

@@ -1,14 +0,0 @@
var path = require('path');
var isPathAbsolute = require('./isPathAbsolute');
function relativeToBaseDir(baseDir) {
return function(filePath) {
if (isPathAbsolute(filePath)) {
return path.resolve(filePath);
} else {
return path.resolve(baseDir, filePath);
}
};
}
module.exports = relativeToBaseDir;

View File

@@ -1,33 +1,26 @@
var path = require('path');
var rimraf = require('../util/rimraf');
var rimraf = require('rimraf');
var fstreamIgnore = require('fstream-ignore');
var mout = require('mout');
var Q = require('q');
function removeIgnores(dir, meta) {
function removeIgnores(dir, ignore) {
var reader;
var applyIgnores;
var deferred = Q.defer();
var ignored = [];
var nonIgnored = ['bower.json'];
// Don't ignore main files
nonIgnored = nonIgnored.concat(meta.main || []);
nonIgnored = nonIgnored.map(function(file) {
return path.join(dir, file);
});
var nonIgnored = [];
reader = fstreamIgnore({
path: dir,
type: 'Directory'
});
reader.addIgnoreRules(meta.ignore || []);
reader.addIgnoreRules(ignore);
// 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,27 +31,28 @@ 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;
});
// Delete all the ignored files
promises = ignored.map(function(file) {
return Q.nfcall(rimraf, file);
});
return Q.all(promises).then(deferred.resolve, deferred.reject);
// 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);
});
return deferred.promise;
}

View File

@@ -1,22 +0,0 @@
var requireg = require('requireg');
var resolve = require('resolve');
function startsWith(string, searchString, position) {
position = position || 0;
return string.substr(position, searchString.length) === searchString;
}
module.exports = function(id, options) {
var resolvedPath;
var cwd = (options || {}).cwd || process.cwd();
try {
resolvedPath = resolve.sync(id, { basedir: cwd });
} catch (e) {
// Fallback to global require
resolvedPath = requireg.resolve(id);
}
return resolvedPath;
};

View File

@@ -1,46 +0,0 @@
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) {
if (err) {
if (err.code === 'ENOENT') return callback();
return callback(e);
}
chmodr(dir, 0777, function(err) {
if (err) return callback(e);
rimraf(dir, callback);
});
});
};
if (process.platform === 'win32') {
checkAndRetry();
} else {
rimraf(dir, checkAndRetry);
}
};
module.exports.sync = function(dir) {
var checkAndRetry = function() {
try {
fs.lstatSync(dir);
chmodr.sync(dir, 0777);
return rimraf.sync(dir);
} catch (e) {
if (e.code === 'ENOENT') return;
throw e;
}
};
try {
return rimraf.sync(dir);
} catch (e) {
return checkAndRetry();
} finally {
return checkAndRetry();
}
};

View File

@@ -1,5 +1,8 @@
var isRoot = require('is-root');
/*jshint multistr:true*/
var sudoBlock = require('sudo-block');
var createError = require('./createError');
var cli = require('./cli');
var renderer;
@@ -7,27 +10,22 @@ function rootCheck(options, config) {
var errorMsg;
// Allow running the command as root
if (options.allowRoot || config.allowRoot) {
if (options.allowRoot) {
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\
http://www.joyent.com/blog/installing-node-and-npm\n\
https://gist.github.com/isaacs/579814\n\n\
You can however run a command with sudo using "--allow-root" option';
You can however run a command with sudo using --allow-root option';
if (isRoot()) {
var cli = require('./cli');
if (sudoBlock.isRoot) {
renderer = cli.getRenderer('', false, config);
renderer.error(
createError('Please do not run with sudo', 'ESUDO', {
details: errorMsg
})
);
renderer.error(createError('Cannot be run with sudo', 'ESUDO', { details : errorMsg }));
process.exit(1);
}
}

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) {
@@ -20,12 +20,12 @@ function maxSatisfying(versions, range, strictMatch) {
}
}
range = typeof range === 'string' ? range.trim() : range;
// When strict match is enabled give priority to non-pre-releases
// When strict match is enabled and range is *,
// give priority to non-pre-releases
// We do this by filtering every pre-release version
if (strictMatch) {
filteredVersions = versions.map(function(version) {
range = typeof range === 'string' ? range.trim() : range;
if (strictMatch && (!range || range === '*')) {
filteredVersions = versions.map(function (version) {
return !isPreRelease(version) ? version : null;
});
@@ -57,10 +57,7 @@ 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

@@ -1,14 +1,14 @@
var path = require('path');
var fs = require('./fs');
var fs = require('graceful-fs');
var Handlebars = require('handlebars');
var mout = require('mout');
var helpers = require('../templates/helpers');
var helpers = require('../../templates/helpers');
var templatesDir = path.resolve(__dirname, '../templates');
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,12 +0,0 @@
var version = require('../version');
module.exports =
'node/' +
process.version +
' ' +
process.platform +
' ' +
process.arch +
' ' +
';Bower ' +
version;

View File

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

View File

@@ -1,51 +0,0 @@
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,3 +0,0 @@
var findup = require('findup-sync');
module.exports = require(findup('package.json', { cwd: __dirname })).version;

View File

@@ -1,109 +1,80 @@
{
"private": true,
"name": "bower",
"version": "1.8.14",
"description": "The browser package manager",
"version": "1.2.7",
"description": "The browser package manager.",
"author": "Twitter",
"license": "MIT",
"repository": "bower/bower",
"licenses": [
{
"type": "MIT",
"url": "https://github.com/bower/bower/blob/master/LICENSE"
}
],
"repository": {
"type": "git",
"url": "git://github.com/bower/bower.git"
},
"main": "lib",
"bin": "bin/bower",
"homepage": "http://bower.io",
"engines": {
"node": ">=0.10.0"
"node": ">=0.8.0"
},
"keywords": [
"bower"
],
"dependencies": {
"abbrev": "^1.0.5",
"archy": "1.0.0",
"cardinal": "0.4.4",
"chalk": "^1.0.0",
"chmodr": "1.0.2",
"configstore": "^2.0.0",
"decompress-zip": "^0.2.2",
"destroy": "^1.0.3",
"findup-sync": "^0.3.0",
"fs-write-stream-atomic": "1.0.8",
"fstream": "^1.0.3",
"fstream-ignore": "^1.0.2",
"github": "^0.2.3",
"glob": "^4.3.2",
"graceful-fs": "^4.1.3",
"handlebars": "^4.5.3",
"inquirer": "0.10.0",
"is-root": "^1.0.0",
"junk": "^1.0.0",
"lockfile": "^1.0.0",
"lru-cache": "^2.5.0",
"md5-hex": "^1.0.2",
"mkdirp": "0.5.0",
"mout": "^0.11.0",
"nopt": "^3.0.1",
"opn": "^4.0.0",
"p-throttler": "0.1.1",
"promptly": "0.2.0",
"q": "^1.1.2",
"request": "2.67.0",
"request-progress": "0.3.1",
"requireg": "^0.1.5",
"resolve": "^1.1.7",
"retry": "0.6.1",
"rimraf": "^2.2.8",
"semver": "^2.3.0",
"semver-utils": "^1.1.1",
"shell-quote": "^1.4.2",
"stringify-object": "^1.0.0",
"tar-fs": "^1.4.1",
"tmp": "0.0.28",
"update-notifier": "^0.6.0",
"user-home": "^1.1.0",
"which": "^1.0.8"
"abbrev": "~1.0.4",
"archy": "0.0.2",
"bower-config": "~0.5.0",
"bower-endpoint-parser": "~0.2.0",
"bower-json": "~0.4.0",
"bower-logger": "~0.2.1",
"bower-registry-client": "~0.1.4",
"cardinal": "~0.4.0",
"chalk": "~0.2.0",
"chmodr": "~0.1.0",
"fstream": "~0.1.22",
"fstream-ignore": "~0.0.6",
"glob": "~3.2.1",
"graceful-fs": "~2.0.0",
"handlebars": "~1.0.11",
"inquirer": "~0.3.0",
"junk": "~0.2.0",
"mkdirp": "~0.3.5",
"mout": "~0.7.0",
"nopt": "~2.1.1",
"lru-cache": "~2.3.0",
"open": "~0.0.3",
"osenv": "0.0.3",
"promptly": "~0.2.0",
"q": "~0.9.2",
"request": "~2.27.0",
"request-progress": "~0.3.0",
"retry": "~0.6.0",
"rimraf": "~2.2.0",
"semver": "~2.1.0",
"stringify-object": "~0.1.4",
"sudo-block": "~0.2.0",
"tar": "~0.1.17",
"tmp": "~0.0.20",
"unzip": "~0.1.7",
"update-notifier": "~0.1.3",
"which": "~1.0.5",
"insight": "~0.2.0"
},
"devDependencies": {
"arr-diff": "^2.0.0",
"chai": "^3.5.0",
"eslint": "^4.18.2",
"expect.js": "^0.3.1",
"husky": "^0.14.3",
"in-publish": "^2.0.0",
"lint-staged": "^9.5.0",
"mocha": "^3.5.3",
"multiline": "^1.0.2",
"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"
"expect.js": "~0.2.0",
"grunt": "~0.4.1",
"grunt-simple-mocha": "~0.4.0",
"grunt-contrib-watch": "~0.5.0",
"grunt-contrib-jshint": "~0.6.0",
"grunt-exec": "~0.4.2",
"mocha": "~1.12.0",
"nock": "~0.22.0",
"istanbul": "~0.1.42",
"proxyquire": "~0.5.0"
},
"scripts": {
"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"
"test": "grunt test"
},
"lint-staged": {
"*.js": [
"prettier --single-quote --tab-width 4"
]
"bin": {
"bower": "bin/bower"
},
"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"
]
"preferGlobal": true
}

View File

@@ -1,12 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@@ -1,7 +0,0 @@
node_modules
npm-debug.log
test/assets/github-test-package
test/assets/github-test-package-copy
test/assets/temp
test/reports

View File

@@ -1,111 +0,0 @@
# 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://
## 1.3.1
- Ignore hook scripts for environment variable expansion
## 1.3.0 - 2015-12-07
- Allow the use of environment variables in .bowerrc. Fixes [#41](https://github.com/bower/config/issues/41)
- Loads the .bowerrc file from the cwd specified on the command line. Fixes [bower/bower#1993](https://github.com/bower/bower/issues/1993)
- Allwow for array notation in ENV variables [#44](https://github.com/bower/config/issues/44)
## 1.2.3 - 2015-11-27
- Restores env variables if they are undefined at the beginning
- Handles default setting for config.ca. Together with [bower/bower PR #1972](https://github.com/bower/bower/pull/1972), fixes downloading with `strict-ssl` using custom CA
- Displays an error message if .bowerrc is a directory instead of file. Fixes [bower/bower#2022](https://github.com/bower/bower/issues/2022)
## 1.2.2 - 2015-10-16
- Fixes registry configurartion expanding [bower/bower#1950](https://github.com/bower/bower/issues/1950)
## 1.2.1 - 2015-10-15
- Fixes case insenstivity HTTP_PROXY setting issue on Windows
## 1.2.0 - 2015-09-28
- Prevent defaulting cwd to process.cwd()
## 1.1.2 - 2015-09-27
- Performs only camel case normalisation before merging
## 1.1.1 - 2015-09-27
- Fix: Merge extra options after camel-case normalisation, instead of before it
## 1.1.0 - 2015-09-27
- Allow for overwriting options with .load(overwrites) / .read(cwd, overwrites)
## 1.0.1 - 2015-09-27
- Update dependencies and relax "mout" version range
- Most significant changes:
- graceful-fs updated from 2.x version to 4.x
- osenv updated to from 0.0.x to 0.1.x, [tmp location changed](https://github.com/npm/osenv/commit/d6eddbc026538b09026b1dbd60fbc081a8c67e03)
## 1.0.0 - 2015-09-27
- Support for no-proxy configuration variable
- Overwrite HTTP_PROXY, HTTPS_PROXY, and NO_PROXY env variables in load method
- Normalise paths to certificates with contents of them, [#28](https://github.com/bower/config/pull/28)
## 0.6.1 - 2015-04-1
- Fixes merging .bowerrc files upward directory tree. [#25](https://github.com/bower/config/issues/25)
## 0.6.0 - 2015-03-30
- Merge .bowerrc files upward directory tree (fixes [bower/bower#1689](https://github.com/bower/bower/issues/1689)) [#24](https://github.com/bower/config/pull/24)
- Allow NPM config variables (resolves [bower/bower#1711](https://github.com/bower/bower/issues/1711)) [#23](https://github.com/bower/config/pull/23)
## 0.5.2 - 2014-06-09
- Fixes downloading of bower modules with ignores when .bowerrc is overridden with a relative tmp path. [#17](https://github.com/bower/config/issues/17) [bower/bower#1299](https://github.com/bower/bower/issues/1299)
## 0.5.1 - 2014-05-21
- [perf] Uses the same mout version as bower
- [perf] Uses only relevant parts of mout. Related [bower/bower#1134](https://github.com/bower/bower/pull/1134)
## 0.5.0 - 2013-08-30
- Adds a DEFAULT_REGISTRY key to the Config class that exposes the bower registry UR. [#6](https://github.com/bower/config/issues/6)
## 0.4.5 - 2013-08-28
- Fixes crashing when home is not set
## 0.4.4 - 2013-08-21
- Supports nested environment variables [#8](https://github.com/bower/config/issues/8)
## 0.4.3 - 2013-08-19
- Improvement in argv.config parsing
## 0.4.2 - 2013-08-18
- Sets interative to auto
## 0.4.1 - 2013-08-18
- Generates a fake user instead of using 'unknown'
## 0.4.0 - 2013-08-16
- Suffixes temp folder with the user and 'bower'
## 0.3.5 - 2013-08-14
- Casts buffer to string
## 0.3.4 - 2013-08-11
- Empty .bowerrc files no longer throw an error.
## 0.3.3 - 2013-08-11
- Changes git folder to empty (was not being used anyway)
## 0.3.2 - 2013-08-07
- Uses a known user agent by default when a proxy.
## 0.3.1 - 2013-08-06
- Fixes Typo
## 0.3.0 - 2013-08-06
- Appends the username when using the temporary folder.

View File

@@ -1,19 +0,0 @@
Copyright (c) 2012 Twitter and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,76 +0,0 @@
# bower-config
> The Bower config (`.bowerrc`) reader and writer.
[Bower](http://bower.io/) can be configured using JSON in a `.bowerrc` file. For example:
{
"directory": "app/components/",
"timeout": 120000,
"registry": {
"search": [
"http://localhost:8000",
"https://registry.bower.io"
]
}
}
View the complete [.bowerrc specification](http://bower.io/docs/config/#bowerrc-specification) on the website for more details. Both the `bower.json` and `.bowerrc` specifications are maintained at [github.com/bower/spec](https://github.com/bower/spec).
## Install
```sh
$ npm install --save bower-config
```
## Usage
#### .load(overwrites)
Loads the bower configuration from the configuration files.
Configuration is overwritten (after camelcase normalisation) with `overwrites` argument.
This method overwrites following environment variables:
- `HTTP_PROXY` with `proxy` configuration variable
- `HTTPS_PROXY` with `https-proxy` configuration variable
- `NO_PROXY` with `no-proxy` configuration variable
It also clears `http_proxy`, `https_proxy`, and `no_proxy` environment variables.
To restore those variables you can use `restore` method.
#### restore()
Restores environment variables overwritten by `.load` method.
#### .toObject()
Returns a deep copy of the underlying configuration object.
The returned configuration is normalised.
The object keys will be camelCase.
#### #create(cwd)
Obtains a instance where `cwd` is the current working directory (defaults to `process.cwd`);
```js
var config = require('bower-config').create();
// You can also specify a working directory
var config2 = require('bower-config').create('./some/path');
```
#### #read(cwd, overrides)
Alias for:
```js
var configObject = (new Config(cwd)).load(overrides).toJson();
```
## License
Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php).

View File

@@ -1,113 +0,0 @@
var lang = require('mout/lang');
var object = require('mout/object');
var rc = require('./util/rc');
var expand = require('./util/expand');
var EnvProxy = require('./util/proxy');
var path = require('path');
var fs = require('fs');
function Config(cwd) {
this._cwd = cwd;
this._proxy = new EnvProxy();
this._config = {};
}
Config.prototype.load = function(overwrites) {
this._config = rc('bower', this._cwd);
this._config = object.merge(
expand(this._config || {}),
expand(overwrites || {})
);
this._config = normalise(this._config);
this._proxy.set(this._config);
return this;
};
Config.prototype.restore = function() {
this._proxy.restore();
};
function readCertFile(path) {
path = path || '';
var sep = '-----END CERTIFICATE-----';
var certificates;
if (path.indexOf(sep) === -1) {
certificates = fs.readFileSync(path, { encoding: 'utf8' });
} else {
certificates = path;
}
return certificates
.split(sep)
.filter(function(s) {
return !s.match(/^\s*$/);
})
.map(function(s) {
return s + sep;
});
}
function loadCAs(caConfig) {
// If a ca file path has been specified, expand that here to the file's
// contents. As a user can specify these individually, we must load them
// one by one.
for (var p in caConfig) {
if (caConfig.hasOwnProperty(p)) {
var prop = caConfig[p];
if (Array.isArray(prop)) {
caConfig[p] = prop.map(function(s) {
return readCertFile(s);
});
} else if (prop) {
caConfig[p] = readCertFile(prop);
}
}
}
}
Config.prototype.toObject = function() {
return lang.deepClone(this._config);
};
Config.create = function(cwd) {
return new Config(cwd);
};
Config.read = function(cwd, overrides) {
var config = Config.create(cwd);
return config.load(overrides).toObject();
};
function normalise(config) {
config = expand(config);
// Some backwards compatible things..
if (config.shorthandResolver) {
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) {
return url.replace(/\/+$/, '');
});
config.registry.register = config.registry.register.replace(/\/+$/, '');
config.registry.publish = config.registry.publish.replace(/\/+$/, '');
config.tmp = path.resolve(config.tmp);
loadCAs(config.ca);
return config;
}
Config.DEFAULT_REGISTRY = require('./util/defaults').registry;
module.exports = Config;

View File

@@ -1,46 +0,0 @@
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 httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy || 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 defaults = {
directory: 'bower_components',
registry: 'https://registry.bower.io',
'shorthand-resolver': 'https://github.com/{{owner}}/{{package}}.git',
tmp: paths.tmp,
proxy: proxy,
'https-proxy': httpsProxy,
'no-proxy': noProxy,
timeout: 30000,
ca: { search: [] },
'strict-ssl': true,
'user-agent': userAgent,
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
}
};
module.exports = defaults;

View File

@@ -1,128 +0,0 @@
var object = require('mout/object');
var lang = require('mout/lang');
var string = require('mout/string');
function camelCase(config) {
var camelCased = {};
// Camel case
object.forOwn(config, function(value, key) {
// Ignore null values
if (value == null) {
return;
}
key = string.camelCase(key.replace(/_/g, '-'));
camelCased[key] = lang.isPlainObject(value) ? camelCase(value) : value;
});
return camelCased;
}
// Function to replace ${VAR} - style variables
// with values set in the environment
// This function expects to be passed a string
function doEnvReplaceStr(f) {
// Un-tildify
var untildify = require('untildify');
f = untildify(f);
// 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 + '}';
}
return process.env[name];
});
}
function envReplace(config) {
var envReplaced = {};
if (lang.isArray(config)) {
envReplaced = [];
}
object.forOwn(config, function(value, key) {
// Ignore null values
if (value == null) {
return;
}
// Ignore 'scripts'
// These hooks run within the shell
// environment variable expansion is not required
if (key === 'scripts' && lang.isPlainObject(value)) {
envReplaced[key] = value;
return;
}
// Perform variable replacements based on var type
if (lang.isPlainObject(value)) {
envReplaced[key] = envReplace(value);
} else if (lang.isArray(value)) {
envReplaced[key] = envReplace(value);
} else if (lang.isString(value)) {
envReplaced[key] = doEnvReplaceStr(value);
} else {
envReplaced[key] = value;
}
});
return envReplaced;
}
function expand(config) {
config = camelCase(config);
config = envReplace(config);
if (typeof config.registry === 'string') {
config.registry = {
default: config.registry,
search: [config.registry],
register: config.registry,
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,
search: config.registry.search || config.registry.default,
register: config.registry.register || config.registry.default,
publish: config.registry.publish || config.registry.default
};
if (config.registry.search && !Array.isArray(config.registry.search)) {
config.registry.search = [config.registry.search];
}
}
// CA
if (typeof config.ca === 'string') {
config.ca = {
default: config.ca,
search: [config.ca],
register: config.ca,
publish: config.ca
};
} else if (typeof config.ca === 'object') {
if (config.ca.search && !Array.isArray(config.ca.search)) {
config.ca.search = [config.ca.search];
}
if (config.ca.default) {
config.ca.search = config.ca.search || config.ca.default;
config.ca.register = config.ca.register || config.ca.default;
config.ca.publish = config.ca.publish || config.ca.default;
}
}
return config;
}
module.exports = expand;

View File

@@ -1,52 +0,0 @@
var os = require('os');
var path = require('path');
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');
}
// Assume XDG defaults
// See: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
var paths = {
config: process.env.XDG_CONFIG_HOME,
data: process.env.XDG_DATA_HOME,
cache: process.env.XDG_CACHE_HOME
};
// Guess some needed properties based on the user OS
var user = (osenv.user() || generateFakeUser()).replace(/\\/g, '-');
var tmp = path.join(os.tmpdir ? os.tmpdir() : os.tmpDir(), user);
var home = osenv.home();
var base;
// Fallbacks for windows
if (process.platform === 'win32') {
base = path.resolve(process.env.LOCALAPPDATA || home || tmp);
base = path.join(base, 'bower');
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
} else {
base = path.resolve(home || tmp);
paths.config = paths.config || path.join(base, '.config/bower');
paths.data = paths.data || path.join(base, '.local/share/bower');
paths.cache = paths.cache || path.join(base, '.cache/bower');
}
paths.tmp = path.resolve(path.join(tmp, 'bower'));
module.exports = paths;

View File

@@ -1,79 +0,0 @@
// EnvProxy uses the proxy vaiables passed to it in set and sets the
// process.env uppercase proxy variables to them with the ability
// to restore the original values later
var EnvProxy = function() {
this.restoreFrom = {};
};
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;
}
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;
}
};
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 (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 (Object.prototype.hasOwnProperty.call(this.config, 'httpsProxy')) {
if (this.restoreFrom.HTTPS_PROXY !== undefined) {
process.env.HTTPS_PROXY = this.restoreFrom.HTTPS_PROXY;
} else {
delete process.env.HTTPS_PROXY;
}
if (this.restoreFrom.https_proxy !== undefined) {
process.env.https_proxy = this.restoreFrom.https_proxy;
} else {
delete process.env.https_proxy;
}
}
};
module.exports = EnvProxy;

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