3136 changed files with 339543 additions and 103863 deletions
Split View
Diff Options
-
19node/Gruntfile.js
-
45node/grunt-config.json
-
6node/grunt.js
-
14node/node_modules/grunt-bower-task/.jshintrc
-
3node/node_modules/grunt-bower-task/.npmignore
-
5node/node_modules/grunt-bower-task/.travis.yml
-
55node/node_modules/grunt-bower-task/Gruntfile.js
-
22node/node_modules/grunt-bower-task/LICENSE-MIT
-
229node/node_modules/grunt-bower-task/README.md
-
1node/node_modules/grunt-bower-task/node_modules/.bin/bower
-
1node/node_modules/grunt-bower-task/node_modules/.bin/lodash
-
9node/node_modules/grunt-bower-task/node_modules/async/.gitmodules
-
4node/node_modules/grunt-bower-task/node_modules/async/.npmignore
-
0node/node_modules/grunt-bower-task/node_modules/async/LICENSE
-
25node/node_modules/grunt-bower-task/node_modules/async/Makefile
-
1021node/node_modules/grunt-bower-task/node_modules/async/README.md
-
3node/node_modules/grunt-bower-task/node_modules/async/index.js
-
692node/node_modules/grunt-bower-task/node_modules/async/lib/async.js
-
31node/node_modules/grunt-bower-task/node_modules/async/package.json
-
62node/node_modules/grunt-bower-task/node_modules/bower/.jshintrc
-
3node/node_modules/grunt-bower-task/node_modules/bower/.npmignore
-
0node/node_modules/grunt-bower-task/node_modules/bower/.travis.yml
-
117node/node_modules/grunt-bower-task/node_modules/bower/CHANGELOG.md
-
7node/node_modules/grunt-bower-task/node_modules/bower/LICENSE
-
4node/node_modules/grunt-bower-task/node_modules/bower/Makefile
-
363node/node_modules/grunt-bower-task/node_modules/bower/README.md
-
44node/node_modules/grunt-bower-task/node_modules/bower/bin/bower
-
164node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/cache-clean.js
-
112node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/completion.js
-
49node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/help.js
-
24node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/index.js
-
50node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/info.js
-
150node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/init.js
-
81node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/install.js
-
121node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/link.js
-
236node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/list.js
-
64node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/lookup.js
-
66node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/register.js
-
61node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/search.js
-
206node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/uninstall.js
-
95node/node_modules/grunt-bower-task/node_modules/bower/lib/commands/update.js
-
60node/node_modules/grunt-bower-task/node_modules/bower/lib/core/config.js
-
361node/node_modules/grunt-bower-task/node_modules/bower/lib/core/manager.js
-
819node/node_modules/grunt-bower-task/node_modules/bower/lib/core/package.js
-
146node/node_modules/grunt-bower-task/node_modules/bower/lib/core/source.js
-
64node/node_modules/grunt-bower-task/node_modules/bower/lib/core/unit_work.js
-
12node/node_modules/grunt-bower-task/node_modules/bower/lib/index.js
-
65node/node_modules/grunt-bower-task/node_modules/bower/lib/util/completion.js
-
26node/node_modules/grunt-bower-task/node_modules/bower/lib/util/file-exists.js
-
28node/node_modules/grunt-bower-task/node_modules/bower/lib/util/git-cmd.js
-
28node/node_modules/grunt-bower-task/node_modules/bower/lib/util/hogan-colors.js
-
23node/node_modules/grunt-bower-task/node_modules/bower/lib/util/is-repo.js
-
103node/node_modules/grunt-bower-task/node_modules/bower/lib/util/prune.js
-
25node/node_modules/grunt-bower-task/node_modules/bower/lib/util/read-json.js
-
97node/node_modules/grunt-bower-task/node_modules/bower/lib/util/save.js
-
36node/node_modules/grunt-bower-task/node_modules/bower/lib/util/spawn.js
-
41node/node_modules/grunt-bower-task/node_modules/bower/lib/util/template.js
-
1node/node_modules/grunt-bower-task/node_modules/bower/node_modules/.bin/hulk
-
1node/node_modules/grunt-bower-task/node_modules/bower/node_modules/.bin/lodash
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/.bin/nopt
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/.bin/semver
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/.travis.yml
-
92node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/README.markdown
-
24node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/examples/beep.js
-
25node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/examples/multi_line.js
-
35node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/index.js
-
41node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/package.json
-
40node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/test/beep.js
-
45node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/test/multi_line.js
-
40node/node_modules/grunt-bower-task/node_modules/bower/node_modules/archy/test/non_unicode.js
-
19node/node_modules/grunt-bower-task/node_modules/bower/node_modules/async/LICENSE
-
1414node/node_modules/grunt-bower-task/node_modules/bower/node_modules/async/README.md
-
952node/node_modules/grunt-bower-task/node_modules/bower/node_modules/async/lib/async.js
-
42node/node_modules/grunt-bower-task/node_modules/bower/node_modules/async/package.json
-
5node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/.npmignore
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/.travis.yml
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/LICENSE
-
76node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/README.md
-
131node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/examples/filter-pipe.js
-
115node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/examples/pipe.js
-
54node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/examples/reader.js
-
24node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/examples/symlink-write.js
-
31node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/fstream.js
-
85node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/abstract.js
-
67node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/collect.js
-
250node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/dir-reader.js
-
171node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/dir-writer.js
-
147node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/file-reader.js
-
100node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/file-writer.js
-
32node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/get-type.js
-
54node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/link-reader.js
-
95node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/link-writer.js
-
93node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/proxy-reader.js
-
109node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/proxy-writer.js
-
259node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/reader.js
-
38node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/socket-reader.js
-
389node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/lib/writer.js
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/node_modules/graceful-fs/.npmignore
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/node_modules/graceful-fs/LICENSE
-
0node/node_modules/grunt-bower-task/node_modules/bower/node_modules/fstream/node_modules/graceful-fs/README.md
@ -0,0 +1,19 @@ |
|||
module.exports = function(grunt) { |
|||
grunt.loadNpmTasks('grunt-css'); |
|||
grunt.loadNpmTasks('grunt-bower-task'); |
|||
grunt.loadNpmTasks('grunt-contrib-watch'); |
|||
var gruntConfig = require('./grunt-config.json'); |
|||
grunt.initConfig({ |
|||
watch: { |
|||
scripts: { |
|||
files: ["../src/**/*"], |
|||
tasks: ["bower"] |
|||
} |
|||
}, |
|||
bower: { |
|||
install: { |
|||
} |
|||
} |
|||
}); |
|||
grunt.registerTask('default', Object.keys(gruntConfig).join(' ')); |
|||
}; |
@ -1,43 +1,8 @@ |
|||
{ |
|||
"min": { |
|||
"js": { |
|||
"src": [ |
|||
"out/javascript/library/jquery.js", |
|||
"out/javascript/library/ace/ace.js", |
|||
"out/javascript/library/sidr.js", |
|||
"out/javascript/library/waypoints.js", |
|||
"out/modules/behavior/state.js", |
|||
"out/modules/ui/shape.js", |
|||
"out/javascript/semantic.js" |
|||
], |
|||
"dest": "out/scripts/all.min.js" |
|||
} |
|||
}, |
|||
"concat": { |
|||
"css": { |
|||
"src": [ |
|||
"out/stylesheets/reset.css", |
|||
"out/stylesheets/semantic.css", |
|||
"out/ui/flat/elements/icons.css", |
|||
"out/ui/flat/elements/button.css", |
|||
"out/ui/flat/elements/checkbox.css", |
|||
"out/ui/flat/elements/label.css", |
|||
"out/ui/flat/elements/divider.css", |
|||
"out/ui/flat/elements/block.css", |
|||
"out/ui/flat/elements/segment.css", |
|||
"out/ui/flat/collections/grid.css", |
|||
"out/ui/flat/collections/form.css", |
|||
"out/ui/flat/collections/menu.css", |
|||
"out/ui/flat/modules/shape.css", |
|||
"out/stylesheets/library/sidr.css" |
|||
], |
|||
"dest": "out/styles/all.css" |
|||
} |
|||
}, |
|||
"cssmin": { |
|||
"all": { |
|||
"src": ["out/styles/all.css"], |
|||
"dest": "out/styles/all.min.css" |
|||
"watch": { |
|||
"scripts": { |
|||
"files": ["../src/**/*"], |
|||
"tasks": ["bower"] |
|||
} |
|||
} |
|||
} |
|||
} |
@ -1,6 +0,0 @@ |
|||
module.exports = function(grunt) { |
|||
grunt.loadNpmTasks('grunt-css'); |
|||
var gruntConfig = require('./grunt-config.json'); |
|||
grunt.initConfig(gruntConfig); |
|||
grunt.registerTask('default', Object.keys(gruntConfig).join(' ')); |
|||
}; |
@ -0,0 +1,14 @@ |
|||
{ |
|||
"curly": true, |
|||
"eqeqeq": true, |
|||
"immed": true, |
|||
"latedef": true, |
|||
"newcap": true, |
|||
"noarg": true, |
|||
"sub": true, |
|||
"undef": true, |
|||
"boss": true, |
|||
"eqnull": true, |
|||
"node": true, |
|||
"es5": true |
|||
} |
@ -0,0 +1,3 @@ |
|||
node_modules |
|||
npm-debug.log |
|||
tmp |
@ -0,0 +1,5 @@ |
|||
language: node_js |
|||
node_js: |
|||
- 0.8 |
|||
before_install: |
|||
- npm install -g grunt-cli |
@ -0,0 +1,55 @@ |
|||
/* |
|||
* grunt-bower-task |
|||
* https://github.com/yatskevich/grunt-bower-task
|
|||
* |
|||
* Copyright (c) 2012 Ivan Yatskevich |
|||
* Licensed under the MIT license. |
|||
*/ |
|||
|
|||
'use strict'; |
|||
|
|||
module.exports = function(grunt) { |
|||
|
|||
grunt.initConfig({ |
|||
jshint: { |
|||
all: [ 'Gruntfile.js', 'tasks/**/*.js', 'test/**/*_test.js' ], |
|||
options: { |
|||
jshintrc: '.jshintrc' |
|||
} |
|||
}, |
|||
|
|||
bower: { |
|||
install: { |
|||
options: { |
|||
cleanTargetDir: true, |
|||
cleanBowerDir: true, |
|||
install: true, |
|||
copy: true |
|||
} |
|||
}, |
|||
cleanup: { |
|||
options: { |
|||
cleanTargetDir: true, |
|||
cleanBowerDir: true, |
|||
install: false, |
|||
copy: false |
|||
} |
|||
} |
|||
}, |
|||
|
|||
nodeunit: { |
|||
tests: ['test/*_test.js'] |
|||
} |
|||
|
|||
}); |
|||
|
|||
grunt.loadTasks('tasks'); |
|||
|
|||
grunt.loadNpmTasks('grunt-contrib-jshint'); |
|||
grunt.loadNpmTasks('grunt-contrib-nodeunit'); |
|||
|
|||
grunt.registerTask('test', ['jshint', 'nodeunit']); |
|||
|
|||
grunt.registerTask('default', ['test']); |
|||
|
|||
}; |
@ -0,0 +1,22 @@ |
|||
Copyright (c) 2012 Ivan Yatskevich |
|||
|
|||
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. |
@ -0,0 +1,229 @@ |
|||
# grunt-bower-task [](https://travis-ci.org/yatskevich/grunt-bower-task) |
|||
|
|||
> Install Bower packages. Smartly. |
|||
|
|||
## Getting Started |
|||
_If you haven't used [grunt][] before, be sure to check out the [Getting Started][] guide._ |
|||
|
|||
Please note, this plugin works **only with grunt 0.4+**. If you are using grunt 0.3.x then consider an [upgrade to 0.4][]. |
|||
|
|||
From the same directory as your project's [Gruntfile][Getting Started] and [package.json][], install this plugin with the following command: |
|||
|
|||
```bash |
|||
npm install grunt-bower-task --save-dev |
|||
``` |
|||
|
|||
Once that's done, add this line to your project's Gruntfile: |
|||
|
|||
```js |
|||
grunt.loadNpmTasks('grunt-bower-task'); |
|||
``` |
|||
|
|||
If the plugin has been installed correctly, running `grunt --help` at the command line should list the newly-installed plugin's task or tasks. In addition, the plugin should be listed in package.json as a `devDependency`, which ensures that it will be installed whenever the `npm install` command is run. |
|||
|
|||
[grunt]: http://gruntjs.com/ |
|||
[Getting Started]: https://github.com/gruntjs/grunt/wiki/Getting-started |
|||
[package.json]: https://npmjs.org/doc/json.html |
|||
[upgrade to 0.4]: https://github.com/gruntjs/grunt/wiki/Upgrading-from-0.3-to-0.4 |
|||
|
|||
## Grunt task for Bower |
|||
|
|||
### Overview |
|||
In your project's Gruntfile, add a section named `bower` to the data object passed into `grunt.initConfig()`. |
|||
|
|||
```js |
|||
grunt.initConfig({ |
|||
bower: { |
|||
install: { |
|||
//just run 'grunt bower:install' and you'll see files from your Bower packages in lib directory |
|||
} |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
### Options |
|||
|
|||
#### options.targetDir |
|||
Type: `String` |
|||
Default value: `./lib` |
|||
|
|||
A directory where you want to keep your Bower packages. |
|||
|
|||
### options.install |
|||
Type: `Boolean` |
|||
Default value: `true` |
|||
|
|||
Whether you want to run bower install task itself (e.g. you might not want to do this each time on CI server). |
|||
|
|||
### options.cleanTargetDir |
|||
Type: `Boolean` |
|||
Default value: `false` |
|||
|
|||
Will clean target dir before running install. |
|||
|
|||
### options.cleanBowerDir |
|||
Type: `Boolean` |
|||
Default value: `false` |
|||
|
|||
Will remove bower's dir after copying all needed files into target dir. |
|||
|
|||
#### options.cleanup |
|||
Type: `boolean` |
|||
Default value: `undefined` |
|||
|
|||
**NOTE:** If set to true or false then both `cleanBowerDir` & `cleanTargetDir` are set to the value of `cleanup`. |
|||
|
|||
#### options.layout |
|||
Type: `string` or `function` |
|||
Default value: `byType` |
|||
|
|||
There are two built-in named layouts: `byType` and `byComponent`. |
|||
|
|||
`byType` layout will produce the following structure: |
|||
|
|||
``` |
|||
lib |
|||
|-- js |
|||
| |- bootstrap |
|||
| \- require |
|||
|-- css |
|||
\- bootstrap |
|||
``` |
|||
where `js`, `css` come from `exportsOverride` section described below. |
|||
|
|||
`byComponent` will group assets by type under component name: |
|||
|
|||
``` |
|||
lib |
|||
|-- bootstrap |
|||
| |- js |
|||
| \- css |
|||
|-- require |
|||
\- js |
|||
``` |
|||
|
|||
If you need to support custom layout then you can specify `layout` as a function of `type` and `component`: |
|||
|
|||
```js |
|||
var path = require('path'); |
|||
|
|||
grunt.initConfig({ |
|||
bower: { |
|||
install: { |
|||
options: { |
|||
layout: function(type, component) { |
|||
var renamedType = type; |
|||
if (type == 'js') renamedType = 'javascripts'; |
|||
else if (type == 'css') renamedType = 'stylesheets'; |
|||
|
|||
return path.join(component, renamedType); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
#### options.verbose |
|||
Type: `boolean` |
|||
Default value: `false` |
|||
|
|||
The task will provide more (debug) output when this option is set to `true`. You can also use `--verbose` when running task for same effect. |
|||
|
|||
### Usage Examples |
|||
|
|||
#### Default Options |
|||
Default options are good enough if you want to install Bower packages and keep only `"main"` files (as specified by package's `component.json`) in separate directory. |
|||
|
|||
```js |
|||
grunt.initConfig({ |
|||
bower: { |
|||
install: { |
|||
options: { |
|||
targetDir: './lib', |
|||
layout: 'byType', |
|||
install: true, |
|||
verbose: false, |
|||
cleanTargetDir: false, |
|||
cleanBowerDir: false |
|||
} |
|||
} |
|||
}, |
|||
}) |
|||
``` |
|||
|
|||
#### Custom Options |
|||
In this initial version there are no more options in plugin itself. **BUT!** |
|||
|
|||
### Advanced usage |
|||
At this point of time "Bower package" = "its git repository". It means that package includes tests, licenses, etc. |
|||
Bower's community actively discusses this issue (GitHub issues [#46][],[#88][], [on Google Groups][GG]) |
|||
That's why you can find such tools like [blittle/bower-installer][] which inspired this project. |
|||
|
|||
[GG]: https://groups.google.com/forum/?fromgroups=#!topic/twitter-bower/SQEDDA_gmh0 |
|||
[#88]: https://github.com/twitter/bower/issues/88 |
|||
[#46]: https://github.com/twitter/bower/issues/46 |
|||
[blittle/bower-installer]: https://github.com/blittle/bower-installer |
|||
|
|||
Okay, if you want more than `"main"` files in `./lib` directory then put `"exportsOverride"` section into your `component.json`: |
|||
|
|||
```json |
|||
{ |
|||
"name": "simple-bower", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"jquery": "~1.8.3", |
|||
"bootstrap-sass": "*", |
|||
"requirejs": "*" |
|||
}, |
|||
"exportsOverride": { |
|||
"bootstrap-sass": { |
|||
"js": "js/*.js", |
|||
"scss": "lib/*.scss", |
|||
"img": "img/*.png" |
|||
}, |
|||
"requirejs": { |
|||
"js": "require.js" |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
`grunt-bower-task` will do the rest: |
|||
|
|||
* If Bower package has defined `"main"` files then they will be copied to `./lib/<package>/`. |
|||
* If `"main"` files are empty then the whole package directory will be copied to `./lib`. |
|||
* When you define `"exportsOverride"` only asset types and files specified by you will be copied to `./lib`. |
|||
|
|||
For the example above you'll get the following files in `.lib` directory: |
|||
|
|||
``` |
|||
jquery/jquery.js |
|||
js/bootstrap-sass/bootstrap-affix.js |
|||
... |
|||
js/bootstrap-sass/bootstrap-typeahead.js |
|||
js/requirejs/require.js |
|||
scss/bootstrap-sass/_accordion.scss |
|||
... |
|||
scss/bootstrap-sass/_wells.scss |
|||
scss/bootstrap-sass/bootstrap.scss |
|||
scss/bootstrap-sass/responsive.scss |
|||
img/bootstrap-sass/glyphicons-halflings-white.png |
|||
img/bootstrap-sass/glyphicons-halflings.png |
|||
``` |
|||
|
|||
## Contributing |
|||
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt][]. |
|||
|
|||
## Release History |
|||
* 2013/04/23 - v0.2.2 - Fix backward-compatibility issue (related to `cleanup` option). |
|||
* 2013/04/22 - v0.2.1 - Split 'install' to 'install' and 'copy' steps to support flexible workflow, add support for grunt's `--base` option. |
|||
* 2013/03/30 - v0.2.0 - Added support for flexible assets layout, honor native Bower's configuration, reduce log output. |
|||
* 2013/03/18 - v0.1.1 - Updated dependencies, minor fixes. |
|||
* 2012/11/25 - v0.1.0 - Initial release. |
|||
|
|||
## License |
|||
Copyright (c) 2012-2013 Ivan Yatskevich |
|||
|
|||
Licensed under the MIT license. |
|||
|
|||
<https://github.com/yatskevich/grunt-bower-task/blob/master/LICENSE-MIT> |
@ -0,0 +1 @@ |
|||
../bower/bin/bower |
@ -0,0 +1 @@ |
|||
../lodash/build.js |
@ -0,0 +1,9 @@ |
|||
[submodule "deps/nodeunit"] |
|||
path = deps/nodeunit |
|||
url = git://github.com/caolan/nodeunit.git |
|||
[submodule "deps/UglifyJS"] |
|||
path = deps/UglifyJS |
|||
url = https://github.com/mishoo/UglifyJS.git |
|||
[submodule "deps/nodelint"] |
|||
path = deps/nodelint |
|||
url = https://github.com/tav/nodelint.git |
@ -0,0 +1,4 @@ |
|||
deps |
|||
dist |
|||
test |
|||
nodelint.cfg |
@ -0,0 +1,25 @@ |
|||
PACKAGE = asyncjs |
|||
NODEJS = $(if $(shell test -f /usr/bin/nodejs && echo "true"),nodejs,node) |
|||
CWD := $(shell pwd) |
|||
NODEUNIT = $(CWD)/node_modules/nodeunit/bin/nodeunit |
|||
UGLIFY = $(CWD)/node_modules/uglify-js/bin/uglifyjs |
|||
NODELINT = $(CWD)/node_modules/nodelint/nodelint |
|||
|
|||
BUILDDIR = dist |
|||
|
|||
all: clean test build |
|||
|
|||
build: $(wildcard lib/*.js) |
|||
mkdir -p $(BUILDDIR) |
|||
$(UGLIFY) lib/async.js > $(BUILDDIR)/async.min.js |
|||
|
|||
test: |
|||
$(NODEUNIT) test |
|||
|
|||
clean: |
|||
rm -rf $(BUILDDIR) |
|||
|
|||
lint: |
|||
$(NODELINT) --config nodelint.cfg lib/async.js |
|||
|
|||
.PHONY: test build all |
1021
node/node_modules/grunt-bower-task/node_modules/async/README.md
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,3 @@ |
|||
// This file is just added for convenience so this repository can be
|
|||
// directly checked out into a project's deps folder
|
|||
module.exports = require('./lib/async'); |
@ -0,0 +1,692 @@ |
|||
/*global setTimeout: false, console: false */ |
|||
(function () { |
|||
|
|||
var async = {}; |
|||
|
|||
// global on the server, window in the browser
|
|||
var root = this, |
|||
previous_async = root.async; |
|||
|
|||
if (typeof module !== 'undefined' && module.exports) { |
|||
module.exports = async; |
|||
} |
|||
else { |
|||
root.async = async; |
|||
} |
|||
|
|||
async.noConflict = function () { |
|||
root.async = previous_async; |
|||
return async; |
|||
}; |
|||
|
|||
//// cross-browser compatiblity functions ////
|
|||
|
|||
var _forEach = function (arr, iterator) { |
|||
if (arr.forEach) { |
|||
return arr.forEach(iterator); |
|||
} |
|||
for (var i = 0; i < arr.length; i += 1) { |
|||
iterator(arr[i], i, arr); |
|||
} |
|||
}; |
|||
|
|||
var _map = function (arr, iterator) { |
|||
if (arr.map) { |
|||
return arr.map(iterator); |
|||
} |
|||
var results = []; |
|||
_forEach(arr, function (x, i, a) { |
|||
results.push(iterator(x, i, a)); |
|||
}); |
|||
return results; |
|||
}; |
|||
|
|||
var _reduce = function (arr, iterator, memo) { |
|||
if (arr.reduce) { |
|||
return arr.reduce(iterator, memo); |
|||
} |
|||
_forEach(arr, function (x, i, a) { |
|||
memo = iterator(memo, x, i, a); |
|||
}); |
|||
return memo; |
|||
}; |
|||
|
|||
var _keys = function (obj) { |
|||
if (Object.keys) { |
|||
return Object.keys(obj); |
|||
} |
|||
var keys = []; |
|||
for (var k in obj) { |
|||
if (obj.hasOwnProperty(k)) { |
|||
keys.push(k); |
|||
} |
|||
} |
|||
return keys; |
|||
}; |
|||
|
|||
//// exported async module functions ////
|
|||
|
|||
//// nextTick implementation with browser-compatible fallback ////
|
|||
if (typeof process === 'undefined' || !(process.nextTick)) { |
|||
async.nextTick = function (fn) { |
|||
setTimeout(fn, 0); |
|||
}; |
|||
} |
|||
else { |
|||
async.nextTick = process.nextTick; |
|||
} |
|||
|
|||
async.forEach = function (arr, iterator, callback) { |
|||
callback = callback || function () {}; |
|||
if (!arr.length) { |
|||
return callback(); |
|||
} |
|||
var completed = 0; |
|||
_forEach(arr, function (x) { |
|||
iterator(x, function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
completed += 1; |
|||
if (completed === arr.length) { |
|||
callback(null); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
async.forEachSeries = function (arr, iterator, callback) { |
|||
callback = callback || function () {}; |
|||
if (!arr.length) { |
|||
return callback(); |
|||
} |
|||
var completed = 0; |
|||
var iterate = function () { |
|||
iterator(arr[completed], function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
completed += 1; |
|||
if (completed === arr.length) { |
|||
callback(null); |
|||
} |
|||
else { |
|||
iterate(); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
iterate(); |
|||
}; |
|||
|
|||
async.forEachLimit = function (arr, limit, iterator, callback) { |
|||
callback = callback || function () {}; |
|||
if (!arr.length || limit <= 0) { |
|||
return callback(); |
|||
} |
|||
var completed = 0; |
|||
var started = 0; |
|||
var running = 0; |
|||
|
|||
(function replenish () { |
|||
if (completed === arr.length) { |
|||
return callback(); |
|||
} |
|||
|
|||
while (running < limit && started < arr.length) { |
|||
started += 1; |
|||
running += 1; |
|||
iterator(arr[started - 1], function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
completed += 1; |
|||
running -= 1; |
|||
if (completed === arr.length) { |
|||
callback(); |
|||
} |
|||
else { |
|||
replenish(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
})(); |
|||
}; |
|||
|
|||
|
|||
var doParallel = function (fn) { |
|||
return function () { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
return fn.apply(null, [async.forEach].concat(args)); |
|||
}; |
|||
}; |
|||
var doSeries = function (fn) { |
|||
return function () { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
return fn.apply(null, [async.forEachSeries].concat(args)); |
|||
}; |
|||
}; |
|||
|
|||
|
|||
var _asyncMap = function (eachfn, arr, iterator, callback) { |
|||
var results = []; |
|||
arr = _map(arr, function (x, i) { |
|||
return {index: i, value: x}; |
|||
}); |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x.value, function (err, v) { |
|||
results[x.index] = v; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, results); |
|||
}); |
|||
}; |
|||
async.map = doParallel(_asyncMap); |
|||
async.mapSeries = doSeries(_asyncMap); |
|||
|
|||
|
|||
// reduce only has a series version, as doing reduce in parallel won't
|
|||
// work in many situations.
|
|||
async.reduce = function (arr, memo, iterator, callback) { |
|||
async.forEachSeries(arr, function (x, callback) { |
|||
iterator(memo, x, function (err, v) { |
|||
memo = v; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, memo); |
|||
}); |
|||
}; |
|||
// inject alias
|
|||
async.inject = async.reduce; |
|||
// foldl alias
|
|||
async.foldl = async.reduce; |
|||
|
|||
async.reduceRight = function (arr, memo, iterator, callback) { |
|||
var reversed = _map(arr, function (x) { |
|||
return x; |
|||
}).reverse(); |
|||
async.reduce(reversed, memo, iterator, callback); |
|||
}; |
|||
// foldr alias
|
|||
async.foldr = async.reduceRight; |
|||
|
|||
var _filter = function (eachfn, arr, iterator, callback) { |
|||
var results = []; |
|||
arr = _map(arr, function (x, i) { |
|||
return {index: i, value: x}; |
|||
}); |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x.value, function (v) { |
|||
if (v) { |
|||
results.push(x); |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
callback(_map(results.sort(function (a, b) { |
|||
return a.index - b.index; |
|||
}), function (x) { |
|||
return x.value; |
|||
})); |
|||
}); |
|||
}; |
|||
async.filter = doParallel(_filter); |
|||
async.filterSeries = doSeries(_filter); |
|||
// select alias
|
|||
async.select = async.filter; |
|||
async.selectSeries = async.filterSeries; |
|||
|
|||
var _reject = function (eachfn, arr, iterator, callback) { |
|||
var results = []; |
|||
arr = _map(arr, function (x, i) { |
|||
return {index: i, value: x}; |
|||
}); |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x.value, function (v) { |
|||
if (!v) { |
|||
results.push(x); |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
callback(_map(results.sort(function (a, b) { |
|||
return a.index - b.index; |
|||
}), function (x) { |
|||
return x.value; |
|||
})); |
|||
}); |
|||
}; |
|||
async.reject = doParallel(_reject); |
|||
async.rejectSeries = doSeries(_reject); |
|||
|
|||
var _detect = function (eachfn, arr, iterator, main_callback) { |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x, function (result) { |
|||
if (result) { |
|||
main_callback(x); |
|||
main_callback = function () {}; |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}); |
|||
}, function (err) { |
|||
main_callback(); |
|||
}); |
|||
}; |
|||
async.detect = doParallel(_detect); |
|||
async.detectSeries = doSeries(_detect); |
|||
|
|||
async.some = function (arr, iterator, main_callback) { |
|||
async.forEach(arr, function (x, callback) { |
|||
iterator(x, function (v) { |
|||
if (v) { |
|||
main_callback(true); |
|||
main_callback = function () {}; |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
main_callback(false); |
|||
}); |
|||
}; |
|||
// any alias
|
|||
async.any = async.some; |
|||
|
|||
async.every = function (arr, iterator, main_callback) { |
|||
async.forEach(arr, function (x, callback) { |
|||
iterator(x, function (v) { |
|||
if (!v) { |
|||
main_callback(false); |
|||
main_callback = function () {}; |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
main_callback(true); |
|||
}); |
|||
}; |
|||
// all alias
|
|||
async.all = async.every; |
|||
|
|||
async.sortBy = function (arr, iterator, callback) { |
|||
async.map(arr, function (x, callback) { |
|||
iterator(x, function (err, criteria) { |
|||
if (err) { |
|||
callback(err); |
|||
} |
|||
else { |
|||
callback(null, {value: x, criteria: criteria}); |
|||
} |
|||
}); |
|||
}, function (err, results) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
else { |
|||
var fn = function (left, right) { |
|||
var a = left.criteria, b = right.criteria; |
|||
return a < b ? -1 : a > b ? 1 : 0; |
|||
}; |
|||
callback(null, _map(results.sort(fn), function (x) { |
|||
return x.value; |
|||
})); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
async.auto = function (tasks, callback) { |
|||
callback = callback || function () {}; |
|||
var keys = _keys(tasks); |
|||
if (!keys.length) { |
|||
return callback(null); |
|||
} |
|||
|
|||
var results = {}; |
|||
|
|||
var listeners = []; |
|||
var addListener = function (fn) { |
|||
listeners.unshift(fn); |
|||
}; |
|||
var removeListener = function (fn) { |
|||
for (var i = 0; i < listeners.length; i += 1) { |
|||
if (listeners[i] === fn) { |
|||
listeners.splice(i, 1); |
|||
return; |
|||
} |
|||
} |
|||
}; |
|||
var taskComplete = function () { |
|||
_forEach(listeners.slice(0), function (fn) { |
|||
fn(); |
|||
}); |
|||
}; |
|||
|
|||
addListener(function () { |
|||
if (_keys(results).length === keys.length) { |
|||
callback(null, results); |
|||
callback = function () {}; |
|||
} |
|||
}); |
|||
|
|||
_forEach(keys, function (k) { |
|||
var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; |
|||
var taskCallback = function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
// stop subsequent errors hitting callback multiple times
|
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
results[k] = args; |
|||
taskComplete(); |
|||
} |
|||
}; |
|||
var requires = task.slice(0, Math.abs(task.length - 1)) || []; |
|||
var ready = function () { |
|||
return _reduce(requires, function (a, x) { |
|||
return (a && results.hasOwnProperty(x)); |
|||
}, true) && !results.hasOwnProperty(k); |
|||
}; |
|||
if (ready()) { |
|||
task[task.length - 1](taskCallback, results); |
|||
} |
|||
else { |
|||
var listener = function () { |
|||
if (ready()) { |
|||
removeListener(listener); |
|||
task[task.length - 1](taskCallback, results); |
|||
} |
|||
}; |
|||
addListener(listener); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
async.waterfall = function (tasks, callback) { |
|||
callback = callback || function () {}; |
|||
if (!tasks.length) { |
|||
return callback(); |
|||
} |
|||
var wrapIterator = function (iterator) { |
|||
return function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
var next = iterator.next(); |
|||
if (next) { |
|||
args.push(wrapIterator(next)); |
|||
} |
|||
else { |
|||
args.push(callback); |
|||
} |
|||
async.nextTick(function () { |
|||
iterator.apply(null, args); |
|||
}); |
|||
} |
|||
}; |
|||
}; |
|||
wrapIterator(async.iterator(tasks))(); |
|||
}; |
|||
|
|||
async.parallel = function (tasks, callback) { |
|||
callback = callback || function () {}; |
|||
if (tasks.constructor === Array) { |
|||
async.map(tasks, function (fn, callback) { |
|||
if (fn) { |
|||
fn(function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
callback.call(null, err, args); |
|||
}); |
|||
} |
|||
}, callback); |
|||
} |
|||
else { |
|||
var results = {}; |
|||
async.forEach(_keys(tasks), function (k, callback) { |
|||
tasks[k](function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
results[k] = args; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, results); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
async.series = function (tasks, callback) { |
|||
callback = callback || function () {}; |
|||
if (tasks.constructor === Array) { |
|||
async.mapSeries(tasks, function (fn, callback) { |
|||
if (fn) { |
|||
fn(function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
callback.call(null, err, args); |
|||
}); |
|||
} |
|||
}, callback); |
|||
} |
|||
else { |
|||
var results = {}; |
|||
async.forEachSeries(_keys(tasks), function (k, callback) { |
|||
tasks[k](function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
results[k] = args; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, results); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
async.iterator = function (tasks) { |
|||
var makeCallback = function (index) { |
|||
var fn = function () { |
|||
if (tasks.length) { |
|||
tasks[index].apply(null, arguments); |
|||
} |
|||
return fn.next(); |
|||
}; |
|||
fn.next = function () { |
|||
return (index < tasks.length - 1) ? makeCallback(index + 1): null; |
|||
}; |
|||
return fn; |
|||
}; |
|||
return makeCallback(0); |
|||
}; |
|||
|
|||
async.apply = function (fn) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
return function () { |
|||
return fn.apply( |
|||
null, args.concat(Array.prototype.slice.call(arguments)) |
|||
); |
|||
}; |
|||
}; |
|||
|
|||
var _concat = function (eachfn, arr, fn, callback) { |
|||
var r = []; |
|||
eachfn(arr, function (x, cb) { |
|||
fn(x, function (err, y) { |
|||
r = r.concat(y || []); |
|||
cb(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, r); |
|||
}); |
|||
}; |
|||
async.concat = doParallel(_concat); |
|||
async.concatSeries = doSeries(_concat); |
|||
|
|||
async.whilst = function (test, iterator, callback) { |
|||
if (test()) { |
|||
iterator(function (err) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
async.whilst(test, iterator, callback); |
|||
}); |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}; |
|||
|
|||
async.until = function (test, iterator, callback) { |
|||
if (!test()) { |
|||
iterator(function (err) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
async.until(test, iterator, callback); |
|||
}); |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}; |
|||
|
|||
async.queue = function (worker, concurrency) { |
|||
var workers = 0; |
|||
var q = { |
|||
tasks: [], |
|||
concurrency: concurrency, |
|||
saturated: null, |
|||
empty: null, |
|||
drain: null, |
|||
push: function (data, callback) { |
|||
if(data.constructor !== Array) { |
|||
data = [data]; |
|||
} |
|||
_forEach(data, function(task) { |
|||
q.tasks.push({ |
|||
data: task, |
|||
callback: typeof callback === 'function' ? callback : null |
|||
}); |
|||
if (q.saturated && q.tasks.length == concurrency) { |
|||
q.saturated(); |
|||
} |
|||
async.nextTick(q.process); |
|||
}); |
|||
}, |
|||
process: function () { |
|||
if (workers < q.concurrency && q.tasks.length) { |
|||
var task = q.tasks.shift(); |
|||
if(q.empty && q.tasks.length == 0) q.empty(); |
|||
workers += 1; |
|||
worker(task.data, function () { |
|||
workers -= 1; |
|||
if (task.callback) { |
|||
task.callback.apply(task, arguments); |
|||
} |
|||
if(q.drain && q.tasks.length + workers == 0) q.drain(); |
|||
q.process(); |
|||
}); |
|||
} |
|||
}, |
|||
length: function () { |
|||
return q.tasks.length; |
|||
}, |
|||
running: function () { |
|||
return workers; |
|||
} |
|||
}; |
|||
return q; |
|||
}; |
|||
|
|||
var _console_fn = function (name) { |
|||
return function (fn) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
fn.apply(null, args.concat([function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (typeof console !== 'undefined') { |
|||
if (err) { |
|||
if (console.error) { |
|||
console.error(err); |
|||
} |
|||
} |
|||
else if (console[name]) { |
|||
_forEach(args, function (x) { |
|||
console[name](x); |
|||
}); |
|||
} |
|||
} |
|||
}])); |
|||
}; |
|||
}; |
|||
async.log = _console_fn('log'); |
|||
async.dir = _console_fn('dir'); |
|||
/*async.info = _console_fn('info'); |
|||
async.warn = _console_fn('warn'); |
|||
async.error = _console_fn('error');*/ |
|||
|
|||
async.memoize = function (fn, hasher) { |
|||
var memo = {}; |
|||
var queues = {}; |
|||
hasher = hasher || function (x) { |
|||
return x; |
|||
}; |
|||
var memoized = function () { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
var callback = args.pop(); |
|||
var key = hasher.apply(null, args); |
|||
if (key in memo) { |
|||
callback.apply(null, memo[key]); |
|||
} |
|||
else if (key in queues) { |
|||
queues[key].push(callback); |
|||
} |
|||
else { |
|||
queues[key] = [callback]; |
|||
fn.apply(null, args.concat([function () { |
|||
memo[key] = arguments; |
|||
var q = queues[key]; |
|||
delete queues[key]; |
|||
for (var i = 0, l = q.length; i < l; i++) { |
|||
q[i].apply(null, arguments); |
|||
} |
|||
}])); |
|||
} |
|||
}; |
|||
memoized.unmemoized = fn; |
|||
return memoized; |
|||
}; |
|||
|
|||
async.unmemoize = function (fn) { |
|||
return function () { |
|||
return (fn.unmemoized || fn).apply(null, arguments); |
|||
}; |
|||
}; |
|||
|
|||
}()); |
31
node/node_modules/grunt-bower-task/node_modules/async/package.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,62 @@ |
|||
{ |
|||
"predef": [ |
|||
"console", |
|||
"describe", |
|||
"it", |
|||
"after", |
|||
"afterEach", |
|||
"before", |
|||
"beforeEach" |
|||
], |
|||
|
|||
"indent": 2, |
|||
"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": true, |
|||
"regexp": false, |
|||
"undef": true, |
|||
"unused": true, |
|||
"quotmark": "single", |
|||
"strict": false, |
|||
"trailing": true, |
|||
|
|||
"asi": false, |
|||
"boss": false, |
|||
"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, |
|||
"onevar": false, |
|||
"white": true |
|||
} |
@ -0,0 +1,3 @@ |
|||
node_modules |
|||
components |
|||
.DS_Store |
@ -0,0 +1,117 @@ |
|||
# Changelog |
|||
|
|||
## 0.8.6 - 2013-04-03 |
|||
- Lock `unzip` to `0.1.4` to fix extraction of zip files in node `0.8.x` |
|||
An upcoming release to fix node `0.10.x` will be available soon. |
|||
- Fix error summary being displayed too soon. |
|||
|
|||
## 0.8.5 - 2013-03-04 |
|||
- Fix `cache-clean` command clearing the completion cache when the command was called with specific packages |
|||
- Add error message when an error is caught parsing an invalid `component.json` |
|||
|
|||
## 0.8.4 - 2013-03-01 |
|||
- Fix some more duplicate async callbacks being called twice |
|||
- Preserve new lines when saving `component.json` ([#285](https://github.com/twitter/bower/issues/285)) |
|||
|
|||
## 0.8.3 - 2013-02-27 |
|||
- Fix error when using the `update` command ([#282](https://github.com/twitter/bower/issues/282)) |
|||
|
|||
## 0.8.2 - 2013-02-26 |
|||
- Fix some errors in windows while removing directories, had to downgrade `rimraf` ([#274](https://github.com/twitter/bower/issues/274)) |
|||
- Prevent duplicate package names in error summaries ([#277](https://github.com/twitter/bower/issues/277)) |
|||
|
|||
## 0.8.1 - 2013-02-25 |
|||
- Fix some async callbacks being fired twice ([#274](https://github.com/twitter/bower/issues/274)) |
|||
|
|||
## 0.8.0 - 2013-02-24 |
|||
- __Add init command similar to `npm init`__ ([#219](https://github.com/twitter/bower/issues/219)) |
|||
- __Add devDependencies__ support ([#251](https://github.com/twitter/bower/issues/251)) |
|||
- __Add `--save-dev` flag to install/uninstall commands__ ([#258](https://github.com/twitter/bower/issues/258)) |
|||
- `cache-clean` command now clears links pointing to nonexistent folders ([#182](https://github.com/twitter/bower/issues/182)) |
|||
- Fix issue when downloading assets behind a proxy using `https` ([#230](https://github.com/twitter/bower/issues/230)) |
|||
- Fix --save saving unresolved components ([#240](https://github.com/twitter/bower/issues/240)) |
|||
- Fix issue when extracting some zip files ([#225](https://github.com/twitter/bower/issues/225)) |
|||
- Fix automatic conflict resolver not selecting the correct version |
|||
- Add `--sources` option to the `list` command ([#235](https://github.com/twitter/bower/issues/235)) |
|||
- Automatically clear cache when git commands fail with code 128 ([#216](https://github.com/twitter/bower/issues/216)) |
|||
- Fix `bower` not working correctly behind a proxy in some commands ([#208](https://github.com/twitter/bower/issues/208)) |
|||
|
|||
## 0.7.1 - 2013-02-20 |
|||
- Remove postinstall script from `bower` installation |
|||
|
|||
## 0.7.0 - 2013-02-01 |
|||
- __Ability to resolve conflicts__ ([#214](https://github.com/twitter/bower/issues/214)) |
|||
- __Ability to search and publish to different endpoints by specifiying them in the `.bowerrc` file__ |
|||
- __Experimental autocompletion__ |
|||
- __Ability to exclude (ignore) files__ |
|||
- Fix minor issues in the cache clean command |
|||
- Better error message for invalid semver tags ([#185](https://github.com/twitter/bower/issues/185)) |
|||
- Only show discover message in the list command only if there are packages |
|||
- Fix mismatch issue due to reading cached component.json files ([#214](https://github.com/twitter/bower/issues/214)) |
|||
- Better error messages when reading invalid .bowerrc files ([#220](https://github.com/twitter/bower/issues/220)) |
|||
- Fix update command when used in packages pointing to assets ([#197](https://github.com/twitter/bower/issues/197)) |
|||
- Bower now obeys packages's `.bowerrc` if they define a different `json` ([#205](https://github.com/twitter/bower/issues/205)) |
|||
|
|||
## 0.6.8 - 2012-12-14 |
|||
- Improve list command |
|||
- Does not fetch versions if not necessary (for --map and --paths options) |
|||
- Add --offline option to prevent versions from being fetched |
|||
- Fix uninstall command not firing the `end` event |
|||
- Fix error when executing an unknown command ([#179](https://github.com/twitter/bower/issues/179)) |
|||
- Fix help for the ls command (alias of list) |
|||
|
|||
## 0.6.7 - 2012-12-10 |
|||
- Fix uninstall removing all unsaved dependencies ([#178](https://github.com/twitter/bower/issues/178)) |
|||
- Fix uninstall --force flag in some cases |
|||
- Add --silent option to the register option, to avoid questioning |
|||
- Fix possible issues with options in some commands |
|||
- Fix error reporting when reading invalid project component.json |
|||
|
|||
## 0.6.6 - 2012-12-03 |
|||
- Improve error handling while reading component.json |
|||
- Fix package name not being correctly collected in the error summary |
|||
|
|||
## 0.6.5 - 2012-12-01 |
|||
- Fix error summary not being displayed in some edge cases |
|||
- Fix bower not fetching latest commits correctly in some cases |
|||
|
|||
## 0.6.4 - 2012-11-29 |
|||
- Fix permission on downloaded files ([#160](https://github.com/twitter/bower/issues/160)) |
|||
|
|||
## 0.6.3 - 2012-11-24 |
|||
- Fix version not being correctly set for local packages ([#155](https://github.com/twitter/bower/issues/155)) |
|||
|
|||
## 0.6.2 - 2012-11-23 |
|||
- Fix uninstall --save when there is no component.json |
|||
|
|||
## 0.6.1 - 2012-11-22 |
|||
- Fix uninstall when the project component.json has no deps saved ([#153](https://github.com/twitter/bower/issues/153)) |
|||
- 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/twitter/bower/issues/140)) |
|||
|
|||
## 0.6.0 - 2012-11-21 |
|||
- __Add link command__ (similar to npm) |
|||
- Fix error reporting for nested deps |
|||
- Abort if a repository is detected when installing. |
|||
This is useful to prevent people from loosing their work |
|||
- Minor fixes and improvements |
|||
|
|||
## 0.5.1 - 2012-11-20 |
|||
- Add errors summary to the end of install/update commands |
|||
- Add windows instructions to the README |
|||
|
|||
## 0.5.0 - 2012-11-19 |
|||
- __Remove package.json support__ |
|||
- __Support for local path repositories__ ([#132](https://github.com/twitter/bower/issues/132)) |
|||
- `install --save` now saves the correct tag (e.g: ~0.0.1) instead of 'latest' |
|||
- `install --save` now saves packages pointing directly to assets correctly |
|||
- Bower automatically creates a component.json when install with `--save` is used |
|||
- Fix issues with list command ([#142](https://github.com/twitter/bower/issues/142)) |
|||
- Fix local paths not being saved when installing with --save ([#114](https://github.com/twitter/bower/issues/114)) |
|||
- `uninstall` now uninstalls nested dependencies if they are not shared ([#83](https://github.com/twitter/bower/issues/83)) |
|||
- `uninstall` now warns when a dependency conflict occurs and aborts. |
|||
It will only proceed if the `--force` flag is passed |
|||
- Bower now detects mismatches between the version specified in the component.json and the tag, informing the user |
|||
- `bower ls` now informs when a package has a new commit (for non-tagged repos) |
|||
- Add jshintrc and fix a lot of issues related with JSHint warnings |
|||
- `bower register` now prompts if the user really wants to proceed |
@ -0,0 +1,7 @@ |
|||
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. |
@ -0,0 +1,4 @@ |
|||
test: |
|||
./node_modules/.bin/mocha -R spec -t 10000 |
|||
|
|||
.PHONY: test |
@ -0,0 +1,363 @@ |
|||
BOWER [](http://travis-ci.org/twitter/bower) |
|||
===== |
|||
|
|||
### Introduction |
|||
|
|||
Bower is a package manager for the web. Bower lets you easily install assets such as images, CSS and JavaScript, and manages dependencies for you. |
|||
|
|||
For example, to install a package, run: |
|||
|
|||
bower install jquery |
|||
|
|||
This will download jQuery to `./components/jquery`. That's it. The idea is that Bower does package management and package management only. |
|||
|
|||
### Installing Bower |
|||
|
|||
Bower is installed using [Node](http://nodejs.org/) and [npm](http://npmjs.org/) (oh my, how meta). |
|||
|
|||
npm install bower -g |
|||
|
|||
### Usage |
|||
|
|||
Your best friend at this stage is probably `bower --help`. |
|||
|
|||
To install a package: |
|||
|
|||
bower install jquery |
|||
bower install git://github.com/components/jquery.git |
|||
bower install components/jquery (same as above) |
|||
bower install http://foo.com/jquery.awesome-plugin.js |
|||
bower install ./repos/jquery |
|||
|
|||
As you can see, packages can be installed by name, Git endpoint, GitHub shorthand, URL or local path. |
|||
If you install from a URL that points to a zip or tar file, bower will automatically extract its contents. |
|||
When tags are available in the endpoint, you can specify a [semver](http://semver.org/) tag to fetch concrete versions: |
|||
|
|||
bower install jquery#1.8.1 |
|||
bower install git://github.com/components/jquery.git#~1.8.1 |
|||
bower install components/jquery#1.8.x |
|||
|
|||
Bower also works with private Git repositories. Simply reference them by their SSH endpoint: |
|||
|
|||
bower install git@github.com:user/private-package.git |
|||
|
|||
[View all packages available through Bower's registry](http://sindresorhus.com/bower-components/). |
|||
|
|||
During install you can have Bower add an entry to your component.json as well: |
|||
|
|||
bower install --save jquery |
|||
|
|||
To update a package, reference it by name: |
|||
|
|||
bower update jquery-ui |
|||
|
|||
To list installed packages: |
|||
|
|||
bower list |
|||
|
|||
To search for packages: |
|||
|
|||
bower search [name] |
|||
|
|||
To list all the available packages, just call `bower search` without specifying a name. |
|||
|
|||
To clean the cache: |
|||
|
|||
bower cache-clean [name] |
|||
|
|||
Several packages can be cleaned at the same time. |
|||
To clean the entire cache, just call `bower cache-clean` without any names. |
|||
Also, both the install and update commands have a `--force` flag that tells bower to bypass the cache and always fetch remote sources. |
|||
|
|||
You can disable colors by using the `--no-color` flag. |
|||
|
|||
### Bower Configuration |
|||
|
|||
Bower can be configured by creating a .bowerrc file in your home folder (usually ~/.bowerrc) with one or all of the following configuration parameters. You can also configure Bower on a per-project basis by creating a .bowerrc file in the project directory, Bower will merge this configuration with the configuration found in your home directory. This allows you to version your project specific Bower configuration with the rest of your code base. |
|||
|
|||
```json |
|||
{ |
|||
"directory" : "components", |
|||
"json" : "component.json", |
|||
"endpoint" : "https://bower.herokuapp.com" |
|||
} |
|||
``` |
|||
|
|||
To run your own Bower Endpoint for custom components/packages that are behind a firewall you can use a simple implementation of bower server at https://github.com/twitter/bower-server. |
|||
|
|||
The __searchpath__ array provides additional URLs of read-only Bower registries that should be consulted to look up components. This is most typically used if your business wishes to |
|||
house some components internally while still taking advantage of public Bower registries. For example, you might configure the following: |
|||
|
|||
```json |
|||
{ |
|||
"directory" : "components", |
|||
"json" : "component.json", |
|||
"endpoint" : "http://bower.mycompany.com", |
|||
"searchpath" : ["https://bower.herokuapp.com"] |
|||
} |
|||
``` |
|||
|
|||
Bower will first look to **http://bower.mycompany.com** while trying to find your components. If not found, the main registry at **https://bower.herokuapp.com** will be consulted to see if a copy of the resource can be retrieved. |
|||
|
|||
|
|||
### Defining a package |
|||
|
|||
You can create a `component.json` file in your project's root, specifying 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. |
|||
|
|||
```json |
|||
{ |
|||
"name": "myProject", |
|||
"version": "1.0.0", |
|||
"main": "./path/to/main.css", |
|||
"dependencies": { |
|||
"jquery": "~1.7.2" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Put this under your project's root, listing all of your dependencies. When you run `bower install`, Bower will read this `component.json` file, resolve all the relevant dependencies and install them. |
|||
|
|||
For now, `name`, `version`, `main`, `dependencies`, `devDependencies`, and `ignore` are the only properties that are used by Bower. If you have several files you're distributing as part of your package, pass an array to `main` like this: |
|||
|
|||
```json |
|||
{ |
|||
"name": "myProject", |
|||
"version": "1.0.0", |
|||
"main": ["./path/to/app.css", "./path/to/app.js", "./path/to/sprite.img"], |
|||
"dependencies": { |
|||
"jquery": "~1.7.2" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Bower only recognizes versions that follow the [semver](http://semver.org/) specification. |
|||
There should only be at most one file per file type in the `main` list. So only one `.js` or `.css`. |
|||
|
|||
You can also point to packages by adding their URL or file path in the dependency's property. |
|||
|
|||
```json |
|||
{ |
|||
"dependencies": { |
|||
"eventEmitter": "Wolfy87/EventEmitter", // GitHub short URL |
|||
"eventEmitter": "Wolfy87/EventEmitter#>=3", // with version |
|||
"eventEmitter": "git://github.com/Wolfy87/EventEmitter", |
|||
"eventEmitter": "git@github.com:Wolfy87/EventEmitter.git" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Chances are you have a bunch of extra stuff in the repo that are not needed in production. List these non-necessary file paths in `ignore`. |
|||
|
|||
```json |
|||
{ |
|||
"ignore": [ |
|||
"tests/", |
|||
"**/*.txt" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
You may add non-essential packages in `devDependencies`. This is useful for packages aren't required to support the package, but that are used in your project, i.e. to build documentation, run a demo, or run tests. |
|||
|
|||
```json |
|||
{ |
|||
"devDependencies": [ |
|||
"qunit": "~1" |
|||
] |
|||
} |
|||
``` |
|||
|
|||
### Installing dependencies |
|||
|
|||
Dependencies are installed locally via the `bower install` command. First they’re resolved to find conflicts. Then they’re downloaded and unpacked in a local subdirectory called `./components`, for example: |
|||
|
|||
|
|||
``` |
|||
/component.json |
|||
/components/jquery/index.js |
|||
/components/jquery/component.json |
|||
``` |
|||
|
|||
You can also install packages one at a time `bower install git://my/git/thing` |
|||
|
|||
There are no system wide dependencies, no dependencies are shared between different apps, and the dependency tree is flat. |
|||
|
|||
### Deploying |
|||
|
|||
The easiest approach is to use Bower statically, just reference the packages manually from a script tag: |
|||
|
|||
<script src="components/jquery/index.js"></script> |
|||
|
|||
For more complex projects, you'll probably want to concatenate your scripts. Bower is just a package manager, but there are lots of awesome libraries out there to help you do this, such as [Sprockets](https://github.com/sstephenson/sprockets) and [RequireJS](http://requirejs.org/). |
|||
|
|||
For example, to use Sprockets: |
|||
|
|||
```ruby |
|||
environment = Sprockets::Environment.new |
|||
environment.append_path 'components' |
|||
environment.append_path 'public' |
|||
run environment |
|||
``` |
|||
|
|||
### Package Consumption |
|||
|
|||
Bower also makes available a source mapping – this can be used by build tools to easily consume Bower components. |
|||
|
|||
If you pass the option `--map` to bower's `list` command, it will generate a JSON with dependency objects. Alternatively, you can pass the `--paths` flag to the `list` command to get a simple path to name mapping: |
|||
|
|||
```json |
|||
{ |
|||
"backbone": "components/backbone/index.js", |
|||
"jquery": "components/jquery/index.js", |
|||
"underscore": "components/underscore/index.js" |
|||
} |
|||
``` |
|||
|
|||
### Authoring packages |
|||
|
|||
To register a new package, it's as simple as specifying a `component.json`, pushing the package to a Git endpoint, say GitHub, and running: |
|||
|
|||
bower register myawesomepackagename git://github.com/maccmans/face |
|||
|
|||
There's no authentication or user management. It's on a first come, first served basis. Think of it like a URL shortener. Now anyone can run `bower install myawesomepackagename`, and get your library installed. |
|||
|
|||
There is no direct way to unregister a package yet. Meanwhile you can request it [here](https://github.com/twitter/bower/issues/120). |
|||
|
|||
### Philosophy |
|||
|
|||
Currently, people are managing dependencies, such as JavaScript libraries, manually. This sucks, and we want to change it. |
|||
|
|||
In a nutshell, Bower is a generic tool which will resolve dependencies and lock packages down to a version. It runs over Git, and is package-agnostic. A package may contain JavaScript, CSS, images, etc., and doesn't rely on any particular transport (AMD, CommonJS, etc.). |
|||
|
|||
Bower then makes available a simple programmatic API which exposes the package dependency model, so that existing build tools (like Sprockets, LoadBuilder, curls.js, Ender, etc.) can consume it and build files accordingly. |
|||
|
|||
|
|||
### Programmatic API |
|||
|
|||
Bower provides a pretty powerful programmatic api. All commands can be accessed through the `bower.commands` object. |
|||
|
|||
```js |
|||
var bower = require('bower'); |
|||
|
|||
bower.commands |
|||
.install(paths, options) |
|||
.on('end', function (data) { |
|||
data && console.log(data); |
|||
}); |
|||
|
|||
bower.commands |
|||
.search('jquery', {}) |
|||
.on('packages', function(packages) { |
|||
/* `packages` is a list of packages returned by searching for 'jquery' */ |
|||
}); |
|||
|
|||
``` |
|||
|
|||
Commands emit four types of events: `data`, `end`, `result`, and `error`. `error` will only be emitted if something goes wrong. Not all commands emit all events; for a detailed look, check out the code in `lib/commands`. `data` is typically a colorized string, ready to show to an end user. `search` and `lookup` emit `packages` and `package`, respectively. Those events contain a json representation of the result of the command. |
|||
|
|||
For a better of idea how this works, you may want to check out [our bin file](https://github.com/twitter/bower/blob/master/bin/bower). |
|||
|
|||
For the install command, there is an additional `package` event that is emitted for each installed/uninstalled package. |
|||
|
|||
|
|||
### Completion |
|||
|
|||
**experimental** |
|||
|
|||
Based on the completion feature and fantastic work done in |
|||
[npm](https://npmjs.org/doc/completion.html), Bower now has an experimental |
|||
`completion` command that works similarly. |
|||
|
|||
This command will output a Bash / ZSH script to put into your `~/.bashrc`, `~/.bash_profile` or `~/.zshrc` file. |
|||
|
|||
``` |
|||
bower completion >> ~/.bash_profile |
|||
``` |
|||
|
|||
*This doesn't work for Windows user, even with Cygwin.* |
|||
|
|||
|
|||
### Windows users |
|||
|
|||
A lot of people are experiencing problems using bower on windows because [msysgit](http://code.google.com/p/msysgit/) must be installed correctly. |
|||
Be sure to check the option shown above, otherwise it will simply not work: |
|||
|
|||
 |
|||
|
|||
|
|||
### FAQ |
|||
|
|||
**What distinguishes Bower from Jam, Volo or Ender? What does it do better?** |
|||
|
|||
Bower is a lower level component than Jam, Volo, or Ender. These managers could consume Bower as a dependency. |
|||
|
|||
Bower's aim is simply to install Git paths, resolve dependencies from a `component.json`, check versions, and then provide an API which reports on these things. Nothing more. This is a major diversion from past attempts at browser package management. |
|||
|
|||
Bower is working under the assumption that there is a single, common problem in frontend application development: dependency resolution. Past attempts (Jam, Volo, Ender) try to tackle this problem in such a way that they actually end up alienating and further segregating the JavaScript community around transports (Sprockets, CommonJS, RequireJS, regular script tags). |
|||
|
|||
Bower offers a generic, unopinionated solution to the problem of package management, while exposing an API that can be consumed by a more opinionated build stack. |
|||
|
|||
**Volo is an arguably more established project and works with the GitHub search API. Will it take long for Bower to contain a decent number of packages?** |
|||
|
|||
Bower (being a Git powered package manager) should, in theory, be capable of consuming every package that Volo does, with the additional benefit of supporting internal networks and other Git repositories not hosted on GitHub. |
|||
|
|||
**We recently saw what happened when the main NPM registry went down. Is a single point of failure possible for Bower and if so, do you have redundancy planned?** |
|||
|
|||
There's no redundancy planned at the moment, as Bower just installs Git URLs. It's up to the URL provider to establish redundancy. |
|||
|
|||
**Isn't having a `package.json` file going to conflict with my npm's `package.json`? Will this be a problem?** |
|||
|
|||
Don't use a `package.json` – use a `component.json`. |
|||
|
|||
**Bower is an open-source Twitter project. How well can we expect it to be maintained in the future?** |
|||
|
|||
Twitter is in the process of migrating its frontend architecture onto Bower, so it's fairly safe to say it will be maintained and invested in going forward. |
|||
|
|||
|
|||
### Contact |
|||
|
|||
Have a question? |
|||
|
|||
- [StackOverflow](http://stackoverflow.com/questions/tagged/bower) |
|||
- [Mailinglist](http://groups.google.com/group/twitter-bower) - twitter-bower@googlegroups.com |
|||
- [\#bower](http://webchat.freenode.net/?channels=bower) on Freenode |
|||
|
|||
|
|||
### Authors |
|||
|
|||
+ [@fat](http://github.com/fat) |
|||
+ [@maccman](http://github.com/maccman) |
|||
+ [@satazor](http://github.com/satazor) |
|||
|
|||
Thanks for assistance and contributions: |
|||
|
|||
+ [@addyosmani](http://github.com/addyosmani) |
|||
+ [@angus-c](http://github.com/angus-c) |
|||
+ [@borismus](http://github.com/borismus) |
|||
+ [@chriseppstein](http://github.com/chriseppstein) |
|||
+ [@danwrong](http://github.com/danwrong) |
|||
+ [@desandro](http://github.com/desandro) |
|||
+ [@isaacs](http://github.com/isaacs) |
|||
+ [@josh](http://github.com/josh) |
|||
+ [@jrburke](http://github.com/jrburke) |
|||
+ [@mklabs](http://github.com/mklabs) |
|||
+ [@paulirish](http://github.com/paulirish) |
|||
+ [@rvagg](http://github.com/rvagg) |
|||
+ [@sindresorhus](http://github.com/sindresorhus) |
|||
+ [@SlexAxton](http://github.com/SlexAxton) |
|||
+ [@sstephenson](http://github.com/sstephenson) |
|||
+ [@tomdale](http://github.com/tomdale) |
|||
+ [@uzquiano](http://github.com/uzquiano) |
|||
+ [@visionmedia](http://github.com/visionmedia) |
|||
+ [@wagenet](http://github.com/wagenet) |
|||
+ [@wycats](http://github.com/wycats) |
|||
+ [@sindresorhus](http://github.com/sindresorhus) |
|||
+ [@hemanth](http://github.com/hemanth) |
|||
+ [@wibblymat](http://github.com/wibblymat) |
|||
+ [@marcelombc](http://github.com/marcelombc) |
|||
|
|||
## License |
|||
|
|||
Copyright 2012 Twitter, Inc. |
|||
|
|||
Licensed under the MIT License |
@ -0,0 +1,44 @@ |
|||
#!/usr/bin/env node |
|||
|
|||
var semver = require('semver'); |
|||
var nopt = require('nopt'); |
|||
var path = require('path'); |
|||
var pkg = require(path.join(__dirname, '..', 'package.json')); |
|||
|
|||
var template = require('../lib/util/template'); |
|||
var bower = require('../lib'); |
|||
|
|||
var command; |
|||
var options; |
|||
var shorthand; |
|||
var input = process.argv; |
|||
var cmdList = Object.keys(bower.commands); |
|||
var nodeVer = process.version; |
|||
var reqVer = pkg.engines.node; |
|||
|
|||
process.title = 'bower'; |
|||
|
|||
if (reqVer && !semver.satisfies(nodeVer, reqVer)) { |
|||
throw new Error('Required: node ' + reqVer); |
|||
} |
|||
|
|||
shorthand = { 'v': ['--version'] }; |
|||
options = { version: Boolean }; |
|||
options = nopt(options, shorthand, process.argv); |
|||
|
|||
bower.version = pkg.version; |
|||
|
|||
if (options.version) return console.log(bower.version); |
|||
if (~cmdList.indexOf(command = options.argv.remain && options.argv.remain.shift())) bower.command = command; |
|||
|
|||
bower.commands[bower.command || 'help'].line(input) |
|||
.on('data', function (data) { |
|||
if (data) console.log(data); |
|||
}) |
|||
.on('end', function (data) { |
|||
if (data) console.log(data); |
|||
}) |
|||
.on('error', function (err) { |
|||
if (options.verbose) throw err; |
|||
else console.log(template('error', { message: err.message }, true)); |
|||
}); |
@ -0,0 +1,164 @@ |
|||
// ==========================================
|
|||
// BOWER: CacheClean API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var async = require('async'); |
|||
var nopt = require('nopt'); |
|||
var rimraf = require('rimraf'); |
|||
var path = require('path'); |
|||
var glob = require('glob'); |
|||
var fs = require('fs'); |
|||
var _ = require('lodash'); |
|||
|
|||
var help = require('./help'); |
|||
var config = require('../core/config'); |
|||
var template = require('../util/template'); |
|||
var fileExists = require('../util/file-exists'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'] }; |
|||
|
|||
var processCachedPackage = function (emitter, pkg, next) { |
|||
removeCachedPackage(pkg, function (err, exists) { |
|||
if (err) { |
|||
emitter.emit('error', err); |
|||
return next(); |
|||
} |
|||
|
|||
if (exists) { |
|||
template('action', { name: 'cache cleared', shizzle: pkg }) |
|||
.on('data', emitter.emit.bind(emitter, 'data')); |
|||
} |
|||
|
|||
next(); |
|||
}); |
|||
}; |
|||
|
|||
var removeCachedPackage = function (pkg, next) { |
|||
var folder = path.join(config.cache, pkg); |
|||
|
|||
fileExists(folder, function (exists) { |
|||
if (!exists) return next(null, false); |
|||
rimraf(folder, function (err) { |
|||
if (err) return next(err); |
|||
next(null, true); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
var processLinkedPackage = function (emitter, pkg, next) { |
|||
checkAndRemoveLinkToPackage(pkg, function (err, removed) { |
|||
if (err) { |
|||
emitter.emit('error', err); |
|||
return next(); |
|||
} |
|||
|
|||
if (removed) { |
|||
template('action', { name: 'link cleared', shizzle: pkg }) |
|||
.on('data', emitter.emit.bind(emitter, 'data')); |
|||
} |
|||
|
|||
next(); |
|||
}); |
|||
}; |
|||
|
|||
var checkAndRemoveLinkToPackage = function (pkg, next) { |
|||
var folder = path.join(config.links, pkg); |
|||
|
|||
fs.readlink(folder, function (err, linkString) { |
|||
if (err && err.code === 'ENOENT') return next(); |
|||
|
|||
fileExists(linkString, function (exists) { |
|||
if (!exists) { |
|||
return rimraf(folder, function (err) { |
|||
if (err) return next(err); |
|||
next(null, true); |
|||
}); |
|||
} |
|||
|
|||
next(null, false); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
module.exports = function (pkgs) { |
|||
var emitter = new Emitter; |
|||
|
|||
async.parallel({ |
|||
cache: function (next) { |
|||
if (!pkgs || !pkgs.length) { |
|||
glob('./*', { cwd: config.cache }, function (err, dirs) { |
|||
if (err) { |
|||
emitter.emit('error', err); |
|||
return next(); |
|||
} |
|||
|
|||
var pkgs = dirs.map(function (dir) { return dir.replace(/^\.\//, ''); }); |
|||
async.forEach(pkgs, processCachedPackage.bind(this, emitter), next); |
|||
}); |
|||
} else { |
|||
pkgs = _.uniq(pkgs); |
|||
async.forEach(pkgs, processCachedPackage.bind(this, emitter), next); |
|||
} |
|||
}, |
|||
links: function (next) { |
|||
if (!pkgs || !pkgs.length) { |
|||
glob('./*', { cwd: config.links }, function (err, dirs) { |
|||
if (err) { |
|||
emitter.emit('error', err); |
|||
return next(); |
|||
} |
|||
|
|||
var pkgs = dirs.map(function (dir) { return dir.replace(/^\.\//, ''); }); |
|||
async.forEach(pkgs, processLinkedPackage.bind(this, emitter), next); |
|||
}); |
|||
} else { |
|||
pkgs = _.uniq(pkgs); |
|||
async.forEach(pkgs, processLinkedPackage.bind(this, emitter), next); |
|||
} |
|||
}, |
|||
completion: function (next) { |
|||
// Do not run completion cache if packages where passed
|
|||
if (pkgs && pkgs.length) return next(); |
|||
|
|||
rimraf(config.completion, function (err) { |
|||
if (err) { |
|||
emitter.emit('error', err); |
|||
return next(); |
|||
} |
|||
|
|||
template('action', { name: 'completion cleared', shizzle: 'completion cache' }) |
|||
.on('data', emitter.emit.bind(emitter, 'data')); |
|||
|
|||
next(); |
|||
}); |
|||
} |
|||
}, emitter.emit.bind(emitter, 'end')); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var pkgs = options.argv.remain.slice(1); |
|||
|
|||
if (options.help) return help('cache-clean'); |
|||
return module.exports(pkgs); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
glob('./*', { cwd: config.cache }, function (err, dirs) { |
|||
if (err) return cb(err); |
|||
dirs = dirs.map(function (dir) { |
|||
return dir.replace(/^\.\//, ''); |
|||
}); |
|||
cb(null, dirs); |
|||
}); |
|||
}; |
|||
|
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,112 @@ |
|||
// ==========================================
|
|||
// BOWER: Completion API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var path = require('path'); |
|||
var nopt = require('nopt'); |
|||
var mkdirp = require('mkdirp'); |
|||
|
|||
var template = require('../util/template'); |
|||
var complete = require('../util/completion'); |
|||
var config = require('../core/config'); |
|||
var help = require('./help'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'] }; |
|||
|
|||
module.exports = function (argv, env) { |
|||
env = env || process.env; |
|||
|
|||
var emitter = new Emitter; |
|||
var commands = require('../commands'); |
|||
|
|||
// top level flags
|
|||
var flags = ['--no-color', '--help', '--version']; |
|||
|
|||
var done = function done() { |
|||
process.nextTick(function () { |
|||
emitter.emit('end'); |
|||
}); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
// if the COMP_* isn't in the env, then just dump the script.
|
|||
if (!env.COMP_CWORD) { |
|||
template('completion').on('data', emitter.emit.bind(emitter, 'end')); |
|||
return emitter; |
|||
} |
|||
|
|||
// parse environment and arguments, returns a hash of completion config.
|
|||
var opts = complete(argv, env); |
|||
|
|||
// if only one word, complete the list of command or top level flags
|
|||
if (opts.w === 1) { |
|||
if (opts.word.charAt(0) === '-') complete.log(flags, opts); |
|||
else complete.log(Object.keys(commands), opts); |
|||
return done(); |
|||
} |
|||
|
|||
// try to find the bower command. first thing after all the configs.
|
|||
var parsed = opts.conf = nopt({}, {}, opts.partialWords, 0); |
|||
var cmd = parsed.argv.remain[0]; |
|||
|
|||
// unable to find a command, complete the lisf of commands
|
|||
if (!cmd) { |
|||
complete.log(Object.keys(commands), opts); |
|||
return done(); |
|||
} |
|||
|
|||
// if words[0] is a bower command, then complete on it.
|
|||
cmd = commands[cmd]; |
|||
|
|||
if (cmd && cmd.completion) { |
|||
// prior to that, ensure the completion cache directory is created first
|
|||
mkdirp(path.join(config.completion), function (err) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
var options = cmd.completion.options; |
|||
if (options && opts.word.charAt(0) === '-') { |
|||
complete.log(Object.keys(options).map(function (option) { |
|||
return opts.word.charAt(1) === '-' ? options[option][0] : '-' + option; |
|||
}), opts); |
|||
return done(); |
|||
} |
|||
|
|||
cmd.completion(opts, function (err, data) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
// completing options, then append top level flags
|
|||
if (opts.word.charAt(0) === '-') data = data.concat(flags); |
|||
|
|||
complete.log(data, opts); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
return emitter; |
|||
} |
|||
|
|||
// otherwise, do nothing
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var emitter = new Emitter; |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
|
|||
if (options.help) return help('completion'); |
|||
|
|||
module.exports(options.argv.remain.slice(2), process.env) |
|||
.on('data', emitter.emit.bind(emitter, 'data')) |
|||
.on('error', emitter.emit.bind(emitter, 'error')) |
|||
.on('end', emitter.emit.bind(emitter, 'end')); |
|||
|
|||
return emitter; |
|||
}; |
@ -0,0 +1,49 @@ |
|||
// ==========================================
|
|||
// BOWER: Help API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var events = require('events'); |
|||
var nopt = require('nopt'); |
|||
var _ = require('lodash'); |
|||
|
|||
var template = require('../util/template'); |
|||
var config = require('../core/config'); |
|||
|
|||
module.exports = function (name) { |
|||
var context = {}; |
|||
var emitter = new events.EventEmitter; |
|||
var commands = require('../commands'); |
|||
|
|||
// Aliases
|
|||
// At the moment we just have the ls, but we might have more
|
|||
switch (name) { |
|||
case 'ls': |
|||
name = 'list'; |
|||
break; |
|||
} |
|||
|
|||
var validCommand = !!(name && commands[name]); |
|||
var templateName = validCommand ? 'help-' + name : 'help'; |
|||
|
|||
if (!validCommand) context = { commands: Object.keys(commands).join(', ') }; |
|||
_.extend(context, config); |
|||
|
|||
template(templateName, context) |
|||
.on('data', emitter.emit.bind(emitter, 'end')); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt({}, {}, argv); |
|||
var paths = options.argv.remain.slice(1); |
|||
return module.exports(paths[0]); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
return cb(null, Object.keys(require('../commands'))); |
|||
}; |
@ -0,0 +1,24 @@ |
|||
// ==========================================
|
|||
// BOWER: Public Commands List
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
module.exports = { |
|||
'help': require('./help'), |
|||
'install': require('./install'), |
|||
'list': require('./list'), |
|||
'ls': require('./list'), |
|||
'uninstall': require('./uninstall'), |
|||
'update': require('./update'), |
|||
'link': require('./link'), |
|||
'lookup': require('./lookup'), |
|||
'info': require('./info'), |
|||
'init': require('./init'), |
|||
'register': require('./register'), |
|||
'search': require('./search'), |
|||
'cache-clean': require('./cache-clean'), |
|||
'completion': require('./completion') |
|||
}; |
@ -0,0 +1,50 @@ |
|||
// ==========================================
|
|||
// BOWER: Lookup API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var nopt = require('nopt'); |
|||
|
|||
var template = require('../util/template'); |
|||
var source = require('../core/source'); |
|||
var install = require('./install'); |
|||
var help = require('./help'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'] }; |
|||
|
|||
module.exports = function (name) { |
|||
var emitter = new Emitter; |
|||
|
|||
if (name) { |
|||
source.info(name, function (err, result) { |
|||
if (err) return emitter.emit('error', err); |
|||
emitter.emit('end', result); |
|||
}); |
|||
} |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var emitter = new Emitter; |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var names = options.argv.remain.slice(1); |
|||
|
|||
if (options.help || !names.length) return help('info'); |
|||
|
|||
module.exports(names[0]) |
|||
.on('error', emitter.emit.bind(emitter, 'error')) |
|||
.on('end', function (data) { |
|||
template('info', data).on('data', emitter.emit.bind(emitter, 'end')); |
|||
}); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.completion = install.completion; |
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,150 @@ |
|||
// ==========================================
|
|||
// BOWER: Init API
|
|||
// ==========================================
|
|||
// Copyright 2013 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
|
|||
var path = require('path'); |
|||
var fs = require('fs'); |
|||
var util = require('util'); |
|||
|
|||
var nopt = require('nopt'); |
|||
var promptly = require('promptly'); |
|||
var _ = require('lodash'); |
|||
|
|||
var help = require('./help'); |
|||
var Manager = require('../core/manager'); |
|||
var config = require('../core/config'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'] }; |
|||
|
|||
var commonIgnore = ['**/.*', 'node_modules', 'components']; |
|||
|
|||
var Init = function () { |
|||
Manager.call(this); |
|||
}; |
|||
|
|||
util.inherits(Init, Manager); |
|||
|
|||
Init.prototype.getDependenciesJSON = function () { |
|||
var dependencies = Object.keys(this.dependencies); |
|||
var remaining = dependencies.length; |
|||
var json = {}; |
|||
|
|||
dependencies.forEach(function (name) { |
|||
var pkg = this.dependencies[name][0]; |
|||
|
|||
pkg.on('loadJSON', function () { |
|||
// TODO: use fetch endpoint here
|
|||
json[pkg.name] = '~' + pkg.version; |
|||
remaining -= 1; |
|||
if (remaining === 0) { |
|||
this.manager.emit('loadDependencies', json); |
|||
} |
|||
}).loadJSON(); |
|||
}, this); |
|||
|
|||
if (remaining === 0) { |
|||
this.emit('loadDependencies', json); |
|||
} |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
Init.prototype.getMain = function (name) { |
|||
name = path.basename(name, '.js'); |
|||
if (fs.existsSync(path.join(process.cwd(), 'index.js'))) { |
|||
return 'index.js'; |
|||
} else if (fs.existsSync(path.join(process.cwd(), name + '.js'))) { |
|||
return name + '.js'; |
|||
} else { |
|||
return null; |
|||
} |
|||
}; |
|||
|
|||
Init.prototype.showPrompt = function (question, cb) { |
|||
var prompt = question.prompt + ': [' + (question.yesno ? 'y' : question.value) + ']'; |
|||
if (question.yesno) { |
|||
promptly.confirm(prompt, {'default': 'y'}, cb); |
|||
} else { |
|||
promptly.prompt(prompt, {'default': question.value}, cb); |
|||
} |
|||
this.emit('prompt', prompt); |
|||
}; |
|||
|
|||
Init.prototype.prompts = function (dependencies) { |
|||
var main = this.json.main || this.getMain(this.json.name) || ''; |
|||
|
|||
var questions = _.compact([ |
|||
{key: 'name', prompt: 'name', value: this.json.name, yesno: false}, |
|||
{key: 'version', prompt: 'version', value: this.json.version, yesno: false}, |
|||
{key: 'main', prompt: 'main file', value: main, yesno: false}, |
|||
_.size(dependencies) ? {key: 'dependencies', prompt: 'add current components as dependencies? (y/n)', value: dependencies, yesno: true} : null, |
|||
{key: 'ignore', prompt: 'add commonly ignored files to ignore list? (y/n)', value: commonIgnore, yesno: true} |
|||
]); |
|||
var index = 0; |
|||
var question = questions[index]; |
|||
|
|||
var cb = function (err, value) { |
|||
if (question.yesno) { |
|||
if (value) { |
|||
this.json[question.key] = question.value; |
|||
} |
|||
} else if (value) { |
|||
this.json[question.key] = value; |
|||
} |
|||
index += 1; |
|||
if (index < questions.length) { |
|||
question = questions[index]; |
|||
this.showPrompt(question, cb); |
|||
} else { |
|||
this.emit('prompts'); |
|||
} |
|||
}.bind(this); |
|||
|
|||
this.showPrompt(question, cb); |
|||
}; |
|||
|
|||
Init.prototype.save = function (data) { |
|||
fs.writeFileSync(path.join(this.cwd, config.json), JSON.stringify(data, null, 2)); |
|||
}; |
|||
|
|||
module.exports = function () { |
|||
var init = new Init(); |
|||
|
|||
init |
|||
.on('resolveLocal', init.loadJSON.bind(init)) |
|||
.on('loadJSON', init.getDependenciesJSON.bind(init)) |
|||
.on('loadDependencies', init.prompts.bind(init)) |
|||
.on('prompts', function () { |
|||
init.save(init.json); |
|||
init.emit('end'); |
|||
}) |
|||
.resolveLocal(); |
|||
|
|||
return init; |
|||
}; |
|||
|
|||
module.exports.Init = Init; // Purely for testing
|
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
if (options.help) return help('init'); |
|||
return module.exports(); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
var word = opts.word; |
|||
|
|||
// completing options?
|
|||
if (word.charAt(0) === '-') { |
|||
return cb(null, Object.keys(optionTypes).map(function (option) { |
|||
return '--' + option; |
|||
})); |
|||
} |
|||
}; |
|||
|
@ -0,0 +1,81 @@ |
|||
// ==========================================
|
|||
// BOWER: Install API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var nopt = require('nopt'); |
|||
var fs = require('fs'); |
|||
var path = require('path'); |
|||
|
|||
var Manager = require('../core/manager'); |
|||
var config = require('../core/config'); |
|||
var source = require('../core/source'); |
|||
var save = require('../util/save'); |
|||
var help = require('./help'); |
|||
|
|||
var optionTypes = { help: Boolean, save: Boolean, 'save-dev': Boolean, force: Boolean, 'force-latest': Boolean, production: Boolean }; |
|||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'D': ['--save-dev'], 'f': ['--force'], 'F': ['--force-latest'], 'p': ['--production'] }; |
|||
|
|||
module.exports = function (paths, options) { |
|||
options = options || {}; |
|||
|
|||
var emitter = new Emitter; |
|||
var manager = new Manager(paths, { |
|||
force: options.force, |
|||
forceLatest: options['force-latest'], |
|||
production: options.production |
|||
}); |
|||
|
|||
manager |
|||
.on('data', emitter.emit.bind(emitter, 'data')) |
|||
.on('error', emitter.emit.bind(emitter, 'error')) |
|||
.on('resolve', function (resolved) { |
|||
// Handle save/save-dev
|
|||
if (resolved && (options.save || options['save-dev'])) { |
|||
save(manager, paths, !options.save, emitter.emit.bind(emitter, 'end')); |
|||
} else { |
|||
emitter.emit('end'); |
|||
} |
|||
}) |
|||
.resolve(); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var paths = options.argv.remain.slice(1); |
|||
|
|||
if (options.help) return help('install'); |
|||
return module.exports(paths, options); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
var cache = path.join(config.completion, 'install.json'); |
|||
var done = function done(err, results) { |
|||
if (err) return cb(err); |
|||
var names = results.map(function (pkg) { |
|||
return pkg.name; |
|||
}); |
|||
|
|||
return cb(null, names); |
|||
}; |
|||
|
|||
fs.readFile(cache, function (err, body) { |
|||
if (!err) return done(null, JSON.parse(body)); |
|||
|
|||
// expected error, do the first request and cache the results
|
|||
source.all(function (err, results) { |
|||
if (err) return cb(err); |
|||
fs.writeFile(cache, JSON.stringify(results, null, 2), function (err) { |
|||
done(err, results); |
|||
}); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,121 @@ |
|||
// ==========================================
|
|||
// BOWER: Link API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var nopt = require('nopt'); |
|||
var fs = require('fs'); |
|||
var path = require('path'); |
|||
var mkdirp = require('mkdirp'); |
|||
var rimraf = require('rimraf'); |
|||
|
|||
var Manager = require('../core/manager'); |
|||
var help = require('./help'); |
|||
var template = require('../util/template'); |
|||
var config = require('../core/config'); |
|||
var isRepo = require('../util/is-repo'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'] }; |
|||
|
|||
function linkSelf(emitter) { |
|||
var manager = new Manager; |
|||
|
|||
manager |
|||
.on('error', emitter.emit.bind('error')) |
|||
.once('loadJSON', function () { |
|||
var destPath = path.join(config.links, manager.name); |
|||
var srcPath = process.cwd(); |
|||
|
|||
deleteLink(destPath, function (err) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
createLink(srcPath, destPath, function (err) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
template('link', { src: srcPath, dest: destPath }) |
|||
.on('data', emitter.emit.bind(emitter, 'end')); |
|||
}); |
|||
}); |
|||
}).loadJSON(); |
|||
} |
|||
|
|||
function linkTo(name, emitter) { |
|||
var destPath = path.join(process.cwd(), config.directory, name); |
|||
var srcPath = path.join(config.links, name); |
|||
|
|||
deleteLink(destPath, function (err) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
createLink(srcPath, destPath, function (err) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
template('link', { src: srcPath, dest: destPath }) |
|||
.on('data', emitter.emit.bind(emitter, 'end')); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function deleteLink(dest, callback) { |
|||
// Delete symlink if already present
|
|||
// Beware that if the target is a git repo, we can't proceed
|
|||
isRepo(dest, function (is) { |
|||
if (is) return callback(new Error(dest + ' is a local repository, please remove it manually')); |
|||
|
|||
fs.lstat(dest, function (err) { |
|||
if (!err || err.code !== 'ENOENT') rimraf(dest, callback); |
|||
else callback(); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function createLink(src, dest, callback) { |
|||
var destDir = path.dirname(dest); |
|||
|
|||
// Create directory
|
|||
mkdirp(destDir, function (err) { |
|||
if (err) return callback(err); |
|||
|
|||
fs.lstat(src, function (err) { |
|||
if (err && err.code === 'ENOENT') { |
|||
return callback(new Error('Attempting to link an unknown package: ' + path.basename(src))); |
|||
} |
|||
|
|||
// Create symlink
|
|||
fs.symlink(src, dest, 'dir', function (err) { |
|||
callback(err); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
module.exports = function (name) { |
|||
var emitter = new Emitter; |
|||
|
|||
if (!name) linkSelf(emitter); |
|||
else linkTo(name, emitter); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var name = options.argv.remain[1]; |
|||
|
|||
if (options.help) return help('link'); |
|||
return module.exports(name); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
fs.readdir(config.links, function (err, dirs) { |
|||
// ignore ENOENT, ~/.bower/links not created yet
|
|||
if (err && err.code === 'ENOENT') return cb(null, []); |
|||
cb(err, dirs); |
|||
}); |
|||
}; |
|||
|
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,236 @@ |
|||
// ==========================================
|
|||
// BOWER: List API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var archy = require('archy'); |
|||
var nopt = require('nopt'); |
|||
var path = require('path'); |
|||
var _ = require('lodash'); |
|||
|
|||
var template = require('../util/template'); |
|||
var Manager = require('../core/manager'); |
|||
var Package = require('../core/package'); |
|||
var config = require('../core/config'); |
|||
var help = require('./help'); |
|||
|
|||
var shorthand = { 'h': ['--help'], 'o': ['--offline'] }; |
|||
var optionTypes = { help: Boolean, paths: Boolean, map: Boolean, offline: Boolean, sources: Boolean }; |
|||
|
|||
var getTree = function (packages, subPackages, result) { |
|||
result = result || {}; |
|||
|
|||
_.each(subPackages || packages, function (pkg) { |
|||
|
|||
result[pkg.name] = {}; |
|||
|
|||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) { |
|||
result[pkg.name][name] = {}; |
|||
}); |
|||
|
|||
var subPackages = {}; |
|||
|
|||
Object.keys(pkg.json.dependencies || {}).forEach(function (name) { |
|||
subPackages[name] = packages[name] || new Package(name, null); |
|||
}); |
|||
|
|||
getTree(packages, subPackages, result[pkg.name]); |
|||
}); |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
var generatePath = function (name, main) { |
|||
if (typeof main === 'string') { |
|||
return path.join(config.directory, name, main); |
|||
} else if (_.isArray(main)) { |
|||
main = main.map(function (main) { return generatePath(name, main); }); |
|||
return main.length === 1 ? main[0] : main; |
|||
} |
|||
}; |
|||
|
|||
var mainTypes = ['main', 'scripts', 'styles', 'templates', 'images']; |
|||
|
|||
var buildSource = function (pkg, shallow) { |
|||
var result = {}; |
|||
|
|||
if (pkg) { |
|||
mainTypes.forEach(function (type) { |
|||
if (pkg.json[type]) result[type] = generatePath(pkg.name, pkg.json[type]); |
|||
}); |
|||
} |
|||
|
|||
if (shallow) { |
|||
result.main = getMain(result) || generatePath(pkg.name, ''); |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
var getMain = function (source) { |
|||
for (var i = 0, len = mainTypes.length; i < len; i += 1) { |
|||
var type = mainTypes[i]; |
|||
if (source[type]) { |
|||
return source[type]; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var shallowTree = function (packages, tree) { |
|||
var result = {}; |
|||
|
|||
Object.keys(tree).forEach(function (packageName) { |
|||
result[packageName] = buildSource(packages[packageName], true).main; |
|||
}); |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
var deepTree = function (packages, tree) { |
|||
|
|||
var result = {}; |
|||
|
|||
Object.keys(tree).forEach(function (packageName) { |
|||
|
|||
result[packageName] = {}; |
|||
result[packageName].source = buildSource(packages[packageName]); |
|||
|
|||
if (Object.keys(tree[packageName]).length) { |
|||
result[packageName].dependencies = deepTree(packages, tree[packageName]); |
|||
} |
|||
|
|||
}); |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
var getNodes = function (packages, tree) { |
|||
return Object.keys(tree).map(function (key) { |
|||
var version = packages[key] ? packages[key].version || '' : null; |
|||
var upgrade; |
|||
|
|||
if (version && packages[key].tags.indexOf(version)) { |
|||
upgrade = packages[key].tags[0]; |
|||
} |
|||
|
|||
if (Object.keys(tree[key]).length) { |
|||
return { |
|||
label: template('tree-branch', { 'package': key, version: version, upgrade: upgrade }, true), |
|||
nodes: getNodes(packages, tree[key]) |
|||
}; |
|||
} else { |
|||
return template('tree-branch', { 'package': key, version: version, upgrade: upgrade }, true); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
var getDependencySrcs = function (list) { |
|||
var srcs = []; |
|||
var dependency, main; |
|||
for (var name in list) { |
|||
dependency = list[name]; |
|||
main = dependency.source && getMain(dependency.source); |
|||
|
|||
if (dependency.dependencies) { |
|||
var depSrcs = getDependencySrcs(dependency.dependencies); |
|||
srcs.push.apply(srcs, depSrcs); |
|||
} |
|||
|
|||
// add main sources to srcs
|
|||
if (main) { |
|||
if (Array.isArray(main)) { |
|||
srcs.push.apply(srcs, main); |
|||
} else { |
|||
srcs.push(main); |
|||
} |
|||
} |
|||
|
|||
} |
|||
return srcs; |
|||
}; |
|||
|
|||
var organizeSources = function (tree) { |
|||
// flat source filepaths
|
|||
var srcs = getDependencySrcs(tree); |
|||
// remove duplicates, organize by file extension
|
|||
var sources = {}; |
|||
|
|||
srcs.forEach(function (src) { |
|||
var ext = path.extname(src); |
|||
sources[ext] = sources[ext] || []; |
|||
if (sources[ext].indexOf(src) === -1) { |
|||
sources[ext].push(src); |
|||
} |
|||
}); |
|||
|
|||
return sources; |
|||
}; |
|||
|
|||
module.exports = function (options) { |
|||
var manager = new Manager; |
|||
var emitter = new Emitter; |
|||
|
|||
options = options || {}; |
|||
|
|||
if (options.sources) { |
|||
options.map = true; |
|||
} |
|||
|
|||
var emitOut = function (obj) { |
|||
// make JSON pretty if started from command line
|
|||
var output = options.argv ? JSON.stringify(obj, null, 2) : obj; |
|||
emitter.emit('data', output); |
|||
}; |
|||
manager |
|||
.on('data', emitter.emit.bind(emitter, 'data')) |
|||
.on('error', emitter.emit.bind(emitter, 'error')) |
|||
.on('list', function (packages) { |
|||
// console.log(packages);
|
|||
// listTree(packages, options);
|
|||
var tree = getTree(packages); |
|||
if (!options.paths && !options.map) { |
|||
emitter.emit('data', archy({ |
|||
label: process.cwd(), |
|||
nodes: getNodes(packages, tree) |
|||
})); |
|||
return; |
|||
} |
|||
|
|||
tree = options.paths ? shallowTree(packages, tree) : deepTree(packages, tree); |
|||
|
|||
if (options.sources) { |
|||
// with map, organize it and emit
|
|||
var sources = organizeSources(tree); |
|||
emitOut(sources); |
|||
} else { |
|||
emitOut(tree); |
|||
} |
|||
|
|||
}) |
|||
.list(options); |
|||
|
|||
return emitter; |
|||
|
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
if (options.help) return help('list'); |
|||
return module.exports(options); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
if (!/^-/.test(opts.word)) return cb(null, []); |
|||
|
|||
var results = Object.keys(optionTypes).map(function (option) { |
|||
return '--' + option; |
|||
}); |
|||
|
|||
cb(null, results); |
|||
}; |
|||
|
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,64 @@ |
|||
// ==========================================
|
|||
// BOWER: Lookup API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var nopt = require('nopt'); |
|||
|
|||
var template = require('../util/template'); |
|||
var source = require('../core/source'); |
|||
var install = require('./install'); |
|||
var help = require('./help'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'] }; |
|||
|
|||
module.exports = function (name) { |
|||
var emitter = new Emitter; |
|||
|
|||
source.lookup(name, function (err, url) { |
|||
if (err) { |
|||
source.search(name, function (err, packages) { |
|||
if (packages.length) { |
|||
template('suggestions', { packages: packages, name: name }) |
|||
.on('data', function (data) { |
|||
emitter.emit('data', data); |
|||
emitter.emit('end'); |
|||
}); |
|||
} else { |
|||
template('warning-missing', {name: name}) |
|||
.on('data', function (data) { |
|||
emitter.emit('data', data); |
|||
emitter.emit('end'); |
|||
}); |
|||
} |
|||
}); |
|||
} else { |
|||
var result = { name: name, url: url }; |
|||
emitter.emit('package', result); |
|||
|
|||
template('lookup', result) |
|||
.on('data', function (data) { |
|||
emitter.emit('data', data); |
|||
emitter.emit('end'); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var names = options.argv.remain.slice(1); |
|||
|
|||
if (options.help || !names.length) return help('lookup'); |
|||
return module.exports(names[0]); |
|||
}; |
|||
|
|||
module.exports.completion = install.completion; |
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,66 @@ |
|||
// ==========================================
|
|||
// BOWER: Lookup API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var nopt = require('nopt'); |
|||
var readline = require('readline'); |
|||
|
|||
var template = require('../util/template'); |
|||
var source = require('../core/source'); |
|||
var help = require('./help'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'], '-s': ['--silent'] }; |
|||
|
|||
var register = function (name, url, emitter) { |
|||
source.register(name, url, function (err) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
template('register', {name: name, url: url}) |
|||
.on('data', emitter.emit.bind(emitter, 'data')); |
|||
}); |
|||
}; |
|||
|
|||
module.exports = function (name, url, options) { |
|||
var emitter = new Emitter; |
|||
|
|||
if (options.silent) register(name, url, emitter); |
|||
else { |
|||
var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); |
|||
|
|||
console.log('Registering a package will make it visible and installable via the registry.'); |
|||
rl.question('Proceed (y/n)? ', function (res) { |
|||
rl.close(); |
|||
|
|||
res = res.toLowerCase(); |
|||
|
|||
if (res === 'y' || res === 'yes') register(name, url, emitter); |
|||
}); |
|||
} |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var args = options.argv.remain.slice(1); |
|||
|
|||
if (options.help || args.length !== 2) return help('register'); |
|||
return module.exports(args[0], args[1], options); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
var word = opts.word; |
|||
|
|||
// completing options?
|
|||
if (word.charAt(0) === '-') { |
|||
return cb(null, Object.keys(optionTypes).map(function (option) { |
|||
return '--' + option; |
|||
})); |
|||
} |
|||
}; |
@ -0,0 +1,61 @@ |
|||
// ==========================================
|
|||
// BOWER: Lookup API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var nopt = require('nopt'); |
|||
|
|||
var template = require('../util/template'); |
|||
var source = require('../core/source'); |
|||
var install = require('./install'); |
|||
var help = require('./help'); |
|||
|
|||
var optionTypes = { help: Boolean }; |
|||
var shorthand = { 'h': ['--help'] }; |
|||
|
|||
module.exports = function (name) { |
|||
var emitter = new Emitter; |
|||
|
|||
var callback = function (err, results) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
emitter.emit('packages', results); |
|||
|
|||
if (results.length) { |
|||
template('search', {results: results}) |
|||
.on('data', function (data) { |
|||
emitter.emit('data', data); |
|||
emitter.emit('end'); |
|||
}); |
|||
} else { |
|||
template('search-empty', {results: results}) |
|||
.on('data', function (data) { |
|||
emitter.emit('data', data); |
|||
emitter.emit('end'); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
if (name) { |
|||
source.search(name, callback); |
|||
} else { |
|||
source.all(callback); |
|||
} |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var names = options.argv.remain.slice(1); |
|||
|
|||
if (options.help) return help('search'); |
|||
return module.exports(names[0]); |
|||
}; |
|||
|
|||
module.exports.completion = install.completion; |
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,206 @@ |
|||
// ==========================================
|
|||
// BOWER: Uninstall API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var async = require('async'); |
|||
var nopt = require('nopt'); |
|||
var fs = require('fs'); |
|||
var path = require('path'); |
|||
var _ = require('lodash'); |
|||
|
|||
var template = require('../util/template'); |
|||
var Manager = require('../core/manager'); |
|||
var config = require('../core/config'); |
|||
var help = require('./help'); |
|||
|
|||
var optionTypes = { help: Boolean, force: Boolean, save: Boolean }; |
|||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'D': ['--save-dev'], 'f': ['--force'] }; |
|||
|
|||
module.exports = function (names, options) { |
|||
var packages, uninstallables, packagesCount = {}; |
|||
var emitter = new Emitter; |
|||
var manager = new Manager; |
|||
var jsonDeps; |
|||
var newLine; |
|||
|
|||
options = options || {}; |
|||
|
|||
manager.on('data', emitter.emit.bind(emitter, 'data')); |
|||
manager.on('error', emitter.emit.bind(emitter, 'error')); |
|||
|
|||
var resolveLocal = function () { |
|||
jsonDeps = manager.json.dependencies || {}; |
|||
packages = _.flatten(_.values(manager.dependencies)); |
|||
uninstallables = packages.filter(function (pkg) { |
|||
return _.include(names, pkg.name); |
|||
}); |
|||
async.forEach(packages, function (pkg, next) { |
|||
pkg.once('loadJSON', next).loadJSON(); |
|||
}, function () { |
|||
if (showWarnings(options.force) && !options.force) return; |
|||
expandUninstallabes(options.force); |
|||
uninstall(); |
|||
}); |
|||
}; |
|||
|
|||
var showWarnings = function (force) { |
|||
var foundConflicts = false; |
|||
|
|||
packages.forEach(function (pkg) { |
|||
if (!pkg.json.dependencies) return; |
|||
if (containsPkg(uninstallables, pkg)) return; |
|||
|
|||
var conflicts = _.intersection( |
|||
Object.keys(pkg.json.dependencies), |
|||
_.pluck(uninstallables, 'name') |
|||
); |
|||
|
|||
if (conflicts.length) { |
|||
foundConflicts = true; |
|||
if (!force) { |
|||
conflicts.forEach(function (conflictName) { |
|||
emitter.emit('data', template('warning-uninstall', { packageName: pkg.name, conflictName: conflictName }, true)); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
if (foundConflicts && !force) { |
|||
emitter.emit('data', template('warn', { message: 'To proceed, run uninstall with the --force flag'}, true)); |
|||
} |
|||
|
|||
return foundConflicts; |
|||
}; |
|||
|
|||
var expandUninstallabes = function (force) { |
|||
var x, |
|||
pkg, |
|||
forcedUninstallables = {}; |
|||
|
|||
// Direct JSON deps have a count of 1
|
|||
for (var key in jsonDeps) { |
|||
packagesCount[key] = 1; |
|||
} |
|||
|
|||
// Count all packages
|
|||
count(packages, packagesCount); |
|||
|
|||
if (force) { |
|||
uninstallables.forEach(function (pkg) { |
|||
forcedUninstallables[pkg.name] = true; |
|||
}); |
|||
} |
|||
|
|||
// Expand the uninstallables deps and nested deps
|
|||
// Also update the count accordingly
|
|||
for (x = uninstallables.length - 1; x >= 0; x -= 1) { |
|||
parseUninstallableDeps(uninstallables[x]); |
|||
} |
|||
|
|||
// Foreach uninstallable, check if it is really to be removed by reading the final count
|
|||
// If the final count is greater than 0, then it is a shared dep
|
|||
// In that case, we remove it from the uninstallables unless it's forced to be uninstalled
|
|||
for (x = uninstallables.length - 1; x >= 0; x -= 1) { |
|||
pkg = uninstallables[x]; |
|||
if (packagesCount[pkg.name] > 0 && !forcedUninstallables[pkg.name]) uninstallables.splice(x, 1); |
|||
} |
|||
}; |
|||
|
|||
var count = function (packages, counts, nested) { |
|||
packages.forEach(function (pkg) { |
|||
counts[pkg.name] = (counts[pkg.name] || 0); |
|||
if (nested) counts[pkg.name] += 1; |
|||
|
|||
if (pkg.json.dependencies) { |
|||
for (var key in pkg.json.dependencies) { |
|||
count(manager.dependencies[key], counts, true); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
var parseUninstallableDeps = function (pkg) { |
|||
if (!containsPkg(uninstallables, pkg)) uninstallables.push(pkg); |
|||
packagesCount[pkg.name] -= 1; |
|||
|
|||
if (pkg.json.dependencies) { |
|||
for (var key in pkg.json.dependencies) { |
|||
parseUninstallableDeps(manager.dependencies[key][0]); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
var containsPkg = function (packages, pkg) { |
|||
for (var x = packages.length - 1; x >= 0; x -= 1) { |
|||
if (packages[x].name === pkg.name) return true; |
|||
} |
|||
|
|||
return false; |
|||
}; |
|||
|
|||
var uninstall = function () { |
|||
async.forEach(uninstallables, function (pkg, next) { |
|||
pkg.on('uninstall', function () { |
|||
emitter.emit('package', pkg); |
|||
next(); |
|||
}).uninstall(); |
|||
}, function () { |
|||
// Finally save
|
|||
if (options.save || options['save-dev']) save(!options.save); |
|||
emitter.emit('end'); |
|||
}); |
|||
}; |
|||
|
|||
var save = function (dev) { |
|||
var key = dev ? 'devDependencies' : 'dependencies'; |
|||
var contents; |
|||
|
|||
if (manager.json[key]) { |
|||
names.forEach(function (name) { |
|||
delete manager.json[key][name]; |
|||
}); |
|||
|
|||
contents = JSON.stringify(manager.json, null, 2) + (newLine ? '\n' : ''); |
|||
fs.writeFileSync(path.join(manager.cwd, config.json), contents); |
|||
} |
|||
}; |
|||
|
|||
manager.on('loadJSON', function (hasNewLine) { |
|||
newLine = hasNewLine; |
|||
manager.on('resolveLocal', resolveLocal).resolveLocal(); |
|||
}).loadJSON(); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
var names = options.argv.remain.slice(1); |
|||
|
|||
if (options.help || !names.length) return help('uninstall'); |
|||
return module.exports(names, options); |
|||
}; |
|||
|
|||
module.exports.completion = function (opts, cb) { |
|||
var word = opts.word; |
|||
|
|||
// completing options?
|
|||
if (opts.words[0] === 'uninstall' && word.charAt(0) === '-') { |
|||
return cb(null, Object.keys(optionTypes).map(function (option) { |
|||
return '--' + option; |
|||
})); |
|||
} |
|||
|
|||
fs.readdir(config.directory, function (err, dirs) { |
|||
// ignore ENOENT, ./components not created yet
|
|||
if (err && err.code === 'ENOENT') return cb(null, []); |
|||
cb(err, dirs); |
|||
}); |
|||
}; |
|||
|
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,95 @@ |
|||
// ==========================================
|
|||
// BOWER: Update API
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var Emitter = require('events').EventEmitter; |
|||
var async = require('async'); |
|||
var nopt = require('nopt'); |
|||
var _ = require('lodash'); |
|||
|
|||
var Manager = require('../core/manager'); |
|||
var help = require('./help'); |
|||
var uninstall = require('./uninstall'); |
|||
var save = require('../util/save'); |
|||
|
|||
var optionTypes = { help: Boolean, save: Boolean, force: Boolean, 'force-latest': Boolean }; |
|||
var shorthand = { 'h': ['--help'], 'S': ['--save'], 'f': ['--force'], 'F': ['--force-latest'] }; |
|||
|
|||
module.exports = function (names, options) { |
|||
options = options || {}; |
|||
|
|||
var emitter = new Emitter; |
|||
var manager = new Manager([], { |
|||
force: options.force, |
|||
forceLatest: options['force-latest'] |
|||
}); |
|||
|
|||
manager.on('data', emitter.emit.bind(emitter, 'data')); |
|||
manager.on('error', emitter.emit.bind(emitter, 'error')); |
|||
|
|||
var installURLS = function (err, arr) { |
|||
var mappings = {}, |
|||
endpoints = []; |
|||
|
|||
arr = _.compact(arr); |
|||
_.each(arr, function (info) { |
|||
endpoints.push(info.endpoint); |
|||
mappings[info.endpoint] = info.name; |
|||
}); |
|||
|
|||
options.endpointNames = mappings; |
|||
|
|||
// By default the manager will guess the name of the package from the url
|
|||
// But this leads to problems when the package name does not match the one in the url
|
|||
// So the manager now has an option (endpointNames) to deal with this
|
|||
manager = new Manager(endpoints, options); |
|||
|
|||
manager |
|||
.on('data', emitter.emit.bind(emitter, 'data')) |
|||
.on('error', emitter.emit.bind(emitter, 'error')) |
|||
.on('resolve', function (resolved) { |
|||
// Handle save
|
|||
if (resolved && options.save) save(manager, null, false, emitter.emit.bind(emitter, 'end')); |
|||
else emitter.emit('end'); |
|||
}) |
|||
.resolve(); |
|||
}; |
|||
|
|||
manager.once('resolveLocal', function () { |
|||
names = names.length ? _.uniq(names) : null; |
|||
|
|||
async.map(_.values(manager.dependencies), function (pkgs, next) { |
|||
var pkg = pkgs[0]; |
|||
pkg.once('loadJSON', function () { |
|||
var endpointInfo = pkg.readEndpoint(); |
|||
if (!endpointInfo) return next(); |
|||
|
|||
// Add tag only if the endpoint is a repository
|
|||
var endpoint = endpointInfo.endpoint; |
|||
var json = pkg.json; |
|||
if (!json.commit && (endpointInfo.type === 'git' || endpointInfo.type === 'local-repo')) { |
|||
endpoint += '#' + ((!names || names.indexOf(pkg.name) > -1) ? '~' : '') + pkg.version; |
|||
} |
|||
|
|||
next(null, { name: pkg.name, endpoint: endpoint }); |
|||
}).loadJSON(); |
|||
}, installURLS); |
|||
}).resolveLocal(); |
|||
|
|||
return emitter; |
|||
}; |
|||
|
|||
module.exports.line = function (argv) { |
|||
var options = nopt(optionTypes, shorthand, argv); |
|||
if (options.help) return help('update'); |
|||
|
|||
var paths = options.argv.remain.slice(1); |
|||
return module.exports(paths, options); |
|||
}; |
|||
|
|||
module.exports.completion = uninstall.completion; |
|||
module.exports.completion.options = shorthand; |
@ -0,0 +1,60 @@ |
|||
var path = require('path'); |
|||
var fs = require('fs'); |
|||
var _ = require('lodash'); |
|||
var tmp = require('tmp'); |
|||
|
|||
var fileExists = require('../util/file-exists').sync; |
|||
|
|||
var temp = process.env.TMPDIR |
|||
|| process.env.TMP |
|||
|| process.env.TEMP |
|||
|| process.platform === 'win32' ? 'c:\\windows\\temp' : '/tmp'; |
|||
|
|||
var home = (process.platform === 'win32' |
|||
? process.env.USERPROFILE |
|||
: process.env.HOME) || temp; |
|||
|
|||
var roaming = process.platform === 'win32' |
|||
? path.resolve(process.env.APPDATA || home || temp) |
|||
: path.resolve(home || temp); |
|||
|
|||
var folder = process.platform === 'win32' |
|||
? 'bower' |
|||
: '.bower'; |
|||
|
|||
var proxy = process.env.HTTPS_PROXY |
|||
|| process.env.https_proxy |
|||
|| process.env.HTTP_PROXY |
|||
|| process.env.http_proxy; |
|||
|
|||
// Bower Config
|
|||
var config; |
|||
try { |
|||
config = require('rc') ('bower', { |
|||
cache : path.join(roaming, folder, 'cache'), |
|||
links : path.join(roaming, folder, 'links'), |
|||
completion : path.join(roaming, folder, 'completion'), |
|||
json : 'component.json', |
|||
endpoint : 'https://bower.herokuapp.com', |
|||
directory : 'components', |
|||
proxy : proxy |
|||
}); |
|||
} catch (e) { |
|||
throw new Error('Unable to parse global .bowerrc file: ' + e.message); |
|||
} |
|||
|
|||
// If there is a local .bowerrc file, merge it
|
|||
var localFile = path.join(process.cwd(), '.bowerrc'); |
|||
if (fileExists(localFile)) { |
|||
try { |
|||
_.extend(config, JSON.parse(fs.readFileSync(localFile))); |
|||
} catch (e) { |
|||
throw new Error('Unable to parse local .bowerrc file: ' + e.message); |
|||
} |
|||
} |
|||
|
|||
// Configure tmp package to use graceful degradationn
|
|||
// If an uncaught exception occurs, the temporary directories will be deleted nevertheless
|
|||
tmp.setGracefulCleanup(); |
|||
|
|||
module.exports = config; |
@ -0,0 +1,361 @@ |
|||
// ==========================================
|
|||
// BOWER: Manager Object Definition
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
// Events:
|
|||
// - install: fired when everything is installed
|
|||
// - package: fired for each installed packaged
|
|||
// - resolve: fired when deps resolved (with a true/false indicating success or error)
|
|||
// - error: fired on all errors
|
|||
// - data: fired when trying to output data
|
|||
// - end: fired when finished installing
|
|||
// ==========================================
|
|||
|
|||
var events = require('events'); |
|||
var semver = require('semver'); |
|||
var async = require('async'); |
|||
var path = require('path'); |
|||
var glob = require('glob'); |
|||
var fs = require('fs'); |
|||
var _ = require('lodash'); |
|||
|
|||
var Package = require('./package'); |
|||
var UnitWork = require('./unit_work'); |
|||
var config = require('./config'); |
|||
var fileExists = require('../util/file-exists'); |
|||
var template = require('../util/template'); |
|||
var prune = require('../util/prune'); |
|||
|
|||
// read local dependencies (with versions)
|
|||
// read json dependencies (resolving along the way into temp dir)
|
|||
// merge local dependencies with json dependencies
|
|||
// prune and move dependencies into local directory
|
|||
|
|||
var Manager = function (endpoints, opts) { |
|||
this.dependencies = {}; |
|||
this.cwd = process.cwd(); |
|||
this.endpoints = endpoints || []; |
|||
this.unitWork = new UnitWork; |
|||
this.opts = opts || {}; |
|||
this.errors = []; |
|||
}; |
|||
|
|||
Manager.prototype = Object.create(events.EventEmitter.prototype); |
|||
Manager.prototype.constructor = Manager; |
|||
|
|||
Manager.prototype.loadJSON = function () { |
|||
var json = path.join(this.cwd, config.json); |
|||
fileExists(json, function (exists) { |
|||
if (!exists) { |
|||
// If the json does not exist, assume one
|
|||
this.json = { |
|||
name: path.basename(this.cwd), |
|||
version: '0.0.0' |
|||
}, |
|||
this.name = this.json.name; |
|||
this.version = this.json.version; |
|||
return this.emit('loadJSON'); |
|||
} |
|||
|
|||
fs.readFile(json, 'utf8', function (err, json) { |
|||
if (err) return this.emit('error', err); |
|||
try { |
|||
this.json = JSON.parse(json); |
|||
} catch (e) { |
|||
return this.emit('error', new Error('There was an error while reading the ' + config.json + ': ' + e.message)); |
|||
} |
|||
this.name = this.json.name; |
|||
this.version = this.json.version; |
|||
this.emit('loadJSON', json.slice(-1) === '\n'); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
Manager.prototype.resolve = function () { |
|||
var resolved = function () { |
|||
// If there is errors, report them
|
|||
if (this.errors.length) return this.reportErrors(); |
|||
// If there is an error while pruning (conflict) then abort installation
|
|||
if (!this.prune()) return this.emit('resolve', false); |
|||
// Otherwise all is fine, so we install
|
|||
this.once('install', this.emit.bind(this, 'resolve', true)).install(); |
|||
}.bind(this); |
|||
|
|||
// Resolve locally first
|
|||
this.once('resolveLocal', function () { |
|||
if (this.endpoints.length) { |
|||
// TODO: When resolving specific endpoints we need to restore all the local
|
|||
// packages and their hierarchy (all from the local folder)
|
|||
// If something goes wrong, simply do resolveFromJSON before
|
|||
// calling resolved() (slower)
|
|||
// This will solve issue #200
|
|||
this.once('resolveEndpoints', resolved).resolveEndpoints(); |
|||
} else { |
|||
this.once('resolveFromJson', resolved).resolveFromJson(); |
|||
} |
|||
}).resolveLocal(); |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
Manager.prototype.resolveLocal = function () { |
|||
glob('./' + config.directory + '/*', function (err, dirs) { |
|||
if (err) return this.emit('error', err); |
|||
dirs.forEach(function (dir) { |
|||
var name = path.basename(dir); |
|||
var pkg = new Package(name, dir, this); |
|||
|
|||
this.dependencies[name] = []; |
|||
this.dependencies[name].push(pkg); |
|||
|
|||
this.gatherPackageErrors(pkg); |
|||
}.bind(this)); |
|||
this.emit('resolveLocal'); |
|||
}.bind(this)); |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
Manager.prototype.resolveEndpoints = function () { |
|||
var endpointNames = this.opts.endpointNames || {}; |
|||
|
|||
async.forEach(this.endpoints, function (endpoint, next) { |
|||
var name = endpointNames[endpoint]; |
|||
var pkg = new Package(name, endpoint, this); |
|||
var errorNext; |
|||
|
|||
pkg.root = true; |
|||
this.dependencies[name] = this.dependencies[name] || []; |
|||
this.dependencies[name].push(pkg); |
|||
|
|||
this.gatherPackageErrors(pkg); |
|||
pkg.once('error', errorNext = next.bind(next, null)); |
|||
pkg.once('resolve', function () { |
|||
pkg.removeListener('error', errorNext); |
|||
next(); |
|||
}).resolve(); |
|||
}.bind(this), this.emit.bind(this, 'resolveEndpoints')); |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
Manager.prototype.resolveFromJson = function () { |
|||
this.once('loadJSON', function () { |
|||
var dependencies = this.json.dependencies || {}; |
|||
|
|||
// add devDependencies
|
|||
if (!this.opts.production && this.json.devDependencies) { |
|||
dependencies = _.extend({}, dependencies, this.json.devDependencies); |
|||
} |
|||
|
|||
async.forEach(Object.keys(dependencies), function (name, next) { |
|||
var endpoint = dependencies[name]; |
|||
var pkg = new Package(name, endpoint, this); |
|||
var errorNext; |
|||
|
|||
pkg.root = true; |
|||
this.dependencies[name] = this.dependencies[name] || []; |
|||
this.dependencies[name].push(pkg); |
|||
|
|||
this.gatherPackageErrors(pkg); |
|||
pkg.once('error', errorNext = next.bind(next, null)); |
|||
pkg.once('resolve', function () { |
|||
pkg.removeListener('error', errorNext); |
|||
next(); |
|||
}).resolve(); |
|||
}.bind(this), this.emit.bind(this, 'resolveFromJson')); |
|||
}.bind(this)).loadJSON(); |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
// Private
|
|||
Manager.prototype.getDeepDependencies = function () { |
|||
var result = {}; |
|||
|
|||
for (var name in this.dependencies) { |
|||
this.dependencies[name].forEach(function (pkg) { |
|||
result[pkg.name] = result[pkg.name] || []; |
|||
result[pkg.name].push(pkg); |
|||
pkg.getDeepDependencies().forEach(function (pkg) { |
|||
result[pkg.name] = result[pkg.name] || []; |
|||
result[pkg.name].push(pkg); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
Manager.prototype.prune = function () { |
|||
var result = prune(this.getDeepDependencies(), this.opts.forceLatest); |
|||
var name; |
|||
|
|||
// If there is conflicted deps, print them and fail
|
|||
if (result.conflicted) { |
|||
for (name in result.conflicted) { |
|||
this.reportConflicts(name, result.conflicted[name]); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
this.dependencies = {}; |
|||
|
|||
// If there is conflicted deps but they where forcebly resolved
|
|||
// Print a warning about them
|
|||
if (result.forceblyResolved) { |
|||
for (name in result.forceblyResolved) { |
|||
this.reportForceblyResolved(name, result.forceblyResolved[name]); |
|||
this.dependencies[name] = result.forceblyResolved[name]; |
|||
this.dependencies[name][0].root = true; |
|||
} |
|||
} |
|||
|
|||
_.extend(this.dependencies, result.resolved); |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
Manager.prototype.gatherPackageErrors = function (pkg) { |
|||
pkg.on('error', function (err, origin) { |
|||
pkg = origin || pkg; |
|||
|
|||
// If the error message starts with the package name, strip it
|
|||
if (!err.message.indexOf(pkg.name + ' ')) { |
|||
err.message = err.message.substr(pkg.name.length + 1); |
|||
} |
|||
|
|||
this.errors.push({ pkg: pkg, error: err }); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Manager.prototype.install = function () { |
|||
async.forEach(Object.keys(this.dependencies), function (name, next) { |
|||
var pkg = this.dependencies[name][0]; |
|||
pkg.once('install', function () { |
|||
this.emit('package', pkg); |
|||
next(); |
|||
}.bind(this)).install(); |
|||
pkg.once('error', next); |
|||
}.bind(this), function () { |
|||
if (this.errors.length) this.reportErrors(); |
|||
return this.emit('install'); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Manager.prototype.muteDependencies = function () { |
|||
for (var name in this.dependencies) { |
|||
this.dependencies[name].forEach(function (pkg) { |
|||
pkg.removeAllListeners(); |
|||
pkg.on('error', function () {}); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
Manager.prototype.reportErrors = function () { |
|||
this.muteDependencies(); |
|||
template('error-summary', { errors: this.errors }).on('data', function (data) { |
|||
this.emit('data', data); |
|||
this.emit('resolve', false); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Manager.prototype.reportConflicts = function (name, packages) { |
|||
var versions = []; |
|||
var requirements = []; |
|||
|
|||
packages = packages.filter(function (pkg) { return !!pkg.version; }); |
|||
packages.forEach(function (pkg) { |
|||
requirements.push({ pkg: pkg, tag: pkg.originalTag || '~' + pkg.version }); |
|||
versions.push((pkg.originalTag || '~' + pkg.version).white); |
|||
}); |
|||
|
|||
this.emit('error', new Error('No resolvable version for ' + name)); |
|||
this.emit('data', template('conflict', { |
|||
name: name, |
|||
requirements: requirements, |
|||
json: config.json, |
|||
versions: versions.slice(0, -1).join(', ') + ' or ' + versions[versions.length - 1] |
|||
}, true)); |
|||
}; |
|||
|
|||
Manager.prototype.reportForceblyResolved = function (name, packages) { |
|||
var requirements = []; |
|||
|
|||
packages = packages.filter(function (pkg) { return !!pkg.version; }); |
|||
packages.forEach(function (pkg) { |
|||
requirements.push({ pkg: pkg, tag: pkg.originalTag || '~' + pkg.version }); |
|||
}); |
|||
|
|||
this.emit('data', template('resolved-conflict', { |
|||
name: name, |
|||
requirements: requirements, |
|||
json: config.json, |
|||
resolvedTo: packages[0].version, |
|||
forceLatest: this.opts.forceLatest |
|||
}, true)); |
|||
}; |
|||
|
|||
|
|||
// ----- list ----- //
|
|||
|
|||
// Used in list command
|
|||
// TODO: not sure if this belongs here.. maybe move it to the list command?
|
|||
Manager.prototype.list = function (options) { |
|||
options = options || {}; |
|||
// If the user passed the paths or map options, we don't need to fetch versions
|
|||
this._isCheckingVersions = !options.offline && !options.paths && !options.map && options.argv; |
|||
this.once('resolveLocal', this.getDependencyList.bind(this)) |
|||
.resolveLocal(); |
|||
}; |
|||
|
|||
Manager.prototype.getDependencyList = function () { |
|||
|
|||
var packages = {}; |
|||
var values; |
|||
var checkVersions = this._isCheckingVersions; |
|||
|
|||
if (checkVersions) { |
|||
template('action', { name: 'discover', shizzle: 'Please wait while newer package versions are being discovered' }) |
|||
.on('data', this.emit.bind(this, 'data')); |
|||
} |
|||
|
|||
Object.keys(this.dependencies).forEach(function (key) { |
|||
packages[key] = this.dependencies[key][0]; |
|||
}.bind(this)); |
|||
|
|||
values = _.values(packages); |
|||
// Do not proceed if no values
|
|||
if (!values.length) { |
|||
return packages; |
|||
} |
|||
// Load JSON and get version for each package
|
|||
async.forEach(values, function (pkg, next) { |
|||
pkg.once('loadJSON', function () { |
|||
// Only check versions if not offline and it's a repo
|
|||
var fetchVersions = checkVersions && |
|||
pkg.json.repository && |
|||
(pkg.json.repository.type === 'git' || pkg.json.repository.type === 'local-repo'); |
|||
|
|||
if (fetchVersions) { |
|||
pkg.once('versions', function (versions) { |
|||
pkg.tags = versions.map(function (ver) { |
|||
return semver.valid(ver) ? semver.clean(ver) : ver; |
|||
}); |
|||
next(); |
|||
}).versions(); |
|||
} else { |
|||
pkg.tags = []; |
|||
next(); |
|||
} |
|||
}).loadJSON(); |
|||
}.bind(this), this.emit.bind(this, 'list', packages)); |
|||
}; |
|||
|
|||
module.exports = Manager; |
@ -0,0 +1,819 @@ |
|||
// ==========================================
|
|||
// BOWER: Package Object Definition
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
// Events:
|
|||
// - install: fired when package installed
|
|||
// - resolve: fired when deps resolved
|
|||
// - error: fired on all errors
|
|||
// - data: fired when trying to output data
|
|||
// ==========================================
|
|||
|
|||
var fstream = require('fstream'); |
|||
var mkdirp = require('mkdirp'); |
|||
var events = require('events'); |
|||
var rimraf = require('rimraf'); |
|||
var semver = require('semver'); |
|||
var async = require('async'); |
|||
var https = require('https'); |
|||
var http = require('http'); |
|||
var path = require('path'); |
|||
var glob = require('glob'); |
|||
var url = require('url'); |
|||
var tmp = require('tmp'); |
|||
var fs = require('fs'); |
|||
var crypto = require('crypto'); |
|||
var unzip = require('unzip'); |
|||
var tar = require('tar'); |
|||
var _ = require('lodash'); |
|||
|
|||
var config = require('./config'); |
|||
var source = require('./source'); |
|||
var template = require('../util/template'); |
|||
var readJSON = require('../util/read-json'); |
|||
var fileExists = require('../util/file-exists'); |
|||
var isRepo = require('../util/is-repo'); |
|||
var git = require('../util/git-cmd'); |
|||
var UnitWork = require('./unit_work'); |
|||
|
|||
var Package = function (name, endpoint, manager) { |
|||
this.dependencies = {}; |
|||
this.json = {}; |
|||
this.name = name; |
|||
this.manager = manager; |
|||
this.unitWork = manager ? manager.unitWork : new UnitWork; |
|||
this.opts = manager ? manager.opts : {}; |
|||
|
|||
if (endpoint) { |
|||
var split; |
|||
|
|||
if (/^(.*\.git)$/.exec(endpoint)) { |
|||
this.gitUrl = RegExp.$1.replace(/^git\+/, ''); |
|||
this.tag = false; |
|||
|
|||
} else if (/^(.*\.git)#(.*)$/.exec(endpoint)) { |
|||
this.tag = RegExp.$2; |
|||
this.gitUrl = RegExp.$1.replace(/^git\+/, ''); |
|||
|
|||
} else if (/^(?:(git):|git\+(https?):)\/\/([^#]+)#?(.*)$/.exec(endpoint)) { |
|||
this.gitUrl = (RegExp.$1 || RegExp.$2) + '://' + RegExp.$3; |
|||
this.tag = RegExp.$4; |
|||
|
|||
} else if (semver.validRange(endpoint)) { |
|||
this.tag = endpoint; |
|||
|
|||
} else if (/^[\.\/~]\.?[^.]*\.(js|css)/.test(endpoint) && fs.statSync(endpoint).isFile()) { |
|||
this.path = path.resolve(endpoint); |
|||
this.assetType = path.extname(endpoint); |
|||
|
|||
} else if (/^https?:\/\//.exec(endpoint)) { |
|||
this.assetUrl = endpoint; |
|||
this.assetType = path.extname(endpoint); |
|||
|
|||
} else if (fileExists.sync((split = endpoint.split('#', 2))[0]) && fs.statSync(split[0]).isDirectory()) { |
|||
this.path = path.resolve(split[0]); |
|||
this.tag = split[1]; |
|||
|
|||
} else if (/^[\.\/~]/.test(endpoint)) { |
|||
this.path = path.resolve(endpoint); |
|||
|
|||
} else if (endpoint.split('/').length === 2) { |
|||
split = endpoint.split('#', 2); |
|||
this.gitUrl = 'git://github.com/' + split[0] + '.git'; |
|||
this.tag = split[1]; |
|||
} else { |
|||
split = endpoint.split('#', 2); |
|||
this.tag = split[1]; |
|||
} |
|||
|
|||
// Guess names
|
|||
if (!this.name) { |
|||
if (this.gitUrl) this.name = path.basename(endpoint).replace(/(\.git)?(#.*)?$/, ''); |
|||
else if (this.path) this.name = path.basename(this.path, this.assetType); |
|||
else if (this.assetUrl) this.name = this.name = path.basename(this.assetUrl, this.assetType); |
|||
else if (split) this.name = split[0]; |
|||
} |
|||
|
|||
// Store a reference to the original tag & original path
|
|||
// This is because the tag & paths can get rewriten later
|
|||
if (this.tag) this.originalTag = this.tag; |
|||
if (this.path) this.originalPath = endpoint; |
|||
|
|||
// The id is an unique id that describes this package
|
|||
this.id = crypto.createHash('md5').update(this.name + '%' + this.tag + '%' + this.gitUrl + '%' + this.path + '%' + this.assetUrl).digest('hex'); |
|||
|
|||
// Generate a resource id
|
|||
if (this.gitUrl) this.generateResourceId(); |
|||
} |
|||
|
|||
if (this.manager) { |
|||
this.on('data', this.manager.emit.bind(this.manager, 'data')); |
|||
this.on('error', function (err, origin) { |
|||
// Unlock the unit of work automatically on error (only if the error is from this package)
|
|||
if (!origin && this.unitWork.isLocked(this.name)) this.unitWork.unlock(this.name, this); |
|||
// Propagate the error event to the parent package/manager
|
|||
this.manager.emit('error', err, origin || this); |
|||
}.bind(this)); |
|||
} |
|||
|
|||
// Cache a self bound function
|
|||
this.waitUnlock = this.waitUnlock.bind(this); |
|||
|
|||
this.setMaxListeners(30); // Increase the number of listeners because a package can have more than the default 10 dependencies
|
|||
}; |
|||
|
|||
Package.prototype = Object.create(events.EventEmitter.prototype); |
|||
|
|||
Package.prototype.constructor = Package; |
|||
|
|||
Package.prototype.resolve = function () { |
|||
// Ensure that nobody is resolving the same dep at the same time
|
|||
// If there is, we wait for the unlock event
|
|||
if (this.unitWork.isLocked(this.name)) return this.unitWork.on('unlock', this.waitUnlock); |
|||
|
|||
var data = this.unitWork.retrieve(this.name); |
|||
if (data) { |
|||
// Check if this exact package is the last resolved one
|
|||
// If so, we copy the resolved result and we don't need to do anything else
|
|||
if (data.id === this.id) { |
|||
this.unserialize(data); |
|||
this.emit('resolve'); |
|||
return this; |
|||
} |
|||
} |
|||
|
|||
// If not, we lock and resolve it
|
|||
this.unitWork.lock(this.name, this); |
|||
|
|||
if (this.assetUrl) { |
|||
this.download(); |
|||
} else if (this.gitUrl) { |
|||
this.clone(); |
|||
} else if (this.path) { |
|||
this.copy(); |
|||
} else { |
|||
this.once('lookup', this.clone).lookup(); |
|||
} |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
Package.prototype.lookup = function () { |
|||
source.lookup(this.name, function (err, url) { |
|||
if (err) return this.emit('error', err); |
|||
this.lookedUp = true; |
|||
this.gitUrl = url; |
|||
this.generateResourceId(); |
|||
this.emit('lookup'); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.install = function () { |
|||
// Only print the installing action if this package has been resolved
|
|||
if (this.unitWork.retrieve(this.name)) { |
|||
template('action', { name: 'installing', shizzle: this.name + (this.version ? '#' + this.version : '') }) |
|||
.on('data', this.emit.bind(this, 'data')); |
|||
} |
|||
|
|||
var localPath = this.localPath; |
|||
|
|||
if (path.resolve(this.path) === localPath) { |
|||
this.emit('install'); |
|||
return this; |
|||
} |
|||
|
|||
// Remove stuff from the local path (if any)
|
|||
// Rename path to the local path
|
|||
// Beware that if the local path exists and is a git repository, the process is aborted
|
|||
isRepo(localPath, function (is) { |
|||
if (is) { |
|||
var err = new Error('Local path is a local repository'); |
|||
err.details = 'To avoid losing work, please remove ' + localPath + ' manually.'; |
|||
return this.emit('error', err, this); |
|||
} |
|||
|
|||
mkdirp(path.dirname(localPath), function (err) { |
|||
if (err) return this.emit('error', err); |
|||
rimraf(localPath, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
return fs.rename(this.path, localPath, function (err) { |
|||
if (!err) return this.cleanUpLocal(); |
|||
|
|||
var writter = fstream.Writer({ |
|||
type: 'Directory', |
|||
path: localPath |
|||
}); |
|||
writter |
|||
.on('error', this.emit.bind(this, 'error')) |
|||
.on('end', rimraf.bind(this, this.path, this.cleanUpLocal.bind(this))); |
|||
|
|||
fstream.Reader(this.path) |
|||
.on('error', this.emit.bind(this, 'error')) |
|||
.pipe(writter); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
Package.prototype.cleanUpLocal = function () { |
|||
this.once('readLocalConfig', function () { |
|||
this.json.name = this.name; |
|||
this.json.version = this.commit ? '0.0.0' : this.version || '0.0.0'; |
|||
|
|||
// Detect commit and save it in the json for later use
|
|||
if (this.commit) this.json.commit = this.commit; |
|||
else delete this.json.commit; |
|||
|
|||
if (this.gitUrl) this.json.repository = { type: 'git', url: this.gitUrl }; |
|||
else if (this.gitPath) this.json.repository = { type: 'local-repo', path: this.originalPath }; |
|||
else if (this.originalPath) this.json.repository = { type: 'local', path: this.originalPath }; |
|||
else if (this.assetUrl) this.json = this.generateAssetJSON(); |
|||
|
|||
var jsonStr = JSON.stringify(this.json, null, 2); |
|||
|
|||
fs.writeFile(path.join(this.localPath, this.localConfig.json), jsonStr); |
|||
if (this.gitUrl || this.gitPath) fs.writeFile(path.join(this.gitPath, this.localConfig.json), jsonStr); |
|||
|
|||
this.removeLocalPaths(); |
|||
}.bind(this)).readLocalConfig(); |
|||
}; |
|||
|
|||
// finish clean up local by removing .git/ and any ignored files
|
|||
Package.prototype.removeLocalPaths = function () { |
|||
var removePatterns = ['.git']; |
|||
if (this.json.ignore) { |
|||
removePatterns.push.apply(removePatterns, this.json.ignore); |
|||
} |
|||
|
|||
var removePaths = []; |
|||
|
|||
// 3: done
|
|||
var pathsRemoved = function (err) { |
|||
if (err) return this.emit('error', err); |
|||
this.emit('install'); |
|||
}.bind(this); |
|||
|
|||
// 2: trigger after paths have been globbed
|
|||
var rimrafPaths = function (err) { |
|||
if (err) return this.emit('error', err); |
|||
async.forEach(removePaths, function (removePath, next) { |
|||
// rimraf all the paths
|
|||
rimraf(path.join(this.localPath, removePath), next); |
|||
}.bind(this), pathsRemoved); |
|||
}.bind(this); |
|||
|
|||
// 1: get paths
|
|||
var globOpts = { dot: true, cwd: this.localPath }; |
|||
async.forEach(removePatterns, function (removePattern, next) { |
|||
// glob path for file path pattern matching
|
|||
glob(removePattern, globOpts, function (err, globPaths) { |
|||
if (err) return next(err); |
|||
removePaths.push.apply(removePaths, globPaths); |
|||
next(); |
|||
}.bind(this)); |
|||
}.bind(this), rimrafPaths); |
|||
}; |
|||
|
|||
Package.prototype.generateAssetJSON = function () { |
|||
return { |
|||
name: this.name, |
|||
main: this.assetType !== '.zip' && this.assetType !== '.tar' ? 'index' + this.assetType : '', |
|||
version: '0.0.0', |
|||
repository: { type: 'asset', url: this.assetUrl } |
|||
}; |
|||
}; |
|||
|
|||
Package.prototype.uninstall = function () { |
|||
template('action', { name: 'uninstalling', shizzle: this.path }) |
|||
.on('data', this.emit.bind(this, 'data')); |
|||
rimraf(this.path, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
this.emit('uninstall'); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
// Private
|
|||
Package.prototype.readLocalConfig = function () { |
|||
if (this.localConfig) return this.emit('readLocalConfig'); |
|||
|
|||
var checkExistence = function () { |
|||
fileExists(path.join(this.path, this.localConfig.json), function (exists) { |
|||
if (!exists) { |
|||
this.localConfig.json = 'component.json'; |
|||
} |
|||
|
|||
this.emit('readLocalConfig'); |
|||
}.bind(this)); |
|||
}.bind(this); |
|||
|
|||
fs.readFile(path.join(this.path, '.bowerrc'), function (err, file) { |
|||
// If the local .bowerrc file do not exists then we check if the
|
|||
// json specific in the config exists (if not, we fallback to component.json)
|
|||
if (err) { |
|||
this.localConfig = { json: config.json }; |
|||
checkExistence(); |
|||
} else { |
|||
// If the local .bowerrc file exists, we read it and check if a custom json file
|
|||
// is defined. If not, we check if the global config json file exists (if not, we fallback to component.json)
|
|||
try { |
|||
this.localConfig = JSON.parse(file); |
|||
} catch (e) { |
|||
return this.emit('error', new Error('Unable to parse local .bowerrc file: ' + e.message)); |
|||
} |
|||
|
|||
if (!this.localConfig.json) { |
|||
this.localConfig.json = config.json; |
|||
return checkExistence(); |
|||
} |
|||
|
|||
this.emit('readLocalConfig'); |
|||
} |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.loadJSON = function () { |
|||
if (!this.path || this.assetUrl) return this.emit('loadJSON'); |
|||
|
|||
this.once('readLocalConfig', function () { |
|||
var jsonFile = path.join(this.path, this.localConfig.json); |
|||
fileExists(jsonFile, function (exists) { |
|||
// If the json does not exists, we attempt to get the version
|
|||
if (!exists) { |
|||
return this.once('describeTag', function (tag) { |
|||
tag = semver.clean(tag); |
|||
if (!tag) this.version = this.tag; |
|||
else { |
|||
this.version = tag; |
|||
if (!this.tag) this.tag = this.version; |
|||
} |
|||
|
|||
this.emit('loadJSON'); |
|||
}.bind(this)).describeTag(); |
|||
} |
|||
|
|||
readJSON(jsonFile, function (err, json) { |
|||
if (err) { |
|||
err.details = 'An error was caught when reading the ' + this.localConfig.json + ':' + err.message; |
|||
return this.emit('error', err); |
|||
} |
|||
|
|||
this.json = json; |
|||
this.version = this.commit || json.commit || json.version; |
|||
this.commit = this.commit || json.commit; |
|||
// Only overwrite the name if not already set
|
|||
// This is because some packages have different names declared in the registry and the json
|
|||
if (!this.name) this.name = json.name; |
|||
|
|||
// Read the endpoint from the json to ensure it is set correctly
|
|||
this.readEndpoint(); |
|||
|
|||
// Detect if the tag mismatches the json.version
|
|||
// This is very often to happen because developers tag their new releases but forget to update the json accordingly
|
|||
var cleanedTag; |
|||
if (this.tag && (cleanedTag = semver.clean(this.tag)) && cleanedTag !== this.version) { |
|||
// Only print the warning once
|
|||
if (!this.unitWork.retrieve('mismatch#' + this.name + '_' + cleanedTag)) { |
|||
template('warning-mismatch', { name: this.name, json: this.localConfig.json, tag: cleanedTag, version: this.version || 'N/A' }) |
|||
.on('data', this.emit.bind(this, 'data')); |
|||
this.unitWork.store('mismatch#' + this.name + '_' + cleanedTag, true); |
|||
} |
|||
// Assume the tag
|
|||
this.version = cleanedTag; |
|||
} |
|||
|
|||
this.emit('loadJSON'); |
|||
}.bind(this), this); |
|||
}.bind(this)); |
|||
}.bind(this)).readLocalConfig(); |
|||
}; |
|||
|
|||
Package.prototype.download = function () { |
|||
template('action', { name: 'downloading', shizzle: this.assetUrl }) |
|||
.on('data', this.emit.bind(this, 'data')); |
|||
|
|||
var src; |
|||
|
|||
if (config.proxy) { |
|||
src = url.parse(config.proxy); |
|||
src.path = this.assetUrl; |
|||
} else { |
|||
src = url.parse(this.assetUrl); |
|||
} |
|||
|
|||
tmp.dir({ prefix: 'bower-' + this.name + '-', mode: parseInt('0777', 8) & (~process.umask()) }, function (err, tmpPath) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
var req = src.protocol === 'https:' ? https : http; |
|||
req.get(src, function (res) { |
|||
// If assetUrl results in a redirect we update the assetUrl to the redirect to url
|
|||
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { |
|||
template('action', { name: 'redirect detected', shizzle: this.assetUrl }) |
|||
.on('data', this.emit.bind(this, 'data')); |
|||
this.assetUrl = res.headers.location; |
|||
return this.download(); |
|||
} |
|||
|
|||
// Detect not OK status codes
|
|||
if (res.statusCode < 200 || res.statusCode >= 300) { |
|||
return this.emit('error', new Error(res.statusCode + ' status code for ' + this.assetUrl)); |
|||
} |
|||
|
|||
var file = fs.createWriteStream(path.join((this.path = tmpPath), 'index' + this.assetType)); |
|||
|
|||
res.on('data', function (data) { |
|||
file.write(data); |
|||
}); |
|||
|
|||
res.on('end', function () { |
|||
file.end(); |
|||
|
|||
var next = function () { |
|||
this.once('loadJSON', this.saveUnit).loadJSON(); |
|||
}.bind(this); |
|||
|
|||
if (this.assetType === '.zip' || this.assetType === '.tar') this.once('extract', next).extract(); |
|||
else next(); |
|||
}.bind(this)); |
|||
}.bind(this)).on('error', this.emit.bind(this, 'error')); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.extract = function () { |
|||
var file = path.join(this.path, 'index' + this.assetType); |
|||
template('action', { name: 'extracting', shizzle: file }).on('data', this.emit.bind(this, 'data')); |
|||
|
|||
fs.createReadStream(file).pipe(this.assetType === '.zip' ? unzip.Extract({ path: this.path }) : tar.Extract({ path: this.path })) |
|||
.on('error', this.emit.bind(this, 'error')) |
|||
.on('close', function () { |
|||
// Delete zip
|
|||
fs.unlink(file, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
// If we extracted only a folder, move all the files within it to the original path
|
|||
fs.readdir(this.path, function (err, files) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
if (files.length !== 1) return this.emit('extract'); |
|||
|
|||
var dir = path.join(this.path, files[0]); |
|||
fs.stat(dir, function (err, stat) { |
|||
if (err) return this.emit('error', err); |
|||
if (!stat.isDirectory()) return this.emit('extract'); |
|||
|
|||
fs.readdir(dir, function (err, files) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
async.forEachSeries(files, function (file, next) { |
|||
fs.rename(path.join(dir, file), path.join(this.path, file), next); |
|||
}.bind(this), function (err) { |
|||
if (err) return this.emit('error'); |
|||
|
|||
fs.rmdir(dir, function (err) { |
|||
if (err) return this.emit('error'); |
|||
this.emit('extract'); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.copy = function () { |
|||
template('action', { name: 'copying', shizzle: this.path }).on('data', this.emit.bind(this, 'data')); |
|||
|
|||
tmp.dir({ prefix: 'bower-' + this.name + '-' }, function (err, tmpPath) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
fs.stat(this.path, function (err, stats) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
// Copy file permission for directory
|
|||
fs.chmod(tmpPath, stats.mode, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
if (this.assetType) { |
|||
return fs.readFile(this.path, function (err, data) { |
|||
fs.writeFile(path.join((this.path = tmpPath), 'index' + this.assetType), data, function () { |
|||
this.once('loadJSON', this.saveUnit).loadJSON(); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
} |
|||
|
|||
this.once('loadJSON', function () { |
|||
if (this.gitUrl) return this.saveUnit(); |
|||
|
|||
// Check if the copied directory is a git repository and is a local endpoint
|
|||
// If so, treat it like a repository.
|
|||
fileExists(path.join(this.path, '.git'), function (exists) { |
|||
if (!exists) return this.saveUnit(); |
|||
|
|||
this.gitPath = this.path; |
|||
this.once('loadJSON', this.saveUnit.bind(this)).checkout(); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
|
|||
var writter = fstream.Writer({ |
|||
type: 'Directory', |
|||
path: tmpPath |
|||
}) |
|||
.on('error', this.emit.bind(this, 'error')) |
|||
.on('end', this.loadJSON.bind(this)); |
|||
|
|||
fstream.Reader(this.path) |
|||
.on('error', this.emit.bind(this, 'error')) |
|||
.pipe(writter); |
|||
|
|||
this.path = tmpPath; |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.getDeepDependencies = function (result) { |
|||
result = result || []; |
|||
for (var name in this.dependencies) { |
|||
result.push(this.dependencies[name]); |
|||
this.dependencies[name].getDeepDependencies(result); |
|||
} |
|||
return result; |
|||
}; |
|||
|
|||
Package.prototype.saveUnit = function () { |
|||
this.unitWork.store(this.name, this.serialize(), this); |
|||
this.unitWork.unlock(this.name, this); |
|||
this.addDependencies(); |
|||
}; |
|||
|
|||
Package.prototype.addDependencies = function () { |
|||
var dependencies = this.json.dependencies || {}; |
|||
var callbacks = Object.keys(dependencies).map(function (name) { |
|||
return function (callback) { |
|||
var endpoint = dependencies[name]; |
|||
this.dependencies[name] = new Package(name, endpoint, this); |
|||
this.dependencies[name].once('resolve', callback).resolve(); |
|||
}.bind(this); |
|||
}.bind(this)); |
|||
async.parallel(callbacks, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
this.emit('resolve'); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.exists = function (callback) { |
|||
fileExists(this.localPath, callback); |
|||
}; |
|||
|
|||
Package.prototype.clone = function () { |
|||
template('action', { name: 'cloning', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data')); |
|||
this.path = this.gitPath; |
|||
this.once('cache', function () { |
|||
this.once('loadJSON', this.copy.bind(this)).checkout(); |
|||
}.bind(this)).cache(); |
|||
}; |
|||
|
|||
Package.prototype.cache = function () { |
|||
// If the force options is true, we need to erase from the cache
|
|||
// Be aware that a similar package might already flushed it
|
|||
// To prevent that we check the unit of work storage
|
|||
if (this.opts.force && !this.unitWork.retrieve('flushed#' + this.name + '_' + this.resourceId)) { |
|||
rimraf(this.path, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
this.unitWork.store('flushed#' + this.name + '_' + this.resourceId, true); |
|||
this.cache(); |
|||
}.bind(this)); |
|||
return this; |
|||
} |
|||
|
|||
mkdirp(config.cache, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
fileExists(this.path, function (exists) { |
|||
if (exists) { |
|||
template('action', { name: 'cached', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data')); |
|||
return this.emit('cache'); |
|||
} |
|||
template('action', { name: 'caching', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data')); |
|||
var url = this.gitUrl; |
|||
if (config.proxy) { |
|||
url = url.replace(/^git:/, 'https:'); |
|||
} |
|||
|
|||
mkdirp(this.path, function (err) { |
|||
if (err) return this.emit('error', err); |
|||
|
|||
var cp = git(['clone', url, this.path], null, this); |
|||
cp.on('close', function (code) { |
|||
if (code) return; |
|||
this.emit('cache'); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.checkout = function () { |
|||
template('action', { name: 'fetching', shizzle: this.name }) |
|||
.on('data', this.emit.bind(this, 'data')); |
|||
|
|||
this.once('versions', function (versions) { |
|||
if (!versions.length) { |
|||
this.emit('checkout'); |
|||
this.loadJSON(); |
|||
} |
|||
|
|||
// If tag is specified, try to satisfy it
|
|||
if (this.tag) { |
|||
if (!semver.validRange(this.tag)) { |
|||
return this.emit('error', new Error('Tag ' + this.tag + ' is not a valid semver range/version')); |
|||
} |
|||
|
|||
versions = versions.filter(function (version) { |
|||
return semver.satisfies(version, this.tag); |
|||
}.bind(this)); |
|||
|
|||
if (!versions.length) { |
|||
var error = new Error('Could not find tag satisfying: ' + this.name + '#' + this.tag); |
|||
error.details = 'The tag ' + this.tag + ' could not be found within the repository'; |
|||
return this.emit('error', error); |
|||
} |
|||
} |
|||
|
|||
// Use latest version
|
|||
this.tag = versions[0]; |
|||
if (!semver.valid(this.tag)) this.commit = this.tag; // If the version is not valid, then its a commit
|
|||
|
|||
if (this.tag) { |
|||
template('action', { |
|||
name: 'checking out', |
|||
shizzle: this.name + '#' + this.tag |
|||
}).on('data', this.emit.bind(this, 'data')); |
|||
|
|||
// Checkout the tag
|
|||
git([ 'checkout', this.tag, '-f'], { cwd: this.path }, this).on('close', function (code) { |
|||
if (code) return; |
|||
// Ensure that checkout the tag as it is, removing all untracked files
|
|||
git(['clean', '-f', '-d'], { cwd: this.path }, this).on('close', function (code) { |
|||
if (code) return; |
|||
this.emit('checkout'); |
|||
this.loadJSON(); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
} |
|||
}).versions(); |
|||
}; |
|||
|
|||
Package.prototype.describeTag = function () { |
|||
var cp = git(['describe', '--always', '--tag'], { cwd: this.gitPath || this.path, ignoreCodes: [128] }, this); |
|||
var tag = ''; |
|||
|
|||
cp.stdout.setEncoding('utf8'); |
|||
cp.stdout.on('data', function (data) { |
|||
tag += data; |
|||
}); |
|||
|
|||
cp.on('close', function (code) { |
|||
if (code === 128) tag = 'unspecified'.grey; // Not a git repo
|
|||
this.emit('describeTag', tag.replace(/\n$/, '')); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.versions = function () { |
|||
this.once('fetch', function () { |
|||
var cp = git(['tag'], { cwd: this.gitPath }, this); |
|||
|
|||
var versions = ''; |
|||
|
|||
cp.stdout.setEncoding('utf8'); |
|||
cp.stdout.on('data', function (data) { |
|||
versions += data; |
|||
}); |
|||
|
|||
cp.on('close', function (code) { |
|||
if (code) return; |
|||
versions = versions.split('\n'); |
|||
versions = versions.filter(function (ver) { |
|||
return semver.valid(ver); |
|||
}); |
|||
versions = versions.sort(function (a, b) { |
|||
return semver.gt(a, b) ? -1 : 1; |
|||
}); |
|||
|
|||
if (versions.length) return this.emit('versions', versions); |
|||
|
|||
// If there is no versions tagged in the repo
|
|||
// then we grab the hash of the last commit
|
|||
versions = ''; |
|||
cp = git(['log', '-n', 1, '--format=%H'], { cwd: this.gitPath }, this); |
|||
|
|||
cp.stdout.setEncoding('utf8'); |
|||
cp.stdout.on('data', function (data) { |
|||
versions += data; |
|||
}); |
|||
cp.on('close', function (code) { |
|||
if (code) return; |
|||
versions = _.compact(versions.split('\n')); |
|||
this.emit('versions', versions); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)).fetch(); |
|||
}; |
|||
|
|||
Package.prototype.fetch = function () { |
|||
fileExists(this.gitPath, function (exists) { |
|||
if (!exists) return this.emit('error', new Error('Unable to fetch package ' + this.name + ' (if the cache was deleted, run install again)')); |
|||
|
|||
var cp = git(['fetch', '--prune'], { cwd: this.gitPath }, this); |
|||
cp.on('close', function (code) { |
|||
if (code) return; |
|||
cp = git(['reset', '--hard', this.gitUrl ? 'origin/HEAD' : 'HEAD'], { cwd: this.gitPath }, this); |
|||
cp.on('close', function (code) { |
|||
if (code) return; |
|||
this.emit('fetch'); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}.bind(this)); |
|||
}; |
|||
|
|||
Package.prototype.readEndpoint = function (replace) { |
|||
if (!this.json.repository) return; |
|||
|
|||
if (this.json.repository.type === 'git') { |
|||
if (replace || !this.gitUrl) { |
|||
this.gitUrl = this.json.repository.url; |
|||
this.generateResourceId(); |
|||
} |
|||
return { type: 'git', endpoint: this.gitUrl }; |
|||
} |
|||
if (this.json.repository.type === 'local-repo') { |
|||
if (replace || !this.gitPath) { |
|||
this.gitPath = path.resolve(this.json.repository.path); |
|||
} |
|||
return { type: 'local', endpoint: this.path }; |
|||
} |
|||
if (this.json.repository.type === 'local') { |
|||
if (replace || !this.path) { |
|||
this.path = path.resolve(this.json.repository.path); |
|||
} |
|||
return { type: 'local', endpoint: this.path }; |
|||
} |
|||
if (this.json.repository.type === 'asset') { |
|||
if (replace || !this.assetUrl) { |
|||
this.assetUrl = this.json.repository.url; |
|||
this.assetType = path.extname(this.assetUrl); |
|||
} |
|||
return { type: 'asset', endpoint: this.assetUrl }; |
|||
} |
|||
}; |
|||
|
|||
Package.prototype.waitUnlock = function (name) { |
|||
if (this.name === name) { |
|||
this.unitWork.removeListener('unlock', this.waitUnlock); |
|||
this.resolve(); |
|||
} |
|||
}; |
|||
|
|||
Package.prototype.serialize = function () { |
|||
return { |
|||
id: this.id, |
|||
resourceId: this.resourceId, |
|||
path: this.path, |
|||
originalPath: this.originalPath, |
|||
tag: this.tag, |
|||
originalTag: this.originalTag, |
|||
commit: this.commit, |
|||
assetUrl: this.assetUrl, |
|||
assetType: this.assetType, |
|||
lookedUp: this.lookedUp, |
|||
json: this.json, |
|||
gitUrl: this.gitUrl, |
|||
gitPath: this.gitPath, |
|||
dependencies: this.dependencies, |
|||
localConfig: this.localConfig |
|||
}; |
|||
}; |
|||
|
|||
Package.prototype.unserialize = function (obj) { |
|||
for (var key in obj) { |
|||
this[key] = obj[key]; |
|||
} |
|||
|
|||
this.version = this.tag; |
|||
}; |
|||
|
|||
Package.prototype.generateResourceId = function () { |
|||
this.resourceId = crypto.createHash('md5').update(this.name + '%' + this.gitUrl).digest('hex'); |
|||
this.gitPath = path.join(config.cache, this.name, this.resourceId); |
|||
}; |
|||
|
|||
Package.prototype.__defineGetter__('localPath', function () { |
|||
return path.join(process.cwd(), config.directory, this.name); |
|||
}); |
|||
|
|||
module.exports = Package; |
@ -0,0 +1,146 @@ |
|||
// ==========================================
|
|||
// BOWER: Source Api
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var request = require('request'); |
|||
var config = require('./config'); |
|||
|
|||
var endpoint = config.endpoint + '/packages'; |
|||
|
|||
if (config.proxy) { |
|||
request = request.defaults({ proxy: config.proxy, timeout: 5000 }); |
|||
} |
|||
|
|||
|
|||
// allow for searchpath endpoints to be used for search and lookup
|
|||
var endpoints = []; |
|||
endpoints.push(endpoint); |
|||
if (config.searchpath) { |
|||
for (var i = 0; i < config.searchpath.length; i += 1) { |
|||
endpoints.push(config.searchpath[i] + '/packages'); |
|||
} |
|||
} |
|||
|
|||
exports.lookup = function (name, callback) { |
|||
// walk all endpoints to find the first matching component
|
|||
var f = function (i) { |
|||
var endpoint = endpoints[i]; |
|||
request.get(endpoint + '/' + encodeURIComponent(name), function (err, response, body) { |
|||
if (err || (response.statusCode !== 200 && response.statusCode !== 404)) { |
|||
return callback(err || new Error(name + ' failed to look up for endpoint: ' + endpoint)); |
|||
} |
|||
|
|||
if (response && response.statusCode !== 404) { |
|||
callback(err, body && JSON.parse(body).url); |
|||
} else { |
|||
if (i + 1 < endpoints.length) f(i + 1); |
|||
else return callback(new Error(name + ' not found')); |
|||
} |
|||
}); |
|||
}; |
|||
f(0); |
|||
}; |
|||
|
|||
exports.register = function (name, url, callback) { |
|||
var body = {name: name, url: url}; |
|||
|
|||
request.post({url: endpoint, form: body}, function (err, response) { |
|||
if (err) return callback(err); |
|||
|
|||
if (response.statusCode === 406) { |
|||
return callback(new Error('Duplicate package')); |
|||
} |
|||
|
|||
if (response.statusCode === 400) { |
|||
return callback(new Error('Incorrect format')); |
|||
} |
|||
|
|||
if (response.statusCode !== 201) { |
|||
return callback(new Error('Unknown error: ' + response.statusCode)); |
|||
} |
|||
|
|||
callback(); |
|||
}); |
|||
}; |
|||
|
|||
exports.search = function (name, callback) { |
|||
// walk all endpoints to produced federated search results
|
|||
var f = function (i, map, results) { |
|||
var endpoint = endpoints[i]; |
|||
|
|||
request.get(endpoint + '/search/' + encodeURIComponent(name), function (err, response, body) { |
|||
if (err || (response.statusCode !== 200 && response.statusCode !== 404)) { |
|||
return callback(err || new Error(name + ' failed to look up for endpoint: ' + endpoint)); |
|||
} |
|||
|
|||
if (response && response.statusCode !== 404) { |
|||
var array = body && JSON.parse(body); |
|||
for (var x = 0; x < array.length; x += 1) { |
|||
var pkgName = array[x].name; |
|||
if (!map[pkgName]) { |
|||
map[pkgName] = pkgName; |
|||
results.push({ name: pkgName, url: array[x].url, endpoint: array[x].endpoint }); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (i + 1 < endpoints.length) f(i + 1, map, results); |
|||
else return callback(null, results); |
|||
}); |
|||
}; |
|||
|
|||
f(0, {}, []); |
|||
}; |
|||
|
|||
exports.info = function (name, callback) { |
|||
exports.lookup(name, function (err, url) { |
|||
if (err) return callback(err); |
|||
|
|||
var Package = require('./package'); |
|||
var pkg = new Package(name, url); |
|||
|
|||
pkg.once('error', function (err) { |
|||
pkg.removeAllListeners(); |
|||
callback(err); |
|||
}); |
|||
pkg.once('resolve', function () { |
|||
pkg.once('versions', function (versions) { |
|||
pkg.removeAllListeners(); |
|||
callback(null, { pkg: pkg, versions: versions }); |
|||
}).versions(); |
|||
}).resolve(); |
|||
}); |
|||
}; |
|||
|
|||
exports.all = function (callback) { |
|||
// walk all endpoints to produced federated search results
|
|||
var f = function (i, map, results) { |
|||
var endpoint = endpoints[i]; |
|||
|
|||
request.get(endpoint, function (err, response, body) { |
|||
if (err || (response.statusCode !== 200 && response.statusCode !== 404)) { |
|||
return callback(err || new Error('Failed to look up endpoint: ' + endpoint)); |
|||
} |
|||
|
|||
if (response && response.statusCode !== 404) { |
|||
var array = body && JSON.parse(body); |
|||
for (var x = 0; x < array.length; x += 1) { |
|||
var pkgName = array[x].name; |
|||
if (!map[pkgName]) { |
|||
map[pkgName] = pkgName; |
|||
results.push({ name: pkgName, url: array[x].url, endpoint: array[x].endpoint }); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (i + 1 < endpoints.length) f(i + 1, map, results); |
|||
else return callback(null, results); |
|||
}); |
|||
}; |
|||
|
|||
f(0, {}, []); |
|||
}; |
@ -0,0 +1,64 @@ |
|||
// ==========================================
|
|||
// BOWER: Package Object Definition
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
// Events:
|
|||
// - lock: fired when a lock write over a key is acquired
|
|||
// - unlock: fired when an unlock write over a key is acquired
|
|||
// ==========================================
|
|||
|
|||
var events = require('events'); |
|||
|
|||
var UnitWork = function () { |
|||
this.locks = []; |
|||
this.data = []; |
|||
|
|||
this.setMaxListeners(100); // Increase the number of listeners because this is a central storage
|
|||
}; |
|||
|
|||
UnitWork.prototype = Object.create(events.EventEmitter.prototype); |
|||
|
|||
UnitWork.prototype.constructor = UnitWork; |
|||
|
|||
UnitWork.prototype.lock = function (key, owner) { |
|||
if (this.locks[key]) throw new Error('A write lock for "' + key + '" was already acquired.'); |
|||
if (!owner) throw new Error('A lock requires an owner.'); |
|||
this.locks[key] = owner; |
|||
|
|||
return this.emit('lock', key); |
|||
}; |
|||
|
|||
UnitWork.prototype.unlock = function (key, owner) { |
|||
if (!owner) throw new Error('A write lock requires an owner.'); |
|||
if (this.locks[key]) { |
|||
if (this.locks[key] !== owner) throw new Error('Lock owner for "' + key + '" mismatch.'); |
|||
delete this.locks[key]; |
|||
this.emit('unlock', key); |
|||
} |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
UnitWork.prototype.isLocked = function (key) { |
|||
return !!this.locks[key]; |
|||
}; |
|||
|
|||
UnitWork.prototype.store = function (key, data, owner) { |
|||
if (this.locks[key] && owner !== this.locks[key]) throw new Error('A write lock for "' + key + '" is acquired therefore only its owner can write to it.'); |
|||
this.data[key] = data; |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
UnitWork.prototype.retrieve = function (key) { |
|||
return this.data[key]; |
|||
}; |
|||
|
|||
UnitWork.prototype.keys = function () { |
|||
return Object.keys(this.data); |
|||
}; |
|||
|
|||
module.exports = UnitWork; |
@ -0,0 +1,12 @@ |
|||
// ==========================================
|
|||
// BOWER: Public API Defintion
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
module.exports = { |
|||
commands: require('./commands'), |
|||
config: require('./core/config') |
|||
}; |
@ -0,0 +1,65 @@ |
|||
// ==========================================
|
|||
// BOWER: completion
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
// This module exposes a simple helper to parse the environment variables in
|
|||
// case of a tab completion command. It parses the provided `argv` (nopt's
|
|||
// remain arguments after `--`) and `env` (should be process.env)
|
|||
//
|
|||
// It is inspired and based off Isaac's work on npm.
|
|||
|
|||
module.exports = function (argv, env) { |
|||
var opts = {}; |
|||
|
|||
// w is the words number, based on the cursor position
|
|||
opts.w = +env.COMP_CWORD; |
|||
|
|||
// words is the escaped sequence of words following `bower`
|
|||
opts.words = argv.map(function (word) { |
|||
return word.charAt(0) === '"' ? |
|||
word.replace(/^"|"$/g, '') : |
|||
word.replace(/\\ /g, ' '); |
|||
}); |
|||
|
|||
// word is a shortcut to the last word in the line
|
|||
opts.word = opts.words[opts.w - 1]; |
|||
|
|||
// line is the sequence of tab completed words.
|
|||
opts.line = env.COMP_LINE; |
|||
|
|||
// point is the cursor position in the line
|
|||
opts.point = +env.COMP_POINT; |
|||
|
|||
// length is the whole line's length.
|
|||
opts.length = opts.line.length; |
|||
|
|||
// partialLine is the line ignoring the sequence of characters after
|
|||
// cursor position, ie. tabbing at: bower install j|qu
|
|||
// gives back a partialLine: bower install j
|
|||
opts.partialLine = opts.line.slice(0, opts.point); |
|||
|
|||
// partialWords is only returning the words based on cursor position,
|
|||
// ie tabbing at: bower install ze|pto backbone
|
|||
// gives back a partialWords array: ['install', 'zepto']
|
|||
opts.partialWords = opts.words.slice(0, opts.w); |
|||
|
|||
return opts; |
|||
}; |
|||
|
|||
module.exports.log = function (arr, opts) { |
|||
arr = Array.isArray(arr) ? arr : [arr]; |
|||
arr.filter(module.exports.abbrev(opts)).forEach(function (word) { |
|||
console.log(word); |
|||
}); |
|||
}; |
|||
|
|||
module.exports.abbrev = function abbrev(opts) { |
|||
var word = opts.word.replace(/\./g, '\\.'); |
|||
return function (it) { |
|||
return new RegExp('^' + word).test(it); |
|||
}; |
|||
}; |
@ -0,0 +1,26 @@ |
|||
// ==========================================
|
|||
// BOWER: file-exists
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var fs = require('fs'); |
|||
|
|||
// This module exposes a version of fs.exists and fs.existsSync with a correct behaviour
|
|||
// See: https://github.com/joyent/node/pull/2603
|
|||
|
|||
module.exports = function (path, callback) { |
|||
fs.stat(path, function (error) { |
|||
callback(!error || error.code !== 'ENOENT'); |
|||
}); |
|||
}; |
|||
|
|||
module.exports.sync = function (path) { |
|||
try { |
|||
return !!fs.statSync(path); |
|||
} catch (e) { |
|||
return e.code !== 'ENOENT'; |
|||
} |
|||
}; |
@ -0,0 +1,28 @@ |
|||
// ==========================================
|
|||
// BOWER: git-cmd
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
// Extension of the spawn command, that only executes git commands
|
|||
// If a git command fails with code 128 in the cache, that directory
|
|||
// will be deleted to prevent more issues
|
|||
|
|||
var path = require('path'); |
|||
var spawn = require('./spawn'); |
|||
var rimraf = require('rimraf'); |
|||
var config = require('../core/config'); |
|||
|
|||
module.exports = function (args, options, emitter) { |
|||
var cp = spawn('git', args, options, emitter); |
|||
var cwd = options ? options.cwd || process.cwd() : process.cwd(); |
|||
var isTmp = path.normalize(cwd).indexOf(config.cache) === 0; |
|||
|
|||
cp.on('exit', function (code) { |
|||
if (code === 128 && isTmp) rimraf.sync(cwd); |
|||
}); |
|||
|
|||
return cp; |
|||
}; |
@ -0,0 +1,28 @@ |
|||
// ==========================================
|
|||
// BOWER: Hogan.js renderWithColors extension
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var colors = require('colors'); |
|||
var hogan = require('hogan.js'); |
|||
var _ = require('lodash'); |
|||
var nopt = require('nopt'); |
|||
|
|||
module.exports = hogan.Template.prototype.renderWithColors = function (context, partials, indent) { |
|||
if (nopt(process.argv).color === false) { |
|||
colors.mode = 'none'; |
|||
} |
|||
|
|||
context = _.extend({ |
|||
yellow : function (s) { return s.yellow; }, |
|||
green : function (s) { return s.green; }, |
|||
cyan : function (s) { return s.cyan; }, |
|||
grey : function (s) { return s.grey; }, |
|||
red : function (s) { return s.red; }, |
|||
white : function (s) { return s.white; } |
|||
}, context); |
|||
return this.ri([context], partials || {}, indent); |
|||
}; |
@ -0,0 +1,23 @@ |
|||
// ==========================================
|
|||
// BOWER: is-repo
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var fs = require('fs'); |
|||
var path = require('path'); |
|||
var fileExists = require('./file-exists'); |
|||
|
|||
// This module checks if a path is a local repository
|
|||
// If the repository is a link, it will be falsy
|
|||
module.exports = function (dir, callback) { |
|||
fileExists(path.join(dir, '.git'), function (exists) { |
|||
if (!exists) return callback(false); |
|||
fs.lstat(dir, function (err, stat) { |
|||
if (err) return callback(false); |
|||
callback(!stat.isSymbolicLink()); |
|||
}); |
|||
}); |
|||
}; |
@ -0,0 +1,103 @@ |
|||
// ==========================================
|
|||
// BOWER: prune
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var semver = require('semver'); |
|||
var sort = require('stable'); |
|||
|
|||
var versionRequirements = function (dependencyMap) { |
|||
var result = {}; |
|||
|
|||
for (var name in dependencyMap) { |
|||
dependencyMap[name].forEach(function (pkg) { |
|||
result[name] = result[name] || []; |
|||
if (pkg.originalTag && result[name].indexOf(pkg.originalTag) === -1) { |
|||
result[name].push(pkg.originalTag); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
var validVersions = function (versions, dependency) { |
|||
if (!versions || !versions.length) return true; |
|||
|
|||
// If a non resolved dependency is passed, we simply ignore it
|
|||
if (!dependency.version) return false; |
|||
|
|||
if (!semver.valid(dependency.version)) { |
|||
throw new Error('Invalid semver version ' + dependency.version + ' specified in ' + dependency.name); |
|||
} |
|||
|
|||
return versions.every(function (version) { |
|||
return semver.satisfies(dependency.version, version); |
|||
}); |
|||
}; |
|||
|
|||
module.exports = function (dependencyMap, forceLatest) { |
|||
// generate version requirements
|
|||
// compare dependency map with version requirements
|
|||
// check for conflicts
|
|||
// select best version
|
|||
// return an object with the resolved deps, conflict deps and forcebly resolved ones
|
|||
|
|||
var resolved = {}; |
|||
var conflicted = null; |
|||
var forceblyResolved = null; |
|||
var versionMap = versionRequirements(dependencyMap); |
|||
|
|||
var sortFunc = function (a, b) { |
|||
if (semver.gt(a.version, b.version)) return -1; |
|||
if (semver.lt(a.version, b.version)) return 1; |
|||
|
|||
// If the comparison determines that both packages are equal, do not give priority to local ones
|
|||
if (a.path === a.localPath && b.path !== b.localPath) return 1; |
|||
return 0; |
|||
}; |
|||
|
|||
// Note that bellow we use a stable sort algorithm
|
|||
// This is because if two packages are equal, the initial order should be respected
|
|||
|
|||
for (var name in dependencyMap) { |
|||
var matches = dependencyMap[name].filter(validVersions.bind(this, versionMap[name])); |
|||
if (!matches.length) { |
|||
// No resolvable dependency
|
|||
// We resolve to the latest package if the forceLatest is true
|
|||
// Otherwise, check if any of those are root packages
|
|||
// If so, we assume that as the resolver (with a warning)
|
|||
// Otherwise there's a conflict
|
|||
if (forceLatest) { |
|||
forceblyResolved = forceblyResolved || {}; |
|||
forceblyResolved[name] = sort(dependencyMap[name], sortFunc); |
|||
continue; |
|||
} |
|||
|
|||
matches = dependencyMap[name].filter(function (pkg) { return !!pkg.root; }); |
|||
if (matches.length) { |
|||
forceblyResolved = forceblyResolved || {}; |
|||
forceblyResolved[name] = dependencyMap[name].sort(function (a, b) { |
|||
if (a.root && b.root) return sortFunc(a, b); |
|||
if (a.root) return -1; |
|||
if (b.root) return 1; |
|||
return sortFunc(a, b); |
|||
}); |
|||
} else { |
|||
conflicted = conflicted || {}; |
|||
conflicted[name] = dependencyMap[name]; |
|||
} |
|||
} else { |
|||
resolved[name] = [ sort(matches, sortFunc)[0] ]; |
|||
} |
|||
} |
|||
|
|||
return { |
|||
resolved: resolved, |
|||
conflicted: conflicted, |
|||
forceblyResolved: forceblyResolved |
|||
}; |
|||
}; |
@ -0,0 +1,25 @@ |
|||
// ==========================================
|
|||
// BOWER: read-json.js - with logging fun
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var readJSON = require('read-package-json'); |
|||
var template = require('./template'); |
|||
|
|||
var read = module.exports = function (path, cb, obj) { |
|||
readJSON.log = { |
|||
info: function () {}, |
|||
verbose: function () {}, |
|||
warn: function (what, name, shizzle) { |
|||
if (read.showWarnings) { |
|||
template('warn', { name: name.replace(/@/, '#'), shizzle: shizzle }) |
|||
.on('data', obj.emit.bind(obj, 'data')); |
|||
} |
|||
} |
|||
}; |
|||
readJSON.cache.reset(); |
|||
readJSON(path, cb); |
|||
}; |
@ -0,0 +1,97 @@ |
|||
// ==========================================
|
|||
// BOWER: save
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var path = require('path'); |
|||
var fs = require('fs'); |
|||
var semver = require('semver'); |
|||
|
|||
var _ = require('lodash'); |
|||
|
|||
var config = require('../core/config'); |
|||
|
|||
function save(manager, paths, dev, cb) { |
|||
// If there is specific paths to save, redirect to the appropriate function
|
|||
if (paths && paths.length) return savePkgs.apply(savePkgs, arguments); |
|||
|
|||
manager.on('loadJSON', function (newLine) { |
|||
manager.json.dependencies = manager.json.dependencies || {}; |
|||
manager.json.devDependencies = manager.json.devDependencies || {}; |
|||
|
|||
// Only include the root packages
|
|||
for (var name in manager.dependencies) { |
|||
var curr = manager.dependencies[name][0]; |
|||
if (curr.root) { |
|||
addDependency(manager.json, curr, !!manager.json.devDependencies[name]); |
|||
} |
|||
} |
|||
|
|||
// Cleanup dependencies from the json if empty
|
|||
if (!Object.keys(manager.json.dependencies).length) { |
|||
delete manager.json.dependencies; |
|||
} |
|||
|
|||
// Cleanup dependencies if empty
|
|||
if (!Object.keys(manager.json.devDependencies).length) { |
|||
delete manager.json.devDependencies; |
|||
} |
|||
|
|||
// Finally save the modified json
|
|||
var contents = JSON.stringify(manager.json, null, 2) + (newLine ? '\n' : ''); |
|||
fs.writeFile(path.join(manager.cwd, config.json), contents, cb); |
|||
}).loadJSON(); |
|||
} |
|||
|
|||
function savePkgs(manager, paths, dev, cb) { |
|||
manager.on('loadJSON', function (newLine) { |
|||
// Find the package names that match the paths
|
|||
var names = _.compact(paths.map(function (endpoint) { |
|||
endpoint = endpoint.split('#')[0]; |
|||
|
|||
return _.find(Object.keys(manager.dependencies), function (key) { |
|||
var dep = manager.dependencies[key][0]; |
|||
if (dep.name === endpoint) return true; |
|||
|
|||
var fetchedEndpoint = dep.readEndpoint(); |
|||
return fetchedEndpoint && fetchedEndpoint.endpoint === endpoint; |
|||
}); |
|||
})); |
|||
|
|||
var key = dev ? 'devDependencies' : 'dependencies'; |
|||
manager.json[key] = manager.json[key] || {}; |
|||
|
|||
// Save each of them
|
|||
// Only include the root packages
|
|||
names.forEach(function (name) { |
|||
addDependency(manager.json, manager.dependencies[name][0], dev); |
|||
}); |
|||
|
|||
// Finally save the modified json
|
|||
var contents = JSON.stringify(manager.json, null, 2) + (newLine ? '\n' : ''); |
|||
fs.writeFile(path.join(manager.cwd, config.json), contents, cb); |
|||
}).loadJSON(); |
|||
} |
|||
|
|||
function addDependency(json, pkg, dev) { |
|||
var path; |
|||
var tag; |
|||
var key = dev ? 'devDependencies' : 'dependencies'; |
|||
|
|||
if (pkg.lookedUp) { |
|||
tag = pkg.originalTag || '~' + pkg.version; |
|||
} else { |
|||
path = (pkg.gitUrl || pkg.assetUrl || pkg.originalPath || ''); |
|||
tag = pkg.originalTag || '~' + pkg.version; |
|||
} |
|||
|
|||
// If the tag is not valid (e.g.: a commit), null it
|
|||
if (!semver.valid(tag) && !semver.validRange(tag)) tag = null; |
|||
|
|||
json[key][pkg.name] = path ? path + (tag ? '#' + tag : '') : tag || 'latest'; |
|||
} |
|||
|
|||
module.exports = save; |
@ -0,0 +1,36 @@ |
|||
// ==========================================
|
|||
// BOWER: spawn
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var spawn = require('child_process').spawn; |
|||
|
|||
// This module is similar to child-process spawn
|
|||
// but automatically handles errors
|
|||
// When an error occurs, it emits the error event containing the details of the error
|
|||
// It also removes all the listeners of the command
|
|||
|
|||
module.exports = function (command, args, options, emitter) { |
|||
var cp = spawn(command, args, options); |
|||
var stderr = ''; |
|||
|
|||
cp.stderr.on('data', function (data) { |
|||
stderr += data; |
|||
}); |
|||
|
|||
cp.on('exit', function (code) { |
|||
if (code && (!options || !options.ignoreCodes || options.ignoreCodes.indexOf(code) === -1)) { |
|||
cp.removeAllListeners(); |
|||
var err = new Error('status code of ' + command + ': ' + code); |
|||
err.details = stderr; |
|||
err.command = command; |
|||
err.code = code; |
|||
emitter.emit('error', err); |
|||
} |
|||
}); |
|||
|
|||
return cp; |
|||
}; |
@ -0,0 +1,41 @@ |
|||
// ==========================================
|
|||
// BOWER: Hogan Renderer w/ template cache
|
|||
// ==========================================
|
|||
// Copyright 2012 Twitter, Inc
|
|||
// Licensed under The MIT License
|
|||
// http://opensource.org/licenses/MIT
|
|||
// ==========================================
|
|||
|
|||
var events = require('events'); |
|||
var hogan = require('hogan.js'); |
|||
var path = require('path'); |
|||
var fs = require('fs'); |
|||
|
|||
require('../util/hogan-colors'); |
|||
|
|||
var templates = {}; |
|||
|
|||
module.exports = function (name, context, sync) { |
|||
var emitter = new events.EventEmitter; |
|||
|
|||
var templateName = name + '.mustache'; |
|||
var templatePath = path.join(__dirname, '../../templates/', templateName); |
|||
|
|||
if (sync) { |
|||
if (!templates[templatePath]) templates[templatePath] = fs.readFileSync(templatePath, 'utf-8'); |
|||
return hogan.compile(templates[templatePath]).renderWithColors(context); |
|||
} else if (templates[templatePath]) { |
|||
process.nextTick(function () { |
|||
emitter.emit('data', hogan.compile(templates[templatePath]).renderWithColors(context)); |
|||
}); |
|||
} else { |
|||
fs.readFile(templatePath, 'utf-8', function (err, file) { |
|||
if (err) return emitter.emit('error', err); |
|||
|
|||
templates[templatePath] = file; |
|||
emitter.emit('data', hogan.compile(file).renderWithColors(context)); |
|||
}); |
|||
} |
|||
|
|||
return emitter; |
|||
}; |
@ -0,0 +1 @@ |
|||
../hogan.js/bin/hulk |
@ -0,0 +1 @@ |
|||
../lodash/build.js |
@ -0,0 +1,92 @@ |
|||
archy |
|||
===== |
|||
|
|||
Render nested hierarchies `npm ls` style with unicode pipes. |
|||
|
|||
[](http://travis-ci.org/substack/node-archy) |
|||
|
|||
example |
|||
======= |
|||
|
|||
``` js |
|||
var archy = require('archy'); |
|||
var s = archy({ |
|||
label : 'beep', |
|||
nodes : [ |
|||
'ity', |
|||
{ |
|||
label : 'boop', |
|||
nodes : [ |
|||
{ |
|||
label : 'o_O', |
|||
nodes : [ |
|||
{ |
|||
label : 'oh', |
|||
nodes : [ 'hello', 'puny' ] |
|||
}, |
|||
'human' |
|||
] |
|||
}, |
|||
'party\ntime!' |
|||
] |
|||
} |
|||
] |
|||
}); |
|||
console.log(s); |
|||
``` |
|||
|
|||
output |
|||
|
|||
``` |
|||
beep |
|||
├── ity |
|||
└─┬ boop |
|||
├─┬ o_O |
|||
│ ├─┬ oh |
|||
│ │ ├── hello |
|||
│ │ └── puny |
|||
│ └── human |
|||
└── party |
|||
time! |
|||
``` |
|||
|
|||
methods |
|||
======= |
|||
|
|||
var archy = require('archy') |
|||
|
|||
archy(obj, prefix='', opts={}) |
|||
------------------------------ |
|||
|
|||
Return a string representation of `obj` with unicode pipe characters like how |
|||
`npm ls` looks. |
|||
|
|||
`obj` should be a tree of nested objects with `'label'` and `'nodes'` fields. |
|||
`'label'` is a string of text to display at a node level and `'nodes'` is an |
|||
array of the descendents of the current node. |
|||
|
|||
If a node is a string, that string will be used as the `'label'` and an empty |
|||
array of `'nodes'` will be used. |
|||
|
|||
`prefix` gets prepended to all the lines and is used by the algorithm to |
|||
recursively update. |
|||
|
|||
If `'label'` has newlines they will be indented at the present indentation level |
|||
with the current prefix. |
|||
|
|||
To disable unicode results in favor of all-ansi output set `opts.unicode` to |
|||
`false`. |
|||
|
|||
install |
|||
======= |
|||
|
|||
With [npm](http://npmjs.org) do: |
|||
|
|||
``` |
|||
npm install archy |
|||
``` |
|||
|
|||
license |
|||
======= |
|||
|
|||
MIT/X11 |
@ -0,0 +1,24 @@ |
|||
var archy = require('../'); |
|||
var s = archy({ |
|||
label : 'beep', |
|||
nodes : [ |
|||
'ity', |
|||
{ |
|||
label : 'boop', |
|||
nodes : [ |
|||
{ |
|||
label : 'o_O', |
|||
nodes : [ |
|||
{ |
|||
label : 'oh', |
|||
nodes : [ 'hello', 'puny' ] |
|||
}, |
|||
'human' |
|||
] |
|||
}, |
|||
'party\ntime!' |
|||
] |
|||
} |
|||
] |
|||
}); |
|||
console.log(s); |
@ -0,0 +1,25 @@ |
|||
var archy = require('../'); |
|||
|
|||
var s = archy({ |
|||
label : 'beep\none\ntwo', |
|||
nodes : [ |
|||
'ity', |
|||
{ |
|||
label : 'boop', |
|||
nodes : [ |
|||
{ |
|||
label : 'o_O\nwheee', |
|||
nodes : [ |
|||
{ |
|||
label : 'oh', |
|||
nodes : [ 'hello', 'puny\nmeat' ] |
|||
}, |
|||
'creature' |
|||
] |
|||
}, |
|||
'party\ntime!' |
|||
] |
|||
} |
|||
] |
|||
}); |
|||
console.log(s); |
@ -0,0 +1,35 @@ |
|||
module.exports = function archy (obj, prefix, opts) { |
|||
if (prefix === undefined) prefix = ''; |
|||
if (!opts) opts = {}; |
|||
var chr = function (s) { |
|||
var chars = { |
|||
'│' : '|', |
|||
'└' : '`', |
|||
'├' : '+', |
|||
'─' : '-', |
|||
'┬' : '-' |
|||
}; |
|||
return opts.unicode === false ? chars[s] : s; |
|||
}; |
|||
|
|||
if (typeof obj === 'string') obj = { label : obj }; |
|||
|
|||
var nodes = obj.nodes || []; |
|||
var lines = (obj.label || '').split('\n'); |
|||
var splitter = '\n' + prefix + (nodes.length ? chr('│') : ' ') + ' '; |
|||
|
|||
return prefix |
|||
+ lines.join(splitter) + '\n' |
|||
+ nodes.map(function (node, ix) { |
|||
var last = ix === nodes.length - 1; |
|||
var more = node.nodes && node.nodes.length; |
|||
var prefix_ = prefix + (last ? ' ' : chr('│')) + ' '; |
|||
|
|||
return prefix |
|||
+ (last ? chr('└') : chr('├')) + chr('─') |
|||
+ (more ? chr('┬') : chr('─')) + ' ' |
|||
+ archy(node, prefix_, opts).slice(prefix.length + 2) |
|||
; |
|||
}).join('') |
|||
; |
|||
}; |
@ -0,0 +1,41 @@ |
|||
{ |
|||
"name": "archy", |
|||
"version": "0.0.2", |
|||
"description": "render nested hierarchies `npm ls` style with unicode pipes", |
|||
"main": "index.js", |
|||
"directories": { |
|||
"lib": ".", |
|||
"example": "example", |
|||
"test": "test" |
|||
}, |
|||
"devDependencies": { |
|||
"tap": "~0.2.3" |
|||
}, |
|||
"scripts": { |
|||
"test": "tap test" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "http://github.com/substack/node-archy.git" |
|||
}, |
|||
"keywords": [ |
|||
"hierarchy", |
|||
"npm ls", |
|||
"unicode", |
|||
"pretty", |
|||
"print" |
|||
], |
|||
"author": { |
|||
"name": "James Halliday", |
|||
"email": "mail@substack.net", |
|||
"url": "http://substack.net" |
|||
}, |
|||
"license": "MIT/X11", |
|||
"engine": { |
|||
"node": ">=0.4" |
|||
}, |
|||
"readme": "archy\n=====\n\nRender nested hierarchies `npm ls` style with unicode pipes.\n\n[](http://travis-ci.org/substack/node-archy)\n\nexample\n=======\n\n``` js\nvar archy = require('archy');\nvar s = archy({\n label : 'beep',\n nodes : [\n 'ity',\n {\n label : 'boop',\n nodes : [\n {\n label : 'o_O',\n nodes : [\n {\n label : 'oh',\n nodes : [ 'hello', 'puny' ]\n },\n 'human'\n ]\n },\n 'party\\ntime!'\n ]\n }\n ]\n});\nconsole.log(s);\n```\n\noutput\n\n```\nbeep\n├── ity\n└─┬ boop\n ├─┬ o_O\n │ ├─┬ oh\n │ │ ├── hello\n │ │ └── puny\n │ └── human\n └── party\n time!\n```\n\nmethods\n=======\n\nvar archy = require('archy')\n\narchy(obj, prefix='', opts={})\n------------------------------\n\nReturn a string representation of `obj` with unicode pipe characters like how\n`npm ls` looks.\n\n`obj` should be a tree of nested objects with `'label'` and `'nodes'` fields.\n`'label'` is a string of text to display at a node level and `'nodes'` is an\narray of the descendents of the current node.\n\nIf a node is a string, that string will be used as the `'label'` and an empty\narray of `'nodes'` will be used.\n\n`prefix` gets prepended to all the lines and is used by the algorithm to\nrecursively update.\n\nIf `'label'` has newlines they will be indented at the present indentation level\nwith the current prefix.\n\nTo disable unicode results in favor of all-ansi output set `opts.unicode` to\n`false`.\n\ninstall\n=======\n\nWith [npm](http://npmjs.org) do:\n\n```\nnpm install archy\n```\n\nlicense\n=======\n\nMIT/X11\n", |
|||
"readmeFilename": "README.markdown", |
|||
"_id": "archy@0.0.2", |
|||
"_from": "archy@~0.0.2" |
|||
} |
@ -0,0 +1,40 @@ |
|||
var test = require('tap').test; |
|||
var archy = require('../'); |
|||
|
|||
test('beep', function (t) { |
|||
var s = archy({ |
|||
label : 'beep', |
|||
nodes : [ |
|||
'ity', |
|||
{ |
|||
label : 'boop', |
|||
nodes : [ |
|||
{ |
|||
label : 'o_O', |
|||
nodes : [ |
|||
{ |
|||
label : 'oh', |
|||
nodes : [ 'hello', 'puny' ] |
|||
}, |
|||
'human' |
|||
] |
|||
}, |
|||
'party!' |
|||
] |
|||
} |
|||
] |
|||
}); |
|||
t.equal(s, [ |
|||
'beep', |
|||
'├── ity', |
|||
'└─┬ boop', |
|||
' ├─┬ o_O', |
|||
' │ ├─┬ oh', |
|||
' │ │ ├── hello', |
|||
' │ │ └── puny', |
|||
' │ └── human', |
|||
' └── party!', |
|||
'' |
|||
].join('\n')); |
|||
t.end(); |
|||
}); |
@ -0,0 +1,45 @@ |
|||
var test = require('tap').test; |
|||
var archy = require('../'); |
|||
|
|||
test('multi-line', function (t) { |
|||
var s = archy({ |
|||
label : 'beep\none\ntwo', |
|||
nodes : [ |
|||
'ity', |
|||
{ |
|||
label : 'boop', |
|||
nodes : [ |
|||
{ |
|||
label : 'o_O\nwheee', |
|||
nodes : [ |
|||
{ |
|||
label : 'oh', |
|||
nodes : [ 'hello', 'puny\nmeat' ] |
|||
}, |
|||
'creature' |
|||
] |
|||
}, |
|||
'party\ntime!' |
|||
] |
|||
} |
|||
] |
|||
}); |
|||
t.equal(s, [ |
|||
'beep', |
|||
'│ one', |
|||
'│ two', |
|||
'├── ity', |
|||
'└─┬ boop', |
|||
' ├─┬ o_O', |
|||
' │ │ wheee', |
|||
' │ ├─┬ oh', |
|||
' │ │ ├── hello', |
|||
' │ │ └── puny', |
|||
' │ │ meat', |
|||
' │ └── creature', |
|||
' └── party', |
|||
' time!', |
|||
'' |
|||
].join('\n')); |
|||
t.end(); |
|||
}); |
@ -0,0 +1,40 @@ |
|||
var test = require('tap').test; |
|||
var archy = require('../'); |
|||
|
|||
test('beep', function (t) { |
|||
var s = archy({ |
|||
label : 'beep', |
|||
nodes : [ |
|||
'ity', |
|||
{ |
|||
label : 'boop', |
|||
nodes : [ |
|||
{ |
|||
label : 'o_O', |
|||
nodes : [ |
|||
{ |
|||
label : 'oh', |
|||
nodes : [ 'hello', 'puny' ] |
|||
}, |
|||
'human' |
|||
] |
|||
}, |
|||
'party!' |
|||
] |
|||
} |
|||
] |
|||
}, '', { unicode : false }); |
|||
t.equal(s, [ |
|||
'beep', |
|||
'+-- ity', |
|||
'`-- boop', |
|||
' +-- o_O', |
|||
' | +-- oh', |
|||
' | | +-- hello', |
|||
' | | `-- puny', |
|||
' | `-- human', |
|||
' `-- party!', |
|||
'' |
|||
].join('\n')); |
|||
t.end(); |
|||
}); |
@ -0,0 +1,19 @@ |
|||
Copyright (c) 2010 Caolan McMahon |
|||
|
|||
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. |
1414
node/node_modules/grunt-bower-task/node_modules/bower/node_modules/async/README.md
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,952 @@ |
|||
/*global setImmediate: false, setTimeout: false, console: false */ |
|||
(function () { |
|||
|
|||
var async = {}; |
|||
|
|||
// global on the server, window in the browser
|
|||
var root, previous_async; |
|||
|
|||
root = this; |
|||
if (root != null) { |
|||
previous_async = root.async; |
|||
} |
|||
|
|||
async.noConflict = function () { |
|||
root.async = previous_async; |
|||
return async; |
|||
}; |
|||
|
|||
function only_once(fn) { |
|||
var called = false; |
|||
return function() { |
|||
if (called) throw new Error("Callback was already called."); |
|||
called = true; |
|||
fn.apply(root, arguments); |
|||
} |
|||
} |
|||
|
|||
//// cross-browser compatiblity functions ////
|
|||
|
|||
var _each = function (arr, iterator) { |
|||
if (arr.forEach) { |
|||
return arr.forEach(iterator); |
|||
} |
|||
for (var i = 0; i < arr.length; i += 1) { |
|||
iterator(arr[i], i, arr); |
|||
} |
|||
}; |
|||
|
|||
var _map = function (arr, iterator) { |
|||
if (arr.map) { |
|||
return arr.map(iterator); |
|||
} |
|||
var results = []; |
|||
_each(arr, function (x, i, a) { |
|||
results.push(iterator(x, i, a)); |
|||
}); |
|||
return results; |
|||
}; |
|||
|
|||
var _reduce = function (arr, iterator, memo) { |
|||
if (arr.reduce) { |
|||
return arr.reduce(iterator, memo); |
|||
} |
|||
_each(arr, function (x, i, a) { |
|||
memo = iterator(memo, x, i, a); |
|||
}); |
|||
return memo; |
|||
}; |
|||
|
|||
var _keys = function (obj) { |
|||
if (Object.keys) { |
|||
return Object.keys(obj); |
|||
} |
|||
var keys = []; |
|||
for (var k in obj) { |
|||
if (obj.hasOwnProperty(k)) { |
|||
keys.push(k); |
|||
} |
|||
} |
|||
return keys; |
|||
}; |
|||
|
|||
//// exported async module functions ////
|
|||
|
|||
//// nextTick implementation with browser-compatible fallback ////
|
|||
if (typeof process === 'undefined' || !(process.nextTick)) { |
|||
if (typeof setImmediate === 'function') { |
|||
async.setImmediate = setImmediate; |
|||
async.nextTick = setImmediate; |
|||
} |
|||
else { |
|||
async.nextTick = function (fn) { |
|||
setTimeout(fn, 0); |
|||
}; |
|||
async.setImmediate = async.nextTick; |
|||
} |
|||
} |
|||
else { |
|||
async.nextTick = process.nextTick; |
|||
if (typeof setImmediate !== 'undefined') { |
|||
async.setImmediate = setImmediate; |
|||
} |
|||
else { |
|||
async.setImmediate = async.nextTick; |
|||
} |
|||
} |
|||
|
|||
async.each = function (arr, iterator, callback) { |
|||
callback = callback || function () {}; |
|||
if (!arr.length) { |
|||
return callback(); |
|||
} |
|||
var completed = 0; |
|||
_each(arr, function (x) { |
|||
iterator(x, only_once(function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
completed += 1; |
|||
if (completed >= arr.length) { |
|||
callback(null); |
|||
} |
|||
} |
|||
})); |
|||
}); |
|||
}; |
|||
async.forEach = async.each; |
|||
|
|||
async.eachSeries = function (arr, iterator, callback) { |
|||
callback = callback || function () {}; |
|||
if (!arr.length) { |
|||
return callback(); |
|||
} |
|||
var completed = 0; |
|||
var iterate = function () { |
|||
iterator(arr[completed], function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
completed += 1; |
|||
if (completed >= arr.length) { |
|||
callback(null); |
|||
} |
|||
else { |
|||
iterate(); |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
iterate(); |
|||
}; |
|||
async.forEachSeries = async.eachSeries; |
|||
|
|||
async.eachLimit = function (arr, limit, iterator, callback) { |
|||
var fn = _eachLimit(limit); |
|||
fn.apply(null, [arr, iterator, callback]); |
|||
}; |
|||
async.forEachLimit = async.eachLimit; |
|||
|
|||
var _eachLimit = function (limit) { |
|||
|
|||
return function (arr, iterator, callback) { |
|||
callback = callback || function () {}; |
|||
if (!arr.length || limit <= 0) { |
|||
return callback(); |
|||
} |
|||
var completed = 0; |
|||
var started = 0; |
|||
var running = 0; |
|||
|
|||
(function replenish () { |
|||
if (completed >= arr.length) { |
|||
return callback(); |
|||
} |
|||
|
|||
while (running < limit && started < arr.length) { |
|||
started += 1; |
|||
running += 1; |
|||
iterator(arr[started - 1], function (err) { |
|||
if (err) { |
|||
callback(err); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
completed += 1; |
|||
running -= 1; |
|||
if (completed >= arr.length) { |
|||
callback(); |
|||
} |
|||
else { |
|||
replenish(); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
})(); |
|||
}; |
|||
}; |
|||
|
|||
|
|||
var doParallel = function (fn) { |
|||
return function () { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
return fn.apply(null, [async.each].concat(args)); |
|||
}; |
|||
}; |
|||
var doParallelLimit = function(limit, fn) { |
|||
return function () { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
return fn.apply(null, [_eachLimit(limit)].concat(args)); |
|||
}; |
|||
}; |
|||
var doSeries = function (fn) { |
|||
return function () { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
return fn.apply(null, [async.eachSeries].concat(args)); |
|||
}; |
|||
}; |
|||
|
|||
|
|||
var _asyncMap = function (eachfn, arr, iterator, callback) { |
|||
var results = []; |
|||
arr = _map(arr, function (x, i) { |
|||
return {index: i, value: x}; |
|||
}); |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x.value, function (err, v) { |
|||
results[x.index] = v; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, results); |
|||
}); |
|||
}; |
|||
async.map = doParallel(_asyncMap); |
|||
async.mapSeries = doSeries(_asyncMap); |
|||
async.mapLimit = function (arr, limit, iterator, callback) { |
|||
return _mapLimit(limit)(arr, iterator, callback); |
|||
}; |
|||
|
|||
var _mapLimit = function(limit) { |
|||
return doParallelLimit(limit, _asyncMap); |
|||
}; |
|||
|
|||
// reduce only has a series version, as doing reduce in parallel won't
|
|||
// work in many situations.
|
|||
async.reduce = function (arr, memo, iterator, callback) { |
|||
async.eachSeries(arr, function (x, callback) { |
|||
iterator(memo, x, function (err, v) { |
|||
memo = v; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, memo); |
|||
}); |
|||
}; |
|||
// inject alias
|
|||
async.inject = async.reduce; |
|||
// foldl alias
|
|||
async.foldl = async.reduce; |
|||
|
|||
async.reduceRight = function (arr, memo, iterator, callback) { |
|||
var reversed = _map(arr, function (x) { |
|||
return x; |
|||
}).reverse(); |
|||
async.reduce(reversed, memo, iterator, callback); |
|||
}; |
|||
// foldr alias
|
|||
async.foldr = async.reduceRight; |
|||
|
|||
var _filter = function (eachfn, arr, iterator, callback) { |
|||
var results = []; |
|||
arr = _map(arr, function (x, i) { |
|||
return {index: i, value: x}; |
|||
}); |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x.value, function (v) { |
|||
if (v) { |
|||
results.push(x); |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
callback(_map(results.sort(function (a, b) { |
|||
return a.index - b.index; |
|||
}), function (x) { |
|||
return x.value; |
|||
})); |
|||
}); |
|||
}; |
|||
async.filter = doParallel(_filter); |
|||
async.filterSeries = doSeries(_filter); |
|||
// select alias
|
|||
async.select = async.filter; |
|||
async.selectSeries = async.filterSeries; |
|||
|
|||
var _reject = function (eachfn, arr, iterator, callback) { |
|||
var results = []; |
|||
arr = _map(arr, function (x, i) { |
|||
return {index: i, value: x}; |
|||
}); |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x.value, function (v) { |
|||
if (!v) { |
|||
results.push(x); |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
callback(_map(results.sort(function (a, b) { |
|||
return a.index - b.index; |
|||
}), function (x) { |
|||
return x.value; |
|||
})); |
|||
}); |
|||
}; |
|||
async.reject = doParallel(_reject); |
|||
async.rejectSeries = doSeries(_reject); |
|||
|
|||
var _detect = function (eachfn, arr, iterator, main_callback) { |
|||
eachfn(arr, function (x, callback) { |
|||
iterator(x, function (result) { |
|||
if (result) { |
|||
main_callback(x); |
|||
main_callback = function () {}; |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}); |
|||
}, function (err) { |
|||
main_callback(); |
|||
}); |
|||
}; |
|||
async.detect = doParallel(_detect); |
|||
async.detectSeries = doSeries(_detect); |
|||
|
|||
async.some = function (arr, iterator, main_callback) { |
|||
async.each(arr, function (x, callback) { |
|||
iterator(x, function (v) { |
|||
if (v) { |
|||
main_callback(true); |
|||
main_callback = function () {}; |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
main_callback(false); |
|||
}); |
|||
}; |
|||
// any alias
|
|||
async.any = async.some; |
|||
|
|||
async.every = function (arr, iterator, main_callback) { |
|||
async.each(arr, function (x, callback) { |
|||
iterator(x, function (v) { |
|||
if (!v) { |
|||
main_callback(false); |
|||
main_callback = function () {}; |
|||
} |
|||
callback(); |
|||
}); |
|||
}, function (err) { |
|||
main_callback(true); |
|||
}); |
|||
}; |
|||
// all alias
|
|||
async.all = async.every; |
|||
|
|||
async.sortBy = function (arr, iterator, callback) { |
|||
async.map(arr, function (x, callback) { |
|||
iterator(x, function (err, criteria) { |
|||
if (err) { |
|||
callback(err); |
|||
} |
|||
else { |
|||
callback(null, {value: x, criteria: criteria}); |
|||
} |
|||
}); |
|||
}, function (err, results) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
else { |
|||
var fn = function (left, right) { |
|||
var a = left.criteria, b = right.criteria; |
|||
return a < b ? -1 : a > b ? 1 : 0; |
|||
}; |
|||
callback(null, _map(results.sort(fn), function (x) { |
|||
return x.value; |
|||
})); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
async.auto = function (tasks, callback) { |
|||
callback = callback || function () {}; |
|||
var keys = _keys(tasks); |
|||
if (!keys.length) { |
|||
return callback(null); |
|||
} |
|||
|
|||
var results = {}; |
|||
|
|||
var listeners = []; |
|||
var addListener = function (fn) { |
|||
listeners.unshift(fn); |
|||
}; |
|||
var removeListener = function (fn) { |
|||
for (var i = 0; i < listeners.length; i += 1) { |
|||
if (listeners[i] === fn) { |
|||
listeners.splice(i, 1); |
|||
return; |
|||
} |
|||
} |
|||
}; |
|||
var taskComplete = function () { |
|||
_each(listeners.slice(0), function (fn) { |
|||
fn(); |
|||
}); |
|||
}; |
|||
|
|||
addListener(function () { |
|||
if (_keys(results).length === keys.length) { |
|||
callback(null, results); |
|||
callback = function () {}; |
|||
} |
|||
}); |
|||
|
|||
_each(keys, function (k) { |
|||
var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; |
|||
var taskCallback = function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
if (err) { |
|||
var safeResults = {}; |
|||
_each(_keys(results), function(rkey) { |
|||
safeResults[rkey] = results[rkey]; |
|||
}); |
|||
safeResults[k] = args; |
|||
callback(err, safeResults); |
|||
// stop subsequent errors hitting callback multiple times
|
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
results[k] = args; |
|||
async.setImmediate(taskComplete); |
|||
} |
|||
}; |
|||
var requires = task.slice(0, Math.abs(task.length - 1)) || []; |
|||
var ready = function () { |
|||
return _reduce(requires, function (a, x) { |
|||
return (a && results.hasOwnProperty(x)); |
|||
}, true) && !results.hasOwnProperty(k); |
|||
}; |
|||
if (ready()) { |
|||
task[task.length - 1](taskCallback, results); |
|||
} |
|||
else { |
|||
var listener = function () { |
|||
if (ready()) { |
|||
removeListener(listener); |
|||
task[task.length - 1](taskCallback, results); |
|||
} |
|||
}; |
|||
addListener(listener); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
async.waterfall = function (tasks, callback) { |
|||
callback = callback || function () {}; |
|||
if (tasks.constructor !== Array) { |
|||
var err = new Error('First argument to waterfall must be an array of functions'); |
|||
return callback(err); |
|||
} |
|||
if (!tasks.length) { |
|||
return callback(); |
|||
} |
|||
var wrapIterator = function (iterator) { |
|||
return function (err) { |
|||
if (err) { |
|||
callback.apply(null, arguments); |
|||
callback = function () {}; |
|||
} |
|||
else { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
var next = iterator.next(); |
|||
if (next) { |
|||
args.push(wrapIterator(next)); |
|||
} |
|||
else { |
|||
args.push(callback); |
|||
} |
|||
async.setImmediate(function () { |
|||
iterator.apply(null, args); |
|||
}); |
|||
} |
|||
}; |
|||
}; |
|||
wrapIterator(async.iterator(tasks))(); |
|||
}; |
|||
|
|||
var _parallel = function(eachfn, tasks, callback) { |
|||
callback = callback || function () {}; |
|||
if (tasks.constructor === Array) { |
|||
eachfn.map(tasks, function (fn, callback) { |
|||
if (fn) { |
|||
fn(function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
callback.call(null, err, args); |
|||
}); |
|||
} |
|||
}, callback); |
|||
} |
|||
else { |
|||
var results = {}; |
|||
eachfn.each(_keys(tasks), function (k, callback) { |
|||
tasks[k](function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
results[k] = args; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, results); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
async.parallel = function (tasks, callback) { |
|||
_parallel({ map: async.map, each: async.each }, tasks, callback); |
|||
}; |
|||
|
|||
async.parallelLimit = function(tasks, limit, callback) { |
|||
_parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); |
|||
}; |
|||
|
|||
async.series = function (tasks, callback) { |
|||
callback = callback || function () {}; |
|||
if (tasks.constructor === Array) { |
|||
async.mapSeries(tasks, function (fn, callback) { |
|||
if (fn) { |
|||
fn(function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
callback.call(null, err, args); |
|||
}); |
|||
} |
|||
}, callback); |
|||
} |
|||
else { |
|||
var results = {}; |
|||
async.eachSeries(_keys(tasks), function (k, callback) { |
|||
tasks[k](function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (args.length <= 1) { |
|||
args = args[0]; |
|||
} |
|||
results[k] = args; |
|||
callback(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, results); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
async.iterator = function (tasks) { |
|||
var makeCallback = function (index) { |
|||
var fn = function () { |
|||
if (tasks.length) { |
|||
tasks[index].apply(null, arguments); |
|||
} |
|||
return fn.next(); |
|||
}; |
|||
fn.next = function () { |
|||
return (index < tasks.length - 1) ? makeCallback(index + 1): null; |
|||
}; |
|||
return fn; |
|||
}; |
|||
return makeCallback(0); |
|||
}; |
|||
|
|||
async.apply = function (fn) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
return function () { |
|||
return fn.apply( |
|||
null, args.concat(Array.prototype.slice.call(arguments)) |
|||
); |
|||
}; |
|||
}; |
|||
|
|||
var _concat = function (eachfn, arr, fn, callback) { |
|||
var r = []; |
|||
eachfn(arr, function (x, cb) { |
|||
fn(x, function (err, y) { |
|||
r = r.concat(y || []); |
|||
cb(err); |
|||
}); |
|||
}, function (err) { |
|||
callback(err, r); |
|||
}); |
|||
}; |
|||
async.concat = doParallel(_concat); |
|||
async.concatSeries = doSeries(_concat); |
|||
|
|||
async.whilst = function (test, iterator, callback) { |
|||
if (test()) { |
|||
iterator(function (err) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
async.whilst(test, iterator, callback); |
|||
}); |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}; |
|||
|
|||
async.doWhilst = function (iterator, test, callback) { |
|||
iterator(function (err) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
if (test()) { |
|||
async.doWhilst(iterator, test, callback); |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
async.until = function (test, iterator, callback) { |
|||
if (!test()) { |
|||
iterator(function (err) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
async.until(test, iterator, callback); |
|||
}); |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}; |
|||
|
|||
async.doUntil = function (iterator, test, callback) { |
|||
iterator(function (err) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
if (!test()) { |
|||
async.doUntil(iterator, test, callback); |
|||
} |
|||
else { |
|||
callback(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
async.queue = function (worker, concurrency) { |
|||
if (concurrency === undefined) { |
|||
concurrency = 1; |
|||
} |
|||
function _insert(q, data, pos, callback) { |
|||
if(data.constructor !== Array) { |
|||
data = [data]; |
|||
} |
|||
_each(data, function(task) { |
|||
var item = { |
|||
data: task, |
|||
callback: typeof callback === 'function' ? callback : null |
|||
}; |
|||
|
|||
if (pos) { |
|||
q.tasks.unshift(item); |
|||
} else { |
|||
q.tasks.push(item); |
|||
} |
|||
|
|||
if (q.saturated && q.tasks.length === concurrency) { |
|||
q.saturated(); |
|||
} |
|||
async.setImmediate(q.process); |
|||
}); |
|||
} |
|||
|
|||
var workers = 0; |
|||
var q = { |
|||
tasks: [], |
|||
concurrency: concurrency, |
|||
saturated: null, |
|||
empty: null, |
|||
drain: null, |
|||
push: function (data, callback) { |
|||
_insert(q, data, false, callback); |
|||
}, |
|||
unshift: function (data, callback) { |
|||
_insert(q, data, true, callback); |
|||
}, |
|||
process: function () { |
|||
if (workers < q.concurrency && q.tasks.length) { |
|||
var task = q.tasks.shift(); |
|||
if (q.empty && q.tasks.length === 0) { |
|||
q.empty(); |
|||
} |
|||
workers += 1; |
|||
var next = function () { |
|||
workers -= 1; |
|||
if (task.callback) { |
|||
task.callback.apply(task, arguments); |
|||
} |
|||
if (q.drain && q.tasks.length + workers === 0) { |
|||
q.drain(); |
|||
} |
|||
q.process(); |
|||
}; |
|||
var cb = only_once(next); |
|||
worker(task.data, cb); |
|||
} |
|||
}, |
|||
length: function () { |
|||
return q.tasks.length; |
|||
}, |
|||
running: function () { |
|||
return workers; |
|||
} |
|||
}; |
|||
return q; |
|||
}; |
|||
|
|||
async.cargo = function (worker, payload) { |
|||
var working = false, |
|||
tasks = []; |
|||
|
|||
var cargo = { |
|||
tasks: tasks, |
|||
payload: payload, |
|||
saturated: null, |
|||
empty: null, |
|||
drain: null, |
|||
push: function (data, callback) { |
|||
if(data.constructor !== Array) { |
|||
data = [data]; |
|||
} |
|||
_each(data, function(task) { |
|||
tasks.push({ |
|||
data: task, |
|||
callback: typeof callback === 'function' ? callback : null |
|||
}); |
|||
if (cargo.saturated && tasks.length === payload) { |
|||
cargo.saturated(); |
|||
} |
|||
}); |
|||
async.setImmediate(cargo.process); |
|||
}, |
|||
process: function process() { |
|||
if (working) return; |
|||
if (tasks.length === 0) { |
|||
if(cargo.drain) cargo.drain(); |
|||
return; |
|||
} |
|||
|
|||
var ts = typeof payload === 'number' |
|||
? tasks.splice(0, payload) |
|||
: tasks.splice(0); |
|||
|
|||
var ds = _map(ts, function (task) { |
|||
return task.data; |
|||
}); |
|||
|
|||
if(cargo.empty) cargo.empty(); |
|||
working = true; |
|||
worker(ds, function () { |
|||
working = false; |
|||
|
|||
var args = arguments; |
|||
_each(ts, function (data) { |
|||
if (data.callback) { |
|||
data.callback.apply(null, args); |
|||
} |
|||
}); |
|||
|
|||
process(); |
|||
}); |
|||
}, |
|||
length: function () { |
|||
return tasks.length; |
|||
}, |
|||
running: function () { |
|||
return working; |
|||
} |
|||
}; |
|||
return cargo; |
|||
}; |
|||
|
|||
var _console_fn = function (name) { |
|||
return function (fn) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
fn.apply(null, args.concat([function (err) { |
|||
var args = Array.prototype.slice.call(arguments, 1); |
|||
if (typeof console !== 'undefined') { |
|||
if (err) { |
|||
if (console.error) { |
|||
console.error(err); |
|||
} |
|||
} |
|||
else if (console[name]) { |
|||
_each(args, function (x) { |
|||
console[name](x); |
|||
}); |
|||
} |
|||
} |
|||
}])); |
|||
}; |
|||
}; |
|||
async.log = _console_fn('log'); |
|||
async.dir = _console_fn('dir'); |
|||
/*async.info = _console_fn('info'); |
|||
async.warn = _console_fn('warn'); |
|||
async.error = _console_fn('error');*/ |
|||
|
|||
async.memoize = function (fn, hasher) { |
|||
var memo = {}; |
|||
var queues = {}; |
|||
hasher = hasher || function (x) { |
|||
return x; |
|||
}; |
|||
var memoized = function () { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
var callback = args.pop(); |
|||
var key = hasher.apply(null, args); |
|||
if (key in memo) { |
|||
callback.apply(null, memo[key]); |
|||
} |
|||
else if (key in queues) { |
|||
queues[key].push(callback); |
|||
} |
|||
else { |
|||
queues[key] = [callback]; |
|||
fn.apply(null, args.concat([function () { |
|||
memo[key] = arguments; |
|||
var q = queues[key]; |
|||
delete queues[key]; |
|||
for (var i = 0, l = q.length; i < l; i++) { |
|||
q[i].apply(null, arguments); |
|||
} |
|||
}])); |
|||
} |
|||
}; |
|||
memoized.memo = memo; |
|||
memoized.unmemoized = fn; |
|||
return memoized; |
|||
}; |
|||
|
|||
async.unmemoize = function (fn) { |
|||
return function () { |
|||
return (fn.unmemoized || fn).apply(null, arguments); |
|||
}; |
|||
}; |
|||
|
|||
async.times = function (count, iterator, callback) { |
|||
var counter = []; |
|||
for (var i = 0; i < count; i++) { |
|||
counter.push(i); |
|||
} |
|||
return async.map(counter, iterator, callback); |
|||
}; |
|||
|
|||
async.timesSeries = function (count, iterator, callback) { |
|||
var counter = []; |
|||
for (var i = 0; i < count; i++) { |
|||
counter.push(i); |
|||
} |
|||
return async.mapSeries(counter, iterator, callback); |
|||
}; |
|||
|
|||
async.compose = function (/* functions... */) { |
|||
var fns = Array.prototype.reverse.call(arguments); |
|||
return function () { |
|||
var that = this; |
|||
var args = Array.prototype.slice.call(arguments); |
|||
var callback = args.pop(); |
|||
async.reduce(fns, args, function (newargs, fn, cb) { |
|||
fn.apply(that, newargs.concat([function () { |
|||
var err = arguments[0]; |
|||
var nextargs = Array.prototype.slice.call(arguments, 1); |
|||
cb(err, nextargs); |
|||
}])) |
|||
}, |
|||
function (err, results) { |
|||
callback.apply(that, [err].concat(results)); |
|||
}); |
|||
}; |
|||
}; |
|||
|
|||
var _applyEach = function (eachfn, fns /*args...*/) { |
|||
var go = function () { |
|||
var that = this; |
|||
var args = Array.prototype.slice.call(arguments); |
|||
var callback = args.pop(); |
|||
return eachfn(fns, function (fn, cb) { |
|||
fn.apply(that, args.concat([cb])); |
|||
}, |
|||
callback); |
|||
}; |
|||
if (arguments.length > 2) { |
|||
var args = Array.prototype.slice.call(arguments, 2); |
|||
return go.apply(this, args); |
|||
} |
|||
else { |
|||
return go; |
|||
} |
|||
}; |
|||
async.applyEach = doParallel(_applyEach); |
|||
async.applyEachSeries = doSeries(_applyEach); |
|||
|
|||
async.forever = function (fn, callback) { |
|||
function next(err) { |
|||
if (err) { |
|||
if (callback) { |
|||
return callback(err); |
|||
} |
|||
throw err; |
|||
} |
|||
fn(next); |
|||
} |
|||
next(); |
|||
}; |
|||
|
|||
// AMD / RequireJS
|
|||
if (typeof define !== 'undefined' && define.amd) { |
|||
define([], function () { |
|||
return async; |
|||
}); |
|||
} |
|||
// Node.js
|
|||
else if (typeof module !== 'undefined' && module.exports) { |
|||
module.exports = async; |
|||
} |
|||
// included directly via <script> tag
|
|||
else { |
|||
root.async = async; |
|||
} |
|||
|
|||
}()); |
42
node/node_modules/grunt-bower-task/node_modules/bower/node_modules/async/package.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,5 @@ |
|||
.*.swp |
|||
node_modules/ |
|||
examples/deep-copy/ |
|||
examples/path/ |
|||
examples/filter-copy/ |
@ -0,0 +1,76 @@ |
|||
Like FS streams, but with stat on them, and supporting directories and |
|||
symbolic links, as well as normal files. Also, you can use this to set |
|||
the stats on a file, even if you don't change its contents, or to create |
|||
a symlink, etc. |
|||
|
|||
So, for example, you can "write" a directory, and it'll call `mkdir`. You |
|||
can specify a uid and gid, and it'll call `chown`. You can specify a |
|||
`mtime` and `atime`, and it'll call `utimes`. You can call it a symlink |
|||
and provide a `linkpath` and it'll call `symlink`. |
|||
|
|||
Note that it won't automatically resolve symbolic links. So, if you |
|||
call `fstream.Reader('/some/symlink')` then you'll get an object |
|||
that stats and then ends immediately (since it has no data). To follow |
|||
symbolic links, do this: `fstream.Reader({path:'/some/symlink', follow: |
|||
true })`. |
|||
|
|||
There are various checks to make sure that the bytes emitted are the |
|||
same as the intended size, if the size is set. |
|||
|
|||
## Examples |
|||
|
|||
```javascript |
|||
fstream |
|||
.Writer({ path: "path/to/file" |
|||
, mode: 0755 |
|||
, size: 6 |
|||
}) |
|||
.write("hello\n") |
|||
.end() |
|||
``` |
|||
|
|||
This will create the directories if they're missing, and then write |
|||
`hello\n` into the file, chmod it to 0755, and assert that 6 bytes have |
|||
been written when it's done. |
|||
|
|||
```javascript |
|||
fstream |
|||
.Writer({ path: "path/to/file" |
|||
, mode: 0755 |
|||
, size: 6 |
|||
, flags: "a" |
|||
}) |
|||
.write("hello\n") |
|||
.end() |
|||
``` |
|||
|
|||
You can pass flags in, if you want to append to a file. |
|||
|
|||
```javascript |
|||
fstream |
|||
.Writer({ path: "path/to/symlink" |
|||
, linkpath: "./file" |
|||
, SymbolicLink: true |
|||
, mode: "0755" // octal strings supported |
|||
}) |
|||
.end() |
|||
``` |
|||
|
|||
If isSymbolicLink is a function, it'll be called, and if it returns |
|||
true, then it'll treat it as a symlink. If it's not a function, then |
|||
any truish value will make a symlink, or you can set `type: |
|||
'SymbolicLink'`, which does the same thing. |
|||
|
|||
Note that the linkpath is relative to the symbolic link location, not |
|||
the parent dir or cwd. |
|||
|
|||
```javascript |
|||
fstream |
|||
.Reader("path/to/dir") |
|||
.pipe(fstream.Writer("path/to/other/dir")) |
|||
``` |
|||
|
|||
This will do like `cp -Rp path/to/dir path/to/other/dir`. If the other |
|||
dir exists and isn't a directory, then it'll emit an error. It'll also |
|||
set the uid, gid, mode, etc. to be identical. In this way, it's more |
|||
like `rsync -a` than simply a copy. |
@ -0,0 +1,131 @@ |
|||
var fstream = require("../fstream.js") |
|||
var path = require("path") |
|||
|
|||
var r = fstream.Reader({ path: path.dirname(__dirname) |
|||
, filter: function () { |
|||
return !this.basename.match(/^\./) && |
|||
!this.basename.match(/^node_modules$/) |
|||
!this.basename.match(/^deep-copy$/) |
|||
!this.basename.match(/^filter-copy$/) |
|||
} |
|||
}) |
|||
|
|||
// this writer will only write directories
|
|||
var w = fstream.Writer({ path: path.resolve(__dirname, "filter-copy") |
|||
, type: "Directory" |
|||
, filter: function () { |
|||
return this.type === "Directory" |
|||
} |
|||
}) |
|||
|
|||
var indent = "" |
|||
var escape = {} |
|||
|
|||
r.on("entry", appears) |
|||
r.on("ready", function () { |
|||
console.error("ready to begin!", r.path) |
|||
}) |
|||
|
|||
function appears (entry) { |
|||
console.error(indent + "a %s appears!", entry.type, entry.basename, typeof entry.basename) |
|||
if (foggy) { |
|||
console.error("FOGGY!") |
|||
var p = entry |
|||
do { |
|||
console.error(p.depth, p.path, p._paused) |
|||
} while (p = p.parent) |
|||
|
|||
throw new Error("\033[mshould not have entries while foggy") |
|||
} |
|||
indent += "\t" |
|||
entry.on("data", missile(entry)) |
|||
entry.on("end", runaway(entry)) |
|||
entry.on("entry", appears) |
|||
} |
|||
|
|||
var foggy |
|||
function missile (entry) { |
|||
if (entry.type === "Directory") { |
|||
var ended = false |
|||
entry.once("end", function () { ended = true }) |
|||
return function (c) { |
|||
// throw in some pathological pause()/resume() behavior
|
|||
// just for extra fun.
|
|||
process.nextTick(function () { |
|||
if (!foggy && !ended) { // && Math.random() < 0.3) {
|
|||
console.error(indent +"%s casts a spell", entry.basename) |
|||
console.error("\na slowing fog comes over the battlefield...\n\033[32m") |
|||
entry.pause() |
|||
entry.once("resume", liftFog) |
|||
foggy = setTimeout(liftFog, 1000) |
|||
|
|||
function liftFog (who) { |
|||
if (!foggy) return |
|||
if (who) { |
|||
console.error("%s breaks the spell!", who && who.path) |
|||
} else { |
|||
console.error("the spell expires!") |
|||
} |
|||
console.error("\033[mthe fog lifts!\n") |
|||
clearTimeout(foggy) |
|||
foggy = null |
|||
if (entry._paused) entry.resume() |
|||
} |
|||
|
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
return function (c) { |
|||
var e = Math.random() < 0.5 |
|||
console.error(indent + "%s %s for %d damage!", |
|||
entry.basename, |
|||
e ? "is struck" : "fires a chunk", |
|||
c.length) |
|||
} |
|||
} |
|||
|
|||
function runaway (entry) { return function () { |
|||
var e = Math.random() < 0.5 |
|||
console.error(indent + "%s %s", |
|||
entry.basename, |
|||
e ? "turns to flee" : "is vanquished!") |
|||
indent = indent.slice(0, -1) |
|||
}} |
|||
|
|||
|
|||
w.on("entry", attacks) |
|||
//w.on("ready", function () { attacks(w) })
|
|||
function attacks (entry) { |
|||
console.error(indent + "%s %s!", entry.basename, |
|||
entry.type === "Directory" ? "calls for backup" : "attacks") |
|||
entry.on("entry", attacks) |
|||
} |
|||
|
|||
ended = false |
|||
var i = 1 |
|||
r.on("end", function () { |
|||
if (foggy) clearTimeout(foggy) |
|||
console.error("\033[mIT'S OVER!!") |
|||
console.error("A WINNAR IS YOU!") |
|||
|
|||
console.log("ok " + (i ++) + " A WINNAR IS YOU") |
|||
ended = true |
|||
// now go through and verify that everything in there is a dir.
|
|||
var p = path.resolve(__dirname, "filter-copy") |
|||
var checker = fstream.Reader({ path: p }) |
|||
checker.checker = true |
|||
checker.on("child", function (e) { |
|||
var ok = e.type === "Directory" |
|||
console.log((ok ? "" : "not ") + "ok " + (i ++) + |
|||
" should be a dir: " + |
|||
e.path.substr(checker.path.length + 1)) |
|||
}) |
|||
}) |
|||
|
|||
process.on("exit", function () { |
|||
console.log((ended ? "" : "not ") + "ok " + (i ++) + " ended") |
|||
}) |
|||
|
|||
r.pipe(w) |
@ -0,0 +1,115 @@ |
|||
var fstream = require("../fstream.js") |
|||
var path = require("path") |
|||
|
|||
var r = fstream.Reader({ path: path.dirname(__dirname) |
|||
, filter: function () { |
|||
return !this.basename.match(/^\./) && |
|||
!this.basename.match(/^node_modules$/) |
|||
!this.basename.match(/^deep-copy$/) |
|||
} |
|||
}) |
|||
|
|||
var w = fstream.Writer({ path: path.resolve(__dirname, "deep-copy") |
|||
, type: "Directory" |
|||
}) |
|||
|
|||
var indent = "" |
|||
var escape = {} |
|||
|
|||
r.on("entry", appears) |
|||
r.on("ready", function () { |
|||
console.error("ready to begin!", r.path) |
|||
}) |
|||
|
|||
function appears (entry) { |
|||
console.error(indent + "a %s appears!", entry.type, entry.basename, typeof entry.basename, entry) |
|||
if (foggy) { |
|||
console.error("FOGGY!") |
|||
var p = entry |
|||
do { |
|||
console.error(p.depth, p.path, p._paused) |
|||
} while (p = p.parent) |
|||
|
|||
throw new Error("\033[mshould not have entries while foggy") |
|||
} |
|||
indent += "\t" |
|||
entry.on("data", missile(entry)) |
|||
entry.on("end", runaway(entry)) |
|||
entry.on("entry", appears) |
|||
} |
|||
|
|||
var foggy |
|||
function missile (entry) { |
|||
if (entry.type === "Directory") { |
|||
var ended = false |
|||
entry.once("end", function () { ended = true }) |
|||
return function (c) { |
|||
// throw in some pathological pause()/resume() behavior
|
|||
// just for extra fun.
|
|||
process.nextTick(function () { |
|||
if (!foggy && !ended) { // && Math.random() < 0.3) {
|
|||
console.error(indent +"%s casts a spell", entry.basename) |
|||
console.error("\na slowing fog comes over the battlefield...\n\033[32m") |
|||
entry.pause() |
|||
entry.once("resume", liftFog) |
|||
foggy = setTimeout(liftFog, 10) |
|||
|
|||
function liftFog (who) { |
|||
if (!foggy) return |
|||
if (who) { |
|||
console.error("%s breaks the spell!", who && who.path) |
|||
} else { |
|||
console.error("the spell expires!") |
|||
} |
|||
console.error("\033[mthe fog lifts!\n") |
|||
clearTimeout(foggy) |
|||
foggy = null |
|||
if (entry._paused) entry.resume() |
|||
} |
|||
|
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
return function (c) { |
|||
var e = Math.random() < 0.5 |
|||
console.error(indent + "%s %s for %d damage!", |
|||
entry.basename, |
|||
e ? "is struck" : "fires a chunk", |
|||
c.length) |
|||
} |
|||
} |
|||
|
|||
function runaway (entry) { return function () { |
|||
var e = Math.random() < 0.5 |
|||
console.error(indent + "%s %s", |
|||
entry.basename, |
|||
e ? "turns to flee" : "is vanquished!") |
|||
indent = indent.slice(0, -1) |
|||
}} |
|||
|
|||
|
|||
w.on("entry", attacks) |
|||
//w.on("ready", function () { attacks(w) })
|
|||
function attacks (entry) { |
|||
console.error(indent + "%s %s!", entry.basename, |
|||
entry.type === "Directory" ? "calls for backup" : "attacks") |
|||
entry.on("entry", attacks) |
|||
} |
|||
|
|||
ended = false |
|||
r.on("end", function () { |
|||
if (foggy) clearTimeout(foggy) |
|||
console.error("\033[mIT'S OVER!!") |
|||
console.error("A WINNAR IS YOU!") |
|||
|
|||
console.log("ok 1 A WINNAR IS YOU") |
|||
ended = true |
|||
}) |
|||
|
|||
process.on("exit", function () { |
|||
console.log((ended ? "" : "not ") + "ok 2 ended") |
|||
}) |
|||
|
|||
r.pipe(w) |
@ -0,0 +1,54 @@ |
|||
var fstream = require("../fstream.js") |
|||
var tap = require("tap") |
|||
var fs = require("fs") |
|||
var path = require("path") |
|||
var children = -1 |
|||
var dir = path.dirname(__dirname) |
|||
|
|||
var gotReady = false |
|||
var ended = false |
|||
|
|||
tap.test("reader test", function (t) { |
|||
|
|||
var r = fstream.Reader({ path: dir |
|||
, filter: function () { |
|||
// return this.parent === r
|
|||
return this.parent === r || this === r |
|||
} |
|||
}) |
|||
|
|||
r.on("ready", function () { |
|||
gotReady = true |
|||
children = fs.readdirSync(dir).length |
|||
console.error("Setting expected children to "+children) |
|||
t.equal(r.type, "Directory", "should be a directory") |
|||
}) |
|||
|
|||
r.on("entry", function (entry) { |
|||
children -- |
|||
if (!gotReady) { |
|||
t.fail("children before ready!") |
|||
} |
|||
t.equal(entry.dirname, r.path, "basename is parent dir") |
|||
}) |
|||
|
|||
r.on("error", function (er) { |
|||
t.fail(er) |
|||
t.end() |
|||
process.exit(1) |
|||
}) |
|||
|
|||
r.on("end", function () { |
|||
t.equal(children, 0, "should have seen all children") |
|||
ended = true |
|||
}) |
|||
|
|||
var closed = false |
|||
r.on("close", function () { |
|||
t.ok(ended, "saw end before close") |
|||
t.notOk(closed, "close should only happen once") |
|||
closed = true |
|||
t.end() |
|||
}) |
|||
|
|||
}) |
@ -0,0 +1,24 @@ |
|||
var fstream = require("../fstream.js") |
|||
, closed = false |
|||
|
|||
fstream |
|||
.Writer({ path: "path/to/symlink" |
|||
, linkpath: "./file" |
|||
, isSymbolicLink: true |
|||
, mode: "0755" // octal strings supported
|
|||
}) |
|||
.on("close", function () { |
|||
closed = true |
|||
var fs = require("fs") |
|||
var s = fs.lstatSync("path/to/symlink") |
|||
var isSym = s.isSymbolicLink() |
|||
console.log((isSym?"":"not ") +"ok 1 should be symlink") |
|||
var t = fs.readlinkSync("path/to/symlink") |
|||
var isTarget = t === "./file" |
|||
console.log((isTarget?"":"not ") +"ok 2 should link to ./file") |
|||
}) |
|||
.end() |
|||
|
|||
process.on("exit", function () { |
|||
console.log((closed?"":"not ")+"ok 3 should be closed") |
|||
}) |
@ -0,0 +1,31 @@ |
|||
exports.Abstract = require("./lib/abstract.js") |
|||
exports.Reader = require("./lib/reader.js") |
|||
exports.Writer = require("./lib/writer.js") |
|||
|
|||
exports.File = |
|||
{ Reader: require("./lib/file-reader.js") |
|||
, Writer: require("./lib/file-writer.js") } |
|||
|
|||
exports.Dir = |
|||
{ Reader : require("./lib/dir-reader.js") |
|||
, Writer : require("./lib/dir-writer.js") } |
|||
|
|||
exports.Link = |
|||
{ Reader : require("./lib/link-reader.js") |
|||
, Writer : require("./lib/link-writer.js") } |
|||
|
|||
exports.Proxy = |
|||
{ Reader : require("./lib/proxy-reader.js") |
|||
, Writer : require("./lib/proxy-writer.js") } |
|||
|
|||
exports.Reader.Dir = exports.DirReader = exports.Dir.Reader |
|||
exports.Reader.File = exports.FileReader = exports.File.Reader |
|||
exports.Reader.Link = exports.LinkReader = exports.Link.Reader |
|||
exports.Reader.Proxy = exports.ProxyReader = exports.Proxy.Reader |
|||
|
|||
exports.Writer.Dir = exports.DirWriter = exports.Dir.Writer |
|||
exports.Writer.File = exports.FileWriter = exports.File.Writer |
|||
exports.Writer.Link = exports.LinkWriter = exports.Link.Writer |
|||
exports.Writer.Proxy = exports.ProxyWriter = exports.Proxy.Writer |
|||
|
|||
exports.collect = require("./lib/collect.js") |
@ -0,0 +1,85 @@ |
|||
// the parent class for all fstreams.
|
|||
|
|||
module.exports = Abstract |
|||
|
|||
var Stream = require("stream").Stream |
|||
, inherits = require("inherits") |
|||
|
|||
function Abstract () { |
|||
Stream.call(this) |
|||
} |
|||
|
|||
inherits(Abstract, Stream) |
|||
|
|||
Abstract.prototype.on = function (ev, fn) { |
|||
if (ev === "ready" && this.ready) { |
|||
process.nextTick(fn.bind(this)) |
|||
} else { |
|||
Stream.prototype.on.call(this, ev, fn) |
|||
} |
|||
return this |
|||
} |
|||
|
|||
Abstract.prototype.abort = function () { |
|||
this._aborted = true |
|||
this.emit("abort") |
|||
} |
|||
|
|||
Abstract.prototype.destroy = function () {} |
|||
|
|||
Abstract.prototype.warn = function (msg, code) { |
|||
var me = this |
|||
, er = decorate(msg, code, me) |
|||
if (!me.listeners("warn")) { |
|||
console.error("%s %s\n" + |
|||
"path = %s\n" + |
|||
"syscall = %s\n" + |
|||
"fstream_type = %s\n" + |
|||
"fstream_path = %s\n" + |
|||
"fstream_unc_path = %s\n" + |
|||
"fstream_class = %s\n" + |
|||
"fstream_stack =\n%s\n", |
|||
code || "UNKNOWN", |
|||
er.stack, |
|||
er.path, |
|||
er.syscall, |
|||
er.fstream_type, |
|||
er.fstream_path, |
|||
er.fstream_unc_path, |
|||
er.fstream_class, |
|||
er.fstream_stack.join("\n")) |
|||
} else { |
|||
me.emit("warn", er) |
|||
} |
|||
} |
|||
|
|||
Abstract.prototype.info = function (msg, code) { |
|||
this.emit("info", msg, code) |
|||
} |
|||
|
|||
Abstract.prototype.error = function (msg, code, th) { |
|||
var er = decorate(msg, code, this) |
|||
if (th) throw er |
|||
else this.emit("error", er) |
|||
} |
|||
|
|||
function decorate (er, code, me) { |
|||
if (!(er instanceof Error)) er = new Error(er) |
|||
er.code = er.code || code |
|||
er.path = er.path || me.path |
|||
er.fstream_type = er.fstream_type || me.type |
|||
er.fstream_path = er.fstream_path || me.path |
|||
if (me._path !== me.path) { |
|||
er.fstream_unc_path = er.fstream_unc_path || me._path |
|||
} |
|||
if (me.linkpath) { |
|||
er.fstream_linkpath = er.fstream_linkpath || me.linkpath |
|||
} |
|||
er.fstream_class = er.fstream_class || me.constructor.name |
|||
er.fstream_stack = er.fstream_stack || |
|||
new Error().stack.split(/\n/).slice(3).map(function (s) { |
|||
return s.replace(/^ at /, "") |
|||
}) |
|||
|
|||
return er |
|||
} |
@ -0,0 +1,67 @@ |
|||
module.exports = collect |
|||
|
|||
function collect (stream) { |
|||
if (stream._collected) return |
|||
|
|||
stream._collected = true |
|||
stream.pause() |
|||
|
|||
stream.on("data", save) |
|||
stream.on("end", save) |
|||
var buf = [] |
|||
function save (b) { |
|||
if (typeof b === "string") b = new Buffer(b) |
|||
if (Buffer.isBuffer(b) && !b.length) return |
|||
buf.push(b) |
|||
} |
|||
|
|||
stream.on("entry", saveEntry) |
|||
var entryBuffer = [] |
|||
function saveEntry (e) { |
|||
collect(e) |
|||
entryBuffer.push(e) |
|||
} |
|||
|
|||
stream.on("proxy", proxyPause) |
|||
function proxyPause (p) { |
|||
p.pause() |
|||
} |
|||
|
|||
|
|||
// replace the pipe method with a new version that will
|
|||
// unlock the buffered stuff. if you just call .pipe()
|
|||
// without a destination, then it'll re-play the events.
|
|||
stream.pipe = (function (orig) { return function (dest) { |
|||
// console.error(" === open the pipes", dest && dest.path)
|
|||
|
|||
// let the entries flow through one at a time.
|
|||
// Once they're all done, then we can resume completely.
|
|||
var e = 0 |
|||
;(function unblockEntry () { |
|||
var entry = entryBuffer[e++] |
|||
// console.error(" ==== unblock entry", entry && entry.path)
|
|||
if (!entry) return resume() |
|||
entry.on("end", unblockEntry) |
|||
if (dest) dest.add(entry) |
|||
else stream.emit("entry", entry) |
|||
})() |
|||
|
|||
function resume () { |
|||
stream.removeListener("entry", saveEntry) |
|||
stream.removeListener("data", save) |
|||
stream.removeListener("end", save) |
|||
|
|||
stream.pipe = orig |
|||
if (dest) stream.pipe(dest) |
|||
|
|||
buf.forEach(function (b) { |
|||
if (b) stream.emit("data", b) |
|||
else stream.emit("end") |
|||
}) |
|||
|
|||
stream.resume() |
|||
} |
|||
|
|||
return dest |
|||
}})(stream.pipe) |
|||
} |
@ -0,0 +1,250 @@ |
|||
// A thing that emits "entry" events with Reader objects
|
|||
// Pausing it causes it to stop emitting entry events, and also
|
|||
// pauses the current entry if there is one.
|
|||
|
|||
module.exports = DirReader |
|||
|
|||
var fs = require("graceful-fs") |
|||
, fstream = require("../fstream.js") |
|||
, Reader = fstream.Reader |
|||
, inherits = require("inherits") |
|||
, mkdir = require("mkdirp") |
|||
, path = require("path") |
|||
, Reader = require("./reader.js") |
|||
, assert = require("assert").ok |
|||
|
|||
inherits(DirReader, Reader) |
|||
|
|||
function DirReader (props) { |
|||
var me = this |
|||
if (!(me instanceof DirReader)) throw new Error( |
|||
"DirReader must be called as constructor.") |
|||
|
|||
// should already be established as a Directory type
|
|||
if (props.type !== "Directory" || !props.Directory) { |
|||
throw new Error("Non-directory type "+ props.type) |
|||
} |
|||
|
|||
me.entries = null |
|||
me._index = -1 |
|||
me._paused = false |
|||
me._length = -1 |
|||
|
|||
if (props.sort) { |
|||
this.sort = props.sort |
|||
} |
|||
|
|||
Reader.call(this, props) |
|||
} |
|||
|
|||
DirReader.prototype._getEntries = function () { |
|||
var me = this |
|||
|
|||
// race condition. might pause() before calling _getEntries,
|
|||
// and then resume, and try to get them a second time.
|
|||
if (me._gotEntries) return |
|||
me._gotEntries = true |
|||
|
|||
fs.readdir(me._path, function (er, entries) { |
|||
if (er) return me.error(er) |
|||
|
|||
me.entries = entries |
|||
|
|||
me.emit("entries", entries) |
|||
if (me._paused) me.once("resume", processEntries) |
|||
else processEntries() |
|||
|
|||
function processEntries () { |
|||
me._length = me.entries.length |
|||
if (typeof me.sort === "function") { |
|||
me.entries = me.entries.sort(me.sort.bind(me)) |
|||
} |
|||
me._read() |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// start walking the dir, and emit an "entry" event for each one.
|
|||
DirReader.prototype._read = function () { |
|||
var me = this |
|||
|
|||
if (!me.entries) return me._getEntries() |
|||
|
|||
if (me._paused || me._currentEntry || me._aborted) { |
|||
// console.error("DR paused=%j, current=%j, aborted=%j", me._paused, !!me._currentEntry, me._aborted)
|
|||
return |
|||
} |
|||
|
|||
me._index ++ |
|||
if (me._index >= me.entries.length) { |
|||
if (!me._ended) { |
|||
me._ended = true |
|||
me.emit("end") |
|||
me.emit("close") |
|||
} |
|||
return |
|||
} |
|||
|
|||
// ok, handle this one, then.
|
|||
|
|||
// save creating a proxy, by stat'ing the thing now.
|
|||
var p = path.resolve(me._path, me.entries[me._index]) |
|||
assert(p !== me._path) |
|||
assert(me.entries[me._index]) |
|||
|
|||
// set this to prevent trying to _read() again in the stat time.
|
|||
me._currentEntry = p |
|||
fs[ me.props.follow ? "stat" : "lstat" ](p, function (er, stat) { |
|||
if (er) return me.error(er) |
|||
|
|||
var who = me._proxy || me |
|||
|
|||
stat.path = p |
|||
stat.basename = path.basename(p) |
|||
stat.dirname = path.dirname(p) |
|||
var childProps = me.getChildProps.call(who, stat) |
|||
childProps.path = p |
|||
childProps.basename = path.basename(p) |
|||
childProps.dirname = path.dirname(p) |
|||
|
|||
var entry = Reader(childProps, stat) |
|||
|
|||
// console.error("DR Entry", p, stat.size)
|
|||
|
|||
me._currentEntry = entry |
|||
|
|||
// "entry" events are for direct entries in a specific dir.
|
|||
// "child" events are for any and all children at all levels.
|
|||
// This nomenclature is not completely final.
|
|||
|
|||
entry.on("pause", function (who) { |
|||
if (!me._paused && !entry._disowned) { |
|||
me.pause(who) |
|||
} |
|||
}) |
|||
|
|||
entry.on("resume", function (who) { |
|||
if (me._paused && !entry._disowned) { |
|||
me.resume(who) |
|||
} |
|||
}) |
|||
|
|||
entry.on("stat", function (props) { |
|||
me.emit("_entryStat", entry, props) |
|||
if (entry._aborted) return |
|||
if (entry._paused) entry.once("resume", function () { |
|||
me.emit("entryStat", entry, props) |
|||
}) |
|||
else me.emit("entryStat", entry, props) |
|||
}) |
|||
|
|||
entry.on("ready", function EMITCHILD () { |
|||
// console.error("DR emit child", entry._path)
|
|||
if (me._paused) { |
|||
// console.error(" DR emit child - try again later")
|
|||
// pause the child, and emit the "entry" event once we drain.
|
|||
// console.error("DR pausing child entry")
|
|||
entry.pause(me) |
|||
return me.once("resume", EMITCHILD) |
|||
} |
|||
|
|||
// skip over sockets. they can't be piped around properly,
|
|||
// so there's really no sense even acknowledging them.
|
|||
// if someone really wants to see them, they can listen to
|
|||
// the "socket" events.
|
|||
if (entry.type === "Socket") { |
|||
me.emit("socket", entry) |
|||
} else { |
|||
me.emitEntry(entry) |
|||
} |
|||
}) |
|||
|
|||
var ended = false |
|||
entry.on("close", onend) |
|||
entry.on("disown", onend) |
|||
function onend () { |
|||
if (ended) return |
|||
ended = true |
|||
me.emit("childEnd", entry) |
|||
me.emit("entryEnd", entry) |
|||
me._currentEntry = null |
|||
if (!me._paused) { |
|||
me._read() |
|||
} |
|||
} |
|||
|
|||
// XXX Remove this. Works in node as of 0.6.2 or so.
|
|||
// Long filenames should not break stuff.
|
|||
entry.on("error", function (er) { |
|||
if (entry._swallowErrors) { |
|||
me.warn(er) |
|||
entry.emit("end") |
|||
entry.emit("close") |
|||
} else { |
|||
me.emit("error", er) |
|||
} |
|||
}) |
|||
|
|||
// proxy up some events.
|
|||
; [ "child" |
|||
, "childEnd" |
|||
, "warn" |
|||
].forEach(function (ev) { |
|||
entry.on(ev, me.emit.bind(me, ev)) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
DirReader.prototype.disown = function (entry) { |
|||
entry.emit("beforeDisown") |
|||
entry._disowned = true |
|||
entry.parent = entry.root = null |
|||
if (entry === this._currentEntry) { |
|||
this._currentEntry = null |
|||
} |
|||
entry.emit("disown") |
|||
} |
|||
|
|||
DirReader.prototype.getChildProps = function (stat) { |
|||
return { depth: this.depth + 1 |
|||
, root: this.root || this |
|||
, parent: this |
|||
, follow: this.follow |
|||
, filter: this.filter |
|||
, sort: this.props.sort |
|||
} |
|||
} |
|||
|
|||
DirReader.prototype.pause = function (who) { |
|||
var me = this |
|||
if (me._paused) return |
|||
who = who || me |
|||
me._paused = true |
|||
if (me._currentEntry && me._currentEntry.pause) { |
|||
me._currentEntry.pause(who) |
|||
} |
|||
me.emit("pause", who) |
|||
} |
|||
|
|||
DirReader.prototype.resume = function (who) { |
|||
var me = this |
|||
if (!me._paused) return |
|||
who = who || me |
|||
|
|||
me._paused = false |
|||
// console.error("DR Emit Resume", me._path)
|
|||
me.emit("resume", who) |
|||
if (me._paused) { |
|||
// console.error("DR Re-paused", me._path)
|
|||
return |
|||
} |
|||
|
|||
if (me._currentEntry) { |
|||
if (me._currentEntry.resume) me._currentEntry.resume(who) |
|||
} else me._read() |
|||
} |
|||
|
|||
DirReader.prototype.emitEntry = function (entry) { |
|||
this.emit("entry", entry) |
|||
this.emit("child", entry) |
|||
} |
@ -0,0 +1,171 @@ |
|||
// It is expected that, when .add() returns false, the consumer
|
|||
// of the DirWriter will pause until a "drain" event occurs. Note
|
|||
// that this is *almost always going to be the case*, unless the
|
|||
// thing being written is some sort of unsupported type, and thus
|
|||
// skipped over.
|
|||
|
|||
module.exports = DirWriter |
|||
|
|||
var fs = require("graceful-fs") |
|||
, fstream = require("../fstream.js") |
|||
, Writer = require("./writer.js") |
|||
, inherits = require("inherits") |
|||
, mkdir = require("mkdirp") |
|||
, path = require("path") |
|||
, collect = require("./collect.js") |
|||
|
|||
inherits(DirWriter, Writer) |
|||
|
|||
function DirWriter (props) { |
|||
var me = this |
|||
if (!(me instanceof DirWriter)) me.error( |
|||
"DirWriter must be called as constructor.", null, true) |
|||
|
|||
// should already be established as a Directory type
|
|||
if (props.type !== "Directory" || !props.Directory) { |
|||
me.error("Non-directory type "+ props.type + " " + |
|||
JSON.stringify(props), null, true) |
|||
} |
|||
|
|||
Writer.call(this, props) |
|||
} |
|||
|
|||
DirWriter.prototype._create = function () { |
|||
var me = this |
|||
mkdir(me._path, Writer.dirmode, function (er) { |
|||
if (er) return me.error(er) |
|||
// ready to start getting entries!
|
|||
me.ready = true |
|||
me.emit("ready") |
|||
me._process() |
|||
}) |
|||
} |
|||
|
|||
// a DirWriter has an add(entry) method, but its .write() doesn't
|
|||
// do anything. Why a no-op rather than a throw? Because this
|
|||
// leaves open the door for writing directory metadata for
|
|||
// gnu/solaris style dumpdirs.
|
|||
DirWriter.prototype.write = function () { |
|||
return true |
|||
} |
|||
|
|||
DirWriter.prototype.end = function () { |
|||
this._ended = true |
|||
this._process() |
|||
} |
|||
|
|||
DirWriter.prototype.add = function (entry) { |
|||
var me = this |
|||
|
|||
// console.error("\tadd", entry._path, "->", me._path)
|
|||
collect(entry) |
|||
if (!me.ready || me._currentEntry) { |
|||
me._buffer.push(entry) |
|||
return false |
|||
} |
|||
|
|||
// create a new writer, and pipe the incoming entry into it.
|
|||
if (me._ended) { |
|||
return me.error("add after end") |
|||
} |
|||
|
|||
me._buffer.push(entry) |
|||
me._process() |
|||
|
|||
return 0 === this._buffer.length |
|||
} |
|||
|
|||
DirWriter.prototype._process = function () { |
|||
var me = this |
|||
|
|||
// console.error("DW Process p=%j", me._processing, me.basename)
|
|||
|
|||
if (me._processing) return |
|||
|
|||
var entry = me._buffer.shift() |
|||
if (!entry) { |
|||
// console.error("DW Drain")
|
|||
me.emit("drain") |
|||
if (me._ended) me._finish() |
|||
return |
|||
} |
|||
|
|||
me._processing = true |
|||
// console.error("DW Entry", entry._path)
|
|||
|
|||
me.emit("entry", entry) |
|||
|
|||
// ok, add this entry
|
|||
//
|
|||
// don't allow recursive copying
|
|||
var p = entry |
|||
do { |
|||
var pp = p._path || p.path |
|||
if (pp === me.root._path || pp === me._path || |
|||
(pp && pp.indexOf(me._path) === 0)) { |
|||
// console.error("DW Exit (recursive)", entry.basename, me._path)
|
|||
me._processing = false |
|||
if (entry._collected) entry.pipe() |
|||
return me._process() |
|||
} |
|||
} while (p = p.parent) |
|||
|
|||
// console.error("DW not recursive")
|
|||
|
|||
// chop off the entry's root dir, replace with ours
|
|||
var props = { parent: me |
|||
, root: me.root || me |
|||
, type: entry.type |
|||
, depth: me.depth + 1 } |
|||
|
|||
var p = entry._path || entry.path || entry.props.path |
|||
if (entry.parent) { |
|||
p = p.substr(entry.parent._path.length + 1) |
|||
} |
|||
// get rid of any ../../ shenanigans
|
|||
props.path = path.join(me.path, path.join("/", p)) |
|||
|
|||
// if i have a filter, the child should inherit it.
|
|||
props.filter = me.filter |
|||
|
|||
// all the rest of the stuff, copy over from the source.
|
|||
Object.keys(entry.props).forEach(function (k) { |
|||
if (!props.hasOwnProperty(k)) { |
|||
props[k] = entry.props[k] |
|||
} |
|||
}) |
|||
|
|||
// not sure at this point what kind of writer this is.
|
|||
var child = me._currentChild = new Writer(props) |
|||
child.on("ready", function () { |
|||
// console.error("DW Child Ready", child.type, child._path)
|
|||
// console.error(" resuming", entry._path)
|
|||
entry.pipe(child) |
|||
entry.resume() |
|||
}) |
|||
|
|||
// XXX Make this work in node.
|
|||
// Long filenames should not break stuff.
|
|||
child.on("error", function (er) { |
|||
if (child._swallowErrors) { |
|||
me.warn(er) |
|||
child.emit("end") |
|||
child.emit("close") |
|||
} else { |
|||
me.emit("error", er) |
|||
} |
|||
}) |
|||
|
|||
// we fire _end internally *after* end, so that we don't move on
|
|||
// until any "end" listeners have had their chance to do stuff.
|
|||
child.on("close", onend) |
|||
var ended = false |
|||
function onend () { |
|||
if (ended) return |
|||
ended = true |
|||
// console.error("* DW Child end", child.basename)
|
|||
me._currentChild = null |
|||
me._processing = false |
|||
me._process() |
|||
} |
|||
} |
@ -0,0 +1,147 @@ |
|||
// Basically just a wrapper around an fs.ReadStream
|
|||
|
|||
module.exports = FileReader |
|||
|
|||
var fs = require("graceful-fs") |
|||
, fstream = require("../fstream.js") |
|||
, Reader = fstream.Reader |
|||
, inherits = require("inherits") |
|||
, mkdir = require("mkdirp") |
|||
, Reader = require("./reader.js") |
|||
, EOF = {EOF: true} |
|||
, CLOSE = {CLOSE: true} |
|||
|
|||
inherits(FileReader, Reader) |
|||
|
|||
function FileReader (props) { |
|||
// console.error(" FR create", props.path, props.size, new Error().stack)
|
|||
var me = this |
|||
if (!(me instanceof FileReader)) throw new Error( |
|||
"FileReader must be called as constructor.") |
|||
|
|||
// should already be established as a File type
|
|||
// XXX Todo: preserve hardlinks by tracking dev+inode+nlink,
|
|||
// with a HardLinkReader class.
|
|||
if (!((props.type === "Link" && props.Link) || |
|||
(props.type === "File" && props.File))) { |
|||
throw new Error("Non-file type "+ props.type) |
|||
} |
|||
|
|||
me._buffer = [] |
|||
me._bytesEmitted = 0 |
|||
Reader.call(me, props) |
|||
} |
|||
|
|||
FileReader.prototype._getStream = function () { |
|||
var me = this |
|||
, stream = me._stream = fs.createReadStream(me._path, me.props) |
|||
|
|||
if (me.props.blksize) { |
|||
stream.bufferSize = me.props.blksize |
|||
} |
|||
|
|||
stream.on("open", me.emit.bind(me, "open")) |
|||
|
|||
stream.on("data", function (c) { |
|||
// console.error("\t\t%d %s", c.length, me.basename)
|
|||
me._bytesEmitted += c.length |
|||
// no point saving empty chunks
|
|||
if (!c.length) return |
|||
else if (me._paused || me._buffer.length) { |
|||
me._buffer.push(c) |
|||
me._read() |
|||
} else me.emit("data", c) |
|||
}) |
|||
|
|||
stream.on("end", function () { |
|||
if (me._paused || me._buffer.length) { |
|||
// console.error("FR Buffering End", me._path)
|
|||
me._buffer.push(EOF) |
|||
me._read() |
|||
} else { |
|||
me.emit("end") |
|||
} |
|||
|
|||
if (me._bytesEmitted !== me.props.size) { |
|||
me.error("Didn't get expected byte count\n"+ |
|||
"expect: "+me.props.size + "\n" + |
|||
"actual: "+me._bytesEmitted) |
|||
} |
|||
}) |
|||
|
|||
stream.on("close", function () { |
|||
if (me._paused || me._buffer.length) { |
|||
// console.error("FR Buffering Close", me._path)
|
|||
me._buffer.push(CLOSE) |
|||
me._read() |
|||
} else { |
|||
// console.error("FR close 1", me._path)
|
|||
me.emit("close") |
|||
} |
|||
}) |
|||
|
|||
me._read() |
|||
} |
|||
|
|||
FileReader.prototype._read = function () { |
|||
var me = this |
|||
// console.error("FR _read", me._path)
|
|||
if (me._paused) { |
|||
// console.error("FR _read paused", me._path)
|
|||
return |
|||
} |
|||
|
|||
if (!me._stream) { |
|||
// console.error("FR _getStream calling", me._path)
|
|||
return me._getStream() |
|||
} |
|||
|
|||
// clear out the buffer, if there is one.
|
|||
if (me._buffer.length) { |
|||
// console.error("FR _read has buffer", me._buffer.length, me._path)
|
|||
var buf = me._buffer |
|||
for (var i = 0, l = buf.length; i < l; i ++) { |
|||
var c = buf[i] |
|||
if (c === EOF) { |
|||
// console.error("FR Read emitting buffered end", me._path)
|
|||
me.emit("end") |
|||
} else if (c === CLOSE) { |
|||
// console.error("FR Read emitting buffered close", me._path)
|
|||
me.emit("close") |
|||
} else { |
|||
// console.error("FR Read emitting buffered data", me._path)
|
|||
me.emit("data", c) |
|||
} |
|||
|
|||
if (me._paused) { |
|||
// console.error("FR Read Re-pausing at "+i, me._path)
|
|||
me._buffer = buf.slice(i) |
|||
return |
|||
} |
|||
} |
|||
me._buffer.length = 0 |
|||
} |
|||
// console.error("FR _read done")
|
|||
// that's about all there is to it.
|
|||
} |
|||
|
|||
FileReader.prototype.pause = function (who) { |
|||
var me = this |
|||
// console.error("FR Pause", me._path)
|
|||
if (me._paused) return |
|||
who = who || me |
|||
me._paused = true |
|||
if (me._stream) me._stream.pause() |
|||
me.emit("pause", who) |
|||
} |
|||
|
|||
FileReader.prototype.resume = function (who) { |
|||
var me = this |
|||
// console.error("FR Resume", me._path)
|
|||
if (!me._paused) return |
|||
who = who || me |
|||
me.emit("resume", who) |
|||
me._paused = false |
|||
if (me._stream) me._stream.resume() |
|||
me._read() |
|||
} |
@ -0,0 +1,100 @@ |
|||
module.exports = FileWriter |
|||
|
|||
var fs = require("graceful-fs") |
|||
, mkdir = require("mkdirp") |
|||
, Writer = require("./writer.js") |
|||
, inherits = require("inherits") |
|||
, EOF = {} |
|||
|
|||
inherits(FileWriter, Writer) |
|||
|
|||
function FileWriter (props) { |
|||
var me = this |
|||
if (!(me instanceof FileWriter)) throw new Error( |
|||
"FileWriter must be called as constructor.") |
|||
|
|||
// should already be established as a File type
|
|||
if (props.type !== "File" || !props.File) { |
|||
throw new Error("Non-file type "+ props.type) |
|||
} |
|||
|
|||
me._buffer = [] |
|||
me._bytesWritten = 0 |
|||
|
|||
Writer.call(this, props) |
|||
} |
|||
|
|||
FileWriter.prototype._create = function () { |
|||
var me = this |
|||
if (me._stream) return |
|||
|
|||
var so = {} |
|||
if (me.props.flags) so.flags = me.props.flags |
|||
so.mode = Writer.filemode |
|||
if (me._old && me._old.blksize) so.bufferSize = me._old.blksize |
|||
|
|||
me._stream = fs.createWriteStream(me._path, so) |
|||
|
|||
me._stream.on("open", function (fd) { |
|||
// console.error("FW open", me._buffer, me._path)
|
|||
me.ready = true |
|||
me._buffer.forEach(function (c) { |
|||
if (c === EOF) me._stream.end() |
|||
else me._stream.write(c) |
|||
}) |
|||
me.emit("ready") |
|||
// give this a kick just in case it needs it.
|
|||
me.emit("drain") |
|||
}) |
|||
|
|||
me._stream.on("drain", function () { me.emit("drain") }) |
|||
|
|||
me._stream.on("close", function () { |
|||
// console.error("\n\nFW Stream Close", me._path, me.size)
|
|||
me._finish() |
|||
}) |
|||
} |
|||
|
|||
FileWriter.prototype.write = function (c) { |
|||
var me = this |
|||
|
|||
me._bytesWritten += c.length |
|||
|
|||
if (!me.ready) { |
|||
if (!Buffer.isBuffer(c) && typeof c !== 'string') |
|||
throw new Error('invalid write data') |
|||
me._buffer.push(c) |
|||
return false |
|||
} |
|||
|
|||
var ret = me._stream.write(c) |
|||
// console.error("\t-- fw wrote, _stream says", ret, me._stream._queue.length)
|
|||
|
|||
// allow 2 buffered writes, because otherwise there's just too
|
|||
// much stop and go bs.
|
|||
return ret || (me._stream._queue && me._stream._queue.length <= 2) |
|||
} |
|||
|
|||
FileWriter.prototype.end = function (c) { |
|||
var me = this |
|||
|
|||
if (c) me.write(c) |
|||
|
|||
if (!me.ready) { |
|||
me._buffer.push(EOF) |
|||
return false |
|||
} |
|||
|
|||
return me._stream.end() |
|||
} |
|||
|
|||
FileWriter.prototype._finish = function () { |
|||
var me = this |
|||
if (typeof me.size === "number" && me._bytesWritten != me.size) { |
|||
me.error( |
|||
"Did not get expected byte count.\n" + |
|||
"expect: " + me.size + "\n" + |
|||
"actual: " + me._bytesWritten) |
|||
} |
|||
Writer.prototype._finish.call(me) |
|||
} |
@ -0,0 +1,32 @@ |
|||
module.exports = getType |
|||
|
|||
function getType (st) { |
|||
var types = |
|||
[ "Directory" |
|||
, "File" |
|||
, "SymbolicLink" |
|||
, "Link" // special for hardlinks from tarballs
|
|||
, "BlockDevice" |
|||
, "CharacterDevice" |
|||
, "FIFO" |
|||
, "Socket" ] |
|||
, type |
|||
|
|||
if (st.type && -1 !== types.indexOf(st.type)) { |
|||
st[st.type] = true |
|||
return st.type |
|||
} |
|||
|
|||
for (var i = 0, l = types.length; i < l; i ++) { |
|||
type = types[i] |
|||
var is = st[type] || st["is" + type] |
|||
if (typeof is === "function") is = is.call(st) |
|||
if (is) { |
|||
st[type] = true |
|||
st.type = type |
|||
return type |
|||
} |
|||
} |
|||
|
|||
return null |
|||
} |
@ -0,0 +1,54 @@ |
|||
// Basically just a wrapper around an fs.readlink
|
|||
//
|
|||
// XXX: Enhance this to support the Link type, by keeping
|
|||
// a lookup table of {<dev+inode>:<path>}, so that hardlinks
|
|||
// can be preserved in tarballs.
|
|||
|
|||
module.exports = LinkReader |
|||
|
|||
var fs = require("graceful-fs") |
|||
, fstream = require("../fstream.js") |
|||
, inherits = require("inherits") |
|||
, mkdir = require("mkdirp") |
|||
, Reader = require("./reader.js") |
|||
|
|||
inherits(LinkReader, Reader) |
|||
|
|||
function LinkReader (props) { |
|||
var me = this |
|||
if (!(me instanceof LinkReader)) throw new Error( |
|||
"LinkReader must be called as constructor.") |
|||
|
|||
if (!((props.type === "Link" && props.Link) || |
|||
(props.type === "SymbolicLink" && props.SymbolicLink))) { |
|||
throw new Error("Non-link type "+ props.type) |
|||
} |
|||
|
|||
Reader.call(me, props) |
|||
} |
|||
|
|||
// When piping a LinkReader into a LinkWriter, we have to
|
|||
// already have the linkpath property set, so that has to
|
|||
// happen *before* the "ready" event, which means we need to
|
|||
// override the _stat method.
|
|||
LinkReader.prototype._stat = function (currentStat) { |
|||
var me = this |
|||
fs.readlink(me._path, function (er, linkpath) { |
|||
if (er) return me.error(er) |
|||
me.linkpath = me.props.linkpath = linkpath |
|||
me.emit("linkpath", linkpath) |
|||
Reader.prototype._stat.call(me, currentStat) |
|||
}) |
|||
} |
|||
|
|||
LinkReader.prototype._read = function () { |
|||
var me = this |
|||
if (me._paused) return |
|||
// basically just a no-op, since we got all the info we need
|
|||
// from the _stat method
|
|||
if (!me._ended) { |
|||
me.emit("end") |
|||
me.emit("close") |
|||
me._ended = true |
|||
} |
|||
} |
@ -0,0 +1,95 @@ |
|||
|
|||
module.exports = LinkWriter |
|||
|
|||
var fs = require("graceful-fs") |
|||
, Writer = require("./writer.js") |
|||
, inherits = require("inherits") |
|||
, path = require("path") |
|||
, rimraf = require("rimraf") |
|||
|
|||
inherits(LinkWriter, Writer) |
|||
|
|||
function LinkWriter (props) { |
|||
var me = this |
|||
if (!(me instanceof LinkWriter)) throw new Error( |
|||
"LinkWriter must be called as constructor.") |
|||
|
|||
// should already be established as a Link type
|
|||
if (!((props.type === "Link" && props.Link) || |
|||
(props.type === "SymbolicLink" && props.SymbolicLink))) { |
|||
throw new Error("Non-link type "+ props.type) |
|||
} |
|||
|
|||
if (props.linkpath === "") props.linkpath = "." |
|||
if (!props.linkpath) { |
|||
me.error("Need linkpath property to create " + props.type) |
|||
} |
|||
|
|||
Writer.call(this, props) |
|||
} |
|||
|
|||
LinkWriter.prototype._create = function () { |
|||
// console.error(" LW _create")
|
|||
var me = this |
|||
, hard = me.type === "Link" || process.platform === "win32" |
|||
, link = hard ? "link" : "symlink" |
|||
, lp = hard ? path.resolve(me.dirname, me.linkpath) : me.linkpath |
|||
|
|||
// can only change the link path by clobbering
|
|||
// For hard links, let's just assume that's always the case, since
|
|||
// there's no good way to read them if we don't already know.
|
|||
if (hard) return clobber(me, lp, link) |
|||
|
|||
fs.readlink(me._path, function (er, p) { |
|||
// only skip creation if it's exactly the same link
|
|||
if (p && p === lp) return finish(me) |
|||
clobber(me, lp, link) |
|||
}) |
|||
} |
|||
|
|||
function clobber (me, lp, link) { |
|||
rimraf(me._path, function (er) { |
|||
if (er) return me.error(er) |
|||
create(me, lp, link) |
|||
}) |
|||
} |
|||
|
|||
function create (me, lp, link) { |
|||
fs[link](lp, me._path, function (er) { |
|||
// if this is a hard link, and we're in the process of writing out a
|
|||
// directory, it's very possible that the thing we're linking to
|
|||
// doesn't exist yet (especially if it was intended as a symlink),
|
|||
// so swallow ENOENT errors here and just soldier in.
|
|||
// Additionally, an EPERM or EACCES can happen on win32 if it's trying
|
|||
// to make a link to a directory. Again, just skip it.
|
|||
// A better solution would be to have fs.symlink be supported on
|
|||
// windows in some nice fashion.
|
|||
if (er) { |
|||
if ((er.code === "ENOENT" || |
|||
er.code === "EACCES" || |
|||
er.code === "EPERM" ) && process.platform === "win32") { |
|||
me.ready = true |
|||
me.emit("ready") |
|||
me.emit("end") |
|||
me.emit("close") |
|||
me.end = me._finish = function () {} |
|||
} else return me.error(er) |
|||
} |
|||
finish(me) |
|||
}) |
|||
} |
|||
|
|||
function finish (me) { |
|||
me.ready = true |
|||
me.emit("ready") |
|||
if (me._ended && !me._finished) me._finish() |
|||
} |
|||
|
|||
LinkWriter.prototype.end = function () { |
|||
// console.error("LW finish in end")
|
|||
this._ended = true |
|||
if (this.ready) { |
|||
this._finished = true |
|||
this._finish() |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
// A reader for when we don't yet know what kind of thing
|
|||
// the thing is.
|
|||
|
|||
module.exports = ProxyReader |
|||
|
|||
var Reader = require("./reader.js") |
|||
, getType = require("./get-type.js") |
|||
, inherits = require("inherits") |
|||
, fs = require("graceful-fs") |
|||
|
|||
inherits(ProxyReader, Reader) |
|||
|
|||
function ProxyReader (props) { |
|||
var me = this |
|||
if (!(me instanceof ProxyReader)) throw new Error( |
|||
"ProxyReader must be called as constructor.") |
|||
|
|||
me.props = props |
|||
me._buffer = [] |
|||
me.ready = false |
|||
|
|||
Reader.call(me, props) |
|||
} |
|||
|
|||
ProxyReader.prototype._stat = function () { |
|||
var me = this |
|||
, props = me.props |
|||
// stat the thing to see what the proxy should be.
|
|||
, stat = props.follow ? "stat" : "lstat" |
|||
|
|||
fs[stat](props.path, function (er, current) { |
|||
var type |
|||
if (er || !current) { |
|||
type = "File" |
|||
} else { |
|||
type = getType(current) |
|||
} |
|||
|
|||
props[type] = true |
|||
props.type = me.type = type |
|||
|
|||
me._old = current |
|||
me._addProxy(Reader(props, current)) |
|||
}) |
|||
} |
|||
|
|||
ProxyReader.prototype._addProxy = function (proxy) { |
|||
var me = this |
|||
if (me._proxyTarget) { |
|||
return me.error("proxy already set") |
|||
} |
|||
|
|||
me._proxyTarget = proxy |
|||
proxy._proxy = me |
|||
|
|||
; [ "error" |
|||
, "data" |
|||
, "end" |
|||
, "close" |
|||
, "linkpath" |
|||
, "entry" |
|||
, "entryEnd" |
|||
, "child" |
|||
, "childEnd" |
|||
, "warn" |
|||
, "stat" |
|||
].forEach(function (ev) { |
|||
// console.error("~~ proxy event", ev, me.path)
|
|||
proxy.on(ev, me.emit.bind(me, ev)) |
|||
}) |
|||
|
|||
me.emit("proxy", proxy) |
|||
|
|||
proxy.on("ready", function () { |
|||
// console.error("~~ proxy is ready!", me.path)
|
|||
me.ready = true |
|||
me.emit("ready") |
|||
}) |
|||
|
|||
var calls = me._buffer |
|||
me._buffer.length = 0 |
|||
calls.forEach(function (c) { |
|||
proxy[c[0]].apply(proxy, c[1]) |
|||
}) |
|||
} |
|||
|
|||
ProxyReader.prototype.pause = function () { |
|||
return this._proxyTarget ? this._proxyTarget.pause() : false |
|||
} |
|||
|
|||
ProxyReader.prototype.resume = function () { |
|||
return this._proxyTarget ? this._proxyTarget.resume() : false |
|||
} |
@ -0,0 +1,109 @@ |
|||
// A writer for when we don't know what kind of thing
|
|||
// the thing is. That is, it's not explicitly set,
|
|||
// so we're going to make it whatever the thing already
|
|||
// is, or "File"
|
|||
//
|
|||
// Until then, collect all events.
|
|||
|
|||
module.exports = ProxyWriter |
|||
|
|||
var Writer = require("./writer.js") |
|||
, getType = require("./get-type.js") |
|||
, inherits = require("inherits") |
|||
, collect = require("./collect.js") |
|||
, fs = require("fs") |
|||
|
|||
inherits(ProxyWriter, Writer) |
|||
|
|||
function ProxyWriter (props) { |
|||
var me = this |
|||
if (!(me instanceof ProxyWriter)) throw new Error( |
|||
"ProxyWriter must be called as constructor.") |
|||
|
|||
me.props = props |
|||
me._needDrain = false |
|||
|
|||
Writer.call(me, props) |
|||
} |
|||
|
|||
ProxyWriter.prototype._stat = function () { |
|||
var me = this |
|||
, props = me.props |
|||
// stat the thing to see what the proxy should be.
|
|||
, stat = props.follow ? "stat" : "lstat" |
|||
|
|||
fs[stat](props.path, function (er, current) { |
|||
var type |
|||
if (er || !current) { |
|||
type = "File" |
|||
} else { |
|||
type = getType(current) |
|||
} |
|||
|
|||
props[type] = true |
|||
props.type = me.type = type |
|||
|
|||
me._old = current |
|||
me._addProxy(Writer(props, current)) |
|||
}) |
|||
} |
|||
|
|||
ProxyWriter.prototype._addProxy = function (proxy) { |
|||
// console.error("~~ set proxy", this.path)
|
|||
var me = this |
|||
if (me._proxy) { |
|||
return me.error("proxy already set") |
|||
} |
|||
|
|||
me._proxy = proxy |
|||
; [ "ready" |
|||
, "error" |
|||
, "close" |
|||
, "pipe" |
|||
, "drain" |
|||
, "warn" |
|||
].forEach(function (ev) { |
|||
proxy.on(ev, me.emit.bind(me, ev)) |
|||
}) |
|||
|
|||
me.emit("proxy", proxy) |
|||
|
|||
var calls = me._buffer |
|||
calls.forEach(function (c) { |
|||
// console.error("~~ ~~ proxy buffered call", c[0], c[1])
|
|||
proxy[c[0]].apply(proxy, c[1]) |
|||
}) |
|||
me._buffer.length = 0 |
|||
if (me._needsDrain) me.emit("drain") |
|||
} |
|||
|
|||
ProxyWriter.prototype.add = function (entry) { |
|||
// console.error("~~ proxy add")
|
|||
collect(entry) |
|||
|
|||
if (!this._proxy) { |
|||
this._buffer.push(["add", [entry]]) |
|||
this._needDrain = true |
|||
return false |
|||
} |
|||
return this._proxy.add(entry) |
|||
} |
|||
|
|||
ProxyWriter.prototype.write = function (c) { |
|||
// console.error("~~ proxy write")
|
|||
if (!this._proxy) { |
|||
this._buffer.push(["write", [c]]) |
|||
this._needDrain = true |
|||
return false |
|||
} |
|||
return this._proxy.write(c) |
|||
} |
|||
|
|||
ProxyWriter.prototype.end = function (c) { |
|||
// console.error("~~ proxy end")
|
|||
if (!this._proxy) { |
|||
this._buffer.push(["end", [c]]) |
|||
return false |
|||
} |
|||
return this._proxy.end(c) |
|||
} |
@ -0,0 +1,259 @@ |
|||
|
|||
module.exports = Reader |
|||
|
|||
var fs = require("graceful-fs") |
|||
, Stream = require("stream").Stream |
|||
, inherits = require("inherits") |
|||
, path = require("path") |
|||
, getType = require("./get-type.js") |
|||
, hardLinks = Reader.hardLinks = {} |
|||
, Abstract = require("./abstract.js") |
|||
|
|||
// Must do this *before* loading the child classes
|
|||
inherits(Reader, Abstract) |
|||
|
|||
var DirReader = require("./dir-reader.js") |
|||
, FileReader = require("./file-reader.js") |
|||
, LinkReader = require("./link-reader.js") |
|||
, SocketReader = require("./socket-reader.js") |
|||
, ProxyReader = require("./proxy-reader.js") |
|||
|
|||
function Reader (props, currentStat) { |
|||
var me = this |
|||
if (!(me instanceof Reader)) return new Reader(props, currentStat) |
|||
|
|||
if (typeof props === "string") { |
|||
props = { path: props } |
|||
} |
|||
|
|||
if (!props.path) { |
|||
me.error("Must provide a path", null, true) |
|||
} |
|||
|
|||
// polymorphism.
|
|||
// call fstream.Reader(dir) to get a DirReader object, etc.
|
|||
// Note that, unlike in the Writer case, ProxyReader is going
|
|||
// to be the *normal* state of affairs, since we rarely know
|
|||
// the type of a file prior to reading it.
|
|||
|
|||
|
|||
var type |
|||
, ClassType |
|||
|
|||
if (props.type && typeof props.type === "function") { |
|||
type = props.type |
|||
ClassType = type |
|||
} else { |
|||
type = getType(props) |
|||
ClassType = Reader |
|||
} |
|||
|
|||
if (currentStat && !type) { |
|||
type = getType(currentStat) |
|||
props[type] = true |
|||
props.type = type |
|||
} |
|||
|
|||
switch (type) { |
|||
case "Directory": |
|||
ClassType = DirReader |
|||
break |
|||
|
|||
case "Link": |
|||
// XXX hard links are just files.
|
|||
// However, it would be good to keep track of files' dev+inode
|
|||
// and nlink values, and create a HardLinkReader that emits
|
|||
// a linkpath value of the original copy, so that the tar
|
|||
// writer can preserve them.
|
|||
// ClassType = HardLinkReader
|
|||
// break
|
|||
|
|||
case "File": |
|||
ClassType = FileReader |
|||
break |
|||
|
|||
case "SymbolicLink": |
|||
ClassType = LinkReader |
|||
break |
|||
|
|||
case "Socket": |
|||
ClassType = SocketReader |
|||
break |
|||
|
|||
case null: |
|||
ClassType = ProxyReader |
|||
break |
|||
} |
|||
|
|||
if (!(me instanceof ClassType)) { |
|||
return new ClassType(props) |
|||
} |
|||
|
|||
Abstract.call(me) |
|||
|
|||
me.readable = true |
|||
me.writable = false |
|||
|
|||
me.type = type |
|||
me.props = props |
|||
me.depth = props.depth = props.depth || 0 |
|||
me.parent = props.parent || null |
|||
me.root = props.root || (props.parent && props.parent.root) || me |
|||
|
|||
me._path = me.path = path.resolve(props.path) |
|||
if (process.platform === "win32") { |
|||
me.path = me._path = me.path.replace(/\?/g, "_") |
|||
if (me._path.length >= 260) { |
|||
// how DOES one create files on the moon?
|
|||
// if the path has spaces in it, then UNC will fail.
|
|||
me._swallowErrors = true |
|||
//if (me._path.indexOf(" ") === -1) {
|
|||
me._path = "\\\\?\\" + me.path.replace(/\//g, "\\") |
|||
//}
|
|||
} |
|||
} |
|||
me.basename = props.basename = path.basename(me.path) |
|||
me.dirname = props.dirname = path.dirname(me.path) |
|||
|
|||
// these have served their purpose, and are now just noisy clutter
|
|||
props.parent = props.root = null |
|||
|
|||
// console.error("\n\n\n%s setting size to", props.path, props.size)
|
|||
me.size = props.size |
|||
me.filter = typeof props.filter === "function" ? props.filter : null |
|||
if (props.sort === "alpha") props.sort = alphasort |
|||
|
|||
// start the ball rolling.
|
|||
// this will stat the thing, and then call me._read()
|
|||
// to start reading whatever it is.
|
|||
// console.error("calling stat", props.path, currentStat)
|
|||
me._stat(currentStat) |
|||
} |
|||
|
|||
function alphasort (a, b) { |
|||
return a === b ? 0 |
|||
: a.toLowerCase() > b.toLowerCase() ? 1 |
|||
: a.toLowerCase() < b.toLowerCase() ? -1 |
|||
: a > b ? 1 |
|||
: -1 |
|||
} |
|||
|
|||
Reader.prototype._stat = function (currentStat) { |
|||
var me = this |
|||
, props = me.props |
|||
, stat = props.follow ? "stat" : "lstat" |
|||
|
|||
// console.error("Reader._stat", me._path, currentStat)
|
|||
if (currentStat) process.nextTick(statCb.bind(null, null, currentStat)) |
|||
else fs[stat](me._path, statCb) |
|||
|
|||
|
|||
function statCb (er, props_) { |
|||
// console.error("Reader._stat, statCb", me._path, props_, props_.nlink)
|
|||
if (er) return me.error(er) |
|||
|
|||
Object.keys(props_).forEach(function (k) { |
|||
props[k] = props_[k] |
|||
}) |
|||
|
|||
// if it's not the expected size, then abort here.
|
|||
if (undefined !== me.size && props.size !== me.size) { |
|||
return me.error("incorrect size") |
|||
} |
|||
me.size = props.size |
|||
|
|||
var type = getType(props) |
|||
// special little thing for handling hardlinks.
|
|||
if (type !== "Directory" && props.nlink && props.nlink > 1) { |
|||
var k = props.dev + ":" + props.ino |
|||
// console.error("Reader has nlink", me._path, k)
|
|||
if (hardLinks[k] === me._path || !hardLinks[k]) hardLinks[k] = me._path |
|||
else { |
|||
// switch into hardlink mode.
|
|||
type = me.type = me.props.type = "Link" |
|||
me.Link = me.props.Link = true |
|||
me.linkpath = me.props.linkpath = hardLinks[k] |
|||
// console.error("Hardlink detected, switching mode", me._path, me.linkpath)
|
|||
// Setting __proto__ would arguably be the "correct"
|
|||
// approach here, but that just seems too wrong.
|
|||
me._stat = me._read = LinkReader.prototype._read |
|||
} |
|||
} |
|||
|
|||
if (me.type && me.type !== type) { |
|||
me.error("Unexpected type: " + type) |
|||
} |
|||
|
|||
// if the filter doesn't pass, then just skip over this one.
|
|||
// still have to emit end so that dir-walking can move on.
|
|||
if (me.filter) { |
|||
var who = me._proxy || me |
|||
// special handling for ProxyReaders
|
|||
if (!me.filter.call(who, who, props)) { |
|||
if (!me._disowned) { |
|||
me.abort() |
|||
me.emit("end") |
|||
me.emit("close") |
|||
} |
|||
return |
|||
} |
|||
} |
|||
|
|||
// last chance to abort or disown before the flow starts!
|
|||
var events = ["_stat", "stat", "ready"] |
|||
var e = 0 |
|||
;(function go () { |
|||
if (me._aborted) { |
|||
me.emit("end") |
|||
me.emit("close") |
|||
return |
|||
} |
|||
|
|||
if (me._paused) { |
|||
me.once("resume", go) |
|||
return |
|||
} |
|||
|
|||
var ev = events[e ++] |
|||
if (!ev) return me._read() |
|||
me.emit(ev, props) |
|||
go() |
|||
})() |
|||
} |
|||
} |
|||
|
|||
Reader.prototype.pipe = function (dest, opts) { |
|||
var me = this |
|||
if (typeof dest.add === "function") { |
|||
// piping to a multi-compatible, and we've got directory entries.
|
|||
me.on("entry", function (entry) { |
|||
var ret = dest.add(entry) |
|||
if (false === ret) { |
|||
me.pause() |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// console.error("R Pipe apply Stream Pipe")
|
|||
return Stream.prototype.pipe.apply(this, arguments) |
|||
} |
|||
|
|||
Reader.prototype.pause = function (who) { |
|||
this._paused = true |
|||
who = who || this |
|||
this.emit("pause", who) |
|||
if (this._stream) this._stream.pause(who) |
|||
} |
|||
|
|||
Reader.prototype.resume = function (who) { |
|||
this._paused = false |
|||
who = who || this |
|||
this.emit("resume", who) |
|||
if (this._stream) this._stream.resume(who) |
|||
this._read() |
|||
} |
|||
|
|||
Reader.prototype._read = function () { |
|||
this.error("Cannot read unknown type: "+this.type) |
|||
} |
|||
|
@ -0,0 +1,38 @@ |
|||
// Just get the stats, and then don't do anything.
|
|||
// You can't really "read" from a socket. You "connect" to it.
|
|||
// Mostly, this is here so that reading a dir with a socket in it
|
|||
// doesn't blow up.
|
|||
|
|||
module.exports = SocketReader |
|||
|
|||
var fs = require("graceful-fs") |
|||
, fstream = require("../fstream.js") |
|||
, inherits = require("inherits") |
|||
, mkdir = require("mkdirp") |
|||
, Reader = require("./reader.js") |
|||
|
|||
inherits(SocketReader, Reader) |
|||
|
|||
function SocketReader (props) { |
|||
var me = this |
|||
if (!(me instanceof SocketReader)) throw new Error( |
|||
"SocketReader must be called as constructor.") |
|||
|
|||
if (!(props.type === "Socket" && props.Socket)) { |
|||
throw new Error("Non-socket type "+ props.type) |
|||
} |
|||
|
|||
Reader.call(me, props) |
|||
} |
|||
|
|||
SocketReader.prototype._read = function () { |
|||
var me = this |
|||
if (me._paused) return |
|||
// basically just a no-op, since we got all the info we have
|
|||
// from the _stat method
|
|||
if (!me._ended) { |
|||
me.emit("end") |
|||
me.emit("close") |
|||
me._ended = true |
|||
} |
|||
} |
@ -0,0 +1,389 @@ |
|||
|
|||
module.exports = Writer |
|||
|
|||
var fs = require("graceful-fs") |
|||
, inherits = require("inherits") |
|||
, rimraf = require("rimraf") |
|||
, mkdir = require("mkdirp") |
|||
, path = require("path") |
|||
, umask = process.platform === "win32" ? 0 : process.umask() |
|||
, getType = require("./get-type.js") |
|||
, Abstract = require("./abstract.js") |
|||
|
|||
// Must do this *before* loading the child classes
|
|||
inherits(Writer, Abstract) |
|||
|
|||
Writer.dirmode = 0777 & (~umask) |
|||
Writer.filemode = 0666 & (~umask) |
|||
|
|||
var DirWriter = require("./dir-writer.js") |
|||
, LinkWriter = require("./link-writer.js") |
|||
, FileWriter = require("./file-writer.js") |
|||
, ProxyWriter = require("./proxy-writer.js") |
|||
|
|||
// props is the desired state. current is optionally the current stat,
|
|||
// provided here so that subclasses can avoid statting the target
|
|||
// more than necessary.
|
|||
function Writer (props, current) { |
|||
var me = this |
|||
|
|||
if (typeof props === "string") { |
|||
props = { path: props } |
|||
} |
|||
|
|||
if (!props.path) me.error("Must provide a path", null, true) |
|||
|
|||
// polymorphism.
|
|||
// call fstream.Writer(dir) to get a DirWriter object, etc.
|
|||
var type = getType(props) |
|||
, ClassType = Writer |
|||
|
|||
switch (type) { |
|||
case "Directory": |
|||
ClassType = DirWriter |
|||
break |
|||
case "File": |
|||
ClassType = FileWriter |
|||
break |
|||
case "Link": |
|||
case "SymbolicLink": |
|||
ClassType = LinkWriter |
|||
break |
|||
case null: |
|||
// Don't know yet what type to create, so we wrap in a proxy.
|
|||
ClassType = ProxyWriter |
|||
break |
|||
} |
|||
|
|||
if (!(me instanceof ClassType)) return new ClassType(props) |
|||
|
|||
// now get down to business.
|
|||
|
|||
Abstract.call(me) |
|||
|
|||
// props is what we want to set.
|
|||
// set some convenience properties as well.
|
|||
me.type = props.type |
|||
me.props = props |
|||
me.depth = props.depth || 0 |
|||
me.clobber = false === props.clobber ? props.clobber : true |
|||
me.parent = props.parent || null |
|||
me.root = props.root || (props.parent && props.parent.root) || me |
|||
|
|||
me._path = me.path = path.resolve(props.path) |
|||
if (process.platform === "win32") { |
|||
me.path = me._path = me.path.replace(/\?/g, "_") |
|||
if (me._path.length >= 260) { |
|||
me._swallowErrors = true |
|||
me._path = "\\\\?\\" + me.path.replace(/\//g, "\\") |
|||
} |
|||
} |
|||
me.basename = path.basename(props.path) |
|||
me.dirname = path.dirname(props.path) |
|||
me.linkpath = props.linkpath || null |
|||
|
|||
props.parent = props.root = null |
|||
|
|||
// console.error("\n\n\n%s setting size to", props.path, props.size)
|
|||
me.size = props.size |
|||
|
|||
if (typeof props.mode === "string") { |
|||
props.mode = parseInt(props.mode, 8) |
|||
} |
|||
|
|||
me.readable = false |
|||
me.writable = true |
|||
|
|||
// buffer until ready, or while handling another entry
|
|||
me._buffer = [] |
|||
me.ready = false |
|||
|
|||
me.filter = typeof props.filter === "function" ? props.filter: null |
|||
|
|||
// start the ball rolling.
|
|||
// this checks what's there already, and then calls
|
|||
// me._create() to call the impl-specific creation stuff.
|
|||
me._stat(current) |
|||
} |
|||
|
|||
// Calling this means that it's something we can't create.
|
|||
// Just assert that it's already there, otherwise raise a warning.
|
|||
Writer.prototype._create = function () { |
|||
var me = this |
|||
fs[me.props.follow ? "stat" : "lstat"](me._path, function (er, current) { |
|||
if (er) { |
|||
return me.warn("Cannot create " + me._path + "\n" + |
|||
"Unsupported type: "+me.type, "ENOTSUP") |
|||
} |
|||
me._finish() |
|||
}) |
|||
} |
|||
|
|||
Writer.prototype._stat = function (current) { |
|||
var me = this |
|||
, props = me.props |
|||
, stat = props.follow ? "stat" : "lstat" |
|||
, who = me._proxy || me |
|||
|
|||
if (current) statCb(null, current) |
|||
else fs[stat](me._path, statCb) |
|||
|
|||
function statCb (er, current) { |
|||
if (me.filter && !me.filter.call(who, who, current)) { |
|||
me._aborted = true |
|||
me.emit("end") |
|||
me.emit("close") |
|||
return |
|||
} |
|||
|
|||
// if it's not there, great. We'll just create it.
|
|||
// if it is there, then we'll need to change whatever differs
|
|||
if (er || !current) { |
|||
return create(me) |
|||
} |
|||
|
|||
me._old = current |
|||
var currentType = getType(current) |
|||
|
|||
// if it's a type change, then we need to clobber or error.
|
|||
// if it's not a type change, then let the impl take care of it.
|
|||
if (currentType !== me.type) { |
|||
return rimraf(me._path, function (er) { |
|||
if (er) return me.error(er) |
|||
me._old = null |
|||
create(me) |
|||
}) |
|||
} |
|||
|
|||
// otherwise, just handle in the app-specific way
|
|||
// this creates a fs.WriteStream, or mkdir's, or whatever
|
|||
create(me) |
|||
} |
|||
} |
|||
|
|||
function create (me) { |
|||
// console.error("W create", me._path, Writer.dirmode)
|
|||
|
|||
// XXX Need to clobber non-dirs that are in the way,
|
|||
// unless { clobber: false } in the props.
|
|||
mkdir(path.dirname(me._path), Writer.dirmode, function (er, made) { |
|||
// console.error("W created", path.dirname(me._path), er)
|
|||
if (er) return me.error(er) |
|||
|
|||
// later on, we have to set the mode and owner for these
|
|||
me._madeDir = made |
|||
return me._create() |
|||
}) |
|||
} |
|||
|
|||
function endChmod (me, want, current, path, cb) { |
|||
var wantMode = want.mode |
|||
, chmod = want.follow || me.type !== "SymbolicLink" |
|||
? "chmod" : "lchmod" |
|||
|
|||
if (!fs[chmod]) return cb() |
|||
if (typeof wantMode !== "number") return cb() |
|||
|
|||
var curMode = current.mode & 0777 |
|||
wantMode = wantMode & 0777 |
|||
if (wantMode === curMode) return cb() |
|||
|
|||
fs[chmod](path, wantMode, cb) |
|||
} |
|||
|
|||
|
|||
function endChown (me, want, current, path, cb) { |
|||
// Don't even try it unless root. Too easy to EPERM.
|
|||
if (process.platform === "win32") return cb() |
|||
if (!process.getuid || !process.getuid() === 0) return cb() |
|||
if (typeof want.uid !== "number" && |
|||
typeof want.gid !== "number" ) return cb() |
|||
|
|||
if (current.uid === want.uid && |
|||
current.gid === want.gid) return cb() |
|||
|
|||
var chown = (me.props.follow || me.type !== "SymbolicLink") |
|||
? "chown" : "lchown" |
|||
if (!fs[chown]) return cb() |
|||
|
|||
if (typeof want.uid !== "number") want.uid = current.uid |
|||
if (typeof want.gid !== "number") want.gid = current.gid |
|||
|
|||
fs[chown](path, want.uid, want.gid, cb) |
|||
} |
|||
|
|||
function endUtimes (me, want, current, path, cb) { |
|||
if (!fs.utimes || process.platform === "win32") return cb() |
|||
|
|||
var utimes = (want.follow || me.type !== "SymbolicLink") |
|||
? "utimes" : "lutimes" |
|||
|
|||
if (utimes === "lutimes" && !fs[utimes]) { |
|||
utimes = "utimes" |
|||
} |
|||
|
|||
if (!fs[utimes]) return cb() |
|||
|
|||
var curA = current.atime |
|||
, curM = current.mtime |
|||
, meA = want.atime |
|||
, meM = want.mtime |
|||
|
|||
if (meA === undefined) meA = curA |
|||
if (meM === undefined) meM = curM |
|||
|
|||
if (!isDate(meA)) meA = new Date(meA) |
|||
if (!isDate(meM)) meA = new Date(meM) |
|||
|
|||
if (meA.getTime() === curA.getTime() && |
|||
meM.getTime() === curM.getTime()) return cb() |
|||
|
|||
fs[utimes](path, meA, meM, cb) |
|||
} |
|||
|
|||
|
|||
// XXX This function is beastly. Break it up!
|
|||
Writer.prototype._finish = function () { |
|||
var me = this |
|||
|
|||
// console.error(" W Finish", me._path, me.size)
|
|||
|
|||
// set up all the things.
|
|||
// At this point, we're already done writing whatever we've gotta write,
|
|||
// adding files to the dir, etc.
|
|||
var todo = 0 |
|||
var errState = null |
|||
var done = false |
|||
|
|||
if (me._old) { |
|||
// the times will almost *certainly* have changed.
|
|||
// adds the utimes syscall, but remove another stat.
|
|||
me._old.atime = new Date(0) |
|||
me._old.mtime = new Date(0) |
|||
// console.error(" W Finish Stale Stat", me._path, me.size)
|
|||
setProps(me._old) |
|||
} else { |
|||
var stat = me.props.follow ? "stat" : "lstat" |
|||
// console.error(" W Finish Stating", me._path, me.size)
|
|||
fs[stat](me._path, function (er, current) { |
|||
// console.error(" W Finish Stated", me._path, me.size, current)
|
|||
if (er) { |
|||
// if we're in the process of writing out a
|
|||
// directory, it's very possible that the thing we're linking to
|
|||
// doesn't exist yet (especially if it was intended as a symlink),
|
|||
// so swallow ENOENT errors here and just soldier on.
|
|||
if (er.code === "ENOENT" && |
|||
(me.type === "Link" || me.type === "SymbolicLink") && |
|||
process.platform === "win32") { |
|||
me.ready = true |
|||
me.emit("ready") |
|||
me.emit("end") |
|||
me.emit("close") |
|||
me.end = me._finish = function () {} |
|||
return |
|||
} else return me.error(er) |
|||
} |
|||
setProps(me._old = current) |
|||
}) |
|||
} |
|||
|
|||
return |
|||
|
|||
function setProps (current) { |
|||
todo += 3 |
|||
endChmod(me, me.props, current, me._path, next("chmod")) |
|||
endChown(me, me.props, current, me._path, next("chown")) |
|||
endUtimes(me, me.props, current, me._path, next("utimes")) |
|||
} |
|||
|
|||
function next (what) { |
|||
return function (er) { |
|||
// console.error(" W Finish", what, todo)
|
|||
if (errState) return |
|||
if (er) { |
|||
er.fstream_finish_call = what |
|||
return me.error(errState = er) |
|||
} |
|||
if (--todo > 0) return |
|||
if (done) return |
|||
done = true |
|||
|
|||
// we may still need to set the mode/etc. on some parent dirs
|
|||
// that were created previously. delay end/close until then.
|
|||
if (!me._madeDir) return end() |
|||
else endMadeDir(me, me._path, end) |
|||
|
|||
function end (er) { |
|||
if (er) { |
|||
er.fstream_finish_call = "setupMadeDir" |
|||
return me.error(er) |
|||
} |
|||
// all the props have been set, so we're completely done.
|
|||
me.emit("end") |
|||
me.emit("close") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function endMadeDir (me, p, cb) { |
|||
var made = me._madeDir |
|||
// everything *between* made and path.dirname(me._path)
|
|||
// needs to be set up. Note that this may just be one dir.
|
|||
var d = path.dirname(p) |
|||
|
|||
endMadeDir_(me, d, function (er) { |
|||
if (er) return cb(er) |
|||
if (d === made) { |
|||
return cb() |
|||
} |
|||
endMadeDir(me, d, cb) |
|||
}) |
|||
} |
|||
|
|||
function endMadeDir_ (me, p, cb) { |
|||
var dirProps = {} |
|||
Object.keys(me.props).forEach(function (k) { |
|||
dirProps[k] = me.props[k] |
|||
|
|||
// only make non-readable dirs if explicitly requested.
|
|||
if (k === "mode" && me.type !== "Directory") { |
|||
dirProps[k] = dirProps[k] | 0111 |
|||
} |
|||
}) |
|||
|
|||
var todo = 3 |
|||
, errState = null |
|||
fs.stat(p, function (er, current) { |
|||
if (er) return cb(errState = er) |
|||
endChmod(me, dirProps, current, p, next) |
|||
endChown(me, dirProps, current, p, next) |
|||
endUtimes(me, dirProps, current, p, next) |
|||
}) |
|||
|
|||
function next (er) { |
|||
if (errState) return |
|||
if (er) return cb(errState = er) |
|||
if (-- todo === 0) return cb() |
|||
} |
|||
} |
|||
|
|||
Writer.prototype.pipe = function () { |
|||
this.error("Can't pipe from writable stream") |
|||
} |
|||
|
|||
Writer.prototype.add = function () { |
|||
this.error("Cannot add to non-Directory type") |
|||
} |
|||
|
|||
Writer.prototype.write = function () { |
|||
return true |
|||
} |
|||
|
|||
function objectToString (d) { |
|||
return Object.prototype.toString.call(d) |
|||
} |
|||
|
|||
function isDate(d) { |
|||
return typeof d === 'object' && objectToString(d) === '[object Date]'; |
|||
} |
xxxxxxxxxx