├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin └── private-bower ├── bower.conf.json ├── features ├── assets │ ├── api.js │ ├── server.js │ ├── tools.js │ └── utils.js ├── authentication.spec.js ├── packageDetails.spec.js └── privateRepository.spec.js ├── gulpfile.js ├── lib ├── api │ ├── controllers │ │ ├── packages │ │ │ ├── $name │ │ │ │ ├── delete.js │ │ │ │ ├── details │ │ │ │ │ └── get.js │ │ │ │ ├── get.js │ │ │ │ └── post.js │ │ │ ├── delete.js │ │ │ ├── get.js │ │ │ ├── index.js │ │ │ ├── post.js │ │ │ └── search │ │ │ │ └── $name │ │ │ │ └── get.js │ │ ├── refresh │ │ │ ├── index.js │ │ │ └── post.js │ │ └── restart │ │ │ ├── index.js │ │ │ └── post.js │ └── infrastructure │ │ ├── authentication.js │ │ ├── controller.js │ │ └── spec │ │ └── controller.spec.js ├── application.js ├── extensions │ ├── spec │ │ └── string.spec.js │ └── string.js ├── infrastructure │ ├── configurationManager.js │ ├── logger.js │ ├── spec │ │ ├── configurationManager.spec.js │ │ └── utils.spec.js │ └── utils.js ├── main.js ├── service │ ├── gitPackageDetailsProvider.js │ ├── packageDetailsProvider.js │ ├── packageManager.js │ ├── packageStores │ │ ├── privatePackageStore.js │ │ └── publicPackageStore.js │ ├── repoCaches │ │ ├── gitRepoCache.js │ │ ├── repoCacheBase.js │ │ ├── repoCacheHandler.js │ │ └── svnRepoCache.js │ └── svnPackageDetailsProvider.js └── spec │ ├── application.spec.js │ └── main.spec.js ├── log4js.conf.json ├── package-lock.json ├── package.json └── site ├── controllers ├── authenticationController.js └── mainController.js ├── directives └── dialog.js ├── index.html ├── interceptors └── notAuthenticatedInterceptor.js ├── logo.png ├── modules.js ├── private-bower.css ├── services └── userContextService.js └── vendor ├── angular ├── angular-animate.min.js └── angular.min.js ├── animate └── animate.min.css ├── bootstrap ├── dist │ └── bootstrap.min.css └── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff └── source-sans-pro └── source-sans-pro.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | .c9/ 4 | cheese.log 5 | pants.log 6 | tmp-test.log 7 | bin/*.json 8 | bin/*Cache 9 | features/sandbox/ 10 | bowerRepository.json 11 | bowerRepositoryPublic.json 12 | gitRepoCache/ 13 | svnRepoCache/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | spec/ 2 | features/ 3 | .idea/ 4 | .c9/ 5 | cheese.log 6 | pants.log 7 | tmp-test.log 8 | bin/*.json 9 | bin/*Cache 10 | features/sandbox/ 11 | bowerRepository.json 12 | bowerRepositoryPublic.json 13 | gitRepoCache/ 14 | svnRepoCache/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.0.1 - 2015.04.18. 4 | 5 | - Refactored whole framework with some automated tests 6 | - Added management to website - create and delete packages ([#126](https://github.com/Hacklone/private-bower/issues/126)) 7 | - Created new design ([#125](https://github.com/Hacklone/private-bower/issues/125)) 8 | - Added option to whitelist / blacklist packages ([#115](https://github.com/Hacklone/private-bower/issues/115)) 9 | - Added api for manually refresh repository caches ([#117](https://github.com/Hacklone/private-bower/issues/117)) 10 | - fixed git-daemon crashes ([#112](https://github.com/Hacklone/private-bower/issues/112)) 11 | - fixed confusing return codes ([#118](https://github.com/Hacklone/private-bower/issues/118)) 12 | 13 | ## 1.1.0 - 2015.06.29. 14 | 15 | - Added bower.json package details to web ui ([#109](https://github.com/Hacklone/private-bower/issues/109)) 16 | - Make private-bower closed network friendly ([#144](https://github.com/Hacklone/private-bower/issues/144)) 17 | - bug fix ([#148](https://github.com/Hacklone/private-bower/issues/148)) 18 | 19 | ## 1.1.1 - 2015.07.26. 20 | 21 | - Added help text to package detail errors ([#149](https://github.com/Hacklone/private-bower/issues/149)) 22 | - Added process.env.PORT and process.env.IP support ([#141](https://github.com/Hacklone/private-bower/issues/141)) 23 | - Added tip for API usage in README.md ([#154](https://github.com/Hacklone/private-bower/issues/154)) 24 | - Registering a public package for the first time, hit count fixed ([#157](https://github.com/Hacklone/private-bower/issues/157)) 25 | 26 | ## 1.1.2 - 2015.08.09. 27 | 28 | - Added configurable protocol to mirrored packages ([#161](https://github.com/Hacklone/private-bower/issues/161)) 29 | - Fixed error flow when git cloning fails ([#156](https://github.com/Hacklone/private-bower/issues/156)) 30 | 31 | ## 1.1.4 - 2015.08.29. 32 | 33 | - enable version consultation ([#168](https://github.com/Hacklone/private-bower/issues/168)) 34 | - Added stdout to log message in case util.exec() fails. ([#164](https://github.com/Hacklone/private-bower/issues/164)) 35 | - fixed JSON parse errors ([#170](https://github.com/Hacklone/private-bower/issues/170)) 36 | - never used utils.process.env.PORT ([#159](https://github.com/Hacklone/private-bower/issues/159)) 37 | 38 | ## 1.1.5 - 2015.10.10. 39 | 40 | - change registry url ([#179](https://github.com/Hacklone/private-bower/issues/179)) 41 | 42 | ## 1.1.6 - 2015.11.08. 43 | 44 | - Added some documentation to clear up confusion regarding public registry. ([#184](https://github.com/Hacklone/private-bower/issues/184)) 45 | - Show the git url of a package (as a link) ([#182](https://github.com/Hacklone/private-bower/issues/182)) 46 | 47 | ## 1.1.7 - 2015.12.02. 48 | 49 | - private-bower sometimes fails to refresh certain cached repositories ([#171](https://github.com/Hacklone/private-bower/issues/171)) 50 | - Support unvalued options for git/svn daemons. ([#200](https://github.com/Hacklone/private-bower/issues/200)) 51 | - Avoid https error on the website ([#191](https://github.com/Hacklone/private-bower/issues/191)) 52 | - Several fixes by @royrico 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to private-bower 2 | 3 | ## Submitting issues 4 | - Look through the tickets, if there's already one with the same issue 5 | - If it's not a duplicate issue submit a ticket in the [GitHub issues page](https://github.com/Hacklone/private-bower/issues) 6 | 7 | ### Is this a feature request? 8 | - Please explain why you need the feature 9 | - Write a user story or a specification if you can 10 | 11 | ### Is this a bug? 12 | - Submit the private-bower version you're using 13 | - Submit the OS version you're private-bower runs on 14 | - Submit browser version if the issue involves a browser 15 | - Submit the bower client version you're using 16 | - What happened? 17 | - What did you expect to happen? 18 | 19 | > If you can please try to fix the issue and submit a Pull Request 20 | 21 | ## Submitting Pull Requests 22 | - Please try to write self documenting code instead of commenting 23 | - The commit message should contain a understandable description of what the commit fixes/adds -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Hacklone 4 | https://github.com/Hacklone 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [npm-url]: https://npmjs.org/package/private-bower 2 | [npm-image]: https://img.shields.io/npm/v/private-bower.svg 3 | [downloads-image]: https://img.shields.io/npm/dm/private-bower.svg 4 | [total-downloads-image]: 5 | https://img.shields.io/npm/dt/private-bower.svg 6 | [codeship-url]: https://codeship.com/projects/54990 7 | [codeship-image]: https://img.shields.io/codeship/662b04e0-7427-0132-ff21-2aca0eeadc1e/master.svg 8 | 9 | private-bower [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Total Downloads][total-downloads-image]][npm-url] 10 | ============ 11 | 12 | 13 | The ultimate private bower server. 14 | 15 | 16 | 17 | 18 | 19 | Twitter: @private_bower, #private_bower 20 | 21 | - [Features](#features) 22 | - [Features to come](#features-to-come) 23 | - [Installation](#installation) 24 | - [Parameters](#parameters) 25 | - [Config file](#config-file) 26 | - [Usage](#usage) 27 | - [Web interface](#web-interface) 28 | - [Project](#project) 29 | - [List packages](#list-packages) 30 | - [Search packages](#search-packages) 31 | - [Register package](#register-package) 32 | - [Register packages](#register-packages) 33 | - [Remove package](#remove-package) 34 | - [Remove packages](#remove-packages) 35 | - [Restart server](#restart-server) 36 | - [Refresh caches](#refresh-caches) 37 | - [Authentication](#authentication) 38 | - [Log4js configuration examples](#log4js-configuration-examples) 39 | - [License](#license) 40 | - [Tips for usage](#tips-for-usage) 41 | - [Contributing](#contributing) 42 | 43 | # Features 44 | 45 | * Register private packages 46 | * Fallback to public packages 47 | * Cache public registry 48 | * Cache public git repositories 49 | * Cache public svn repositories 50 | * Web UI with package details 51 | * Web UI package management 52 | * Blacklist public packages 53 | * Whitelist public packages 54 | 55 | # Installation 56 | 57 | Install 58 | > npm install -g private-bower 59 | 60 | Run 61 | > private-bower 62 | 63 | Run with config file specified 64 | > private-bower --config ./myBowerConfig.json 65 | 66 | If there's no private package with requested package name the servers calls through to the public bower registry. 67 | 68 | # Parameters 69 | 70 | | name | description | 71 | |------------|------------------------------------------------| 72 | | --help | print out help | 73 | | --config | path to config file (Must be a valid json) | 74 | 75 | # Config file 76 | 77 | Must be a valid JSON 78 | ```javascript 79 | { 80 | "registryFile": "./bowerRepository.json", 81 | "timeout": 144000, 82 | "server": { 83 | "port": 5678, 84 | "hostName": null, 85 | "siteBaseUrl": null 86 | }, 87 | "public": { 88 | "disabled": false, 89 | "registry": "http://bower.herokuapp.com/packages", 90 | "registryFile": "./bowerRepositoryPublic.json", 91 | "whitelist": [], 92 | "blacklist": [] 93 | }, 94 | "authentication": { 95 | "enabled": false, 96 | "key": "password" 97 | }, 98 | "repositoryCache": { 99 | "cachePrivate": false, 100 | "git": { 101 | "enabled": false, 102 | "cacheDirectory": "./gitRepoCache", 103 | "host": "localhost", 104 | "port": 6789, 105 | "protocol": "git", 106 | "publicAccessURL": null, 107 | "refreshTimeout": 10, 108 | "refreshDisabled": false 109 | }, 110 | "svn": { 111 | "enabled": false, 112 | "cacheDirectory": "./svnRepoCache", 113 | "host": "localhost", 114 | "port": 7891, 115 | "protocol": "svn", 116 | "publicAccessURL": null, 117 | "refreshTimeout": 10, 118 | "refreshDisabled": false 119 | } 120 | }, 121 | "proxySettings" : { 122 | "enabled": false, 123 | "host": "proxy", 124 | "username": "name", 125 | "password": "pass", 126 | "port": 8080, 127 | "tunnel": false 128 | }, 129 | "log4js" : { 130 | "enabled": false, 131 | "configPath": "log4js.conf.json" 132 | } 133 | } 134 | ``` 135 | 136 | | name | description | default | 137 | |--------------------------------------------|--------------------------------------------------------------------------------------|---------------------------------------| 138 | | server.port | Port on which the private bower server will listen | 5678 (process.env.PORT if set) | 139 | | server.hostName | Host name on which the private bower server will listen | null (process.env.IP if set) | 140 | | server.siteBaseUrl | Load private bower server on a specific path, useful for using a reverse proxy | null | 141 | | registryFile | File for persisting private packages (must be a valid json) | ./bowerRepository.json | 142 | | timeout | Server package timeout | 144 000 | 143 | | public.disabled | Disable fallback feature for public packages | false | 144 | | public.registry | Public bower registry's url | https://registry.bower.io | 145 | | public.registryFile | File for persisting public packages (must be a valid json) | ./bowerRepositoryPublic.json | 146 | | public.whitelist | Define public packages that are allowed to be installed | \[\] | 147 | | public.blacklist | Define public packages that are not allowed to be installed | \[\] | 148 | | authentication.enabled | Authentication enabled for registering packages | false | 149 | | authentication.key | Authentication key (Auth-Key header) | password | 150 | | repositoryCache.(svn, git).enabled | Public repository caching enabled | false | 151 | | repositoryCache.cachePrivate | Also cache privately registered packages | false | 152 | | repositoryCache.(svn, git).host | Server's host name for repository access | localhost | 153 | | repositoryCache.(svn, git).port | Port to open repository server on | 7891, 6789 | 154 | | repositoryCache.(svn, git).protocol | Protocol the mirrored repositories will use | git, svn, https, http | 155 | | repositoryCache.(svn, git).publicAccessURL | Public address to access repository cache (useful if repository is behind an apache) | null | 156 | | repositoryCache.(svn, git).cacheDirectory | Directory where the public repository cache will save repositories | ./svnRepoCache, ./gitRepoCache | 157 | | repositoryCache.(svn, git).refreshTimeout | Time to wait between repository cache refresh (minutes) | 10 minutes | 158 | | repositoryCache.(svn, git).refreshDisabled | Disable automatic updates of the cached repository source code | false | 159 | | repositoryCache.(svn, git).parameters.X | Custom parameters for git-daemon and svnserve | undefined | 160 | | proxySettings.enabled | Enable the proxy, use the proxy to call the bower remote repo | false | 161 | | proxySettings.host | Proxy host | proxy | 162 | | proxySettings.username | Proxy username | name | 163 | | proxySettings.password | Proxy password | pass | 164 | | proxySettings.port | Proxy port | 8080 | 165 | | proxySettings.tunnel | Use tunnel? | false | 166 | | log4js.enabled | Use log4js ? | false | 167 | | log4js.configPath | Log4js configuration file. See: log4js-node for configuration options | none | 168 | 169 | 170 | 171 | 172 | # Usage 173 | 174 | ## Web interface 175 | Convenient way for viewing your packages in a browser. The web interface will only list your private packages, it will 176 | not list the public packages if you have a public registry enabled. However, when searching for packages in bower, the 177 | public ones will show up just fine. 178 | 179 | > http://localhost:5678/ 180 | 181 | ## Project 182 | Within your project, you will need to create a .bowerrc file containing the URL of your private bower server: 183 | ```json 184 | { 185 | "registry": "http://yourPrivateBowerRepo:5678", 186 | "timeout": 300000 187 | } 188 | ``` 189 | 190 | If you are using private bower with `server.siteBaseURL` option, you need to add the same path the registry url in your .bowerrc file: 191 | 192 | Config 193 | ```json 194 | { 195 | "server": { 196 | "port": 6789, 197 | "hostName": "yourPrivateBowerRepo", 198 | "setBaseURL": "/my-private-bower" 199 | } 200 | } 201 | ``` 202 | 203 | .bowerrc 204 | ```json 205 | { 206 | "registry": "http://yourPrivateBowerRepo:6789/my-private-bower", 207 | "timeout": 300000 208 | } 209 | ``` 210 | 211 | ## List packages 212 | GET 213 | > bower-server:5678/packages 214 | 215 | ## Search packages 216 | > bower search \[packageName\] 217 | 218 | ## Register package 219 | > bower register \[packageName\] \[gitRepo\] 220 | 221 | or 222 | POST 223 | > bower-server:5678/packages 224 | 225 | > { "name": "package-name", "url": "git://repoPath" } 226 | 227 | or 228 | POST 229 | > bower-server:5678/packages/\ 230 | 231 | > { "url": "git://repoPath" } 232 | 233 | ## Register packages 234 | POST 235 | > bower-server:5678/packages 236 | 237 | > [ { "name": "package-name", "url": "git://repoPath" } ] 238 | 239 | ## Remove package 240 | DELETE 241 | > bower-server:5678/packages/\ 242 | 243 | ## Remove packages 244 | DELETE 245 | > bower-server:5678/packages 246 | 247 | > ["package-name"] 248 | 249 | ## Restart server 250 | POST 251 | > bower-server:5678/restart 252 | 253 | ## Refresh caches 254 | POST 255 | > bower-server:5678/refresh 256 | 257 | ## Authentication 258 | 259 | Authentication can be enabled for the following features: 260 | * Register package 261 | * Register packages 262 | * Remove package 263 | * Remove packages 264 | * Restart server 265 | 266 | Add ```Auth-Key``` header to request. 267 | > Auth-Key = password 268 | 269 | 270 | ## Log4js configuration 271 | >There are two appenders set in the example configuration. 272 | >You need to remove one of the two if you want to use it. 273 | >fileDate appender will write the log to a file which will be rotated daily. 274 | >Console will write the logging to the console in the log4js format. 275 | >You need to set the replaceConsole to true if you want to write the logging to the log4j appenders. 276 | >See github.com/nomiddlename/log4js-node for more information 277 | 278 | # Tips for usage 279 | ## Server as a service 280 | - [Installing on Ubuntu](https://github.com/Hacklone/private-bower/wiki/Installing%20on%20Ubuntu) 281 | - [Install as a Windows service](https://github.com/Hacklone/private-bower/wiki/Install%20as%20a%20Windows%20service) 282 | 283 | ## Use behind proxy 284 | > git config --global url."https://".insteadOf git:// 285 | 286 | ## Calling the API 287 | - do not forget to set the ```Content-Type``` header to ```application/json``` 288 | 289 | # Contributing 290 | Please read the rules of contributing on the [contribution page](https://github.com/Hacklone/private-bower/blob/master/CONTRIBUTING.md). 291 | 292 | # License 293 | > The MIT License (MIT) 294 | 295 | > Copyright (c) 2014 Hacklone 296 | > https://github.com/Hacklone 297 | 298 | > Permission is hereby granted, free of charge, to any person obtaining a copy 299 | > of this software and associated documentation files (the "Software"), to deal 300 | > in the Software without restriction, including without limitation the rights 301 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 302 | > copies of the Software, and to permit persons to whom the Software is 303 | > furnished to do so, subject to the following conditions: 304 | 305 | > The above copyright notice and this permission notice shall be included in all 306 | > copies or substantial portions of the Software. 307 | 308 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 309 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 310 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 311 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 312 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 313 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 314 | > SOFTWARE. 315 | -------------------------------------------------------------------------------- /bin/private-bower: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/main')().start(); -------------------------------------------------------------------------------- /bower.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "registryFile": "./bowerRepository.json", 3 | "timeout": 144000, 4 | "server": { 5 | "port": 5678, 6 | "hostName": null, 7 | "siteBaseURL": null 8 | }, 9 | "public": { 10 | "disabled": false, 11 | "registry": "https://registry.bower.io/packages", 12 | "registryFile": "./bowerRepositoryPublic.json", 13 | "whitelist": [], 14 | "blacklist": [] 15 | }, 16 | "authentication": { 17 | "enabled": false, 18 | "key": "password" 19 | }, 20 | "repositoryCache": { 21 | "cachePrivate": false, 22 | "git": { 23 | "enabled": false, 24 | "cacheDirectory": "./gitRepoCache", 25 | "host": "localhost", 26 | "port": 6789, 27 | "protocol": "git", 28 | "publicAccessURL": null, 29 | "refreshDisabled": false, 30 | "refreshTimeout": 10, 31 | "parameters": { 32 | "timeout": 60000, 33 | "max-connections": 100 34 | } 35 | }, 36 | "svn": { 37 | "enabled": false, 38 | "cacheDirectory": "./svnRepoCache", 39 | "host": "localhost", 40 | "port": 7891, 41 | "protocol": "svn", 42 | "publicAccessURL": null, 43 | "refreshDisabled": false, 44 | "refreshTimeout": 10 45 | } 46 | }, 47 | "proxySettings" : { 48 | "enabled": false, 49 | "host": "proxy", 50 | "username": "name", 51 | "password" : "pass", 52 | "port": 8080, 53 | "tunnel": false 54 | }, 55 | "log4js" : { 56 | "enabled": false, 57 | "configPath" : "log4js.conf.json" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /features/assets/api.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | var request = require('superagent'); 3 | 4 | module.exports = function() { 5 | var baseUrl = 'http://localhost:5678'; 6 | 7 | function _get(url, headers) { 8 | return new Promise(function(resolve, reject) { 9 | var req = request.get(baseUrl + url); 10 | 11 | _setHeaders(req, headers); 12 | 13 | req.end(function(err, res) { 14 | if(err) { 15 | reject({ 16 | response: res, 17 | error: err 18 | }); 19 | } 20 | else { 21 | resolve(res); 22 | } 23 | }); 24 | }); 25 | } 26 | 27 | function _post(url, data, headers) { 28 | return new Promise(function(resolve, reject) { 29 | var req = request 30 | .post(baseUrl + url) 31 | .send(data); 32 | 33 | _setHeaders(req, headers); 34 | 35 | req.end(function(err, res) { 36 | if(err) { 37 | reject(res, err); 38 | } 39 | else { 40 | resolve(res); 41 | } 42 | }); 43 | }); 44 | } 45 | 46 | function _delete(url, data, headers) { 47 | return new Promise(function(resolve, reject) { 48 | var req = request 49 | .del(baseUrl + url) 50 | .send(data); 51 | 52 | _setHeaders(req, headers); 53 | 54 | req.end(function(err, res) { 55 | if(err) { 56 | reject(res, err); 57 | } 58 | else { 59 | resolve(res); 60 | } 61 | }); 62 | }); 63 | } 64 | 65 | function _setHeaders(request, headers) { 66 | for(var prop in headers) { 67 | if(headers.hasOwnProperty(prop)) { 68 | request.set(prop, headers[prop]); 69 | } 70 | } 71 | } 72 | 73 | return { 74 | get: _get, 75 | post: _post, 76 | delete: _delete 77 | }; 78 | }(); -------------------------------------------------------------------------------- /features/assets/server.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | var Server = require('../../lib/main'); 5 | 6 | var sandboxPath = path.resolve('features/sandbox/'); 7 | var configPath = path.join(sandboxPath, 'bower.conf.json'); 8 | var originalConfigPath = path.resolve('bower.conf.json'); 9 | 10 | module.exports = function() { 11 | var server; 12 | 13 | function _start(configurationModifier) { 14 | createSandbox(); 15 | 16 | goToSandbox(); 17 | 18 | copyConfig(); 19 | 20 | return startServer(); 21 | 22 | function createSandbox() { 23 | if(!fs.existsSync(sandboxPath)) { 24 | fs.mkdir(sandboxPath); 25 | } 26 | } 27 | 28 | function goToSandbox() { 29 | if(process.cwd() !== sandboxPath) { 30 | process.chdir(sandboxPath); 31 | } 32 | } 33 | 34 | function copyConfig() { 35 | var config = JSON.parse(fs.readFileSync(originalConfigPath).toString()); 36 | 37 | if(configurationModifier) { 38 | config = configurationModifier(config); 39 | } 40 | 41 | fs.writeFileSync(configPath, JSON.stringify(config, null, ' ')); 42 | } 43 | 44 | function startServer() { 45 | server = Server(); 46 | 47 | return server.start(configPath); 48 | } 49 | } 50 | 51 | function _stop() { 52 | try { 53 | server.shutDown(true); 54 | } 55 | catch(e) {} 56 | 57 | require('../../lib/service/packageStores/privatePackageStore').packages = {}; 58 | 59 | clearSandbox(); 60 | 61 | function clearSandbox() { 62 | forAllItemsInDirectory(sandboxPath, deleteItemsSync); 63 | 64 | function deleteItemsSync(itemPath) { 65 | if(fs.statSync(itemPath).isDirectory()) { 66 | forAllItemsInDirectory(itemPath, deleteItemsSync); 67 | 68 | fs.rmdirSync(itemPath); 69 | } 70 | else { 71 | fs.unlinkSync(itemPath); 72 | } 73 | } 74 | 75 | function forAllItemsInDirectory(folderPath, callback) { 76 | fs.readdirSync(folderPath) 77 | .forEach(function(childName) { 78 | callback(path.join(folderPath, childName)); 79 | }); 80 | } 81 | } 82 | } 83 | 84 | return { 85 | start: _start, 86 | stop: _stop 87 | }; 88 | }; -------------------------------------------------------------------------------- /features/assets/tools.js: -------------------------------------------------------------------------------- 1 | var api = require('./api'); 2 | 3 | module.exports = function() { 4 | function _registerPackage(name, url, authKey) { 5 | return api.post('/packages', { 6 | name: name, 7 | url: url 8 | }, authKey ? { 'Auth-Key': authKey } : undefined); 9 | } 10 | 11 | function _getPackages() { 12 | return api.get('/packages'); 13 | } 14 | 15 | function _getPackage(packageName) { 16 | return api.get('/packages/{0}'.format(packageName)); 17 | } 18 | 19 | function _getPackageDetails(packageName) { 20 | return api.get('/packages/{0}/details'.format(packageName)); 21 | } 22 | 23 | return { 24 | getPackage: _getPackage, 25 | getPackages: _getPackages, 26 | registerPackage: _registerPackage, 27 | getPackageDetails: _getPackageDetails 28 | }; 29 | }(); -------------------------------------------------------------------------------- /features/assets/utils.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | var request = require('superagent'); 3 | 4 | module.exports = function() { 5 | function _catch(done, fn) { 6 | try { 7 | fn(); 8 | done(); 9 | } catch(err) { 10 | done(err); 11 | } 12 | } 13 | 14 | function _get(url, headers) { 15 | return new Promise(function(resolve, reject) { 16 | var req = request.get(url); 17 | 18 | _setHeaders(req, headers); 19 | 20 | req.end(function(err, res) { 21 | if(err) { 22 | reject({ 23 | response: res, 24 | error: err 25 | }); 26 | } 27 | else { 28 | resolve(res); 29 | } 30 | }); 31 | }); 32 | } 33 | 34 | function _setHeaders(request, headers) { 35 | for(var prop in headers) { 36 | if(headers.hasOwnProperty(prop)) { 37 | request.set(prop, headers[prop]); 38 | } 39 | } 40 | } 41 | 42 | return { 43 | get: _get, 44 | catch: _catch 45 | }; 46 | }(); -------------------------------------------------------------------------------- /features/authentication.spec.js: -------------------------------------------------------------------------------- 1 | var expect = require("chai").expect; 2 | 3 | var api = require('./assets/api'); 4 | var utils = require('./assets/utils'); 5 | var tools = require('./assets/tools'); 6 | var Server = require('./assets/server'); 7 | 8 | describe.skip('Authentication', function() { 9 | var _testAuthKey = 'testKey'; 10 | var _server; 11 | 12 | beforeEach(function() { 13 | _server = new Server(); 14 | 15 | _server.start(function(config) { 16 | config.authentication = { 17 | enabled: true, 18 | key: _testAuthKey 19 | }; 20 | 21 | return config; 22 | }); 23 | }); 24 | 25 | afterEach(function() { 26 | _server.stop(); 27 | }); 28 | 29 | describe('install package', function() { 30 | it('should fail without auth key', function(done) { 31 | api.post('/packages/testPackage', { 32 | url: 'git://something.com/testRepoUrl.git' 33 | }) 34 | .then(loaded, loaded); 35 | 36 | function loaded(response) { 37 | expect(response.status).to.equal(401); 38 | expect(response.text).to.equal('Unauthorized'); 39 | 40 | done(); 41 | } 42 | }); 43 | 44 | it('should succeed with auth key', function(done) { 45 | api.post('/packages/testPackage', { 46 | url: 'git://something.com/testRepoUrl.git' 47 | }, { 'Auth-Key': _testAuthKey }) 48 | .then(loaded, loaded); 49 | 50 | function loaded(response) { 51 | expect(response.status).to.equal(201); 52 | 53 | done(); 54 | } 55 | }); 56 | }); 57 | 58 | describe('install packages', function() { 59 | it('should be authenticated', function(done) { 60 | api.post('/packages', [ 61 | { 62 | name: 'testPackage', 63 | url: 'git://something.com/testRepoUrl.git' 64 | }, 65 | { 66 | name: 'testPackage2', 67 | url: 'git://something.com/testRepoUrl2.git' 68 | } 69 | ]) 70 | .then(loaded, loaded); 71 | 72 | function loaded(response) { 73 | expect(response.status).to.equal(401); 74 | expect(response.text).to.equal('Unauthorized'); 75 | 76 | done(); 77 | } 78 | }); 79 | 80 | it('should succeed with auth key', function(done) { 81 | api.post('/packages', [ 82 | { 83 | name: 'testPackage', 84 | url: 'git://something.com/testRepoUrl.git' 85 | }, 86 | { 87 | name: 'testPackage2', 88 | url: 'git://something.com/testRepoUrl2.git' 89 | } 90 | ], { 'Auth-Key': _testAuthKey }) 91 | .then(loaded, loaded); 92 | 93 | function loaded(response) { 94 | expect(response.status).to.equal(201); 95 | 96 | done(); 97 | } 98 | }); 99 | }); 100 | 101 | describe('remove package', function() { 102 | it('should fail without auth key', function(done) { 103 | tools.registerPackage('testPackage', 'testUrl') 104 | .then(function() { 105 | return api.delete('/packages/testPackage') 106 | }) 107 | .then(loaded, loaded); 108 | 109 | function loaded(response) { 110 | expect(response.status).to.equal(401); 111 | expect(response.text).to.equal('Unauthorized'); 112 | 113 | done(); 114 | } 115 | }); 116 | 117 | it('should succeed with auth key', function(done) { 118 | tools.registerPackage('testPackage', 'testUrl', _testAuthKey) 119 | .then(function() { 120 | return api.delete('/packages/testPackage', {}, { 'Auth-Key': _testAuthKey }) 121 | }) 122 | .then(loaded, loaded); 123 | 124 | function loaded(response) { 125 | expect(response.status).to.equal(200); 126 | 127 | done(); 128 | } 129 | }); 130 | }); 131 | 132 | describe('remove packages', function() { 133 | it('should fail without auth key', function(done) { 134 | tools.registerPackage('testPackage', 'testUrl') 135 | .then(function() { 136 | return tools.registerPackage('testPackage1', 'testUrl') 137 | }) 138 | .then(function() { 139 | return api.delete('/packages', ['testPackage', 'testPackage1']); 140 | }) 141 | .then(loaded, loaded); 142 | 143 | function loaded(response) { 144 | expect(response.status).to.equal(401); 145 | expect(response.text).to.equal('Unauthorized'); 146 | 147 | done(); 148 | } 149 | }); 150 | 151 | it('should succeed with auth key', function(done) { 152 | tools.registerPackage('testPackage', 'testUrl', _testAuthKey) 153 | .then(function() { 154 | return tools.registerPackage('testPackage1', 'testUrl', _testAuthKey) 155 | }) 156 | .then(function() { 157 | return api.delete('/packages', ['testPackage', 'testPackage1'], { 'Auth-Key': _testAuthKey }); 158 | }) 159 | .then(loaded, loaded); 160 | 161 | function loaded(response) { 162 | expect(response.status).to.equal(200); 163 | 164 | done(); 165 | } 166 | }); 167 | }); 168 | }); -------------------------------------------------------------------------------- /features/packageDetails.spec.js: -------------------------------------------------------------------------------- 1 | var expect = require("chai").expect; 2 | 3 | var api = require('./assets/api'); 4 | var utils = require('./assets/utils'); 5 | var tools = require('./assets/tools'); 6 | var Server = require('./assets/server'); 7 | 8 | var Promise = require('bluebird'); 9 | 10 | describe.skip('PackageDetails', function() { 11 | var _server; 12 | 13 | beforeEach(function() { 14 | _server = new Server(); 15 | 16 | _server.start(); 17 | }); 18 | 19 | afterEach(function() { 20 | _server.stop(); 21 | }); 22 | 23 | describe('Get package details', function() { 24 | it('should get the bower.json of the package', function(done) { 25 | var bowerJsonFromRepo; 26 | 27 | Promise.all([ 28 | tools.registerPackage('angular', 'git://github.com/angular/bower-angular.git'), 29 | utils.get('http://cdn.rawgit.com/angular/bower-angular/master/bower.json') 30 | ]) 31 | .then(function(data) { 32 | bowerJsonFromRepo = JSON.stringify(data[1].body); 33 | }) 34 | .then(function() { 35 | return tools.getPackageDetails('angular'); 36 | }) 37 | .then(function(data) { 38 | var bowerJsonFromDetailsAPI = JSON.stringify(data.body); 39 | 40 | utils.catch(done, function() { 41 | expect(bowerJsonFromDetailsAPI).to.equal(bowerJsonFromRepo); 42 | }); 43 | }) 44 | .catch(function(err) { 45 | utils.catch(done, function() { 46 | console.log(err.error); 47 | 48 | expect(false).to.be.true; 49 | }); 50 | }); 51 | }); 52 | }); 53 | }); -------------------------------------------------------------------------------- /features/privateRepository.spec.js: -------------------------------------------------------------------------------- 1 | var expect = require("chai").expect; 2 | 3 | var api = require('./assets/api'); 4 | var utils = require('./assets/utils'); 5 | var tools = require('./assets/tools'); 6 | var Server = require('./assets/server'); 7 | 8 | describe.skip('PrivateRepository', function() { 9 | var _server; 10 | 11 | beforeEach(function() { 12 | _server = new Server(); 13 | 14 | _server.start(); 15 | }); 16 | 17 | afterEach(function() { 18 | _server.stop(); 19 | }); 20 | 21 | describe('install private package', function() { 22 | it('should return 201 if the install succeeds', function(done) { 23 | api.post('/packages', { 24 | name: 'testPackage', 25 | url: 'git://something.com/testRepoUrl.git' 26 | }) 27 | .then(loaded, loaded); 28 | 29 | function loaded(response) { 30 | expect(response.status).to.equal(201); 31 | 32 | done(); 33 | } 34 | }); 35 | 36 | it('should register package', function(done) { 37 | api.post('/packages', { 38 | name: 'testPackage2', 39 | url: 'git://something.com/testRepoUrl2.git' 40 | }) 41 | .then(tools.getPackages) 42 | .then(loaded, loaded); 43 | 44 | function loaded(response) { 45 | utils.catch(done, function() { 46 | expect(response.body).to.deep.equal([{ 47 | name: 'testPackage2', 48 | hits: 0, 49 | url: 'git://something.com/testRepoUrl2.git' 50 | }]); 51 | }); 52 | } 53 | }); 54 | 55 | it('should register a specific package', function(done) { 56 | api.post('/packages/testPackage5', { 57 | url: 'git://something.com/testRepoUrl5.git' 58 | }) 59 | .then(tools.getPackages) 60 | .then(loaded, loaded); 61 | 62 | function loaded(response) { 63 | utils.catch(done, function() { 64 | expect(response.body).to.deep.equal([{ 65 | name: 'testPackage5', 66 | hits: 0, 67 | url: 'git://something.com/testRepoUrl5.git' 68 | }]); 69 | }); 70 | } 71 | }); 72 | 73 | it('should register multiple packages', function(done) { 74 | api.post('/packages', [ 75 | { 76 | name: 'testPackage3', 77 | url: 'git://something.com/testRepoUrl3.git' 78 | }, 79 | { 80 | name: 'testPackage4', 81 | url: 'git://something.com/testRepoUrl4.git' 82 | } 83 | ]) 84 | .then(tools.getPackages) 85 | .then(loaded, loaded); 86 | 87 | function loaded(response) { 88 | utils.catch(done, function() { 89 | expect(response.body).to.deep.equal([ 90 | { 91 | hits: 0, 92 | name: 'testPackage3', 93 | url: 'git://something.com/testRepoUrl3.git' 94 | }, 95 | { 96 | hits: 0, 97 | name: 'testPackage4', 98 | url: 'git://something.com/testRepoUrl4.git' 99 | } 100 | ]); 101 | }); 102 | } 103 | }); 104 | }); 105 | 106 | describe('get packages', function() { 107 | it('should return every packages', function(done) { 108 | tools.registerPackage('testPackage', 'git://something.com/testRepoUrl.git') 109 | .then(function() { 110 | return tools.registerPackage('testPackage2', 'git://something.com/testRepoUrl2.git') 111 | }) 112 | .then(function() { 113 | return tools.registerPackage('testPackage3', 'git://something.com/testRepoUrl3.git') 114 | }) 115 | .then(function() { 116 | return api.get('/packages') 117 | }) 118 | .then(loaded, loaded); 119 | 120 | function loaded(response) { 121 | utils.catch(done, function() { 122 | expect(response.body).to.deep.equal([ 123 | { 124 | hits: 0, 125 | name: 'testPackage', 126 | url: 'git://something.com/testRepoUrl.git' 127 | }, 128 | { 129 | hits: 0, 130 | name: 'testPackage2', 131 | url: 'git://something.com/testRepoUrl2.git' 132 | }, 133 | { 134 | hits: 0, 135 | name: 'testPackage3', 136 | url: 'git://something.com/testRepoUrl3.git' 137 | } 138 | ]); 139 | }); 140 | } 141 | }); 142 | 143 | it('should return separate package', function(done) { 144 | tools.registerPackage('testPackage', 'git://something.com/testRepoUrl.git') 145 | .then(function() { 146 | return api.get('/packages/testPackage'); 147 | }) 148 | .then(loaded, loaded); 149 | 150 | function loaded(response) { 151 | utils.catch(done, function() { 152 | expect(response.body).to.deep.equal({ 153 | name: 'testPackage', 154 | url: 'git://something.com/testRepoUrl.git', 155 | hits: 1 156 | }); 157 | }); 158 | } 159 | }); 160 | 161 | it('should increase hit count on getting separate package', function(done) { 162 | tools.registerPackage('testPackage', 'git://something.com/testRepoUrl.git') 163 | .then(function() { 164 | return api.get('/packages/testPackage'); 165 | }) 166 | .then(function() { 167 | return api.get('/packages/testPackage'); 168 | }) 169 | .then(loaded, loaded); 170 | 171 | function loaded(response) { 172 | utils.catch(done, function() { 173 | expect(response.body).to.deep.equal({ 174 | name: 'testPackage', 175 | url: 'git://something.com/testRepoUrl.git', 176 | hits: 2 177 | }); 178 | }); 179 | } 180 | }); 181 | }); 182 | }); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var mocha = require('gulp-mocha'); 3 | var runSequence = require('run-sequence'); 4 | 5 | gulp.task('test', function(callback) { 6 | runSequence('unit-test', 'feature-test', callback); 7 | }); 8 | 9 | gulp.task('unit-test', function () { 10 | var files = [ 11 | 'lib/**/*.js' 12 | ]; 13 | 14 | return gulp.src(files, {read: false}) 15 | .pipe(mocha({reporter: 'nyan'})); 16 | }); 17 | 18 | gulp.task('feature-test', function() { 19 | var files = [ 20 | 'lib/**/*.js', 21 | '!lib/**/*.spec.js', 22 | 'features/**/*.js', 23 | '!features/sandbox/**/*.js' 24 | ]; 25 | 26 | return gulp.src(files, {read: false}) 27 | .pipe(mocha({ 28 | timeout: 6000, 29 | reporter: 'nyan' 30 | })); 31 | }); -------------------------------------------------------------------------------- /lib/api/controllers/packages/$name/delete.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | var packageName = req.params.name; 5 | 6 | packageManager.removePackages([packageName]); 7 | 8 | res.sendStatus(200); 9 | }; -------------------------------------------------------------------------------- /lib/api/controllers/packages/$name/details/get.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | var packageName = req.params.name; 5 | 6 | packageManager.getPackageDetails(packageName) 7 | .then(function(packageDetails) { 8 | res.send(packageDetails); 9 | }) 10 | .catch(function() { 11 | res.status(404).send('Not found'); 12 | }); 13 | }; -------------------------------------------------------------------------------- /lib/api/controllers/packages/$name/get.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | var packageName = req.params.name; 5 | 6 | packageManager.getPackageForInstall(packageName) 7 | .then(function(installPackage) { 8 | res.send(installPackage); 9 | }) 10 | .catch(function() { 11 | res.status(404).send('Not found'); 12 | }); 13 | }; -------------------------------------------------------------------------------- /lib/api/controllers/packages/$name/post.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | packageManager.registerPackages([ 5 | { 6 | name: req.params.name, 7 | url: req.body.url 8 | } 9 | ]); 10 | 11 | res.sendStatus(201); 12 | }; -------------------------------------------------------------------------------- /lib/api/controllers/packages/delete.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | var packages = req.body; 5 | 6 | packageManager.removePackages(packages); 7 | 8 | res.sendStatus(200); 9 | }; -------------------------------------------------------------------------------- /lib/api/controllers/packages/get.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | var packages = packageManager.getPrivatePackages(); 5 | 6 | res.send(packages); 7 | }; -------------------------------------------------------------------------------- /lib/api/controllers/packages/index.js: -------------------------------------------------------------------------------- 1 | var Controller = require('../../infrastructure/controller'); 2 | var authenticate = require('../../infrastructure/authentication'); 3 | 4 | var packagesController = module.exports = new Controller('/packages'); 5 | 6 | packagesController.get('/', require('./get')); 7 | packagesController.post('/', authenticate, require('./post')); 8 | packagesController.delete('/', authenticate, require('./delete')); 9 | 10 | packagesController.get('/:name', require('./$name/get')); 11 | packagesController.post('/:name', authenticate, require('./$name/post')); 12 | packagesController.delete('/:name', authenticate, require('./$name/delete')); 13 | 14 | packagesController.get('/:name/details', require('./$name/details/get')); 15 | 16 | packagesController.get('/search/:name', require('./search/$name/get')); 17 | -------------------------------------------------------------------------------- /lib/api/controllers/packages/post.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | if(Array.isArray(req.body)) { 5 | packageManager.registerPackages(req.body); 6 | } 7 | else { 8 | packageManager.registerPackages([ 9 | { 10 | name: req.body.name, 11 | url: req.body.url 12 | } 13 | ]); 14 | } 15 | 16 | res.sendStatus(201); 17 | }; -------------------------------------------------------------------------------- /lib/api/controllers/packages/search/$name/get.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | var searchName = req.params.name; 5 | 6 | var packages = packageManager.searchPackage(searchName); 7 | 8 | res.send(packages); 9 | }; -------------------------------------------------------------------------------- /lib/api/controllers/refresh/index.js: -------------------------------------------------------------------------------- 1 | var Controller = require('../../infrastructure/controller'); 2 | var authenticate = require('../../infrastructure/authentication'); 3 | 4 | var refreshController = module.exports = new Controller('/refresh'); 5 | 6 | refreshController.post('/', authenticate, require('./post')); -------------------------------------------------------------------------------- /lib/api/controllers/refresh/post.js: -------------------------------------------------------------------------------- 1 | var packageManager = require('../../../service/packageManager'); 2 | 3 | module.exports = function(req, res) { 4 | packageManager.refresh() 5 | .then(function() { 6 | res.send('Refreshed'); 7 | }) 8 | .catch(function() { 9 | res.sendStatus(500); 10 | }); 11 | }; -------------------------------------------------------------------------------- /lib/api/controllers/restart/index.js: -------------------------------------------------------------------------------- 1 | var Controller = require('../../infrastructure/controller'); 2 | var authenticate = require('../../infrastructure/authentication'); 3 | 4 | var restartController = module.exports = new Controller('/restart'); 5 | 6 | restartController.post('/', authenticate, require('./post')); -------------------------------------------------------------------------------- /lib/api/controllers/restart/post.js: -------------------------------------------------------------------------------- 1 | var application = require('../../../application'); 2 | 3 | module.exports = function(req, res) { 4 | res.send('Restarting...'); 5 | 6 | application.restart(); 7 | }; -------------------------------------------------------------------------------- /lib/api/infrastructure/authentication.js: -------------------------------------------------------------------------------- 1 | var config = require('../../infrastructure/configurationManager').config; 2 | 3 | module.exports = function Authentication(req, res, next) { 4 | if(!config.authentication || !config.authentication.enabled) { 5 | return next(); 6 | } 7 | 8 | if(req.get('Auth-Key') === config.authentication.key) { 9 | return next(); 10 | } 11 | 12 | res.status(401); 13 | res.send('Unauthorized'); 14 | }; -------------------------------------------------------------------------------- /lib/api/infrastructure/controller.js: -------------------------------------------------------------------------------- 1 | var Router = require('express').Router; 2 | 3 | function Controller(basePath) { 4 | this.basePath = basePath; 5 | 6 | this.router = Router(); 7 | } 8 | 9 | Controller.prototype = { 10 | get: function(path, middleware, handler) { 11 | if(handler) { 12 | this.router.get(path, middleware, handler); 13 | } 14 | else { 15 | this.router.get(path, middleware); 16 | } 17 | }, 18 | post: function(path, middleware, handler) { 19 | if(handler) { 20 | this.router.post(path, middleware, handler); 21 | } 22 | else { 23 | this.router.post(path, middleware); 24 | } 25 | }, 26 | put: function(path, middleware, handler) { 27 | if(handler) { 28 | this.router.put(path, middleware, handler); 29 | } 30 | else { 31 | this.router.put(path, middleware); 32 | } 33 | }, 34 | delete: function(path, middleware, handler) { 35 | if(handler) { 36 | this.router.delete(path, middleware, handler); 37 | } 38 | else { 39 | this.router.delete(path, middleware); 40 | } 41 | }, 42 | bind: function(app, siteBasePath) { 43 | if (siteBasePath) { 44 | this.basePath = siteBasePath + this.basePath; 45 | } 46 | app.use(this.basePath, this.router); 47 | } 48 | }; 49 | 50 | module.exports = Controller; -------------------------------------------------------------------------------- /lib/api/infrastructure/spec/controller.spec.js: -------------------------------------------------------------------------------- 1 | //TODO Controller Unit test -------------------------------------------------------------------------------- /lib/application.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var utils = require('./infrastructure/utils'); 4 | var logger = require('./infrastructure/logger'); 5 | var privatePackageStore = require('./service/packageStores/privatePackageStore'); 6 | var repoCacheHandler = require('./service/repoCaches/repoCacheHandler'); 7 | var publicPackageStore = require('./service/packageStores/publicPackageStore'); 8 | var configurationManager = require('./infrastructure/configurationManager'); 9 | 10 | module.exports = function Application() { 11 | var _serverApp; 12 | var _staticHandler; 13 | var _repoCacheHandler; 14 | var _listeningServer; 15 | var _siteBaseUrl; 16 | 17 | function _setup(serverApp, staticHandler, siteBaseUrl) { 18 | _serverApp = serverApp; 19 | _staticHandler = staticHandler; 20 | _siteBaseUrl = siteBaseUrl; 21 | } 22 | 23 | function _startPrivatePackageStore(registryFile) { 24 | privatePackageStore.start({ 25 | persistFilePath: registryFile 26 | }); 27 | } 28 | 29 | function _startPublicPackageStore() { 30 | return publicPackageStore.start(); 31 | } 32 | 33 | function _startPublicRepositoryCache(repoCacheOptions) { 34 | repoCacheHandler.start(repoCacheOptions); 35 | } 36 | 37 | function _listen(port, hostName) { 38 | _listeningServer = _serverApp.listen(port, hostName); 39 | } 40 | 41 | function _serveStatic(staticPath) { 42 | _addMiddleware(_staticHandler(staticPath)); 43 | } 44 | 45 | function _addMiddleware(middleware) { 46 | if (_siteBaseUrl) { 47 | _serverApp.use(_siteBaseUrl, middleware); 48 | } 49 | else { 50 | _serverApp.use(middleware); 51 | } 52 | } 53 | 54 | function _addMiddlewareWithMount(route,middleware) { 55 | _serverApp.use(route, middleware); 56 | } 57 | 58 | function _loadControllers(controllersRoot) { 59 | fs.readdirSync(controllersRoot).forEach(loadControllerAtByName); 60 | 61 | function loadControllerAtByName(controllerPath) { 62 | var controller = require(path.join(controllersRoot, controllerPath)); 63 | controller.bind(_serverApp, _siteBaseUrl); 64 | } 65 | } 66 | 67 | function _shutDown() { 68 | logger.log('Shutting down private-bower'); 69 | if(_listeningServer){ 70 | _listeningServer.close(); 71 | } 72 | 73 | publicPackageStore.shutDown(); 74 | 75 | if(_repoCacheHandler) { 76 | _repoCacheHandler.shutDown(); 77 | } 78 | } 79 | 80 | function _restart() { 81 | logger.log('Shutting down server for restart'); 82 | 83 | _shutDown(); 84 | 85 | logger.log('Restarting private-bower with config set to ' + configurationManager.configPath); 86 | 87 | utils.startDetachedChildProcess('private-bower', ['--config', configurationManager.configPath]); 88 | } 89 | 90 | return { 91 | setup: _setup, 92 | restart: _restart, 93 | 94 | listen: _listen, 95 | addMiddleware: _addMiddleware, 96 | addMiddlewareWithMount: _addMiddlewareWithMount, 97 | loadControllers: _loadControllers, 98 | serveStatic: _serveStatic, 99 | 100 | shutDown: _shutDown, 101 | 102 | startPrivatePackageStore: _startPrivatePackageStore, 103 | startPublicPackageStore: _startPublicPackageStore, 104 | startPublicRepositoryCache: _startPublicRepositoryCache 105 | }; 106 | }(); -------------------------------------------------------------------------------- /lib/extensions/spec/string.spec.js: -------------------------------------------------------------------------------- 1 | var chai = require("chai"); 2 | var expect = chai.expect; 3 | 4 | describe('String', function() { 5 | describe('startsWith(str)', function() { 6 | it('should return true if the string starts with the argument', function() { 7 | var text = 'abcd'; 8 | 9 | expect(text.startsWith('ab')).to.be.true; 10 | }); 11 | 12 | it('should return false if the string does not start with the argument', function() { 13 | var text = 'abcd'; 14 | 15 | expect(text.startsWith('cd')).to.be.false; 16 | }); 17 | }); 18 | 19 | describe('format(params)', function() { 20 | it('should replace {0}', function() { 21 | var stringToFormat = 'it should replace {0}'; 22 | 23 | var formattedString = stringToFormat.format('this'); 24 | 25 | expect(formattedString).to.equal('it should replace this'); 26 | }); 27 | 28 | it('should replace {0}', function() { 29 | var stringToFormat = 'it should replace {0} {1}'; 30 | 31 | var formattedString = stringToFormat.format('several', 'parameters'); 32 | 33 | expect(formattedString).to.equal('it should replace several parameters'); 34 | }); 35 | 36 | it('should replace {0}', function() { 37 | var stringToFormat = 'it should {3} {1} {2} in any {0}'; 38 | 39 | var formattedString = stringToFormat.format('order', 'several', 'parameters', 'replace'); 40 | 41 | expect(formattedString).to.equal('it should replace several parameters in any order'); 42 | }); 43 | }); 44 | }); -------------------------------------------------------------------------------- /lib/extensions/string.js: -------------------------------------------------------------------------------- 1 | String.prototype.format = String.prototype.format || function format() { 2 | var args = arguments; 3 | 4 | return this.replace(/\{(\d+)\}/g, function($0, $1) { 5 | return args[+$1]; 6 | }); 7 | }; 8 | 9 | String.prototype.startsWith = String.prototype.startsWith || function startsWith(searchString, position) { 10 | position = position || 0; 11 | 12 | return this.indexOf(searchString, position) === position; 13 | }; -------------------------------------------------------------------------------- /lib/infrastructure/configurationManager.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var log4js = require('log4js'); 4 | var utils = require('./utils'); 5 | var pathIsAbsolute = require('path-is-absolute'); 6 | 7 | var logger = require('./logger'); 8 | 9 | module.exports = function ConfigurationManager() { 10 | var self = { 11 | config: { 12 | port: 5678, 13 | timeout: 2 * 60 * 1200 14 | }, 15 | loadConfiguration: _loadConfiguration 16 | }; 17 | 18 | function _loadConfiguration(configPath) { 19 | if(!fs.existsSync(configPath)) { 20 | logger.error('config file not found at ' + configPath); 21 | } 22 | 23 | self.configPath = configPath; 24 | 25 | var configDirectory = path.join(configPath, '../'); 26 | 27 | var json = fs.readFileSync(configPath).toString(); 28 | var configFile = JSON.parse(json); 29 | 30 | utils.extend(self.config, configFile); 31 | 32 | setConfigValues(); 33 | configureLog4Js(); 34 | 35 | function setConfigValues() { 36 | self.config.registryFile = getRelativeFilePath(configFile.registryFile || './bowerRepository.json'); 37 | self.config.server = self.config.server || {}; 38 | self.config.server.port = utils.process.env.PORT || self.config.server.port || self.config.port; 39 | self.config.server.hostName = utils.process.env.IP || self.config.server.hostName || self.config.hostName || '0.0.0.0'; 40 | self.config.server.siteBaseURL = self.config.server.siteBaseURL || null; 41 | 42 | self.config.public = self.config.public || {}; 43 | 44 | self.config.public.registryFile = getRelativeFilePath(configFile.public && configFile.public.registryFile || './bowerRepositoryPublic.json'); 45 | self.config.public.registry = self.config.public.registry || 'https://bower.herokuapp.com/packages'; 46 | 47 | self.config.public.whitelist = self.config.public.whitelist || []; 48 | self.config.public.whitelist.enabled = !!self.config.public.whitelist.length; 49 | 50 | self.config.public.blacklist = self.config.public.blacklist || []; 51 | self.config.public.blacklist.enabled = !!self.config.public.blacklist.length; 52 | 53 | self.config.repoCacheOptions = {}; 54 | self.config.repositoryCache = self.config.repositoryCache || {}; 55 | 56 | if(self.config.repositoryCache.svn && self.config.repositoryCache.svn.enabled) { 57 | self.config.repoCacheOptions.svn = { 58 | repoCacheRoot: getRelativeFilePath(self.config.repositoryCache.svn.cacheDirectory || './svnRepoCache'), 59 | hostName: self.config.repositoryCache.svn.host, 60 | port: self.config.repositoryCache.svn.port || 7891, 61 | protocol: self.config.repositoryCache.svn.protocol || 'svn', 62 | refreshDisabled: self.config.repositoryCache.svn.refreshDisabled || false, 63 | refreshTimeout: self.config.repositoryCache.svn.refreshTimeout || 10, 64 | parameters: self.config.repositoryCache.svn.parameters 65 | }; 66 | } 67 | 68 | if(self.config.repositoryCache.git && self.config.repositoryCache.git.enabled) { 69 | self.config.repoCacheOptions.git = { 70 | repoCacheRoot: getRelativeFilePath(self.config.repositoryCache.git.cacheDirectory || './gitRepoCache'), 71 | hostName: self.config.repositoryCache.git.host, 72 | publicAccessURL: self.config.repositoryCache.git.publicAccessURL || null, 73 | port: self.config.repositoryCache.git.port || 6789, 74 | protocol: self.config.repositoryCache.git.protocol || 'git', 75 | refreshDisabled: self.config.repositoryCache.git.refreshDisabled || false, 76 | refreshTimeout: self.config.repositoryCache.git.refreshTimeout || 10, 77 | parameters: self.config.repositoryCache.git.parameters 78 | }; 79 | } 80 | 81 | self.config.repositoryCache.enabled = self.config.repoCacheOptions.svn || self.config.repoCacheOptions.git; 82 | 83 | function getRelativeFilePath(filePath) { 84 | if((path.isAbsolute && path.isAbsolute(filePath)) || pathIsAbsolute(filePath)) { 85 | return filePath; 86 | } 87 | 88 | return path.resolve(path.join(configDirectory, filePath)); 89 | } 90 | } 91 | 92 | function configureLog4Js() { 93 | if(self.config.log4js && self.config.log4js.enabled) { 94 | log4js.configure(self.config.log4js.configPath); 95 | } 96 | } 97 | } 98 | 99 | return self; 100 | }(); 101 | -------------------------------------------------------------------------------- /lib/infrastructure/logger.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | 3 | var moment = require('moment'); 4 | 5 | module.exports = function() { 6 | function _log(text) { 7 | var timeStamp = moment().format('D/M/YY HH:mm:ss'); 8 | 9 | console.log('[bower] '.green, timeStamp.cyan, ' ', text); 10 | } 11 | 12 | function _error(text, exception) { 13 | _log(text.red); 14 | 15 | console.log(exception); 16 | } 17 | 18 | function _logHelp() { 19 | console.log([ 20 | ' _ _ _', 21 | ' _ __ _ _(_)_ ____ _| |_ ___ ___| |__ _____ __ _____ _ _', 22 | ' | \'_ \\ \'_| \\ V / _` | _/ -_)___| \'_ \\/ _ \\ V V / -_) \'_|', 23 | ' | .__/_| |_|\\_/\\__,_|\\__\\___| |_.__/\\___/\\_/\\_/\\___|_|', 24 | ' |_|', 25 | 'usage: private-bower [options]', 26 | '', 27 | 'options:', 28 | ' --h --help Print this list and exit.', 29 | ' --config Path to the config file (Must be a valid json)' 30 | ].join('\n')); 31 | } 32 | 33 | return { 34 | log: _log, 35 | error: _error, 36 | 37 | logHelp: _logHelp 38 | }; 39 | }(); 40 | -------------------------------------------------------------------------------- /lib/infrastructure/spec/configurationManager.spec.js: -------------------------------------------------------------------------------- 1 | //TODO: TEST - configuration spec -------------------------------------------------------------------------------- /lib/infrastructure/spec/utils.spec.js: -------------------------------------------------------------------------------- 1 | var sinon = require("sinon"); 2 | var chai = require("chai"); 3 | var sinonChai = require("sinon-chai"); 4 | var expect = chai.expect; 5 | chai.use(sinonChai); 6 | 7 | var path = require('path'); 8 | var mockery = require('mockery'); 9 | 10 | describe('Utils', function() { 11 | var execMock; 12 | var loggerMock; 13 | var utils; 14 | 15 | beforeEach(function() { 16 | loggerMock = { 17 | log: sinon.stub() 18 | }; 19 | 20 | execMock = sinon.stub(); 21 | 22 | mockery.registerMock('./logger', loggerMock); 23 | mockery.registerMock('child_process', { 24 | exec: execMock 25 | }); 26 | 27 | mockery.enable({ 28 | useCleanCache: true, 29 | warnOnUnregistered: false 30 | }); 31 | 32 | createUtils(); 33 | }); 34 | 35 | afterEach(function() { 36 | mockery.deregisterAll(); 37 | mockery.disable(); 38 | }); 39 | 40 | function createUtils() { 41 | utils = require('../utils'); 42 | } 43 | 44 | it('should have these properties', function() { 45 | expect(utils.exec).to.be.a('function'); 46 | expect(utils.getChildDirectories).to.be.a('function'); 47 | expect(utils.removeDirectory).to.be.a('function'); 48 | expect(utils.extend).to.be.a('function'); 49 | expect(utils.dirname).to.be.a('string'); 50 | }); 51 | 52 | describe('dirname', function() { 53 | it('should be the current __dirname of root', function() { 54 | expect(utils.dirname).to.equal(path.join(__dirname, '../../')); 55 | }); 56 | }); 57 | 58 | describe('extend()', function() { 59 | it('should extend a object', function() { 60 | var a = { 61 | a: { text: 'a' }, 62 | b: { text: 'b' }, 63 | c: { text: 'c' } 64 | }; 65 | 66 | var b = { 67 | c: { text: 'otherC' }, 68 | d: { text: 'd' } 69 | }; 70 | 71 | var result = utils.extend(a, b); 72 | 73 | expect(result.a).to.equal(a.a); 74 | expect(result.b).to.equal(a.b); 75 | expect(result.c).to.equal(b.c); 76 | expect(result.d).to.equal(b.d); 77 | }); 78 | }); 79 | 80 | describe('exec()', function() { 81 | it('should call child_process.exec() with the correct parameters', function() { 82 | var command = 'testCommand'; 83 | var cwd = 'testCWD'; 84 | 85 | utils.exec(command, cwd); 86 | 87 | expect(execMock).to.have.been.calledWith(); 88 | 89 | var args = execMock.args[0]; 90 | 91 | expect(args[0]).to.equal(command); 92 | expect(args[1]).to.deep.equal({ cwd: cwd }); 93 | expect(args[2]).to.be.a('function'); 94 | }); 95 | 96 | it.skip('should return a Promise that resolves if the exec succeeds', function() { 97 | var stdOut = 'thisIsSTDOut'; 98 | var resolved = sinon.stub(); 99 | var rejected = sinon.stub(); 100 | 101 | utils.exec('testCommand', 'testCWD') 102 | .then(resolved) 103 | .catch(rejected); 104 | 105 | var callback = execMock.args[0][2]; 106 | 107 | callback(undefined, stdOut); 108 | 109 | expect(resolved).to.have.been.calledWith(stdOut); 110 | expect(rejected).not.to.have.been.calledWith(); 111 | }); 112 | 113 | it.skip('should return a Promise that rejects if the exec fails', function() { 114 | var error = 'error'; 115 | var resolved = sinon.stub(); 116 | var rejected = sinon.stub(); 117 | 118 | utils.exec('testCommand', 'testCWD') 119 | .then(resolved) 120 | .catch(rejected); 121 | 122 | var callback = execMock.args[0][2]; 123 | 124 | callback(error); 125 | 126 | expect(rejected).to.have.been.calledWith(error); 127 | expect(resolved).not.to.have.been.calledWith(); 128 | }); 129 | 130 | it('should return log if error occurs', function() { 131 | var error = 'error'; 132 | var stdOut = 'testStdOut'; 133 | var stdErr = 'testStdErr'; 134 | 135 | utils.exec('testCommand', 'testCWD') 136 | .catch(function() {}); 137 | 138 | var callback = execMock.args[0][2]; 139 | 140 | callback(error, stdOut, stdErr); 141 | 142 | expect(loggerMock.log).to.have.been.calledWith('Error during "{0}" in "{1}".\n\tOutput:\n\t\tstdout: {2}\n\t\tstderr: {3}'.format('testCommand', 'testCWD', stdOut, stdErr)); 143 | }); 144 | }); 145 | 146 | describe('getChildDirectories()', function() { 147 | var directoryName = 'directory'; 148 | var childDirectories = [ 'a', 'b', 'c', 'd' ]; 149 | var childDirectoriesPaths = childDirectories.map(function(file) { 150 | return path.join(directoryName, file); 151 | }); 152 | 153 | var filesInDirectory = [ 154 | 'a.js', 155 | 'b.doc', 156 | 'c.xml', 157 | 'd.html' 158 | ]; 159 | 160 | var allFilesInDirectory = [].concat(childDirectories, filesInDirectory); 161 | 162 | it('should return child directories', function() { 163 | var fsMock = { 164 | readdirSync: sinon.stub().returns(allFilesInDirectory), 165 | lstatSync: function() {} 166 | }; 167 | 168 | sinon.stub(fsMock, 'lstatSync', function(filePath) { 169 | return { 170 | isDirectory: function() { 171 | return childDirectoriesPaths.indexOf(filePath) !== -1 172 | } 173 | }; 174 | }); 175 | 176 | mockery.registerMock('fs', fsMock); 177 | 178 | mockery.resetCache(); 179 | 180 | createUtils(); 181 | 182 | var resultChildDirectories = utils.getChildDirectories(directoryName); 183 | 184 | expect(resultChildDirectories).to.deep.equal(childDirectories); 185 | }); 186 | }); 187 | 188 | //TODO: TEST - removeDirectory 189 | }); 190 | -------------------------------------------------------------------------------- /lib/infrastructure/utils.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var logger = require('./logger'); 4 | var Promise = require('bluebird'); 5 | var exec = require('child_process').exec; 6 | var spawn = require('child_process').spawn; 7 | 8 | module.exports = function Utils() { 9 | _init(); 10 | function _init() { 11 | initExtensions(); 12 | 13 | function initExtensions() { 14 | String.prototype.format = String.prototype.format || function format() { 15 | var args = arguments; 16 | 17 | return this.replace(/\{(\d+)\}/g, function($0, $1) { 18 | return args[+$1]; 19 | }); 20 | }; 21 | 22 | String.prototype.startsWith = String.prototype.startsWith || function startsWith(searchString, position) { 23 | position = position || 0; 24 | 25 | return this.indexOf(searchString, position) === position; 26 | }; 27 | } 28 | } 29 | 30 | function _getChildDirectories(directory) { 31 | return fs.readdirSync(directory).filter(function(file) { 32 | var filePath = path.join(directory, file); 33 | 34 | return fs.lstatSync(filePath).isDirectory(); 35 | }); 36 | } 37 | 38 | function _removeDirectory(dirPath) { 39 | if(!fs.existsSync(dirPath)) { 40 | return; 41 | } 42 | 43 | fs.readdirSync(dirPath).forEach(function(file) { 44 | var currentFilePathPath = path.join(dirPath, file); 45 | 46 | if(fs.lstatSync(currentFilePathPath).isDirectory()) { 47 | _removeDirectory(currentFilePathPath); 48 | } 49 | else { 50 | fs.unlinkSync(currentFilePathPath); 51 | } 52 | }); 53 | 54 | fs.rmdirSync(dirPath); 55 | } 56 | 57 | function _exec(command, cwd) { 58 | return new Promise(function(resolve, reject) { 59 | exec(command, {cwd: cwd}, function(error, stdout, stderr) { 60 | if(error) { 61 | logger.log('Error during "{0}" in "{1}".\n\tOutput:\n\t\tstdout: {2}\n\t\tstderr: {3}'.format(command, cwd, stdout, stderr)); 62 | 63 | reject(error); 64 | } 65 | else { 66 | resolve(stdout); 67 | } 68 | }); 69 | }); 70 | } 71 | 72 | function _extend(destination) { 73 | var args = Array.prototype.slice.call(arguments, 1); 74 | 75 | args.forEach(function(srcArg) { 76 | extendObject(destination, srcArg); 77 | }); 78 | 79 | return destination; 80 | 81 | function extendObject(dest, src) { 82 | for(var propertyName in src) { 83 | if(src.hasOwnProperty(propertyName)) { 84 | dest[propertyName] = src[propertyName]; 85 | } 86 | } 87 | 88 | return dest; 89 | } 90 | } 91 | 92 | function _startDetachedChildProcess(command, args) { 93 | var child = spawn(command, args, { 94 | detached: true 95 | }); 96 | 97 | child.stdout.pipe(process.stdout); 98 | child.stderr.pipe(process.stderr); 99 | 100 | child.unref(); 101 | } 102 | 103 | function _getRandomString() { 104 | return Math.random().toString(36).substring(2); 105 | } 106 | 107 | return { 108 | exec: _exec, 109 | extend: _extend, 110 | process: process, 111 | getRandomString: _getRandomString, 112 | dirname: path.join(__dirname, '../'), 113 | removeDirectory: _removeDirectory, 114 | getChildDirectories: _getChildDirectories, 115 | startDetachedChildProcess: _startDetachedChildProcess 116 | }; 117 | }(); 118 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var Express = require('express'); 3 | var logger = require('./infrastructure/logger'); 4 | var argv = require('optimist').argv; 5 | var bodyParser = require('body-parser'); 6 | 7 | var utils = require('./infrastructure/utils'); 8 | var application = require('./application'); 9 | var configurationManager = require('./infrastructure/configurationManager'); 10 | var version = require('../package.json').version; 11 | 12 | module.exports = function Main() { 13 | var _config; 14 | 15 | function _start(configPath) { 16 | if(argv.h || argv.help) { 17 | logger.logHelp(); 18 | 19 | utils.process.exit(); 20 | } 21 | if (argv.version) { 22 | console.log(version); 23 | utils.process.exit(); 24 | } 25 | 26 | _handleErrors(); 27 | _handleShutDown(); 28 | 29 | var serverApp = Express(); 30 | 31 | var defaultConfigPath = path.join(utils.dirname, '../bower.conf.json'); 32 | configurationManager.loadConfiguration(configPath || argv.config || defaultConfigPath); 33 | 34 | _config = configurationManager.config; 35 | 36 | application.setup(serverApp, Express.static, _config.server.siteBaseURL); 37 | 38 | _initializePackageStores(); 39 | _initializeService(); 40 | 41 | logger.log('private-bower server started'); 42 | } 43 | 44 | function _initializePackageStores() { 45 | application.startPrivatePackageStore(_config.registryFile); 46 | 47 | if(!_config.public.disabled) { 48 | application.startPublicPackageStore(); 49 | } 50 | 51 | if(_config.repositoryCache && _config.repositoryCache.enabled) { 52 | application.startPublicRepositoryCache(_config.repoCacheOptions); 53 | } 54 | } 55 | 56 | function _initializeService() { 57 | application.addMiddleware(bodyParser.urlencoded({ 58 | extended: true 59 | })); 60 | application.addMiddleware(bodyParser.json()); 61 | 62 | application.serveStatic(path.join(utils.dirname, '../site')); 63 | 64 | application.loadControllers(path.join(utils.dirname, 'api/controllers')); 65 | 66 | if (_config.server.siteBaseURL) { 67 | application.addMiddlewareWithMount('/', function redirectToBaseUrl(req, res, next) { 68 | res.redirect(_config.server.siteBaseURL); 69 | }); 70 | } 71 | 72 | application.addMiddleware(function errorHandler(err, req, res, next) { 73 | logger.error(err); 74 | logger.error(err.stack); 75 | 76 | res.status(500).send('Something wen\'t wrong :('); 77 | }); 78 | 79 | application.listen(_config.server.port, _config.server.hostName); 80 | } 81 | 82 | function _handleErrors() { 83 | utils.process.on('uncaughtException', function(err) { 84 | logger.log('Exception message:' + (err.stack || err.message)); 85 | 86 | _shutDown(); 87 | }); 88 | } 89 | 90 | function _handleShutDown() { 91 | utils.process.on('SIGINT', _shutDown); 92 | } 93 | 94 | function _shutDown(skipProcessExit) { 95 | application.shutDown(); 96 | 97 | if(!skipProcessExit) { 98 | utils.process.exit(); 99 | } 100 | } 101 | 102 | return { 103 | start: _start, 104 | shutDown: _shutDown 105 | }; 106 | }; -------------------------------------------------------------------------------- /lib/service/gitPackageDetailsProvider.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var Promise = require('bluebird'); 5 | 6 | var utils = require('../infrastructure/utils'); 7 | 8 | module.exports = function GitPackageDetailsProvider() { 9 | var tempFolder = path.join(utils.dirname, 'temp/packageDetails'); 10 | 11 | function _getPackageDetails(packageUrl) { 12 | return new Promise(function(resolve, reject) { 13 | var tempName = utils.getRandomString(); 14 | var gitCloneFolder = path.join(tempFolder, tempName); 15 | 16 | utils.exec('git clone {0} {1} --depth=1'.format(packageUrl, gitCloneFolder)) 17 | .then(function() { 18 | var bowerJsonLocation = path.join(gitCloneFolder, 'bower.json'); 19 | 20 | var fileContent = fs.readFileSync(bowerJsonLocation); 21 | var bowerJson = JSON.parse(fileContent); 22 | 23 | utils.removeDirectory(gitCloneFolder); 24 | 25 | resolve(bowerJson); 26 | }) 27 | .catch(reject); 28 | }); 29 | } 30 | 31 | return { 32 | getPackageDetails: _getPackageDetails 33 | }; 34 | }(); 35 | -------------------------------------------------------------------------------- /lib/service/packageDetailsProvider.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var Promise = require('bluebird'); 5 | 6 | var GitPackageDetailsProvider = require('./gitPackageDetailsProvider'); 7 | var SvnPackageDetailsProvider = require('./svnPackageDetailsProvider'); 8 | 9 | var utils = require('../infrastructure/utils'); 10 | 11 | module.exports = function PackageDetailsProvider() { 12 | function _getPackageDetails(packageUrl) { 13 | if(packageUrl.startsWith('svn+')) { 14 | return SvnPackageDetailsProvider.getPackageDetails(packageUrl); 15 | } 16 | return GitPackageDetailsProvider.getPackageDetails(packageUrl); 17 | } 18 | 19 | return { 20 | getPackageDetails: _getPackageDetails 21 | }; 22 | }(); 23 | -------------------------------------------------------------------------------- /lib/service/packageManager.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | 3 | var repoCacheHandler = require('./repoCaches/repoCacheHandler'); 4 | var privatePackageStore = require('./packageStores/privatePackageStore'); 5 | var publicPackageStore = require('./packageStores/publicPackageStore'); 6 | var packageDetailsProvider = require('./packageDetailsProvider'); 7 | 8 | var config = require('../infrastructure/configurationManager').config; 9 | var logger = require('../infrastructure/logger'); 10 | 11 | module.exports = function PackageManager() { 12 | 13 | function _getPrivatePackages() { 14 | var packages = []; 15 | 16 | for(var packageName in privatePackageStore.packages) { 17 | if(privatePackageStore.packages.hasOwnProperty(packageName)) { 18 | var item = privatePackageStore.packages[packageName]; 19 | 20 | packages.push({ 21 | name: item.name, 22 | url: item.url, 23 | hits: item.hits 24 | }); 25 | } 26 | } 27 | 28 | return packages; 29 | } 30 | 31 | function _registerPackages(packages) { 32 | privatePackageStore.registerPackages(packages); 33 | } 34 | 35 | function _removePackages(packages) { 36 | privatePackageStore.removePackages(packages); 37 | 38 | if(repoCacheHandler.enabled) { 39 | repoCacheHandler.removePackages(packages); 40 | } 41 | } 42 | 43 | function _searchPackage(searchName) { 44 | var packages = privatePackageStore.searchPackage(searchName); 45 | 46 | if(!config.public.disabled) { 47 | var publicPackages = publicPackageStore.searchPackage(searchName); 48 | 49 | publicPackages = filterOnlyWhitelist(publicPackages); 50 | 51 | publicPackages = filterBlacklist(publicPackages); 52 | 53 | packages = packages.concat(publicPackages); 54 | } 55 | 56 | return packages; 57 | 58 | function filterOnlyWhitelist(list) { 59 | if(!config.public.whitelist.enabled) { 60 | return list; 61 | } 62 | 63 | return list.filter(function(packageName) { 64 | return config.public.whitelist.indexOf(packageName) > -1; 65 | }); 66 | } 67 | 68 | function filterBlacklist(list) { 69 | if(!config.public.blacklist.enabled) { 70 | return list; 71 | } 72 | 73 | return list.filter(function(packageName) { 74 | return config.public.blacklist.indexOf(packageName) === -1; 75 | }); 76 | } 77 | } 78 | 79 | function _getPackageDetails(packageName) { 80 | return _getPackageForInstall(packageName) 81 | .then(function(packageDescription) { 82 | return packageDetailsProvider.getPackageDetails(packageDescription.url) 83 | }); 84 | } 85 | 86 | //TODO: should refactor this 87 | function _getPackageForInstall(packageName) { 88 | return new Promise(function(resolve, reject) { 89 | var privatePackage = privatePackageStore.getPackage(packageName); 90 | 91 | if(privatePackage) { 92 | handlePrivatePackage(); 93 | } 94 | else if(canHandlePublicPackage()) { 95 | handlePublicPackage(); 96 | } 97 | else { 98 | reject(); 99 | } 100 | 101 | function canHandlePublicPackage() { 102 | var isValidByWhitelist = !config.public.whitelist.enabled || config.public.whitelist.indexOf(packageName) > -1; 103 | var notExcludedByBlacklist = !config.public.blacklist.enabled || config.public.blacklist.indexOf(packageName) !== -1; 104 | 105 | return !config.public.disabled && isValidByWhitelist && notExcludedByBlacklist; 106 | } 107 | 108 | function handlePrivatePackage() { 109 | if(config.repositoryCache && config.repositoryCache.cachePrivate) { 110 | if(privatePackage.cachedRepo) { 111 | resolve({ 112 | name: packageName, 113 | url: privatePackage.cachedRepo, 114 | hits: privatePackage.hits 115 | }); 116 | } 117 | else if(repoCacheHandler) { 118 | cachePrivateRepoAndSend(); 119 | } 120 | else { 121 | sendPrivatePackage(); 122 | } 123 | } 124 | else { 125 | sendPrivatePackage(); 126 | } 127 | 128 | function cachePrivateRepoAndSend() { 129 | var repoCache = repoCacheHandler.getRepoCache(privatePackage.url); 130 | 131 | repoCache.cacheRepo(packageName, privatePackage.url) 132 | .then(function(pack) { 133 | privatePackage.cachedRepo = pack.url; 134 | privatePackageStore.persistPackages(); 135 | 136 | resolve({ 137 | name: packageName, 138 | url: privatePackage.cachedRepo, 139 | hits: privatePackage.hits 140 | }); 141 | }) 142 | .catch(sendPrivatePackage); 143 | } 144 | 145 | function sendPrivatePackage() { 146 | resolve({ 147 | name: packageName, 148 | url: privatePackage.url, 149 | hits: privatePackage.hits 150 | }); 151 | } 152 | } 153 | 154 | function handlePublicPackage() { 155 | var publicPackage = publicPackageStore.getPackage(packageName); 156 | if(publicPackage) { 157 | if(repoCacheHandler.enabled) { 158 | cachePublicRepo(); 159 | } 160 | else { 161 | resolve(publicPackage); 162 | } 163 | } 164 | else { 165 | reject(); 166 | } 167 | 168 | function cachePublicRepo() { 169 | var repoCache = repoCacheHandler.getRepoCache(publicPackage.url); 170 | 171 | repoCache.cacheRepo(packageName, publicPackage.url) 172 | .then(function(pack) { 173 | var privatePackage = { 174 | name: packageName, 175 | url: pack.url, 176 | hits: 1 177 | }; 178 | 179 | privatePackageStore.registerPackages([privatePackage]); 180 | 181 | resolve({ 182 | name: privatePackage.name, 183 | url: privatePackage.url, 184 | hits: privatePackage.hits 185 | }); 186 | }) 187 | .catch(function() { 188 | resolve(publicPackage); 189 | }); 190 | } 191 | } 192 | }); 193 | } 194 | 195 | function _refresh() { 196 | logger.log('Refreshing repository'); 197 | 198 | return repoCacheHandler.getLatestForRepos(); 199 | } 200 | 201 | return { 202 | refresh: _refresh, 203 | removePackages: _removePackages, 204 | searchPackage: _searchPackage, 205 | registerPackages: _registerPackages, 206 | getPrivatePackages: _getPrivatePackages, 207 | getPackageDetails: _getPackageDetails, 208 | getPackageForInstall: _getPackageForInstall 209 | }; 210 | }(); -------------------------------------------------------------------------------- /lib/service/packageStores/privatePackageStore.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var logger = require('../../infrastructure/logger'); 3 | 4 | module.exports = function PackageStore() { 5 | var self = { 6 | start: _start, 7 | 8 | packages: {}, 9 | 10 | getPackage: _getPackage, 11 | registerPackages: _registerPackages, 12 | removePackages: _removePackages, 13 | 14 | searchPackage: _searchPackage, 15 | 16 | persistPackages: _persistPackages 17 | }; 18 | 19 | var _options; 20 | 21 | function _start(options) { 22 | _options = options; 23 | 24 | _loadPackages(); 25 | } 26 | 27 | function _getPackage(packageName) { 28 | var item = self.packages[packageName]; 29 | 30 | if(!item) { 31 | return null; 32 | } 33 | 34 | item.name = packageName; 35 | item.hits = item.hits || 0; 36 | item.hits++; 37 | 38 | //could use yield 39 | setTimeout(_persistPackages, 10); 40 | 41 | return item; 42 | } 43 | 44 | function _registerPackages(register) { 45 | for(var i = 0, len = register.length; i < len; i++) { 46 | var registerPackage = register[i]; 47 | 48 | if(!registerPackage.name) { 49 | logger.log('Undefined package name'); 50 | 51 | continue; 52 | } 53 | 54 | self.packages[registerPackage.name] = { 55 | name: registerPackage.name, 56 | url: registerPackage.url, 57 | hits: 0 58 | }; 59 | 60 | logger.log('Registered package: ' + registerPackage.name); 61 | } 62 | 63 | _persistPackages(); 64 | } 65 | 66 | function _removePackages(remove) { 67 | for(var i = 0, len = remove.length; i < len; i++) { 68 | delete self.packages[remove[i]]; 69 | 70 | logger.log('Removed package: ' + remove[i]); 71 | } 72 | 73 | _persistPackages(); 74 | } 75 | 76 | function _persistPackages() { 77 | if(fs.existsSync(_options.persistFilePath)) { 78 | fs.unlinkSync(_options.persistFilePath); 79 | } 80 | 81 | fs.writeFileSync(_options.persistFilePath, JSON.stringify(self.packages, null, ' ')); 82 | } 83 | 84 | function _loadPackages() { 85 | if(!fs.existsSync(_options.persistFilePath)) { 86 | return; 87 | } 88 | 89 | var json = fs.readFileSync(_options.persistFilePath).toString(); 90 | 91 | try { 92 | self.packages = modifyRepoProperty(JSON.parse(json)); 93 | } 94 | catch(e) { 95 | logger.error('Malformed registry file. It must be a valid json: ' + _options.persistFilePath, e); 96 | 97 | throw e; 98 | } 99 | 100 | function modifyRepoProperty(packages) { 101 | var loadedPackages = {}; 102 | 103 | for(var key in packages) { 104 | var pack = packages[key]; 105 | 106 | loadedPackages[pack.name] = { 107 | name: pack.name, 108 | url: pack.repo || pack.url, 109 | hits: pack.hits 110 | }; 111 | } 112 | 113 | return loadedPackages; 114 | } 115 | } 116 | 117 | function _searchPackage(name) { 118 | var searchName = name.toLowerCase(); 119 | var packages = []; 120 | 121 | for(var packageName in self.packages) { 122 | if(self.packages.hasOwnProperty(packageName) && 123 | packageName.toLowerCase().indexOf(searchName) !== -1) { 124 | 125 | var item = self.packages[packageName]; 126 | packages.push({ 127 | name: item.name, 128 | url: item.url 129 | }); 130 | } 131 | } 132 | 133 | return packages; 134 | } 135 | 136 | return self; 137 | }(); -------------------------------------------------------------------------------- /lib/service/packageStores/publicPackageStore.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var logger = require('../../infrastructure/logger'); 3 | var Client = require('node-rest-client').Client; 4 | var config = require('../../infrastructure/configurationManager').config; 5 | var URI = require('URIjs'); 6 | var Promise = require('bluebird'); 7 | 8 | module.exports = function PublicPackageStore() { 9 | var _packages = {}; 10 | var _publicBowerUrl; 11 | 12 | var _timer; 13 | 14 | function _start() { 15 | logger.log('Refreshing public packages...'); 16 | 17 | _publicBowerUrl = config.public.registry; 18 | 19 | _loadPublicRegistryFromCache(); 20 | 21 | return _loadPublicPackagesPeriodically(); 22 | } 23 | 24 | function _getPackage(packageName) { 25 | return _packages[packageName]; 26 | } 27 | 28 | function _parsePackage(data) { 29 | var jsonData; 30 | try { 31 | if (data instanceof Array || data instanceof Object) { 32 | jsonData = data; 33 | } else { 34 | jsonData = JSON.parse(data); 35 | } 36 | 37 | for(var i = 0, len = jsonData.length; i < len; i++) { 38 | var item = jsonData[i]; 39 | _packages[item.name] = item; 40 | } 41 | 42 | return true; 43 | } catch(e) { 44 | logger.error('Could not load public packages, invalid data!', e); 45 | logger.error('data = ' + data); 46 | } 47 | } 48 | 49 | function _loadPublicRegistryFromCache() { 50 | if(!config.public.registryFile || !fs.existsSync(config.public.registryFile)) { 51 | return; 52 | } 53 | 54 | var data = fs.readFileSync(config.public.registryFile); 55 | 56 | var result = _parsePackage(data); 57 | 58 | if(result) { 59 | logger.log("Cached public packages loaded"); 60 | } 61 | } 62 | 63 | function _cachePublicRegistry(data) { 64 | if(!config.public.registryFile) { 65 | return; 66 | } 67 | 68 | fs.writeFile(config.public.registryFile, JSON.stringify(data), function(err) { 69 | if(err) { 70 | logger.error('Error caching public registry', err); 71 | } 72 | else { 73 | logger.log('Public registry cached'); 74 | } 75 | }); 76 | } 77 | 78 | function _loadPublicPackagesPeriodically() { 79 | return new Promise(function(resolve, reject) { 80 | var client = createClient(); 81 | 82 | var url = new URI(_publicBowerUrl); 83 | // set content-type header and data as json in args parameter 84 | var args = { 85 | headers:{ 86 | "Content-Type": "application/json", 87 | 'host': url.hostname() 88 | } 89 | }; 90 | 91 | client.get(_publicBowerUrl, args, function(data) { 92 | processData(data); 93 | 94 | resolve(); 95 | }).on('error', function(err) { 96 | logger.error('something went wrong on the request', err.request.options); 97 | 98 | reject(); 99 | }); 100 | 101 | client.on('error', function(err) { 102 | logger.error('Something went wrong on the client', err); 103 | 104 | reject(); 105 | }); 106 | 107 | function processData(data) { 108 | try { 109 | var result = _parsePackage(data); 110 | 111 | if(result) { 112 | _cachePublicRegistry(data); 113 | 114 | logger.log('Loaded public packages'); 115 | } 116 | } 117 | catch (e) { 118 | logger.error('Could not load public packages: data=' + data, e); 119 | } 120 | } 121 | 122 | function createClient() { 123 | var clientOptions; 124 | 125 | if(config.proxySettings && config.proxySettings.enabled) { 126 | clientOptions = { 127 | proxy: { 128 | host: config.proxySettings.host, 129 | port: config.proxySettings.port, 130 | user: config.proxySettings.username, 131 | password: config.proxySettings.password, 132 | tunnel: config.proxySettings.tunnel 133 | } 134 | }; 135 | } 136 | 137 | return new Client(clientOptions); 138 | } 139 | 140 | // re-schedule the function 141 | _timer = setTimeout(_loadPublicPackagesPeriodically, 1000 * 60 * 30); 142 | }); 143 | } 144 | 145 | function _searchPackage(name) { 146 | var searchName = name.toLowerCase(); 147 | var packages = []; 148 | 149 | for(var packageName in _packages) { 150 | if(_packages.hasOwnProperty(packageName) && 151 | packageName.toLowerCase().indexOf(searchName) !== -1) { 152 | 153 | var item = _packages[packageName]; 154 | packages.push({ 155 | name: item.name, 156 | url: item.url 157 | }); 158 | } 159 | } 160 | 161 | return packages; 162 | } 163 | 164 | function _shutDown() { 165 | clearTimeout(_timer); 166 | } 167 | 168 | return { 169 | start: _start, 170 | 171 | getPackage: _getPackage, 172 | searchPackage: _searchPackage, 173 | shutDown: _shutDown 174 | }; 175 | }(); 176 | -------------------------------------------------------------------------------- /lib/service/repoCaches/gitRepoCache.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var mkdirp = require('mkdirp'); 4 | var utils = require('../../infrastructure/utils'); 5 | var Promise = require('bluebird'); 6 | var logger = require('../../infrastructure/logger'); 7 | var exec = require('child_process').exec; 8 | 9 | var RepoCacheBase = require('./repoCacheBase'); 10 | 11 | module.exports = function GitRepoCache(options) { 12 | var base = new RepoCacheBase(options); 13 | var _daemon; 14 | 15 | _init(); 16 | function _init() { 17 | return _createDirectory(options.repoCacheRoot) 18 | .then(_checkGitInstalled) 19 | .then(function() { 20 | return new Promise(function(resolve) { 21 | if (options.refreshDisabled) { 22 | logger.log('Refresh of cached Git repositories is disabled'); 23 | } 24 | else { 25 | setInterval(_getLatestForRepos, options.refreshTimeout * 60 * 1000); 26 | } 27 | resolve(); 28 | }); 29 | }) 30 | .then(_startGitDaemon) 31 | .catch(function(err) { 32 | logger.error('Failed to initialize public repository cache'); 33 | process.nextTick(function() { 34 | throw err; 35 | }); 36 | }); 37 | } 38 | 39 | function _cacheRepo(repoName, repoUrl) { 40 | return new Promise(function(resolve, reject) { 41 | var repoAccessAddress = base.getRepoAccessAddress(); 42 | var repo = '{0}://{1}/{2}'.format(options.protocol, repoAccessAddress, repoName); 43 | 44 | var repoObject = { 45 | name: repoName, 46 | url: repo 47 | }; 48 | 49 | var repoDirectory = path.join(options.repoCacheRoot, repoName); 50 | 51 | if(fs.existsSync(repoDirectory)) { 52 | resolve(repoObject); 53 | 54 | return promise; 55 | } 56 | 57 | _cloneGitRepo(repoUrl, repoName) 58 | .then(function() { 59 | resolve(repoObject); 60 | }) 61 | .catch(function(err) { 62 | logger.error('Failed to clone (maybe folder exists)' + repoUrl); 63 | logger.error(err); 64 | 65 | reject(); 66 | }); 67 | }); 68 | } 69 | 70 | function _checkGitInstalled() { 71 | return utils.exec('git --version') 72 | .catch(function(error) { 73 | logger.error('Git must be installed'); 74 | return error; 75 | }); 76 | } 77 | 78 | function _createDirectory(dir) { 79 | return new Promise(function(resolve, reject) { 80 | mkdirp(dir, function(err) { 81 | if(err) { 82 | reject(); 83 | return; 84 | } 85 | 86 | resolve(); 87 | }); 88 | }); 89 | } 90 | 91 | function _startGitDaemon() { 92 | return new Promise(function(resolve, reject) { 93 | var customParameters = base.generateCustomParameters(); 94 | 95 | var gitCommand = 'git daemon --reuseaddr --base-path="{0}" --listen={1} --port={2} --export-all{3}'; 96 | if (!options.hostName) { 97 | gitCommand = 'git daemon --reuseaddr --base-path="{0}" --port={2} --export-all{3}'; 98 | } 99 | gitCommand = gitCommand.format(options.repoCacheRoot, options.hostName, options.port, customParameters); 100 | 101 | logger.log('Starting git cache server'); 102 | 103 | _daemon = exec(gitCommand, {cwd: options.repoCacheRoot}, function(error) { 104 | if(error) { 105 | reject(error); 106 | return; 107 | } 108 | 109 | logger.log('Git cache server started'); 110 | 111 | resolve(); 112 | }); 113 | }); 114 | } 115 | 116 | function _cloneGitRepo(repoUrl, repoName) { 117 | var gitCommand = 'git clone {0} {1}'.format(repoUrl, repoName); 118 | 119 | logger.log('Cloning {0} ...'.format(repoName)); 120 | 121 | return utils.exec(gitCommand, options.repoCacheRoot) 122 | .then(function() { 123 | logger.log('Cloned {0} git repository to private'.format(repoName)); 124 | }); 125 | } 126 | 127 | function _getLatestForRepos() { 128 | logger.log('Refreshing cached public git repositories'); 129 | 130 | return base.getLatestForRepos(pullLatest); 131 | 132 | function pullLatest(packageDirectory) { 133 | var packageDirPath = path.join(options.repoCacheRoot, packageDirectory); 134 | 135 | return new Promise(function(resolve, reject) { 136 | if(fs.existsSync(packageDirPath)) { 137 | fetchRepository() 138 | .then(hardResetRepository) 139 | .then(function() { 140 | logger.log('Pulled latest for {0}'.format(path.basename(packageDirectory))); 141 | resolve(); 142 | }) 143 | .catch(function(error) { 144 | if(error && error.message) { 145 | logger.error(error.message) 146 | } 147 | reject(error); 148 | }); 149 | } 150 | else { 151 | logger.log('Could not pull latest, because "{0}" directory cannot be found'.format(packageDirPath)); 152 | 153 | resolve(); 154 | } 155 | }); 156 | 157 | function fetchRepository() { 158 | return utils.exec('git fetch --prune --tags', packageDirPath); 159 | } 160 | 161 | function hardResetRepository() { 162 | return utils.exec('git reset --hard origin/master', packageDirPath); 163 | } 164 | } 165 | } 166 | 167 | function _shutDown() { 168 | logger.log('Stopping git cache server'); 169 | 170 | if(_daemon) { 171 | _daemon.kill(); 172 | } 173 | } 174 | 175 | return utils.extend({}, base, { 176 | shutDown: _shutDown, 177 | cacheRepo: _cacheRepo, 178 | getLatestForRepos: _getLatestForRepos 179 | }); 180 | }; 181 | -------------------------------------------------------------------------------- /lib/service/repoCaches/repoCacheBase.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var utils = require('../../infrastructure/utils'); 3 | var Promise = require('bluebird'); 4 | var exec = require('child_process').exec; 5 | 6 | module.exports = function RepoCacheBase(options) { 7 | function _getLatestForRepos(pullLatest) { 8 | return new Promise(function(resolve, reject) { 9 | var childDirectories = utils.getChildDirectories(options.repoCacheRoot); 10 | 11 | pullLatestForAllRepos(childDirectories) 12 | .then(resolve) 13 | .catch(reject); 14 | }); 15 | 16 | function pullLatestForAllRepos(childDirectories) { 17 | return new Promise(function(resolve, reject) { 18 | var pullLatestProcesses = []; 19 | 20 | childDirectories.forEach(function(directory) { 21 | pullLatestProcesses.push(pullLatest(directory)); 22 | }); 23 | 24 | Promise.all(pullLatestProcesses) 25 | .then(resolve) 26 | .catch(reject); 27 | }); 28 | } 29 | } 30 | 31 | function _getRepoAccessAddress() { 32 | if(options.publicAccessURL){ 33 | return options.publicAccessURL; 34 | } 35 | else { 36 | return options.hostName + ':' + options.port; 37 | } 38 | } 39 | 40 | function _removeRepo(repoName) { 41 | var childDirectories = utils.getChildDirectories(options.repoCacheRoot); 42 | 43 | if(childDirectories.indexOf(repoName) === -1) { 44 | return; 45 | } 46 | 47 | utils.removeDirectory(path.join(options.repoCacheRoot, repoName)); 48 | } 49 | 50 | function _generateCustomParameters() { 51 | if(!options.parameters) { 52 | return ''; 53 | } 54 | 55 | var customParameters = ''; 56 | 57 | for(var prop in options.parameters) { 58 | if(options.parameters.hasOwnProperty(prop)) { 59 | customParameters += ' --{0}'.format(prop); 60 | if(options.parameters[prop] !== '') { 61 | customParameters += '={0}'.format(options.parameters[prop]); 62 | } 63 | } 64 | } 65 | 66 | return customParameters; 67 | } 68 | 69 | function _shutDown() { 70 | throw new Error('Must implement shutDown function on repoCache'); 71 | } 72 | 73 | return { 74 | getLatestForRepos: _getLatestForRepos, 75 | getRepoAccessAddress: _getRepoAccessAddress, 76 | removeRepo: _removeRepo, 77 | 78 | generateCustomParameters: _generateCustomParameters, 79 | 80 | shutDown: _shutDown 81 | }; 82 | }; -------------------------------------------------------------------------------- /lib/service/repoCaches/repoCacheHandler.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | 3 | var GitRepoCache = require('./gitRepoCache'); 4 | var SvnRepoCache = require('./svnRepoCache'); 5 | 6 | module.exports = function RepoCacheHandler() { 7 | var self = { 8 | start: _start, 9 | 10 | getRepoCache: _getRepoCache, 11 | removePackages: _removePackages, 12 | getRepoAllCaches: _getRepoAllCaches, 13 | 14 | getLatestForRepos: _getLatestForRepos, 15 | 16 | shutDown: _shutDown 17 | }; 18 | 19 | var _gitRepoCache; 20 | var _svnRepoCache; 21 | 22 | var _repoCaches = []; 23 | 24 | function _start(options) { 25 | self.enabled = true; 26 | 27 | if(options.git) { 28 | _gitRepoCache = new GitRepoCache(options.git); 29 | _repoCaches.push(_gitRepoCache); 30 | } 31 | 32 | if(options.svn) { 33 | _svnRepoCache = new SvnRepoCache(options.svn); 34 | _repoCaches.push(_svnRepoCache); 35 | } 36 | } 37 | 38 | function _getRepoCache(repoUrl) { 39 | if(repoUrl.indexOf('svn+') !== -1) { 40 | return _svnRepoCache; 41 | } 42 | 43 | return _gitRepoCache; 44 | } 45 | 46 | function _getRepoAllCaches() { 47 | return _repoCaches; 48 | } 49 | 50 | function _shutDown() { 51 | _repoCaches.forEach(function(repoCache) { 52 | repoCache.shutDown(); 53 | }); 54 | } 55 | 56 | function _removePackages(packageNames) { 57 | _repoCaches.forEach(function(repoCache) { 58 | packageNames.forEach(function(packageName) { 59 | repoCache.removeRepo(packageName); 60 | }); 61 | }); 62 | } 63 | 64 | function _getLatestForRepos() { 65 | return Promise.all(_repoCaches.map(function(repoCache) { 66 | return repoCache.getLatestForRepos(); 67 | })); 68 | } 69 | 70 | return self; 71 | }(); -------------------------------------------------------------------------------- /lib/service/repoCaches/svnRepoCache.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var mkdirp = require('mkdirp'); 3 | var utils = require('../../infrastructure/utils'); 4 | var Promise = require('bluebird'); 5 | var logger = require('../../infrastructure/logger'); 6 | var exec = require('child_process').exec; 7 | 8 | var RepoCacheBase = require('./repoCacheBase'); 9 | 10 | module.exports = function SvnRepoCache(options) { 11 | var base = new RepoCacheBase(options); 12 | var _daemon; 13 | 14 | _init(); 15 | function _init() { 16 | return _createDirectory(options.repoCacheRoot) 17 | .then(_checkSvnInstalled) 18 | .then(function() { 19 | if (options.refreshDisabled) { 20 | logger.log('Refresh of cached SVN repositories is disabled'); 21 | } 22 | else { 23 | setInterval(_getLatestForRepos, options.refreshTimeout * 60 * 1000); 24 | } 25 | }) 26 | .then(_startSvnDaemon) 27 | .catch(function(err) { 28 | logger.error('Failed to initialize public repository cache'); 29 | process.nextTick(function() { 30 | throw err; 31 | }); 32 | }); 33 | } 34 | 35 | function _cacheRepo(repoName, repoUrl) { 36 | return new Promise(function(resolve, reject) { 37 | var packageDirectory = path.join(options.repoCacheRoot, repoName); 38 | 39 | _createDirectory(packageDirectory) 40 | .then(function() { 41 | return _cloneSvnRepo(repoUrl, packageDirectory, repoName); 42 | }) 43 | .then(function() { 44 | var repoAccessAddress = base.getRepoAccessAddress(); 45 | var repo = '{0}://{1}/{2}'.format(options.protocol, repoAccessAddress, repoName); 46 | 47 | resolve({ 48 | name: repoName, 49 | url: repo 50 | }); 51 | }) 52 | .catch(function(err) { 53 | logger.error('Failed to clone (maybe folder exists)' + repoUrl); 54 | logger.error(err); 55 | 56 | reject(); 57 | }); 58 | }); 59 | } 60 | 61 | function _checkSvnInstalled() { 62 | return utils.exec('svnserve --version'); 63 | } 64 | 65 | function _createDirectory(dir) { 66 | return new Promise(function(resolve, reject) { 67 | mkdirp(dir, function(err) { 68 | if(err) { 69 | reject(); 70 | return; 71 | } 72 | 73 | resolve(); 74 | }); 75 | }); 76 | } 77 | 78 | function _startSvnDaemon() { 79 | return new Promise(function(resolve, reject) { 80 | var customParameters = base.generateCustomParameters(); 81 | 82 | var svnCommand = 'svnserve -d --foreground -r "{0}" --listen-host {1} --listen-port {2}{3}'; 83 | if (!options.hostName) { 84 | svnCommand = 'svnserve -d --foreground -r "{0}" --listen-port {2}{3}'; 85 | } 86 | 87 | svnCommand = svnCommand.format(options.repoCacheRoot, options.hostName, options.port, customParameters); 88 | 89 | logger.log('Starting svn cache server'); 90 | 91 | _daemon = exec(svnCommand, {cwd: options.repoCacheRoot}, function(error, stdout, stderr) { 92 | if(error) { 93 | reject(stderr); 94 | return; 95 | } 96 | 97 | logger.log('Svn cache server started'); 98 | 99 | resolve(); 100 | }); 101 | }); 102 | } 103 | 104 | function _cloneSvnRepo(repoUrl, packageDirectory, repoName) { 105 | logger.log('Cloning {0} ...'.format(repoName)); 106 | 107 | // remove string 'svn+' if isn't 'ssh' url. 108 | if (repoUrl.indexOf('ssh://') == -1) { 109 | repoUrl = repoUrl.replace('svn+', ''); 110 | } 111 | 112 | var svnCommand = 'svn co {0} ./'.format(repoUrl); 113 | 114 | return utils.exec(svnCommand, packageDirectory) 115 | .then(function() { 116 | logger.log('Cloned {0} svn repository to private'.format(repoName)); 117 | }); 118 | } 119 | 120 | function _getLatestForRepos() { 121 | logger.log('Refreshing cached public svn repositories'); 122 | 123 | return base.getLatestForRepos(pullLatest); 124 | 125 | function pullLatest(packageDirectory) { 126 | return utils.exec('svn update', packageDirectory) 127 | .then(function() { 128 | logger.log('Updated latest for {0}'.format(path.basename(packageDirectory))); 129 | }); 130 | } 131 | } 132 | 133 | function _shutDown() { 134 | logger.log('Stopping svn cache server'); 135 | 136 | if(_daemon) { 137 | _daemon.kill(); 138 | } 139 | } 140 | 141 | return utils.extend({}, base, { 142 | shutDown: _shutDown, 143 | cacheRepo: _cacheRepo, 144 | getLatestForRepos: _getLatestForRepos 145 | }); 146 | }; 147 | -------------------------------------------------------------------------------- /lib/service/svnPackageDetailsProvider.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var Promise = require('bluebird'); 5 | 6 | var utils = require('../infrastructure/utils'); 7 | 8 | module.exports = function SvnPackageDetailsProvider() { 9 | var tempFolder = path.join(utils.dirname, 'temp/packageDetails'); 10 | 11 | function _getPackageDetails(packageUrl) { 12 | return new Promise(function(resolve, reject) { 13 | var tempName = utils.getRandomString(); 14 | var svnCloneFolder = path.join(tempFolder, tempName); 15 | 16 | // remove string 'svn+' if isn't 'ssh' url. 17 | if (packageUrl.indexOf('ssh://') == -1) { 18 | packageUrl = packageUrl.replace('svn+', ''); 19 | } 20 | 21 | // set to trunk to get the info. 22 | if (packageUrl.slice(-1) === '/') { 23 | packageUrl = packageUrl + 'trunk'; 24 | } else { 25 | packageUrl = packageUrl + '/trunk'; 26 | } 27 | 28 | utils.exec('svn co {0} {1} --depth files'.format(packageUrl, svnCloneFolder)) 29 | .then(function() { 30 | var bowerJsonLocation = path.join(svnCloneFolder, 'bower.json'); 31 | 32 | var fileContent = fs.readFileSync(bowerJsonLocation); 33 | var bowerJson = JSON.parse(fileContent); 34 | 35 | utils.removeDirectory(svnCloneFolder); 36 | 37 | resolve(bowerJson); 38 | }) 39 | .catch(reject); 40 | }); 41 | } 42 | 43 | return { 44 | getPackageDetails: _getPackageDetails 45 | }; 46 | }(); 47 | -------------------------------------------------------------------------------- /lib/spec/application.spec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var chai = require("chai"); 3 | var sinon = require("sinon"); 4 | var sinonChai = require("sinon-chai"); 5 | var expect = chai.expect; 6 | chai.use(sinonChai); 7 | 8 | var mockery = require('mockery'); 9 | 10 | 11 | describe('Application', function() { 12 | var fs; 13 | var mockServerApp; 14 | var staticHandlerMock, staticHandlerMiddleware; 15 | var application; 16 | var testModule1, testModule2; 17 | 18 | beforeEach(function() { 19 | testModule1 = { 20 | bind: sinon.stub() 21 | }; 22 | 23 | testModule2 = { 24 | bind: sinon.stub() 25 | }; 26 | 27 | fs = { 28 | readdirSync: sinon.stub().returns([ 29 | 'mock1', 30 | 'mock2' 31 | ]) 32 | }; 33 | 34 | mockery.registerMock('fs', fs); 35 | mockery.registerMock(path.join('testModules', 'mock1'), testModule1); 36 | mockery.registerMock(path.join('testModules', 'mock2'), testModule2); 37 | 38 | mockery.enable({ 39 | useCleanCache: true, 40 | warnOnUnregistered: false 41 | }); 42 | 43 | application = require('../application'); 44 | mockServerApp = { 45 | use: sinon.stub(), 46 | listen: sinon.stub() 47 | }; 48 | 49 | staticHandlerMiddleware = function() {}; 50 | staticHandlerMock = sinon.stub().returns(staticHandlerMiddleware); 51 | 52 | application.setup(mockServerApp, staticHandlerMock); 53 | }); 54 | 55 | afterEach(function() { 56 | mockery.deregisterAll(); 57 | mockery.disable(); 58 | }); 59 | 60 | it('should have these properties', function() { 61 | expect(application.setup).to.be.a('function'); 62 | expect(application.shutDown).to.be.a('function'); 63 | expect(application.loadControllers).to.be.a('function'); 64 | expect(application.listen).to.be.a('function'); 65 | expect(application.addMiddleware).to.be.a('function'); 66 | expect(application.serveStatic).to.be.a('function'); 67 | expect(application.startPrivatePackageStore).to.be.a('function'); 68 | expect(application.startPublicPackageStore).to.be.a('function'); 69 | expect(application.startPublicRepositoryCache).to.be.a('function'); 70 | }); 71 | 72 | describe('loadControllers(controllersRoot)', function() { 73 | beforeEach(function() { 74 | application.loadControllers('testModules'); 75 | }); 76 | 77 | it('should bind all controllers', function() { 78 | expect(testModule1.bind).to.have.been.calledWith(mockServerApp); 79 | expect(testModule2.bind).to.have.been.calledWith(mockServerApp); 80 | }); 81 | }); 82 | 83 | describe('addMiddleware()', function() { 84 | it('should call serverApp use', function() { 85 | var middleware = function() {}; 86 | 87 | application.addMiddleware(middleware); 88 | 89 | expect(mockServerApp.use).to.have.been.calledWith(middleware); 90 | }); 91 | }); 92 | 93 | describe('listen()', function() { 94 | it('should call serverApp listen', function() { 95 | var port = 1234; 96 | 97 | application.listen(port); 98 | 99 | expect(mockServerApp.listen).to.have.been.calledWith(port); 100 | }); 101 | }); 102 | 103 | describe('serveStatic()', function() { 104 | it('should call serverApp static with the given path', function() { 105 | var path = 'fakePath'; 106 | 107 | application.serveStatic(path); 108 | 109 | expect(staticHandlerMock).to.have.been.calledWith(path); 110 | }); 111 | 112 | it('should register the middleware', function() { 113 | application.serveStatic('fakePath'); 114 | 115 | expect(mockServerApp.use).to.have.been.calledWith(staticHandlerMiddleware); 116 | }); 117 | }); 118 | 119 | describe('startPrivatePackageStore()', function() { 120 | //TODO 121 | }); 122 | 123 | describe('startPublicPackageStore()', function() { 124 | //TODO 125 | }); 126 | 127 | describe('startPublicRepositoryCache', function() { 128 | //TODO 129 | }); 130 | }); -------------------------------------------------------------------------------- /lib/spec/main.spec.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var chai = require("chai"); 3 | var sinon = require("sinon"); 4 | var sinonChai = require("sinon-chai"); 5 | var expect = chai.expect; 6 | chai.use(sinonChai); 7 | 8 | var mockery = require('mockery'); 9 | 10 | describe('Main', function() { 11 | var applicationMock; 12 | var expressCons, expressMock; 13 | var configurationManagerMock, fakeConfig; 14 | var utilsMock; 15 | var bodyparserMock; 16 | var main; 17 | 18 | beforeEach(function() { 19 | applicationMock = { 20 | setup: sinon.stub(), 21 | listen: sinon.stub(), 22 | addMiddleware: sinon.stub(), 23 | serveStatic: sinon.stub(), 24 | loadControllers: sinon.stub(), 25 | 26 | shutDown: sinon.stub(), 27 | 28 | startPrivatePackageStore: sinon.stub(), 29 | startPublicPackageStore: sinon.stub(), 30 | startPublicRepositoryCache: sinon.stub() 31 | }; 32 | 33 | expressMock = { 34 | listen: sinon.stub() 35 | }; 36 | 37 | expressCons = sinon.stub().returns(expressMock); 38 | expressCons.static = sinon.stub(); 39 | 40 | fakeConfig = { 41 | port: 11123, 42 | server: {}, 43 | public: {}, 44 | repositoryCache: { 45 | enabled: true 46 | } 47 | }; 48 | 49 | configurationManagerMock = { 50 | loadConfiguration: function() {} 51 | }; 52 | 53 | sinon.stub(configurationManagerMock, 'loadConfiguration', function() { 54 | configurationManagerMock.config = fakeConfig; 55 | }); 56 | 57 | var jsonReturn = {abc:1}; 58 | var urlencodedReturn = {abc:2}; 59 | 60 | bodyparserMock = { 61 | urlencodedReturn: urlencodedReturn, 62 | urlencoded: sinon.stub().returns(urlencodedReturn), 63 | jsonReturn: jsonReturn, 64 | json: sinon.stub().returns(jsonReturn) 65 | }; 66 | 67 | utilsMock = { 68 | dirname: 'thisIsADir', 69 | process: { 70 | on: sinon.stub(), 71 | exit: sinon.stub() 72 | } 73 | }; 74 | 75 | mockery.registerMock('./infrastructure/utils', utilsMock); 76 | mockery.registerMock('./application', applicationMock); 77 | mockery.registerMock('./infrastructure/configurationManager', configurationManagerMock); 78 | mockery.registerMock('express', expressCons); 79 | mockery.registerMock('body-parser', bodyparserMock); 80 | 81 | mockery.warnOnUnregistered(false); 82 | 83 | mockery.enable({ 84 | useCleanCache: true, 85 | warnOnUnregistered: false 86 | }); 87 | 88 | createMain(); 89 | }); 90 | 91 | afterEach(function() { 92 | mockery.deregisterAll(); 93 | mockery.disable(); 94 | }); 95 | 96 | function createMain() { 97 | main = require('../main')(); 98 | } 99 | 100 | it('should have these properties', function() { 101 | expect(main.start).to.be.a('function'); 102 | }); 103 | 104 | describe('start()', function() { 105 | it('should create express app', function() { 106 | main.start(); 107 | 108 | expect(expressCons).to.have.been.calledWith(); 109 | }); 110 | 111 | it('should setup the Application and pass the serverApp', function() { 112 | main.start(); 113 | 114 | expect(applicationMock.setup).to.have.been.calledWith(expressMock, expressCons.static); 115 | }); 116 | 117 | it('should loadControllers on Application', function() { 118 | main.start(); 119 | 120 | expect(applicationMock.loadControllers).to.have.been.calledWith(path.join('thisIsADir', 'api', 'controllers')); 121 | }); 122 | 123 | it('should load configuration', function() { 124 | main.start(); 125 | 126 | expect(configurationManagerMock.loadConfiguration).to.have.been.calledWith(); 127 | }); 128 | 129 | it('should call load configuration with default config path', function() { 130 | main.start(); 131 | 132 | expect(configurationManagerMock.loadConfiguration).to.have.been.calledWith('bower.conf.json'); 133 | }); 134 | 135 | it('should call load configuration with parametered config path', function() { 136 | var fakeConfigPath = 'fakeConfig.conf'; 137 | mockery.registerMock('optimist', { argv: { config: fakeConfigPath } }); 138 | 139 | configurationManagerMock.loadConfiguration.reset(); 140 | mockery.resetCache(); 141 | 142 | createMain(); 143 | 144 | main.start(); 145 | 146 | expect(configurationManagerMock.loadConfiguration).to.have.been.calledWith(fakeConfigPath); 147 | 148 | mockery.deregisterMock('optimist'); 149 | }); 150 | 151 | it('should start the private package store', function() { 152 | main.start(); 153 | 154 | expect(applicationMock.startPrivatePackageStore).to.have.been.calledWith(); 155 | }); 156 | 157 | it('should startPublicPackageStore if the configuration is not disabled', function() { 158 | main.start(); 159 | 160 | expect(applicationMock.startPublicPackageStore).to.have.been.calledWith(); 161 | }); 162 | 163 | it('should NOT startPublicPackageStore if the configuration is disabled', function() { 164 | fakeConfig = { 165 | server: {}, 166 | public:{ 167 | disabled: true 168 | } 169 | }; 170 | 171 | mockery.resetCache(); 172 | 173 | createMain(); 174 | 175 | main.start(); 176 | 177 | expect(applicationMock.startPublicPackageStore).not.to.have.been.calledWith(); 178 | }); 179 | 180 | it('should startPublicRepositoryCache if the configuration is enabled', function() { 181 | main.start(); 182 | 183 | expect(applicationMock.startPublicRepositoryCache).to.have.been.calledWith(); 184 | }); 185 | 186 | it('should NOT startPublicRepositoryCache if the configuration is enabled but the public is disabled', function() { 187 | fakeConfig = { 188 | server: {}, 189 | public: { 190 | disabled: true 191 | }, 192 | repositoryCache: {} 193 | }; 194 | 195 | mockery.resetCache(); 196 | 197 | createMain(); 198 | 199 | main.start(); 200 | 201 | expect(applicationMock.startPublicRepositoryCache).not.to.have.been.calledWith(); 202 | }); 203 | 204 | it('should startPublicRepositoryCache even if the configuration is disabled', function() { 205 | fakeConfig = { 206 | server: {}, 207 | public: { 208 | disabled: false 209 | }, 210 | repositoryCache: { 211 | enabled: true 212 | } 213 | }; 214 | 215 | mockery.resetCache(); 216 | 217 | createMain(); 218 | 219 | main.start(); 220 | 221 | expect(applicationMock.startPublicRepositoryCache).to.have.been.calledWith(); 222 | }); 223 | 224 | it('should use bodyparser', function() { 225 | main.start(); 226 | 227 | expect(bodyparserMock.urlencoded).to.have.been.calledWith({ extended: true }); 228 | expect(bodyparserMock.json).to.have.been.calledWith(); 229 | 230 | expect(applicationMock.addMiddleware).to.have.been.calledWith(bodyparserMock.jsonReturn); 231 | expect(applicationMock.addMiddleware).to.have.been.calledWith(bodyparserMock.urlencodedReturn); 232 | }); 233 | 234 | it('should serve the site', function() { 235 | main.start(); 236 | 237 | expect(applicationMock.serveStatic).to.have.been.calledWith('site'); 238 | }); 239 | 240 | it('should start listening on config port', function() { 241 | main.start(); 242 | 243 | expect(applicationMock.listen).to.have.been.calledWith(configurationManagerMock.config.server.port); 244 | }); 245 | }); 246 | }); -------------------------------------------------------------------------------- /log4js.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": [ 3 | { 4 | "type": "dateFile", 5 | "filename": "private-bower.log", 6 | "pattern": "-yyyy-MM-dd", 7 | "alwaysIncludePattern": false 8 | }, 9 | { 10 | "type": "console" 11 | } 12 | ], 13 | "replaceConsole": true 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "private-bower", 3 | "version": "1.1.9", 4 | "author": "Hacklone ", 5 | "description": "A simple private bower registry", 6 | "main": "./bin/private-bower", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/Hacklone/private-bower.git" 10 | }, 11 | "keywords": [ 12 | "bower", 13 | "private", 14 | "registry", 15 | "repository", 16 | "repo", 17 | "cache", 18 | "git", 19 | "svn", 20 | "package" 21 | ], 22 | "scripts": { 23 | "start": "node ./bin/private-bower" 24 | }, 25 | "dependencies": { 26 | "bluebird": "^2.9.14", 27 | "body-parser": "^1.12.0", 28 | "colors": "^0.6.2", 29 | "express": "^4.12.2", 30 | "log4js": "^0.6.14", 31 | "mkdirp": "0.5.x", 32 | "moment": "^2.7.0", 33 | "node-rest-client": "^1.0.0", 34 | "optimist": "^0.5.2", 35 | "path-is-absolute": "^1.0.0", 36 | "URIjs": "^1.5.0" 37 | }, 38 | "devDependencies": { 39 | "chai": "^2.2.0", 40 | "gulp": "3.8.11", 41 | "gulp-mocha": "^2.0.0", 42 | "mockery": "1.4.0", 43 | "run-sequence": "^1.0.2", 44 | "sinon": "^1.14.1", 45 | "sinon-chai": "^2.7.0", 46 | "superagent": "^1.1.0" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/Hacklone/private-bower/issues" 50 | }, 51 | "homepage": "http://hacklone.github.io/private-bower", 52 | "licenses": [ 53 | { 54 | "type": "MIT", 55 | "url": "https://github.com/Hacklone/private-bower/raw/master/LICENSE" 56 | } 57 | ], 58 | "analyze": false, 59 | "preferGlobal": true, 60 | "bin": { 61 | "private-bower": "./bin/private-bower" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /site/controllers/authenticationController.js: -------------------------------------------------------------------------------- 1 | angular.module('PrivateBower') 2 | .controller('authenticationController', function($q, $http, userContextService, notAuthenticatedInterceptor) { 3 | var self = angular.extend(this, { 4 | error: null, 5 | originalCallRejection: null, 6 | authenticationDeferred: null, 7 | authenticationDialogOpened: false, 8 | 9 | authenticate: _authenticate, 10 | cancelAuthentication: _cancelAuthentication 11 | }); 12 | 13 | _init(); 14 | function _init() { 15 | if(!notAuthenticatedInterceptor.authenticationResolver) { 16 | notAuthenticatedInterceptor.authenticationResolver = _onAuthenticationError; 17 | } 18 | } 19 | 20 | function _onAuthenticationError(originalCallRejection) { 21 | if(self.authenticationDialogOpened && self.authenticationDeferred) { 22 | return $q.reject(originalCallRejection); 23 | } 24 | 25 | self.authenticationDeferred = $q.defer(); 26 | self.originalCallRejection = originalCallRejection; 27 | 28 | self.authenticationDialogOpened = true; 29 | 30 | return self.authenticationDeferred.promise; 31 | } 32 | 33 | function _authenticate(authenticationToken) { 34 | userContextService.setAuthenticationToken(authenticationToken); 35 | 36 | makeOriginalCall(); 37 | 38 | function makeOriginalCall() { 39 | var originalCall = self.originalCallRejection.config; 40 | $http[originalCall.method.toLowerCase()](originalCall.url, originalCall.data) 41 | .success(function(data) { 42 | self.error = null; 43 | self.authenticationDialogOpened = false; 44 | self.authenticationDeferred.resolve(data); 45 | self.authenticationDeferred = null; 46 | self.originalCallRejection = null; 47 | }) 48 | .error(function() { 49 | self.error = 'Invalid authentication token!'; 50 | }); 51 | } 52 | } 53 | 54 | function _cancelAuthentication() { 55 | self.error = null; 56 | self.authenticationDialogOpened = false; 57 | 58 | self.authenticationDeferred.reject({ 59 | data: 'Authentication failed!' 60 | }); 61 | 62 | self.authenticationDeferred = null; 63 | self.originalCallRejection = null; 64 | } 65 | }); -------------------------------------------------------------------------------- /site/controllers/mainController.js: -------------------------------------------------------------------------------- 1 | angular.module('PrivateBower') 2 | .controller('mainController', function($http) { 3 | var self = angular.extend(this, { 4 | packages: null, 5 | error: false, 6 | 7 | addPackageError: null, 8 | addPackageDialogOpened: false, 9 | addPackage: _addPackage, 10 | addPackageButtonClick: _addPackageButtonClick, 11 | cancelAddPackageClick: _cancelAddPackageClick, 12 | 13 | packageToRemove: null, 14 | removePackageError: null, 15 | removePackageDialogOpened: false, 16 | removePackage: _removePackage, 17 | removePackageButtonClick: _removePackageButtonClick, 18 | cancelRemovePackageClick: _cancelRemovePackageClick, 19 | 20 | togglePackageDetailsOpened: _togglePackageDetailsOpened 21 | }); 22 | 23 | _init(); 24 | function _init() { 25 | _getPackages(); 26 | } 27 | 28 | function _getPackages() { 29 | $http.get('packages') 30 | .success(function(packages) { 31 | self.packages = packages.map(function(pack) { 32 | pack.siteUrl = pack.url.replace('git://', 'https://'); 33 | 34 | return pack; 35 | }); 36 | }) 37 | .error(function(error) { 38 | self.error = true; 39 | }); 40 | } 41 | 42 | function _addPackageButtonClick() { 43 | self.addPackageDialogOpened = true; 44 | } 45 | 46 | function _cancelAddPackageClick() { 47 | self.addPackageDialogOpened = false; 48 | } 49 | 50 | function _addPackage(packageName, packageUrl) { 51 | $http.post('packages/' + packageName, { 52 | url: packageUrl 53 | }) 54 | .success(function() { 55 | self.addPackageDialogOpened = false; 56 | 57 | _getPackages(); 58 | }) 59 | .error(function(error) { 60 | self.addPackageError = error; 61 | }); 62 | } 63 | 64 | function _removePackageButtonClick(packageName) { 65 | self.packageToRemove = packageName; 66 | 67 | self.removePackageDialogOpened = true; 68 | } 69 | 70 | function _cancelRemovePackageClick() { 71 | self.removePackageDialogOpened = false; 72 | } 73 | 74 | function _removePackage(packageName) { 75 | $http.delete('packages/' + packageName) 76 | .success(function() { 77 | self.packageToRemove = null; 78 | self.removePackageDialogOpened = false; 79 | 80 | _getPackages(); 81 | }) 82 | .error(function(error) { 83 | self.removePackageError = error; 84 | }); 85 | } 86 | 87 | function _togglePackageDetailsOpened(bowerPackage) { 88 | bowerPackage.detailsOpened = !bowerPackage.detailsOpened; 89 | 90 | if(bowerPackage.detailsOpened && !bowerPackage.details) { 91 | loadPackageDetails(bowerPackage); 92 | } 93 | 94 | function loadPackageDetails(bowerPackage) { 95 | $http.get('packages/' + bowerPackage.name + '/details') 96 | .success(function(details) { 97 | bowerPackage.details = details; 98 | }) 99 | .error(function() { 100 | bowerPackage.detailsError = true; 101 | }) 102 | } 103 | } 104 | }); -------------------------------------------------------------------------------- /site/directives/dialog.js: -------------------------------------------------------------------------------- 1 | angular.module('PrivateBower') 2 | .directive('dialog', function() { 3 | return { 4 | restrict: 'E', 5 | template: '', 6 | transclude: true, 7 | replace: true, 8 | scope: { 9 | isOpened: '=', 10 | onClose: '&' 11 | } 12 | }; 13 | }); -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Private Bower Registry 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | private-bower 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | Docs 51 | 52 | 53 | Public Packages 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Private Bower service is down 62 | 63 | 64 | 65 | No private packages registered 66 | 67 | read docs about how to register packages. 68 | 69 | 70 | 71 | 72 | Loading Packages... 73 | 74 | 75 | 76 | 77 | 78 | 79 | {{ package.name }} 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | hits: {{ package.hits }} 90 | 91 | 92 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | Loading package details... 102 | 103 | 104 | Failed to load package details! (Your private-bower server needs read access to this Git repository and the git repository must contain a bower.json file!) 105 | 106 | 107 | 108 | 109 | 110 | Url 111 | {{ package.url }} 112 | 113 | 114 | Details 115 | 116 | 117 | {{ package.details | json }} 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | Add package 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | Cancel 139 | 140 | 141 | Add 142 | 143 | 144 | 145 | {{ mainController.addPackageError }} 146 | 147 | 148 | 149 | 150 | 151 | Are you sure you want to delete "{{ mainController.packageToRemove }}"? 152 | 153 | 154 | Cancel 155 | 156 | 157 | Continue 158 | 159 | 160 | 161 | {{ mainController.removePackageError }} 162 | 163 | 164 | 165 | 166 | 167 | 168 | You must authenticate 169 | 170 | 171 | 172 | 173 | 174 | Cancel 175 | 176 | 177 | Continue 178 | 179 | 180 | 181 | {{ authenticationController.error }} 182 | 183 | 184 | 185 | 186 | 187 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /site/interceptors/notAuthenticatedInterceptor.js: -------------------------------------------------------------------------------- 1 | angular.module('PrivateBower') 2 | .factory('notAuthenticatedInterceptor', function NotAuthenticatedInterceptor($q) { 3 | var self = { 4 | authenticationResolver: null, 5 | 6 | responseError: _onResponseError, 7 | setAuthenticationResolver: _setAuthenticationResolver 8 | }; 9 | 10 | function _onResponseError(rejection) { 11 | if(rejection.status === 401 && self.authenticationResolver) { 12 | return self.authenticationResolver(rejection); 13 | } 14 | 15 | return $q.reject(rejection); 16 | } 17 | 18 | function _setAuthenticationResolver(authenticationResolver) { 19 | self.authenticationResolver = authenticationResolver; 20 | } 21 | 22 | return self; 23 | }) 24 | .config(function($httpProvider) { 25 | $httpProvider.interceptors.push('notAuthenticatedInterceptor'); 26 | }); -------------------------------------------------------------------------------- /site/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hacklone/private-bower/c2eab270b2bad1e612e5868e5e7c3e2fce553c35/site/logo.png -------------------------------------------------------------------------------- /site/modules.js: -------------------------------------------------------------------------------- 1 | angular.module('PrivateBower', ['ngAnimate']); -------------------------------------------------------------------------------- /site/private-bower.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | height: 100%; 3 | 4 | font-family: 'Source Sans Pro', sans-serif; 5 | } 6 | 7 | body { 8 | overflow-y: scroll; 9 | } 10 | 11 | #wrap { 12 | min-height: 100%; 13 | height: auto !important; 14 | height: 100%; 15 | margin: 0 auto -50px; 16 | padding-bottom: 60px; 17 | } 18 | 19 | footer { 20 | position: relative; 21 | bottom: 0; 22 | height: 50px; 23 | 24 | background-color: #777; 25 | 26 | text-align: center; 27 | 28 | padding: 5px 0; 29 | box-sizing: border-box; 30 | } 31 | 32 | footer img { 33 | height: 80px; 34 | } 35 | 36 | footer .author { 37 | display: inline-block; 38 | max-width: 200px; 39 | } 40 | 41 | footer h2 { 42 | font-size: 22px; 43 | margin-top: 10px; 44 | } 45 | 46 | footer h4 a { 47 | color: #b3fcfa; 48 | } 49 | 50 | #logo { 51 | position: absolute; 52 | height: 200px; 53 | width: 200px; 54 | } 55 | 56 | .btn-warning { 57 | background-color: #1695A3; 58 | border-color: #16818f; 59 | } 60 | 61 | .btn-warning:hover { 62 | background-color: #16b3c1; 63 | border-color: #1692a0; 64 | } 65 | 66 | .bg-gray { 67 | background-color: #777; 68 | } 69 | 70 | .info-box { 71 | max-width: 750px; 72 | margin: 10px auto; 73 | 74 | padding: 20px; 75 | box-sizing: border-box; 76 | border-radius: 5px; 77 | } 78 | 79 | .jumbotron { 80 | background-color: #F4C53B; 81 | padding-top: 30px; 82 | padding-bottom: 15px; 83 | } 84 | 85 | .jumbotron h1 { 86 | margin-bottom: 20px; 87 | } 88 | 89 | .jumbotron h1, h2 { 90 | color: #ffffff; 91 | 92 | text-shadow: 0 4px 0 rgba(0, 0, 0, .1); 93 | 94 | font-family: 'Source Sans Pro', sans-serif; 95 | font-weight: bold; 96 | 97 | margin-top: 10px; 98 | } 99 | 100 | .glyphicon-search { 101 | position: absolute; 102 | 103 | margin-left: 10px; 104 | margin-top: 10px; 105 | color: #444; 106 | } 107 | 108 | .search { 109 | display: inline-block; 110 | 111 | width: 70%; 112 | max-width: 750px; 113 | 114 | padding-left: 30px; 115 | box-sizing: border-box; 116 | } 117 | 118 | .keep-right { 119 | margin-right: 10px; 120 | } 121 | 122 | .keep-top { 123 | margin-top: 10px; 124 | } 125 | 126 | .package { 127 | max-width: 750px; 128 | margin: 10px auto; 129 | 130 | border-radius: 5px; 131 | padding: 20px; 132 | box-sizing: border-box; 133 | 134 | background-color: #f7f7f9; 135 | border: 1px solid #e1e1e8; 136 | 137 | -webkit-transition: all 2s; 138 | transition: all 2s; 139 | } 140 | 141 | .package h3 { 142 | margin: 0; 143 | } 144 | 145 | .package.ng-enter { 146 | -webkit-animation: fadeInRight 0.25s; 147 | -moz-animation: fadeInRight 0.25s; 148 | -ms-animation: fadeInRight 0.25s; 149 | -o-animation: fadeInRight 0.25s; 150 | animation: fadeInRight 0.25s; 151 | } 152 | 153 | .package.ng-leave { 154 | -webkit-animation: fadeOutLeft 0.25s; 155 | -moz-animation: fadeOutLeft 0.25s; 156 | -ms-animation: fadeOutLeft 0.25s; 157 | -o-animation: fadeOutLeft 0.25s; 158 | animation: fadeOutLeft 0.25s; 159 | } 160 | 161 | .dialog { 162 | display: block; 163 | } 164 | 165 | .dialog .dimmer { 166 | position: fixed; 167 | z-index: 2; 168 | 169 | height: 100%; 170 | width: 100%; 171 | 172 | top: 0; 173 | left: 0; 174 | 175 | background-color: rgba(0, 0, 0, 0.4); 176 | 177 | text-align: center; 178 | } 179 | 180 | .dialog .dimmer .inner { 181 | display: inline-block; 182 | 183 | min-width: 400px; 184 | 185 | margin-top: 10%; 186 | 187 | border: 3px solid #777; 188 | border-radius: 5px; 189 | background-color: #F4C53B; 190 | 191 | padding: 20px; 192 | } 193 | -------------------------------------------------------------------------------- /site/services/userContextService.js: -------------------------------------------------------------------------------- 1 | angular.module('PrivateBower') 2 | .service('userContextService', function UserContextService($http) { 3 | var self = { 4 | authenticationToken: null, 5 | 6 | setAuthenticationToken: _setAuthenticationToken 7 | }; 8 | 9 | function _setAuthenticationToken(authenticationToken) { 10 | self.authenticationToken = authenticationToken; 11 | 12 | $http.defaults.headers.common['Auth-Key'] = self.authenticationToken; 13 | } 14 | 15 | return self; 16 | }); -------------------------------------------------------------------------------- /site/vendor/angular/angular-animate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.15 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(N,f,W){'use strict';f.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(X,C,g){g=g.ngAnimateChildren;f.isString(g)&&0===g.length?C.data("$$ngAnimateChildren",!0):X.$watch(g,function(f){C.data("$$ngAnimateChildren",!!f)})}}).factory("$$animateReflow",["$$rAF","$document",function(f,C){return function(g){return f(function(){g()})}}]).config(["$provide","$animateProvider",function(X,C){function g(f){for(var n=0;n=C&&b>=x&&c()}var m=g(e);a=e.data("$$ngAnimateCSS3Data");if(-1!=m.getAttribute("class").indexOf(b)&&a){var k="",t="";n(b.split(" "),function(a, 26 | b){var e=(0
117 | {{ package.details | json }} 118 |
145 | {{ mainController.addPackageError }} 146 |
161 | {{ mainController.removePackageError }} 162 |
181 | {{ authenticationController.error }} 182 |