Compare commits

...

1217 Commits

Author SHA1 Message Date
Juan Picado @jotadeveloper
2fd38dbf12 chore(release): 2.4.0 2017-09-23 09:39:11 +02:00
Juan Picado @jotadeveloper
6210288085 Merge pull request #326 from miroslavpokorny/Fix_Maximum_call_stack_size_exceeded
fix: bug with Maximum call stack size exceeded on packages web API.
2017-09-23 09:27:08 +02:00
Miroslav Pokorný
11742db503 Changed setImmediate from async library to node default. 2017-09-17 17:35:22 +02:00
Miroslav Pokorný
b4195b15c6 Fixed bug with Maximum call stack size exceeded on packages web API. 2017-09-16 10:46:15 +02:00
Juan Picado @jotadeveloper
519840761c Merge pull request #325 from robi-wan/wiki_directory_structure
docs: Use current directory structure in documentation
2017-09-15 21:35:20 +02:00
robi-wan
4bd174b96f Use current directory structure in documentation
Commit 7fef14c322 refactored the directory structure - reflect these changes in the documentation.
2017-09-14 15:49:57 +02:00
Juan Picado @jotadeveloper
57fbb59889 Merge pull request #323 from ayusharma/issue-322
MessageBox replaced with Alert in login dialog
2017-09-13 11:55:22 +02:00
Ayush Sharma
64839bd61b Fix: #322 - Message box replaced with alert
Review: Minor fixes
2017-09-13 15:08:33 +05:30
Juan Picado @jotadeveloper
e81d50162f Merge pull request #320 from ayusharma/issue-319
Load logo from config
2017-09-13 11:34:46 +02:00
Ayush Sharma
2442571e2f Fix: Issue #319 Load logo from config
Review: catch block
2017-09-13 12:03:18 +05:30
Juan Picado @jotadeveloper
da7aaf3ec4 Merge pull request #317 from Meeeeow/fix_package_metadata_cache
fix: 🐛 Package metadata cache does not work
2017-09-13 07:50:45 +02:00
Meeeeow
59b8bcf817 Merge branch 'master' into fix_package_metadata_cache 2017-09-12 09:50:11 +08:00
Meeeeow
4d6a4471b2 fix: 🐛 Package metadata cache not work 2017-09-10 23:41:09 +08:00
Juan Picado @jotadeveloper
9cde5adfd4 Merge pull request #316 from wpasternak/patch-1
docs: fixed Typo
2017-09-10 14:44:18 +02:00
Willi Pasternak
7b03059819 Fixed Typo 2017-09-10 13:55:14 +02:00
Juan Picado @jotadeveloper
8dea48ed5b docs: Update server section to include AWS info 2017-09-08 08:11:27 +02:00
Juan Picado @jotadeveloper
397a5301fb Merge pull request #315 from ayusharma/issue-311
Fix #311: Debug text in green color
2017-09-08 07:42:58 +02:00
Ayush Sharma
17847ae415 Fix #311: Debug text in green color 2017-09-07 23:10:01 +05:30
Juan Picado @jotadeveloper
ebb033ce1a Merge pull request #313 from Jinshichi/search_fix
fix: add Date and Version in a search result
2017-09-07 08:30:32 +02:00
Yuuki Tada
5d7e0bc1e5 Use singlequote instead of doublequote
modified:   src/lib/storage/local/local-storage.js
2017-09-07 06:14:38 +00:00
Yuuki Tada
f80ff856ba Add DATE and VERSION in search result
modified:   src/lib/storage/local/local-storage.js
	modified:   test/functional/tags/preserve_tags.spec.js
2017-09-07 02:53:26 +00:00
Juan Picado @jotadeveloper
0582e05edc Merge pull request #308 from verdaccio/feature_wiki_upgrade
docs:  Add new sections to documentation
2017-08-29 20:09:48 +02:00
Juan Picado @jotadeveloper
54dbf115ba docs: Merge cli and installation doc 2017-08-29 08:23:06 +02:00
Juan Picado @jotadeveloper
506ae57aa3 docs: Add links to wiki 2017-08-29 08:13:10 +02:00
Juan Picado @jotadeveloper
429d30a390 docs: Updates wiki readme 2017-08-29 07:56:15 +02:00
Juan Picado @jotadeveloper
a3bb5d1363 docs: Updates wiki readme 2017-08-29 07:54:43 +02:00
Juan Picado @jotadeveloper
b29de5c8fd docs: Add installation section 2017-08-29 07:52:43 +02:00
Juan Picado @jotadeveloper
cf7f4a6409 docs: Updates wiki readme 2017-08-28 23:11:08 +02:00
Juan Picado @jotadeveloper
a5b33bbffc docs: Add logger documentation 2017-08-28 22:23:15 +02:00
Juan Picado @jotadeveloper
97f8c120aa docs: Add web ui documentation 2017-08-28 19:57:12 +02:00
Juan Picado @jotadeveloper
ec0746fc8f docs: Upgrade main config file documentation 2017-08-28 19:57:11 +02:00
Juan Picado @jotadeveloper
7c88cb4392 docs: Add main config file documentation 2017-08-28 19:57:11 +02:00
Juan Picado @jotadeveloper
c30555c699 Merge pull request #309 from verdaccio/conf_remove_invalid_section
refactor: remove invalid section and modify comment
2017-08-28 17:10:35 +02:00
Meeeeow
ae413e13be refactor: 📝 remove invalid section and modify comment 2017-08-28 22:42:26 +08:00
Meeeeow
dd623dc3a0 Merge pull request #306 from verdaccio/fix_possible_data_loss_upstream
Fix possible data loss upstream
2017-08-27 19:58:10 +08:00
Juan Picado @jotadeveloper
04710d8fc0 refactor: update lock file 2017-08-26 07:44:18 +02:00
Juan Picado @jotadeveloper
23bcf6fb86 test: Increase coverage for local-data storage 2017-08-26 07:44:18 +02:00
Juan Picado @jotadeveloper
2c3a8f9d42 test: Add unit test for a scenario when the database is corrupted 2017-08-26 07:44:17 +02:00
Juan Picado @jotadeveloper
e4f7f32f91 refactor: Parse database and error handling in separated methods 2017-08-26 07:44:17 +02:00
Meeeeow
552527c085 refactor: 🔨 don't crash main thread when the database file is corrupted
Add a lock for LocalData instance and throw error message while user trying modify package list
2017-08-26 07:44:17 +02:00
Meeeeow
5d73dcaf89 fix: 🐛 check error code to prevent data loss 2017-08-26 07:44:17 +02:00
Meeeeow
5e8b26893e docs: 📝 update docs about not support pm2 cluster mode 2017-08-26 07:43:40 +02:00
Meeeeow
93aae05e63 fix: 🐛 check error code to prevent data loss 2017-08-26 07:43:39 +02:00
Juan Picado @jotadeveloper
a91a0d3bef Merge pull request #298 from verdaccio/feature-docker-upgrade
feat: Update node alpine version to 8.4.0
2017-08-26 07:42:50 +02:00
Juan Picado @jotadeveloper
3f96ce3665 feat: Update node alpine version to 8.4.0 2017-08-18 06:57:41 +02:00
Juan Picado @jotadeveloper
15bd1383be chore(release): 2.3.6 2017-08-17 06:26:02 +02:00
Juan Picado @jotadeveloper
17e81b24b9 Merge branch 'master' of github.com:verdaccio/verdaccio 2017-08-17 06:21:32 +02:00
Juan Picado @jotadeveloper
a9481cc941 fix: link was broken 2017-08-17 06:21:23 +02:00
Juan Picado @jotadeveloper
5c51e485f6 Merge pull request #293 from 8eo/master
Fix auth process to check against username also and not just groups
2017-08-17 06:20:10 +02:00
Juan Picado @jotadeveloper
b00be1baf5 Merge pull request #296 from mrblackus/patch-1
Update SSL documentation
2017-08-17 05:42:17 +02:00
Juan Picado @jotadeveloper
8153e1b109 Merge pull request #295 from jrussellsmyth/fix-accept-header
Correct accept header set for registry requests.
2017-08-17 05:36:40 +02:00
Mathieu Savy
25f9a8dadb Update SSL documentation
Hi,

I had some troubles setting up SSL for my Verdaccio registry because document was not perfectly clear.
I made little improvements on the SSL wiki page by adding a section about Docker (`listen` config entry is ignored because it's already set on Dockerfile), and I also added a link to the Node documentation pointing at more documentation for the `ca`, `cert` and `key` options for the `https` entry on the configuration.
2017-08-16 11:27:43 +02:00
J. Russell Smyth
eb0c87d77a add semicolon after media type in accept statement. Necessary for more pedantic servers such as Artifactory 2017-08-15 23:59:00 +00:00
Adam Szmyd
d0e0bc4ff3 Fix auth process to check against username also and not just groups 2017-08-15 12:21:28 +02:00
Juan Picado @jotadeveloper
0feccf9b4a chore(release): 2.3.5 2017-08-14 08:18:11 +02:00
Juan Picado @jotadeveloper
836bb348fb Merge pull request #290 from verdaccio/fix-289-285
fix: Remove accept header that seems cause issues #285 #289
2017-08-12 13:07:47 +02:00
Juan Picado @jotadeveloper
b6ee1100ef refactor: remove the feature for multi accept strings 2017-08-12 13:01:25 +02:00
Juan Picado @jotadeveloper
fab8391daf fix: Remove accept header that seems cause issues #285 #289 and npm search fails 2017-08-12 12:51:06 +02:00
Juan Picado @jotadeveloper
368fe92a10 Merge pull request #283 from RodrigoBalest/patch-1
doc: amend proxy instructions for Apache
2017-08-07 18:30:17 +02:00
RodrigoBalest
21df66d622 Amend proxy instructions for Apache
Hi.
I had issues regarding to resources when accessing Verdaccio through Apache proxy.
I found out that removing ending slashes at `ProxyPass` and `ProxyPassReverse` settings solved it.
I also set Verdaccio's config `url_prefix` to match the proxy location.
Hope it helps.
2017-08-07 08:44:21 -03:00
Juan Picado @jotadeveloper
d7e6bae469 refactor: move stream tarball helper as a dependency 2017-08-06 22:52:30 +02:00
Juan Picado @jotadeveloper
ba99fd5ba1 refactor: restore previous package check logic 2017-08-06 22:41:14 +02:00
Juan Picado @jotadeveloper
88a29f5928 Merge pull request #262 from verdaccio/fix_unit_test
refactor: unit test, reorganize and group then correctly
2017-08-06 22:08:59 +02:00
Juan Picado @jotadeveloper
32a1b6e6e2 refactor: restore default time out to 20s 2017-08-06 22:04:35 +02:00
Juan Picado @jotadeveloper
8b93b579d3 refactor: naming clean up, relocation, organize by category 2017-08-06 21:54:15 +02:00
Juan Picado @jotadeveloper
b804e96f4a refactor: use utility to parse the yaml config file 2017-08-05 10:35:14 +02:00
Juan Picado @jotadeveloper
d53ce9750b refactor: rename unit test clean up 2017-08-05 10:34:31 +02:00
Juan Picado @jotadeveloper
644c0981db fix: configuration files inconsistencies, add unit test 2017-08-02 20:46:06 +02:00
Juan Picado @jotadeveloper
cda92ac5ab refactor: relocate unit test 2017-08-02 20:45:21 +02:00
Juan Picado @jotadeveloper
a038b282ec refactor: scoped packages unit test, relocate other unit test 2017-07-30 15:56:00 +00:00
Juan Picado @jotadeveloper
1e6c7dd6ea refactor: unit test for readme, preserve tags on publish 2017-07-30 15:56:00 +00:00
Juan Picado @jotadeveloper
6f006fbf40 test: Add functional test to un publish a single version 2017-07-30 17:55:41 +02:00
Juan Picado @jotadeveloper
442305c62f refactor: local storage get rid of error codes dependency 2017-07-30 16:55:22 +02:00
Juan Picado @jotadeveloper
00cf9c25bb refactor: local storage with less external dependencies 2017-07-30 15:12:32 +02:00
Juan Picado @jotadeveloper
5b52447408 refactor: local storage methods cleanup for getTarball 2017-07-30 12:34:23 +02:00
Juan Picado @jotadeveloper
4e2853bb79 doc: add new suggestion to the readme fix #32 2017-07-29 22:27:25 +02:00
Juan Picado @jotadeveloper
cc32186328 refactor: methods clean up on uplink storage 2017-07-29 21:39:22 +02:00
Juan Picado @jotadeveloper
bb080cae17 doc: Add headers options to uplink section 2017-07-29 20:22:32 +02:00
Juan Picado @jotadeveloper
c4555cd64e refactor: storage classes clean up 2017-07-29 19:11:52 +02:00
Juan Picado @jotadeveloper
14bbd93722 chore(release): 2.3.4 2017-07-29 12:16:34 +02:00
Juan Picado @jotadeveloper
901a7bec62 fix: Docker image fails due lock file localhost references 2017-07-29 12:16:28 +02:00
Juan Picado @jotadeveloper
fd0e9190f2 chore(release): 2.3.3 2017-07-29 12:04:14 +02:00
Juan Picado @jotadeveloper
94fb6adf4f fix: refactor #268 in a better way, amended to elegant way 2017-07-29 12:03:51 +02:00
Juan Picado @jotadeveloper
fdc9624d70 Merge pull request #274 from verdaccio/fix_268_refactor
refactor: fix #268 in a better way
2017-07-29 11:17:19 +02:00
Juan Picado @jotadeveloper
420a256832 refactor: fix #268 in a better way 2017-07-29 11:04:46 +02:00
Juan Picado @jotadeveloper
5ba07d7420 Update README.md
doc: add vote section for new logo
2017-07-29 08:42:38 +02:00
Juan Picado @jotadeveloper
124e89867f Merge branch 'master' of github.com:verdaccio/verdaccio 2017-07-29 01:47:19 +02:00
Juan Picado @jotadeveloper
8847ed4e28 chore: update lock file 2017-07-29 01:47:16 +02:00
Juan Picado @jotadeveloper
bd03fc34d2 Merge pull request #271 from amirmohsen/patch-1
Adding X-Forwarded-Proto header to docs
2017-07-29 01:37:15 +02:00
Amir Mohsen Abdolrazaghi
8ae5fa0c0f Adding X-Forwarded-Proto header to docs 2017-07-29 00:23:03 +01:00
Juan Picado @jotadeveloper
c20bf97b5b chore(release): 2.3.2 2017-07-29 01:01:45 +02:00
Juan Picado @jotadeveloper
f589b1be07 build: add new dependency for auto update changelog 2017-07-29 00:19:40 +02:00
Meeeeow
d725a81398 Merge pull request #270 from verdaccio/fix_268
fix: #268 remove the accept header that avoids request with some regi…
2017-07-28 17:38:15 +08:00
Juan Picado @jotadeveloper
e7dcf3cc86 fix: #268 remove the accept header that avoids request with some regiestries 2017-07-28 07:20:13 +02:00
Meeeeow
a4c829d836 Merge pull request #266 from verdaccio/fix_265
fix: Param web.title from config.yaml does not work on docker image #265
2017-07-27 19:14:21 +08:00
Juan Picado @jotadeveloper
ff96d2ec0c fix: undefined check 2017-07-27 08:18:34 +02:00
Juan Picado @jotadeveloper
d0afe78e98 fix: lint warning 2017-07-27 07:42:22 +02:00
Juan Picado @jotadeveloper
b1a396d9c5 fix: Param web.title from config.yaml does not work on docker image #265 2017-07-27 07:35:47 +02:00
Juan Picado @jotadeveloper
b249e14a66 doc: update packages
Add small not for deprecated properties
2017-07-25 22:59:16 +02:00
Juan Picado @jotadeveloper
513f426623 Merge pull request #263 from jachstet-sea/wiki_fixes
Wiki fixes
2017-07-25 20:05:04 +02:00
Jannis Achstetter
7aa25ca0e3 Fix typo 2017-07-25 13:59:45 +01:00
Jannis Achstetter
e13f1f73fa Rename file iss-server.md -> iis-server.md and update the only reference to it 2017-07-25 13:56:25 +01:00
Juan Picado @jotadeveloper
95401ce6de 2.3.1 2017-07-25 07:15:41 +02:00
Juan Picado @jotadeveloper
f9ae0d0ffd chore: prepare release 2017-07-25 07:15:23 +02:00
Juan Picado @jotadeveloper
fb7863439e Merge pull request #261 from verdaccio/fix_scoped_package_detail_and_leaks
Detail page can't handle scoped package
2017-07-24 12:40:32 +02:00
Meeeeow
1c9fbfce50 fix: 🐛 detail page can't handle scoped package
fix: 🐛 readme leak
2017-07-24 17:16:28 +08:00
Juan Picado @jotadeveloper
90854b2c51 Merge pull request #223 from verdaccio/fix_78
Fix #78 Add new setting to allow publish when uplinks are offline
2017-07-24 07:46:03 +02:00
Juan Picado @jotadeveloper
672589b553 refactor: check if the param config is boolean 2017-07-24 07:38:26 +02:00
Juan Picado @jotadeveloper
430425ce2f fix: #78 add new setting to allow publish when uplinks are offline 2017-07-24 07:38:13 +02:00
Juan Picado @jotadeveloper
4a587825f3 doc: add recipe to protect your packages 2017-07-24 07:25:50 +02:00
Juan Picado @jotadeveloper
2b418811ff doc: Add anonymous publish section 2017-07-24 07:25:34 +02:00
Juan Picado @jotadeveloper
aeb9580870 Merge pull request #260 from conorhastings/use-react-synax-highlighter-light
use light version of syntax highlighter
2017-07-23 11:44:17 +02:00
Conor Hastings
d1b30a58e2 use light version of syntax highlighter 2017-07-23 05:39:43 -04:00
Juan Picado @jotadeveloper
4e435e1ac7 doc: update server side section title 2017-07-23 07:32:14 +02:00
Juan Picado @jotadeveloper
9fb0e14897 fix: broken link 2017-07-23 07:19:52 +02:00
Juan Picado @jotadeveloper
d8244e5f49 2.3.0 2017-07-23 01:02:02 +02:00
Juan Picado @jotadeveloper
66f67c026d doc: remove links 2017-07-23 01:01:18 +02:00
Juan Picado @jotadeveloper
fc7d499bf7 docs: prepare release, update readme and add documentation 2017-07-23 00:52:45 +02:00
Juan Picado @jotadeveloper
acb6c8ca25 fix: if author property is not part of the pkg body ui crash 2017-07-22 20:20:29 +02:00
Juan Picado @jotadeveloper
3a187945dc fix: Display message if there are no items on search 2017-07-22 19:32:32 +02:00
Juan Picado @jotadeveloper
19490ffc51 fix: highlight is not a component, must include all dependency 😞 2017-07-22 19:00:13 +02:00
Juan Picado @jotadeveloper
f783ec3df3 chore: ignore npm lock file 2017-07-22 18:25:48 +02:00
Juan Picado @jotadeveloper
de1c6b15fe refactor: yarn lock file is the main one 2017-07-22 18:24:46 +02:00
Juan Picado @jotadeveloper
7cbe4279dd refactor: better script order, update references 2017-07-22 18:21:49 +02:00
Juan Picado @jotadeveloper
a7926a72b5 fix: Better use of imports, save more than 1mb of bundle size. 2017-07-22 18:16:33 +02:00
Juan Picado @jotadeveloper
e84ff197ec fix: extend scss lint to webpack dev and prod env 2017-07-22 17:34:29 +02:00
Juan Picado @jotadeveloper
d72ee76d74 fix: Fix 404 packages view, refactor readme and some components. Split them up for easy testing. 2017-07-22 16:52:31 +02:00
Juan Picado @jotadeveloper
0f31b364e8 fix: eslint missing warnings on ui 2017-07-22 16:48:05 +02:00
Juan Picado @jotadeveloper
b0edff4963 fix: sass lint 2017-07-22 11:33:05 +02:00
Juan Picado @jotadeveloper
b5fccd1ac9 refactor: hide search and title when there is no packages to display 2017-07-22 10:50:53 +02:00
Juan Picado @jotadeveloper
acd4349b75 Merge pull request #259 from Meeeeow/add_publish_introduce
Add publish introduce
2017-07-21 20:33:27 +02:00
Meeeeow
c9c22ed080 fix: 🐛 remove wrong code added in merge cause app crash 2017-07-22 02:25:00 +08:00
Meeeeow
8ff89a7141 feature: 💄 add publish introduce 2017-07-22 02:11:17 +08:00
Juan Picado @jotadeveloper
8ecf4ae0a9 Merge pull request #242 from verdaccio/update_wiki
(doc): Upload wiki documentation
2017-07-19 15:20:38 +02:00
Juan Picado @jotadeveloper
3e78ad3471 (doc): Upload authorization documentation 2017-07-18 19:59:39 +00:00
Juan Picado @jotadeveloper
ee8063401e (doc): Upload plugins documentation 2017-07-18 19:59:39 +00:00
Juan Picado @jotadeveloper
51aa107cb8 release: update changelong 2017-07-18 21:58:21 +02:00
Juan Picado @jotadeveloper
855ace7f95 Merge pull request #220 from verdaccio/refactor-ui
Refactor ui - React, Webpack and CSS Modules
2017-07-18 21:54:23 +02:00
Juan Picado @jotadeveloper
b64dbfe499 chore: update yarn lock file 2017-07-18 21:25:25 +02:00
Juan Picado @jotadeveloper
29294bdabb fix: warnings after rebase 2017-07-18 21:25:01 +02:00
Juan Picado @jotadeveloper
f9dea8a52c refactor: Dockerfile command in one line, save one layer 2017-07-18 21:14:14 +02:00
Juan Picado @jotadeveloper
09656471de fix: update webpack prepublish script and lock file, exclude from npm pack build files 2017-07-18 21:14:14 +02:00
Juan Picado @jotadeveloper
84388ff1da fix: minor UI bug fixes 2017-07-18 21:14:14 +02:00
Juan Picado @jotadeveloper
08c5358a8c refactor: clean non used dependencies and move to dev dependencies for ui 2017-07-18 21:14:14 +02:00
Juan Picado @jotadeveloper
24c1f92644 fix: remove styled-component dependency in favor of css modules 2017-07-18 21:14:14 +02:00
Juan Picado @jotadeveloper
9c175a597b fix: update dependencies, add webpack-merge 2017-07-18 21:14:14 +02:00
Juan Picado @jotadeveloper
945f833ebd fix: fix style lint issue with css module 2017-07-18 21:14:13 +02:00
Juan Picado @jotadeveloper
27470c0e8d fix: avoid include complete lodash library to the bundle, save 400kb. 2017-07-18 21:14:13 +02:00
Juan Picado @jotadeveloper
3738221bf5 fix: eslint warnings 2017-07-18 21:14:13 +02:00
Juan Picado @jotadeveloper
1465ce2b92 fix: Dockerfile to run webpack step 2017-07-18 21:14:13 +02:00
Juan Picado @jotadeveloper
f8a7483b08 refactor: move webpack to root folder and rewrite to es6 node style (legacy compatibility) 2017-07-18 21:14:13 +02:00
Juan Picado @jotadeveloper
6be3c6841c fix: update lock file 2017-07-18 21:14:13 +02:00
Juan Picado @jotadeveloper
ffdd682137 feature: add sass lint 2017-07-18 21:14:13 +02:00
Juan Picado @jotadeveloper
ceb44aa22b fix: typo entry point 2017-07-18 21:14:12 +02:00
Juan Picado @jotadeveloper
a6321a0961 refactor: encourage usage of lodash 2017-07-18 21:14:12 +02:00
Juan Picado @jotadeveloper
2df4f7b628 fix: eslint globally read all files, rename jsx to js. Reduce amount of repeated configuration 2017-07-18 21:14:12 +02:00
Juan Picado @jotadeveloper
2e5a1e7fd9 fix: add template folder instead hosting it in the root 2017-07-18 21:14:12 +02:00
Juan Picado @jotadeveloper
a6d6e58a80 fix: lodash instead typeof 2017-07-18 21:14:12 +02:00
Meeeeow
68a559ebac fix: 🐛 SPA not work with sub-directory reverse proxy 2017-07-18 21:14:12 +02:00
Meeeeow
beafd31cab refactor: 🔨 add home page link to logo in the header 2017-07-18 21:14:12 +02:00
Meeeeow
fd2821fb92 fix: 🐛 use hash router to avoid pathname conflict with package name 2017-07-18 21:14:11 +02:00
Meeeeow
1351f582b0 feature: check token expire before render login button 2017-07-18 21:14:11 +02:00
Meeeeow
3a3f4ed960 fix: incorrect api url in webpack config 2017-07-18 21:14:11 +02:00
Juan Picado @jotadeveloper
2e3c8ed2cb fix: typo on build 2017-07-18 21:14:11 +02:00
Juan Picado @jotadeveloper
b743ee7e3c fix: improve circle build time 2017-07-18 21:14:11 +02:00
Juan Picado @jotadeveloper
5e086d1830 build: force rebuild node-sass after each version switch in circle 2017-07-18 21:14:11 +02:00
Juan Picado @jotadeveloper
2e38c0ebae fix: update test scripts webpack build included 2017-07-18 21:14:11 +02:00
Juan Picado @jotadeveloper
31be70a18c fix: we force travis to update npm to latest 2017-07-18 21:14:11 +02:00
Juan Picado @jotadeveloper
c96dd560cd fix: update travis webpack script 2017-07-18 21:14:10 +02:00
Juan Picado @jotadeveloper
5d924c0cf2 fix: eslint warnings 2017-07-18 21:14:10 +02:00
Juan Picado @jotadeveloper
68fd3d15cc fix: replace ruby-sass-loader by sass-loader 2017-07-18 21:14:10 +02:00
Juan Picado @jotadeveloper
56d3610907 feature: add node-sass dependency 2017-07-18 21:14:10 +02:00
Juan Picado @jotadeveloper
d072428249 fix: update git ignore, not needed files 2017-07-18 21:14:10 +02:00
Juan Picado @jotadeveloper
cfffd04736 refactor: disable readme test 2017-07-18 21:14:10 +02:00
Juan Picado @jotadeveloper
5b1ac962f2 fix: unit test library reference 2017-07-18 21:14:09 +02:00
Juan Picado @jotadeveloper
53349fd096 fix: eslint errors 2017-07-18 21:14:09 +02:00
Juan Picado @jotadeveloper
5ee2faf125 fix: Missing code from pr #222 lost in rebase 2017-07-18 21:14:09 +02:00
Juan Picado @jotadeveloper
5f345a90df fix: dot files, dependencies and update scripts 2017-07-18 21:14:09 +02:00
Meeeeow
6d12e40078 refactor: new webui 2017-07-18 21:14:09 +02:00
Meeeeow
3ba8d0827b refactor: modify webui dependencies & add some code 2017-07-18 21:14:09 +02:00
Meeeeow
bffd8c3d7c refactor: remove unused resource 2017-07-18 21:14:09 +02:00
Meeeeow
d6e04be39f refactor: login & whoami api
- Remove jju from dependencies
- Add JWT to issue token
- Remove cookie middleware
- Add JWT middleware for WebUI
2017-07-18 21:14:08 +02:00
Meeeeow
58b167268e refactor: packages & readme & search api 2017-07-18 21:08:25 +02:00
Meeeeow
7fef14c322 refactor: directory structure 2017-07-18 21:08:10 +02:00
Meeeeow
e3b1a33596 fix: verdaccio is a dependency of verdaccio 2017-07-18 21:02:06 +02:00
Juan Picado @jotadeveloper
3639e32939 Update dependencies #217 2017-07-18 21:02:06 +02:00
Juan Picado @jotadeveloper
9707e0c336 Add new web ui, replace the old one based on jQuery by React components and webpack. 2017-07-18 21:02:06 +02:00
Juan Picado @jotadeveloper
e9f3d0d0c3 Refactor api, relocate routes and clean up the code 2017-07-18 21:02:06 +02:00
Juan Picado @jotadeveloper
c9bf4ed20e Refactor web, relocate files to web folder 2017-07-18 21:02:06 +02:00
Juan Picado @jotadeveloper
999a65e412 #217 Fix yarn install fails on Node 4.x 2017-07-18 21:02:06 +02:00
Juan Picado @jotadeveloper
cf57834019 Update yarn lock file #217 2017-07-18 21:02:06 +02:00
Juan Picado @jotadeveloper
16d7627ba6 Update dependencies #217 2017-07-18 21:02:05 +02:00
Juan Picado @jotadeveloper
3b4b8d94d5 Add ui test to build 2017-07-18 21:02:05 +02:00
Juan Picado @jotadeveloper
18553ba130 Update yarn lock file 2017-07-18 21:02:05 +02:00
Juan Picado @jotadeveloper
e49846bb1b Add new web ui, replace the old one based on jQuery by React components and webpack. 2017-07-18 21:02:05 +02:00
Juan Picado @jotadeveloper
a6d3745cd4 Refactor api, relocate routes and clean up the code 2017-07-18 21:02:05 +02:00
Juan Picado @jotadeveloper
b3a82bc294 Refactor web, relocate files to web folder 2017-07-18 21:02:05 +02:00
Juan Picado @jotadeveloper
4829e5286e Merge pull request #254 from gbtechhub/fix-lb
fix running behind of loadbalancer with TLS termination
2017-07-18 20:56:40 +02:00
Patrik Votocek
7df6948fe6 fix running behind of loadbalancer with TLS termination 2017-07-18 14:24:18 +02:00
Juan Picado @jotadeveloper
44dfb877f5 Merge pull request #253 from verdaccio/juanpicado-patch-2
doc: update readme, add docker badge
2017-07-16 09:26:00 +02:00
Juan Picado @jotadeveloper
5476cf6c21 doc: update readme, add docker badge 2017-07-16 09:21:10 +02:00
Juan Picado @jotadeveloper
789e7590bf Merge pull request #252 from verdaccio/juanpicado-patch-1
Update README.md
2017-07-16 08:51:38 +02:00
Juan Picado @jotadeveloper
4cc2e0b44b Update README.md
Add beta version
Add npx command
2017-07-16 08:39:21 +02:00
Juan Picado @jotadeveloper
b55a0b6c31 Merge commit 'e2180cf507254c37fcf8f3f8536ddb8befac70db'
* commit 'e2180cf507254c37fcf8f3f8536ddb8befac70db':
  2.2.6
  release: update changelog
2017-07-13 07:55:13 +02:00
Juan Picado @jotadeveloper
e2180cf507 2.2.6 2017-07-13 07:03:44 +02:00
Juan Picado @jotadeveloper
249fcff93a release: update changelog 2017-07-13 07:03:35 +02:00
Juan Picado @jotadeveloper
609a7e75ed Merge pull request #251 from verdaccio/node_update
build: update node version due security update announcement
2017-07-13 07:00:21 +02:00
Juan Picado @jotadeveloper
3a518e3959 build: update node version due security update announcement 2017-07-13 06:54:19 +02:00
Juan Picado @jotadeveloper
279e7d1212 Update README.md 2017-07-10 06:46:01 +02:00
Juan Picado @jotadeveloper
a8df59bdf8 build: add circle ci config file 2017-07-09 10:26:06 +02:00
Juan Picado @jotadeveloper
2a817ec25f Merge pull request #245 from verdaccio/locking-file-feature
refactor(storage): extract common library need it for plugins
2017-07-08 16:28:01 +02:00
Juan Picado @jotadeveloper
2187a9e8b8 refactor(storage): extract common library need it for plugins 2017-07-08 16:17:09 +02:00
Juan Picado @jotadeveloper
770ef82fe1 Merge pull request #243 from rbpinheiro/patch-1
fixing yarn syntax
2017-07-05 20:59:55 +02:00
Rafael Pinheiro
436fb47057 fixing yarn syntax
The current command was installing the packages "global" and "verdaccio" locally instead of installing verdaccio globally.
2017-07-05 15:58:07 -03:00
Juan Picado @jotadeveloper
87bdc7013f 2.2.5 2017-07-05 19:33:49 +02:00
Juan Picado @jotadeveloper
7989000ef8 (release): prepare release v2.2.5 (skip 2.2.4 due npm publish issue) 2017-07-05 19:33:42 +02:00
Juan Picado @jotadeveloper
1867bd3184 2.2.4 2017-07-05 19:30:29 +02:00
Juan Picado @jotadeveloper
cc96236dac (release): prepare release v2.2.4 2017-07-05 19:30:29 +02:00
Juan Picado @jotadeveloper
807f170b72 Merge pull request #241 from markpeterfejes/master
Fixed adding the verdaccio user into the group
2017-07-05 19:23:52 +02:00
markpeterfejes
109d80a39e Fixed adding the verdaccio user into the group 2017-07-05 16:30:31 +02:00
Juan Picado @jotadeveloper
4f59b0d2c6 Merge pull request #240 from markpeterfejes/master
Added documentation to the PROTOCOL configuration
2017-07-05 09:58:25 +02:00
markpeterfejes
1d07e65997 Added documentation to the PROTOCOL configuration 2017-07-05 09:47:29 +02:00
Meeeeow
df87eb3224 Merge pull request #236 from verdaccio/fix_105
Increase Documentation
2017-07-05 10:49:12 +08:00
Juan Picado @jotadeveloper
7a5fe2afac 2.2.3 2017-07-04 22:43:12 +02:00
Juan Picado @jotadeveloper
7fd2357245 (release): Preparing release 2.2.3 2017-07-04 22:43:00 +02:00
Juan Picado @jotadeveloper
6cdf8f9007 Merge pull request #239 from markpeterfejes/master
Updated Dockerfile & added proper signal handling
2017-07-04 21:31:02 +02:00
markpeterfejes
e1d447c450 Easy configuration for listen protocol 2017-07-04 18:53:45 +02:00
markpeterfejes
32709843fb Updated Dockerfile - added proper signal handling 2017-07-04 18:10:41 +02:00
Juan Picado @jotadeveloper
02431bf71e (fix): Fix notification table 2017-07-04 08:05:41 +02:00
Juan Picado @jotadeveloper
ef347442be (fix): Fix tabs instead spaces 2017-07-04 08:02:29 +02:00
Juan Picado @jotadeveloper
8e009d8fe8 (doc): Add notifications documentation 2017-07-04 08:00:31 +02:00
Juan Picado @jotadeveloper
432a6c4eaf (doc): Upload cli documentation 2017-07-04 07:20:13 +02:00
Juan Picado @jotadeveloper
da0618c442 Merge pull request #235 from verdaccio/refactor_test
Refactor functional testing
2017-07-03 20:59:39 +02:00
Juan Picado @jotadeveloper
4ea5ec7194 2.2.2 2017-07-02 15:11:07 +02:00
Juan Picado @jotadeveloper
7f16f90186 (release): Update changelog 2017-07-02 15:11:01 +02:00
Juan Picado @jotadeveloper
71a22be8ca (fix): Fix wrong reference to config packages 2017-07-02 15:00:05 +02:00
Juan Picado @jotadeveloper
f504c6c181 (fix): Update broken link 2017-07-02 14:59:45 +02:00
Juan Picado @jotadeveloper
c54f886525 (doc): Upload uplinks documentation, added section 2017-07-02 14:40:00 +02:00
Juan Picado @jotadeveloper
d8177a7724 (doc): Upload packages documentation 2017-07-02 14:39:46 +02:00
Juan Picado @jotadeveloper
17bc128546 (doc): Update ansible doc 2017-07-02 09:38:25 +02:00
Juan Picado @jotadeveloper
09b8c73e47 (doc): Update uplink documentation 2017-07-02 09:35:24 +02:00
Juan Picado @jotadeveloper
3618508640 (doc): Upload uplinks documentation 2017-07-02 00:11:54 +02:00
Juan Picado @jotadeveloper
2a25bce808 (fix): Update doc for issue found on #108 2017-07-02 00:07:42 +02:00
Juan Picado @jotadeveloper
6e91aa88be (test): Refactor and add documentation some unit test, relocate storages 2017-07-02 00:05:58 +02:00
Juan Picado @jotadeveloper
1ae08b6b78 (test): Add functional test for notifications on publish 2017-07-01 11:17:46 +02:00
Juan Picado @jotadeveloper
19f969d442 (fix): fix formatting, easy to read 2017-07-01 09:10:26 +02:00
Juan Picado @jotadeveloper
2f0609d6c7 (fix): coverage, fix reporter 2017-07-01 09:10:00 +02:00
Juan Picado @jotadeveloper
aeed698ed2 (fix): add functional test, unpublish package 2017-07-01 09:09:44 +02:00
Juan Picado @jotadeveloper
6e1a04ecd8 (fix): clean up travis, only codecov enabled 2017-07-01 09:08:38 +02:00
Juan Picado @jotadeveloper
559d4c0ece (fix): clean up test scripts, remove old dependencies 2017-07-01 09:07:16 +02:00
Juan Picado @jotadeveloper
9640730d6c (fix): renamed smart request file 2017-07-01 00:06:21 +02:00
Juan Picado @jotadeveloper
a702e69e16 (fix): increase mocha timeout 2017-06-30 23:17:58 +02:00
Juan Picado @jotadeveloper
492eeef721 (fix): trying to fix hangs promises on travis 2017-06-30 23:11:40 +02:00
Juan Picado @jotadeveloper
b2008958f1 (test): Refactor smart request module 2017-06-30 23:11:12 +02:00
Juan Picado @jotadeveloper
e41dad708e (test): Refactor hash method 2017-06-30 23:10:30 +02:00
Juan Picado @jotadeveloper
582285a37f (cli): encouraging to use lodash 2017-06-30 23:09:59 +02:00
Juan Picado @jotadeveloper
1926522f99 (test): Move mocha options to mocha config file 2017-06-30 23:09:17 +02:00
Juan Picado @jotadeveloper
96c9f459c1 (test): Refactor server class, renamed methods to camelCase 2017-06-28 22:56:02 +02:00
Juan Picado @jotadeveloper
15c6cb09fd (test): Fix mocha debug mode for > Node 7 2017-06-28 22:47:16 +02:00
Juan Picado @jotadeveloper
0651c20990 Merge pull request #233 from verdaccio/fix_109
Fix #109 Remove puppet section from Readme file
2017-06-26 19:46:35 +02:00
Juan Picado @jotadeveloper
eb1cb24723 Fix #109 Remove puppet section from Readme file 2017-06-26 19:44:57 +02:00
Juan Picado @jotadeveloper
912f4e4fc4 Merge pull request #231 from nedelenbos/patch-1
Fixed grammar error in README
2017-06-26 17:50:18 +02:00
Nick
80aebfc26f Fixed grammar error in README
More then just NPM dependencies -> More than just NPM ...
2017-06-26 09:23:35 +02:00
Juan Picado @jotadeveloper
f6ebdb9aec Merge pull request #230 from idangozlan/patch-1
Update plugins.md
2017-06-26 06:58:30 +02:00
Idan Gozlan
011a9c944b Update plugins.md
Added `verdaccio-bitbucket` plugin, a new version for old `sinopia-bitbucket`
2017-06-26 00:43:31 +03:00
Juan Picado @jotadeveloper
efb25cf611 Merge pull request #227 from karfau/fix-docker-port
Fix #213 Allow to configure port for docker
2017-06-24 14:04:17 +02:00
Christian Bewernitz
a1c396ea27 added hint about excluding files from being ignored from docker 2017-06-24 12:03:44 +02:00
Christian Bewernitz
8cfd437b7e moving docker section to new wiki folder 2017-06-24 11:59:25 +02:00
Christian Bewernitz
9a4f81c2cb allow to configure port for docker
- also works for docker-compose
- updated docs accordingly
- included .dockerignore to speed up build
2017-06-24 11:21:28 +02:00
Juan Picado @jotadeveloper
96c6604252 (doc): Update readme, add yarn support 2017-06-24 10:22:06 +02:00
Juan Picado @jotadeveloper
9036126359 (wiki): Relocated server advanced configuration to wiki 2017-06-24 10:11:08 +02:00
Juan Picado @jotadeveloper
73cc898f6b (other): Remove checkboxes from template 2017-06-24 09:47:21 +02:00
Juan Picado @jotadeveloper
a5d0949bb4 (wiki): Fix sections 2017-06-24 09:44:37 +02:00
Juan Picado @jotadeveloper
c8b50ba2d3 (wiki): Update wiki readme 2017-06-24 09:43:09 +02:00
Juan Picado @jotadeveloper
226a0895a9 (wiki): Rename entry folder file 2017-06-24 09:34:02 +02:00
Juan Picado @jotadeveloper
a77371e9b4 Merge pull request #229 from verdaccio/add_wiki
Add wiki section
2017-06-24 09:29:42 +02:00
Juan Picado @jotadeveloper
3ed7610e8d (wiki): Add plugins list 2017-06-24 09:27:55 +02:00
Juan Picado @jotadeveloper
2351b733cd (wiki): Add SSL conf 2017-06-24 09:26:21 +02:00
Juan Picado @jotadeveloper
c76559a4d4 (wiki): Add reverse proxy conf 2017-06-24 09:26:10 +02:00
Juan Picado @jotadeveloper
a5f44c464a (wiki): Add ansible doc 2017-06-24 09:25:51 +02:00
Juan Picado @jotadeveloper
51cb473141 (build): Ignore wiki from dot file 2017-06-24 09:22:03 +02:00
Juan Picado @jotadeveloper
99bc24c0ef (wiki): Add wiki home 2017-06-24 09:21:08 +02:00
Juan Picado @jotadeveloper
5a7e08c4e5 (wiki): Add windows ISS configuration 2017-06-24 09:18:38 +02:00
Juan Picado @jotadeveloper
9d89393695 (wiki): Add windows service documentation 2017-06-24 09:12:21 +02:00
Juan Picado @jotadeveloper
b436e1dc88 Merge pull request #228 from verdaccio/fix_73
Fix 73
2017-06-23 08:33:44 +02:00
Juan Picado @jotadeveloper
172b271c6f Fix #73 Add time property to new published local packages 2017-06-23 08:13:19 +02:00
Juan Picado @jotadeveloper
8fbf77a76d Fix #73 Add time property to deliver uplink last modified package 2017-06-22 22:37:02 +02:00
Juan Picado @jotadeveloper
9fdc251b83 Fix #73 Refactor Storage methods affected 2017-06-22 22:35:09 +02:00
jotadeveloper
2d4f1b2362 Merge pull request #225 from verdaccio/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2017-06-19 21:15:05 +02:00
jotadeveloper
450c22359f Create CODE_OF_CONDUCT.md 2017-06-19 21:14:23 +02:00
Waldemar Reusch
ef360d6927 enable verdaccio behind a https nginx proxy (#222)
* enable verdaccio to run with http protcol behind a https reverse proxy

* add semicolon
2017-06-19 12:12:36 +08:00
jotadeveloper
9db2372c05 Update issue_template.md
Remove annoying checkboxes from issue template
2017-06-18 17:38:30 +02:00
jotadeveloper
2cceccad55 Merge pull request #181 from kba/better-docker-cntd
Better docker cntd
2017-06-18 17:34:16 +02:00
jotadeveloper
78b54060b5 Merge branch 'master' into better-docker-cntd 2017-06-18 17:23:46 +02:00
Juan Picado @jotadeveloper
da4c04a22e 2.2.1 2017-06-17 18:22:51 +02:00
Juan Picado @jotadeveloper
5ec951622d Update changelog, prelease release 2017-06-17 18:22:29 +02:00
jotadeveloper
42fb6c3b14 Merge pull request #216 from Meeeeow/feature_support_x_forwarded_for
Add remote ip to request log
2017-06-17 17:22:21 +02:00
Meeeeow
d5a3d085aa add remote ip to request log 2017-06-17 22:13:29 +08:00
jotadeveloper
fdd98ace07 Merge pull request #214 from Meeeeow/update_doc_reverse_proxy
docs: update docs with behind reverse proxy
2017-06-15 18:50:23 +02:00
Meeeeow
733f13528d docs: update docs with behind reverse proxy 2017-06-16 00:27:21 +08:00
jotadeveloper
f21c3f0b26 Update README
Update docker run command, namespace docker image was wrong
2017-06-15 00:56:30 +02:00
jotadeveloper
6f67870f70 Merge pull request #211 from r3wald/205-documentation-for-proxy-configuration
config section moved up, some keywords added.
2017-06-12 19:44:43 +02:00
Robert Ewald
0e9d2454d8 config section moved up, some keywords added. 2017-06-12 11:59:05 +02:00
jotadeveloper
b33f50a1ae Delete _config.yml 2017-06-11 08:51:54 +02:00
jotadeveloper
f3b4fb68ef Merge pull request #210 from verdaccio/storage-refactor-103
Storage refactor
2017-06-11 08:47:07 +02:00
Juan Picado @jotadeveloper
2ee4f38743 Refactor Storage, move local storage to inner folder.
Refactor methods to camelCase
Relocate utilities
2017-06-10 23:41:24 +02:00
Juan Picado @jotadeveloper
d2a4cf3264 Secret key should be persited in config file if exist in the data store 2017-06-10 23:18:50 +02:00
Juan Picado @jotadeveloper
1358d53dd9 Refactor storage, moves secret key generator to config file 2017-06-10 23:07:08 +02:00
Juan Picado @jotadeveloper
ac456232bb Rename local storage class 2017-06-10 23:03:50 +02:00
Juan Picado @jotadeveloper
181267f256 display additional info in case of test fails 2017-06-10 21:41:32 +02:00
Juan Picado @jotadeveloper
e799500893 Restore config refactoring 2017-06-10 21:40:58 +02:00
Juan Picado @jotadeveloper
75c3cbafd0 (eslint) set restrictive rule with non used variables 2017-06-10 20:29:36 +02:00
Juan Picado @jotadeveloper
c7b44ddcaa Set a more descriptive mocha reporter 2017-06-10 20:29:35 +02:00
Juan Picado @jotadeveloper
fcae1fa91d #103 Refactoring Uplink Storage, Config and Storage class 2017-06-10 20:29:35 +02:00
Juan Picado @jotadeveloper
09ca08baaf Refactored addPackage on Storage 2017-06-10 20:29:35 +02:00
Juan Picado @jotadeveloper
391e98de9f Refactor streams, removed not needed dependency 2017-06-10 20:29:35 +02:00
Juan Picado @jotadeveloper
fa4952408a #103 Remove Search references from local storage 2017-06-10 20:28:42 +02:00
Juan Picado @jotadeveloper
4cb0424d23 #103 Rename variables, organize methods, privates goes to the bottom 2017-06-10 20:26:43 +02:00
Juan Picado @jotadeveloper
db3233075c #103 Config file must not be aware of storage database, storage class should handle it. 2017-06-10 20:16:20 +02:00
Juan Picado @jotadeveloper
1307181005 Refactor streams, removed not needed dependency 2017-06-10 18:48:20 +02:00
Juan Picado @jotadeveloper
e9929c23e1 #103 Remove Search references from local storage 2017-06-10 18:46:02 +02:00
Juan Picado @jotadeveloper
fe73eba54e #103 Rename variables, organize methods, privates goes to the bottom 2017-06-10 18:43:20 +02:00
Juan Picado @jotadeveloper
f08e733cc4 #103 Config file must not be aware of storage database, storage class should handle it. 2017-06-10 18:40:03 +02:00
jotadeveloper
a3ebb7942d Update CONTRIBUTING.md 2017-06-10 12:52:00 +02:00
Juan Picado @jotadeveloper
c0bf077974 (release) update authors 2017-06-08 21:18:45 +02:00
Juan Picado @jotadeveloper
c1b2d09cd6 (release) fix typo, wrong version 2017-06-08 21:06:49 +02:00
Juan Picado @jotadeveloper
ce465f0b03 2.2.0 2017-06-08 21:02:44 +02:00
Juan Picado @jotadeveloper
794689304f (release) update changelog, prepare release 2017-06-08 20:58:07 +02:00
jotadeveloper
0cabd62dea Merge pull request #132 from coldrye-collaboration/gh-131
Adds cache option to uplinks
2017-06-08 20:47:14 +02:00
cklein
7018fc99a2 [GH-131] add cache option to uplinks 2017-06-08 17:59:28 +02:00
jotadeveloper
c2741683b2 Update .travis.yml
Add node 8 to travis configuration
2017-06-03 19:19:48 +02:00
jotadeveloper
e58ef5ed9c Merge pull request #206 from strongloop-forks/dont-clobber-tags
don't blindly clobber local dist-tags
2017-06-03 18:56:02 +02:00
Juan Picado @jotadeveloper
fe2d954555 Update lock files 2017-06-03 13:47:35 +02:00
Ryan Graham
64c3ea445b don't blindly clobber local dist-tags
If packages are being published to verdaccio as well as upstream to
npmjs.org, then when the cache is updated from npmjs.org it uses the
dist-tags from the upstream even if the locally published version is
actually newer. This makes it very difficult to use verdaccio as a
staging registry for testing out potential releases.

This change partially reverts a change in behaviour that was introduced
in #8 which caused a regression for the staging style workflow that was
supported by sinopia.
2017-06-01 13:59:56 -07:00
jotadeveloper
163852615a Update README.md
Add docker examples repository reference
2017-05-31 23:34:01 +02:00
jotadeveloper
717b8aa194 Update README.md
Add new badge
2017-05-23 21:40:59 +02:00
jotadeveloper
3e37e54158 Update CONTRIBUTING.md
Gitter over slack channel
2017-05-23 21:10:18 +02:00
jotadeveloper
16f8ae0f30 Merge pull request #197 from BartDubois/url_prefix-as-path
Allow url_prefix to be only the path
2017-05-21 23:00:09 +02:00
Bart Dubois
5336a0a611 Fix ES string delimiters
Use ` in stand of '
2017-05-21 22:55:49 +02:00
Bart Dubois
f8dc1da537 Fix ES6 template string
should be ${...} not @{...}
2017-05-21 22:50:11 +02:00
Bart Dubois
8927d7d144 Aplly sugestions form review
* Reneame the methof and use camelCase
* Use ES6 template string
2017-05-21 22:39:47 +02:00
jotadeveloper
3b65f66584 Update .travis.yml 2017-05-20 21:47:34 +02:00
jotadeveloper
6fe99a6f1b Delete .coveralls.yml 2017-05-20 21:46:21 +02:00
jotadeveloper
9626250140 Merge pull request #198 from mysiar/apache-reverse-proxy
Apache reverse proxy configuration
2017-05-20 21:45:02 +02:00
Piotr Synowiec
a514e254a2 Apache reverse proxy configuration - ip address update 2017-05-20 19:52:19 +02:00
Piotr Synowiec
bf8c018fc5 Apache reverse proxy configuration 2017-05-20 19:44:41 +02:00
Juan Picado @jotadeveloper
1bdb651636 Setup codacy coverage 2017-05-20 12:44:04 +02:00
Juan Picado @jotadeveloper
abc40b8e8e Refactor, cleaned all eslint warnings, eslint set to error to avoid more warnings 2017-05-20 12:33:23 +02:00
Juan Picado @jotadeveloper
a1e6368c29 Refactor, remove eslint warnings and for npm < 2.x, npm minimum higher as 2.15.x 2017-05-20 11:50:20 +02:00
Juan Picado @jotadeveloper
ba6543a322 Refactor Auth class, reduce eslint warnings, moved to es6 2017-05-20 11:13:08 +02:00
Juan Picado @jotadeveloper
2961d21124 Refactor, reduce eslint warnings 2017-05-20 10:47:43 +02:00
Juan Picado @jotadeveloper
45e4e66b4c Update badge 2017-05-20 09:49:42 +02:00
Juan Picado @jotadeveloper
b99d77ec29 Integrating codecov 2017-05-20 09:42:45 +02:00
Juan Picado @jotadeveloper
addd3605f6 Update readme, add coveralls badge 2017-05-20 09:23:32 +02:00
Juan Picado @jotadeveloper
54729bffa2 Set up coveralls 2017-05-20 09:21:24 +02:00
Juan Picado
01dc7dc1d8 Refactor, reduce eslint warnings, add doc 2017-05-20 08:56:06 +02:00
Bart Dubois
ff05d779c4 Allow url_prefix to be only the path
* Define utils function `get_base_url`.
 * If url_prefix start with `/` construct base URL using protocol and host form request.
 * Update SERVER.md with description of new `url_prefix` option.
2017-05-17 00:20:33 +02:00
Juan Picado
93bea50176 Update readme, docker tags support 2017-05-14 16:15:04 +02:00
Juan Picado
7bf1a5a802 Update changelog 2017-05-14 16:05:57 +02:00
Juan Picado
83979b3468 2.1.7 2017-05-14 15:49:38 +02:00
Juan Picado
e04a6784dd Update changelog 2017-05-14 15:49:30 +02:00
Juan Picado
dc8fe022bc fixed publish fail in YARN #183 fix eslint error @W1U02 patch 2017-05-14 15:46:19 +02:00
Juan Picado
502d20e2cf Update contribute guide 2017-05-14 10:17:41 +02:00
Juan Picado
fd66425de6 Update contribute guide 2017-05-14 10:14:32 +02:00
Juan Picado
19310b2078 Update authors 2017-05-13 11:23:41 +02:00
Juan Picado
aea51476da #189 Update https conf full yaml file 2017-05-13 11:23:05 +02:00
Juan Picado
8e729c79d8 Add pr template 2017-05-12 15:45:06 +02:00
Juan Picado
98e334b7fb Increase PR guidelines 2017-05-12 14:46:39 +02:00
Juan Picado
3fd63e5348 2.1.6 2017-05-12 09:40:41 +02:00
Juan Picado
e95064fef5 Update changelog, prepare release 2017-05-12 09:40:17 +02:00
Juan Picado
2215dc3397 Fix #189 Increase info whether https certs are missing 2017-05-12 09:32:08 +02:00
Juan Picado
4b9af22c95 Fix #189 ca safety check is missing 2017-05-12 09:29:22 +02:00
jotadeveloper
54481095ce Update README.md
Reduce the font size server section
2017-05-12 00:57:48 +02:00
Juan Picado
214b3dcde8 Improve contribute guidelines 2017-05-12 00:39:07 +02:00
jotadeveloper
ecb9f52bf3 Update issue_template.md
Add config file as suggestion
2017-05-10 00:02:27 +02:00
Juan Picado
92df8bd178 Fix uppercase lint warning, streams use lowercase function name 2017-05-09 10:11:09 +02:00
Juan Picado
445df5b30e Fix eslint, add jsdoc on storage lib 2017-05-09 09:56:54 +02:00
Juan Picado
57692cd8b1 Fix issue on handle error, missing context 2017-05-09 09:48:02 +02:00
jotadeveloper
f4058bb5c2 Merge pull request #170 from Meeeeow/fix_yarn_upstream
Fix upstream search not work with gzip
2017-05-08 10:40:30 +02:00
Meeeeow
c80dd8958a fix upstream search not work with gzip, close #156 2017-05-08 15:21:54 +08:00
Meeeeow
24879f8198 rename .eslintrc to .eslintrc.yml for compability 2017-05-08 15:19:33 +08:00
Konstantin Baierer
24411f7a68 Dockerfile: Set maintainer, COPY -> ADD 2017-05-07 17:38:53 +02:00
jotadeveloper
93d28f66b2 Merge pull request #184 from marnel/fix-https-suggestion-output
Add additional requirement to output message
2017-05-07 15:22:25 +02:00
Michael Arnel
f4c584cfce add trailing comma for eslint rule 2017-05-07 08:44:53 -04:00
Michael Arnel
a85978f2be Add additional requirement to output message
With out the ca parameter in the https config section the server fails to start with a 500 error and complains that path must be a string.
2017-05-07 08:03:00 -04:00
Juan Picado
90e1a66930 Display version on startup 2017-05-04 21:38:19 +02:00
Juan Picado
2a1f1a7dfa Fix more eslint warnings, add doc 2017-05-04 21:05:47 +02:00
Konstantin Baierer
6fd47b333e Address feedback by @cosaquee and @juanpicado in verdaccio/verdaccio#110 2017-05-03 00:06:06 +02:00
Jeff Mixon
5b8fe3aeb5 Better Docker
Improves the Docker experience by leveraging Data volumes, dropping root permissions, using a more lightweight and secure base image (node:alpine), and moving unwieldy run arguments into a docker-compose file. The net result is you can now issue a single command `docker-compose up --build` to have a fully-functional verdaccio instance running.
2017-05-02 23:18:48 +02:00
Juan Picado
7c6c2d2932 Merge branch 'master' of github.com:verdaccio/verdaccio 2017-04-30 16:57:27 +02:00
jotadeveloper
babf679914 Merge pull request #179 from verdaccio/ping-feature
Implement npm ping endpoint
2017-04-30 16:26:39 +02:00
Juan Picado
f8adc5e1a2 Update readme ping command 2017-04-30 10:01:41 +02:00
Juan Picado
aaedc13d2c npmjs returns an emtpy object 2017-04-30 09:58:11 +02:00
Juan Picado
1e4ac1f842 Fix eslint warnings on up-storage.js 2017-04-29 23:08:50 +02:00
Juan Picado
4ee0ca29d4 Add endpoint for the command npm ping 2017-04-29 22:29:05 +02:00
jotadeveloper
b264e9f3df Merge pull request #176 from verdaccio/clean-eslint
Clean eslint in a couple of files
2017-04-27 06:58:04 +02:00
Juan Picado
90328d5faf Fix config.js new shape 2017-04-27 06:54:53 +02:00
Juan Picado
1d062075c4 Refactor config.js to es6, add jsdoc 2017-04-27 06:54:15 +02:00
Juan Picado
acfd865bb0 Fix eslint issues, add jsdoc 2017-04-27 05:52:46 +02:00
Juan Picado
ff7deb4712 set up coveralls 2017-04-27 04:37:56 +02:00
Juan Picado
09c60e68e0 Add missing @return on search.js 2017-04-27 03:59:11 +02:00
Juan Picado
318634f072 Add jsdoc to stream.js 2017-04-27 03:50:04 +02:00
Juan Picado
105a34f912 Apply es6 feature utils.js 2017-04-27 00:36:19 +02:00
Juan Picado
5a17a04090 Add jsdoc and fix lint warnings for utils.js 2017-04-27 00:35:25 +02:00
Juan Picado
7dde31546e Add jsdoc search.js 2017-04-26 23:48:55 +02:00
jotadeveloper
472e8e94b0 Merge pull request #108 from ryan-codingintrigue/master
Add support for multiple notification endpoints to existing webhook s…
2017-04-26 18:23:35 +02:00
jotadeveloper
3250bc5513 Add app version suggestion to issue template 2017-04-26 17:20:03 +02:00
Ryan Graham
1092c89f86 Added use strict 2017-04-26 13:43:27 +01:00
Ryan Graham
977946a319 Update code style to ES6 2017-04-26 13:38:51 +01:00
Ryan Graham
373c5cb348 Merge branch 'master' into master-fork
# Conflicts:
#	lib/notify.js
2017-04-26 13:31:29 +01:00
Ryan Graham
fee0864ba1 Fixed code style 2017-04-26 13:20:24 +01:00
jotadeveloper
6d8d05b0cc Merge pull request #159 from verdaccio/moving_es6_gradually
Upgrade syntax to ES6
2017-04-24 07:13:52 +02:00
Juan Picado
fcc40d65ae Merge branch 'moving_es6_gradually' of github.com:verdaccio/verdaccio into moving_es6_gradually 2017-04-23 22:34:57 +02:00
Juan Picado
3457e0925d rebase from master branch 2017-04-23 22:28:35 +02:00
Juan Picado
f282941075 Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors 2017-04-23 22:19:31 +02:00
Juan Picado
e3e1485369 Update gitignore, IDE hidden folder: 2017-04-23 21:28:41 +02:00
Juan Picado
b05050da5b Update unit test es6 2017-04-23 21:28:40 +02:00
Juan Picado
7970e52068 Migrate storages to classes 2017-04-23 21:28:08 +02:00
Juan Picado
d79f12d45a Migrate Storages to classes 2017-04-23 21:25:24 +02:00
Juan Picado
e6561e10d4 Migrate Search to class 2017-04-23 21:24:30 +02:00
Juan Picado
b5acc054bf clean up phase, moving es6 local storage 2017-04-23 21:24:30 +02:00
Juan Picado
ce59c07b0b Update mocha, clean warning 2017-04-23 21:18:39 +02:00
Juan Picado
f6edac245d clean minimach warning 2017-04-23 21:18:39 +02:00
Juan Picado
c21239a634 Force use npm on travis 2017-04-23 21:18:39 +02:00
Juan Picado
8372674d09 Allow use yarn on dev 2017-04-23 21:09:49 +02:00
Juan Picado
df697b9461 Update min node version, md5 dependency requires it 2017-04-23 21:09:49 +02:00
Juan Picado
0ef1286c37 update eslint, add coverage to ignore list 2017-04-23 21:09:48 +02:00
Juan Picado
52cb33ffdd Remove es6 polyfills and use native Promises 2017-04-23 21:09:48 +02:00
Juan Picado
dd3a4b4821 Apply partially new eslint rules, upgrade es6 and replace octal literals by chalk colors 2017-04-23 20:02:26 +02:00
Juan Picado
896f083000 2.1.5 2017-04-22 11:07:12 +02:00
Juan Picado
05b23fdadc Prepare release, update changelog 2017-04-22 11:06:01 +02:00
jotadeveloper
fe6e503ec3 Merge pull request #166 from Meeeeow/fix_upstream_search
fix upstream search
2017-04-22 10:51:35 +02:00
jotadeveloper
d824821b7a Merge pull request #163 from Meeeeow/fix_search
Fix search feature
2017-04-22 10:42:42 +02:00
Meeeeow
e5ad620a15 fix upstream search (registry.npmjs.org) 2017-04-22 16:41:33 +08:00
Meeeeow
0a769008cf modify varable name to accord with origin code 2017-04-22 16:33:56 +08:00
Juan Picado
34bbde1a7c Merge branch 'master' of github.com:verdaccio/verdaccio 2017-04-22 09:46:12 +02:00
Juan Picado
cb1a7af3c2 eslint ignore coverage folder 2017-04-22 09:46:00 +02:00
jotadeveloper
51b6c6006b Merge pull request #167 from verdaccio/plugin-unit-test
Add unit test for plugin-loader
2017-04-22 09:40:05 +02:00
Juan Picado
580d3e77f7 Fix undefined path value 2017-04-22 09:31:12 +02:00
Juan Picado
7df6962f43 Add unit test for plugin-loader 2017-04-22 08:23:16 +02:00
Meeeeow
caabe0ebda WIP: fix upstream search 2017-04-22 02:34:30 +08:00
Cedric Darne
cb37b38ee4 Package search requires 'access' authorization
Added 'access' check to search route
Handle failed Ajax search request as 'No result'
2017-04-21 12:21:20 +08:00
Meeeeow
601caa8711 update compiled resource 2017-04-21 11:54:28 +08:00
Meeeeow
5150ecb973 fix loading not show after first search 2017-04-21 11:54:28 +08:00
Meeeeow
1ce74482bc fix loading icon not align to center 2017-04-21 11:54:28 +08:00
Meeeeow
a18ac6bed6 fix npm search not show author name 2017-04-21 11:54:28 +08:00
Meeeeow
13d653e4dd fix search not work for multiple storage 2017-04-21 11:53:05 +08:00
Meeeeow
ea4f8474c1 fix deprecared warning with fs.unlink
Since NodeJS 7,  call async api without callback is deprecared.
2017-04-21 11:53:05 +08:00
Meeeeow
62ef3d91b6 update compiled resources 2017-04-21 11:53:05 +08:00
Meeeeow
38518baf44 fix: search leak private package and ui bug
- Check permission in search API
- Fix author's name not show in search result
2017-04-21 11:53:05 +08:00
Meeeeow
04e8638291 remove unused allow_failures in travis config 2017-04-21 11:53:05 +08:00
Emmanuel Narh
5eea1bac6c ADDED intermediate ssl certificate option
The https module allows for an intermediate certificate in the options.
It was somehow missed. Adding it back since I had a certificate that
included an intermediate certificate.
2017-04-21 11:53:05 +08:00
Juan Picado
a57f99cbe0 Update gitignore, IDE hidden folder: 2017-04-19 21:15:52 +02:00
Juan Picado
338ea47b6c Update unit test es6 2017-04-19 21:15:28 +02:00
Juan Picado
20f9436672 Migrate storages to classes 2017-04-19 20:45:48 +02:00
jotadeveloper
d11d9ea5a4 Merge pull request #160 from Meeeeow/docs_behind_proxy
add docs about run behind proxy
2017-04-19 13:52:04 +02:00
Meeeeow
dc8abe99c3 add docs about run behind reverse proxy
- Change word 'proxy' to 'reverse proxy'
2017-04-19 19:35:25 +08:00
Meeeeow
ce65cfff23 add docs about run behind proxy
- Add docs about run behind proxy with different domain and port
- Add server-side configure document link in README.md
2017-04-19 18:21:47 +08:00
Ryan Graham
adb40fc5f4 Fixed Changed headers array to be local to notifyEntry 2017-04-18 13:56:36 +01:00
Ryan Graham
7d62824936 Merge branch 'master' into master-fork
# Conflicts:
#	lib/notify.js
2017-04-18 13:49:24 +01:00
Juan Picado
e267ef1f54 Migrate Storages to classes 2017-04-17 16:10:21 +02:00
Juan Picado
ad7cf21d0e Migrate Search to class 2017-04-17 15:52:36 +02:00
Juan Picado
d484bb4f2f clean up phase, moving es6 local storage 2017-04-17 15:12:31 +02:00
Juan Picado
d66aa7097a Update mocha, clean warning 2017-04-17 13:51:42 +02:00
Juan Picado
69d51e88d8 clean minimach warning 2017-04-17 12:57:03 +02:00
Juan Picado
f51ea1f206 Force use npm on travis 2017-04-17 12:53:17 +02:00
Juan Picado
e50d143d96 Allow use yarn on dev 2017-04-17 12:20:35 +02:00
Juan Picado
92a9174603 Update min node version, md5 dependency requires it 2017-04-17 12:18:13 +02:00
Juan Picado
4edbd47088 update eslint, add coverage to ignore list 2017-04-17 11:54:08 +02:00
Juan Picado
c166c20701 Remove es6 polyfills and use native Promises 2017-04-17 11:47:37 +02:00
Juan Picado
640240e5c2 Remove Symbols dependency, Node 4 as min version supported 2017-04-17 11:36:35 +02:00
Juan Picado
2793914d80 Update authors 2017-04-14 05:18:25 +02:00
Juan Picado
90bb89660c Update readme 2017-04-13 22:15:26 +02:00
Juan Picado
e3af9f023c 2.1.4 2017-04-13 22:02:21 +02:00
Juan Picado
95c8a1fb82 Update changelog 2017-04-13 22:02:10 +02:00
jotadeveloper
32d7e8d891 Merge pull request #154 from verdaccio/unit_testing
Unit testing for Search.js
2017-04-13 21:41:01 +02:00
jotadeveloper
ee99d1fb26 Merge pull request #155 from verdaccio/notify-issue-144
#144 Add fallback support to previous config files
2017-04-13 21:39:20 +02:00
Juan Picado
098675516b #144 Add fallback support to previous config files 2017-04-13 21:32:49 +02:00
jotadeveloper
9350963f58 Update README.md 2017-04-13 08:44:26 +02:00
Juan Picado
999abc5b73 Update changelong, remove duplicated file 2017-04-13 07:03:19 +02:00
Juan Picado
c00456f6fe update readme 2017-04-12 23:18:57 +02:00
Juan Picado
ea1ed4159e Merge branch 'unit_testing' of github.com:verdaccio/verdaccio into unit_testing 2017-04-12 21:18:27 +02:00
Juan Picado
18d52a4103 Add unit test Search indexer 2017-04-12 21:17:28 +02:00
Juan Picado
4bb6a223be Merge branch 'master' of github.com:verdaccio/verdaccio 2017-04-12 21:07:22 +02:00
Juan Picado
d4710164c7 update AUTHORS 2017-04-12 21:06:36 +02:00
jotadeveloper
ee2439d8da Merge pull request #152 from Verikon/master
Allows retrieval of all local package contents via http://server/-/search/~
2017-04-12 06:27:27 +02:00
Bren Norris
94f4cd2be1 wildcarded searches now use asterisk, not tilda 2017-04-11 20:17:24 -04:00
jotadeveloper
2ed6d7f750 Merge pull request #153 from verdaccio/issue-144
Increase verbose on notify request, helps #144
2017-04-11 22:03:20 +02:00
Juan Picado
f1ff7d0eaf Increase verbose on notify request, helps #144 2017-04-11 21:53:21 +02:00
Juan Picado
389b6d006c Add unit test Search indexer 2017-04-10 22:00:41 +02:00
Bren Norris
71d5e5f150 search now accepts a tilda to list all contents of the local package list 2017-04-09 10:05:31 -04:00
jotadeveloper
8c2329795d Merge pull request #147 from nedelenbos/patch-1
Added Nexus Repository OSS as similar existing software
2017-04-05 21:11:44 +02:00
Nick
977e4adfc6 Added Nexus Repository OSS as similar existing software 2017-04-05 20:51:24 +02:00
jotadeveloper
b22bb17d90 Merge pull request #146 from lbguilherme/patch-1
Changelog: Fix pull request link
2017-04-02 07:28:05 +02:00
Guilherme Bernal
7031b9294f Changelog: Fix pull request link 2017-04-01 18:39:17 -03:00
Juan Picado
3bd1e82b54 2.1.3 2017-03-29 22:03:09 +02:00
Juan Picado
af4df69a16 update changelog 2017-03-29 21:39:52 +02:00
jotadeveloper
08864ce302 Merge pull request #143 from jachstet-sea/TaglineOnWebpage
Allow configuring a tagline that is displayed on the webpage between …
2017-03-28 18:18:38 +02:00
Jannis Achstetter
a00be0c20d Allow configuring a tagline that is displayed on the webpage between the header and the list of packages. 2017-03-28 16:48:16 +02:00
jotadeveloper
4f22d6fec6 Merge pull request #137 from danielo515/rpi-docker
Added docker image for rpi
2017-03-23 06:23:40 +01:00
Daniel Rodríguez Rivero
216d376598 Update README.md 2017-03-22 09:55:35 +01:00
jotadeveloper
773ac4922b Merge pull request #136 from psychocode/fix-webreadme
optional scope in the readme package name.
2017-03-18 14:07:07 +01:00
Cody Droz
3a131f380d optional scope in the readme package name. 2017-03-15 08:57:24 -05:00
jotadeveloper
9f06ea4b05 Merge pull request #89 from 030/gh83-systemd-script
[GH-83] create systemd service
2017-03-10 07:22:24 +01:00
Juan Picado
9e3ec11c70 2.1.2 2017-03-09 07:24:33 +01:00
Juan Picado
f3bbc7957e update changelog 2017-03-09 07:24:23 +01:00
danielo515
8e8c8b67f5 - Added docker image for rpi
- Added instructions for build rpi image
- Added npm scripts for building images
2017-03-06 09:33:52 +01:00
jotadeveloper
dbb5fa2c9c Merge pull request #129 from Alexandre-io/fix/plugin-loader
fix(plugin-loader): plugins verdaccio-* overwrite by sinopia-*
2017-03-04 14:25:11 +01:00
jotadeveloper
b82861a5e8 Merge pull request #133 from verdaccio/contribute-guidelines
Contribute guidelines
2017-03-04 14:18:00 +01:00
Alexandre-io
6fa9ed7f07 fix(plugin-loader): plugins verdaccio-* overwrite by sinopia-* 2017-03-02 17:35:27 +01:00
Juan Picado @jotadeveloper
fdd6a21102 change header size 2017-02-18 14:36:56 +01:00
Juan Picado @jotadeveloper
8a0b20719e contributing guidelines, just a rough try example 2017-02-18 12:05:12 +01:00
jotadeveloper
03c7142fa6 Set theme jekyll-theme-cayman 2017-02-17 20:39:40 +01:00
jotadeveloper
03340c1eae Delete CNAME 2017-02-17 20:06:28 +01:00
jotadeveloper
e588e281b6 Create CNAME 2017-02-17 19:51:20 +01:00
jotadeveloper
cf8b906e54 Set theme jekyll-theme-cayman 2017-02-17 19:47:12 +01:00
jotadeveloper
59b8b6f037 Merge pull request #90 from 030/gh-84-ansible
[GH-84] reference added to verdaccio ansible playbook
2017-02-07 12:20:32 +01:00
Juan Picado @jotadeveloper
0b7dc598b2 2.1.1 2017-02-07 07:41:48 +01:00
Juan Picado @jotadeveloper
c609284d0f prepare release 2.1.1 2017-02-07 07:41:37 +01:00
jotadeveloper
90c1ec9d36 Merge pull request #117 from kgrubb/update-readme
[GH-86] updated readme to point to new chef cookbook
2017-02-05 21:13:36 +01:00
jotadeveloper
b76546084c Merge pull request #93 from kgrubb/master
[GH-88] rename to Verdaccio instead of Sinopia
2017-02-05 20:38:20 +01:00
Madison Grubb
e984bd212b updating travis to reflect verdaccio's current setup (removed iojs & 0.12 support) 2017-02-05 12:16:58 -05:00
jotadeveloper
83de901c8a Update .npmignore
Ignore coverage reporter before publish
2017-02-05 14:28:03 +01:00
Madison Grubb
c9f7ba8004 updated readme to point to new chef cookbook 2017-02-04 23:07:33 -05:00
Madison Grubb
01a6c22103 merging in changes from upstream & fixing conflicts 2017-02-04 22:58:26 -05:00
jotadeveloper
9f1b1619e8 Merge pull request #116 from verdaccio/unit-testing-coverage
Unit testing coverage
2017-02-04 09:36:26 +01:00
Juan Picado @jotadeveloper
ffb3c0e6d0 add travis coverage configuration 2017-02-04 00:32:54 +01:00
Juan Picado @jotadeveloper
0470a02b2f Add istanbul unit testing coverage 2017-02-04 00:29:34 +01:00
jotadeveloper
6bbeff6d05 Update README.md 2017-02-03 21:04:43 +01:00
jotadeveloper
0bc3e6c8a0 Merge pull request #112 from imsnif/master
Allow htpasswd-created users to log in
2017-02-03 21:03:23 +01:00
jmwilkinson
53018704e0 Merge pull request #115 from juanpicado/remove-iojs-support
remove travis io.js support
2017-02-03 09:17:17 -08:00
jmwilkinson
e8c16cfb4b Merge pull request #114 from juanpicado/master
rename clean up
2017-01-24 09:20:44 -08:00
Juan Carlos Picado
27ea2f91c0 remove travis io.js support 2017-01-24 08:34:59 +01:00
Juan Carlos Picado
6c6646873c Rename cleanup 2017-01-22 22:23:30 +01:00
Aram Drevekenin
223d6492d4 feat: adduser created with htpasswd 2017-01-19 17:14:53 +02:00
Aram Drevekenin
ccd3d26059 test: adduser created with htpasswd 2017-01-19 17:14:39 +02:00
Ryan Graham
f63e690b15 Add support for multiple notification endpoints to existing webhook system 2017-01-10 08:35:10 +00:00
Juan Carlos Picado
674057b1af replace project names 2016-12-07 23:18:16 +01:00
jmwilkinson
014a0b53ae Merge pull request #101 from juanpicado/master
Fix #65 and also PR on fl4re#4
2016-12-07 12:46:53 -08:00
Juan Carlos Picado
32cc6ea617 Removed testing support for 0.10 and 0.12 (already deprecated) 2016-12-05 20:33:11 +01:00
Juan Carlos Picado
d6dd63f012 add travis node 4,6 and 7 2016-12-05 08:52:52 +01:00
Juan Carlos Picado
0210752ea5 clean warnings on unit test 2016-12-05 08:42:43 +01:00
Juan Carlos Picado
3c060766e7 Fix #65 and also PR on fl4re#4 2016-11-27 15:07:45 +01:00
jmwilkinson
7915bc3b59 Merge pull request #96 from robertgroh/readme_fix
Docs: correct config attribute `proxy_access`

Thanks!
2016-11-18 10:32:12 -08:00
Robert Groh
69c5628184 fix old config attribute name in readme
rename `proxy_access` to `proxy`
see 6075034521 for reference
2016-11-18 16:03:01 +01:00
jmwilkinson
03d66b0664 Merge pull request #72 from josedepaz/patch-1
Problem with docker.yaml
2016-11-15 16:50:40 -08:00
jmwilkinson
7c82d7e485 Merge pull request #94 from tlvince/rm-secrets-log
Prevent logging of user and password
2016-11-15 16:48:28 -08:00
Tom Vincent
c927517e37 Prevent logging of user and password 2016-11-13 16:25:14 +00:00
Madison Grubb
9fd42ffb34 allow sinopia plugins to install 2016-11-09 13:12:15 -05:00
Madison Grubb
a7c4285a68 renaming 'sinopia' to verdaccio. 2016-11-07 12:15:38 -05:00
030
eb6d30537b [GH-84] reference added to verdaccio ansible playbook 2016-11-04 10:26:28 +01:00
030
b9b61c7e97 [GH-83] systemd service could not be enabled 2016-11-03 14:00:21 +01:00
030
eebc466e34 [GH-83] create systemd service 2016-11-03 10:49:49 +01:00
jmwilkinson
f84ca18798 Merge pull request #71 from jmwilkinson/master
Updated README.md to reflect the availability of the docker image
2016-10-20 09:29:15 -07:00
José De Paz
831083a976 Problem with docker.yaml
There is a problem with the docker.yaml file.

auth:
   htpasswd:
     file:/verdaccio/config/htpasswd

The file property should point to /verdaccio/conf/htpasswd because folder /verdaccio/config dosen't exist and therefore dosen't let to create users.

Thank you for working on this great tool.

Regards
2016-10-19 16:38:51 -06:00
jmwilkinson
7530aee68a Updated README.md,
added docker instructions for pre-built images
2016-10-18 09:21:54 -07:00
jmwilkinson
18cc0495ef Merge pull request #1 from verdaccio/master
Upstream pull
2016-10-18 08:43:26 -07:00
trent.earl
047fbb22f8 2.1.0 2016-10-11 19:46:35 -05:00
Manuel de Brito Fontes
4fa150e077 Use __dirname to resolve local plugins
https://github.com/verdaccio/verdaccio/pull/25
2016-10-11 19:00:25 -05:00
trent.earl
5e00e46a9b Logout endpoint should support tokens with /s 2016-10-11 18:53:20 -05:00
Miguel Mejias
25e00a3172 Implement logout endpoint 2016-10-11 18:44:47 -05:00
Jannis Achstetter
3d9af3152e Implement timestamped pretty logging to stdout or stderr
https://github.com/verdaccio/verdaccio/pull/68
2016-10-11 18:43:20 -05:00
Jannis Achstetter
9f9c0fae71 Allow adding/overriding HTTP headers of uplinks via config
https://github.com/verdaccio/verdaccio/pull/67
2016-10-11 18:36:37 -05:00
Denis Babineau
3729c13a91 run as root in docker container to avoid issues with volume permissions 2016-10-11 18:18:38 -05:00
Denis Babineau
eec65929da change process name in Dockerfile 2016-10-11 18:18:38 -05:00
Chad Killingsworth
6f7a28b359 Update the configs to fully support proxying scoped packages from the npm registry 2016-10-11 18:15:02 -05:00
Michael Crowe
a6fa0432ee Prevent the server from crashing if a repo is accessed that the user does not have access to
https://github.com/verdaccio/verdaccio/pull/58
https://github.com/verdaccio/verdaccio/issues/56
2016-10-11 18:06:15 -05:00
Nate Ziarek
6fb1dc2342 Simple notification system to send publish commands to external systems (ala Slack) 2016-09-17 11:09:20 -05:00
Trent Earl
bb7138c3f6 Merge pull request #46 from plitex/fix-custom-template
Register entry partial even if custom template is provided
2016-09-17 10:35:53 -05:00
Trent Earl
c6923c9686 Merge pull request #57 from juanpicado/master
rename process to verdaccio
2016-09-01 14:22:51 -05:00
Juan Carlos Picado
c20cf6d6e5 rename process to verdaccio 2016-09-01 20:34:53 +02:00
trent.earl
d42a5ad1c5 2.0.1 2016-08-29 08:25:33 -05:00
Trent Earl
5e7faae788 Merge pull request #53 from juanpicado/master
Fix #52 binary file missing
2016-08-29 08:25:11 -05:00
Juan Carlos Picado
e4e4a104fe remove sinopia reference 2016-08-27 18:09:28 +02:00
Juan Carlos Picado
2d5cbaf461 Fix #52 binary file missing 2016-08-27 09:12:01 +02:00
trent.earl
f89ca4423a Updated to 2.0.0 2016-08-26 17:31:35 -05:00
Trent Earl
548c7475d6 Merge pull request #51 from juanpicado/master
update readme,  sinopia renamed to verdaccio
2016-08-24 16:12:06 -05:00
Juan Carlos Picado
fea001efcf update readme, sinopia renamed to verdaccio 2016-08-24 23:06:50 +02:00
trent.earl
4050e063c0 Revert "Start version off as 1.0.0"
It appears that user lonelyclick has already published this to npm.
I have contacted him about getting added as a collaborator

This reverts commit 564b91fba9.
2016-08-22 15:45:25 -05:00
trent.earl
564b91fba9 Start version off as 1.0.0 2016-08-22 15:37:56 -05:00
trent.earl
9fe9e38786 Remove shrinkwrap 2016-08-21 09:34:42 -05:00
trent.earl
05fde696c7 Update dependencies 2016-08-21 08:58:12 -05:00
trent.earl
b31198f685 Tests should check for presence of title with "Verdaccio" not "Sinopia" 2016-08-20 12:07:52 -05:00
trent.earl
1efd94dfe6 HTML title should default to verdaccio 2016-08-20 11:57:36 -05:00
trent.earl
3b95710f27 Remove references to sinopia in Documentation and configuration 2016-08-20 11:46:54 -05:00
trent.earl
8083df1361 bin/* should be executable 2016-08-13 10:11:15 -05:00
trent.earl
6de559c020 Rename config path from sinopia to verdaccio 2016-08-13 10:04:12 -05:00
Trent Earl
7579bb21c0 Merge pull request #49 from juanpicado/master
Fix test node6
2016-08-13 09:36:34 -05:00
Juan Carlos Picado
5cdadbd7fa Remove sync write file 2016-08-13 08:21:55 +02:00
Juan Carlos Picado
115a02b0eb restore binary cli command 2016-08-13 08:03:16 +02:00
Miguel Mejias
7b4c105046 Register entry partial even if custom template is provided 2016-08-10 11:52:21 +02:00
trent.earl
d8f2d95f92 Rename binary to verdaccio 2016-07-30 19:37:30 -05:00
trent.earl
dd406da3e2 Fix tests for node 6
Node 6 introduced two breaking changes to verdaccio/sinopia
Path functions now throw on null/undefined arguments
Buffer defaults to binary now A lot of test code explicitly converted data to utf8 which broke on checksum
2016-07-30 19:37:04 -05:00
Trent Earl
cafbb5f76b Merge pull request #24 from aledbf/docker-image
Add Dockerfile to build docker image
2016-05-15 19:31:34 -05:00
Manuel de Brito Fontes
cca5196752 Add Dockerfile to build docker image 2016-05-09 14:59:34 -03:00
Trent Earl
86b9f512a5 Update README.md 2016-05-01 10:46:23 -05:00
Trent Earl
06980e0cc1 Merge pull request #18 from steve-p-com/master
Remove platform specific deps. Introduce bundled-in plugins.
2016-05-01 09:23:05 -05:00
steve-p-com
e45fe466ee Remove optional dependencies that are not truly cross-platform 2016-05-01 10:02:01 +01:00
Trent Earl
2c76a5f8ac Merge pull request #14 from steve-p-com/master
Fix missing version numbers
2016-04-26 12:21:07 -05:00
steve-p-com
3cbbb281a5 Fix missing version numbers 2016-04-22 13:36:29 +01:00
Trent Earl
5fe64d41ec Merge pull request #13 from steve-p-com/master
Replace YAML package definitions with standard package.json, fix dependencies
2016-04-21 17:44:31 -05:00
steve-p-com
6301c3f169 Replace YAML package definitions with standard package.json, fix dependencies 2016-04-21 21:13:49 +01:00
Trent Earl
1ccf645638 Merge pull request #8 from steve-p-com/master
Sort distribution tags in a conisistent way with npmjs.org
2016-04-19 11:15:21 -05:00
steve-p-com
dfdcaa893e Change the way package distribution tags are processed and stored 2016-04-18 20:53:00 +01:00
Trent Earl
d260231737 Merge pull request #2 from jmwilkinson/master
Update README.md to say that this project is a fork
2016-04-15 12:43:24 -05:00
jmwilkinson
ecded4a440 Update README.md
Identifies that verdaccio is a fork of sinopia.
2016-04-15 10:34:05 -07:00
Alex Kocharin
3f55fb4c0c update dependencies 2015-10-03 20:01:46 +03:00
Alex Kocharin
b5d40b083a req.param -> req.query 2015-10-03 20:01:02 +03:00
Alex Kocharin
602f17c8b2 fix "unimplemented" error when uplink is offline 2015-10-03 19:22:45 +03:00
Alex Kocharin
74e89e94e9 Merge pull request #280 from jameslnewell/master
only encode the / character to fix issue installing scoped packages f…
2015-09-27 02:19:03 +03:00
Alex Kocharin
4be7a2028e Merge pull request #308 from 7eggs/ui-sort-packages
Sort packages before rendering in UI
2015-09-27 01:31:48 +03:00
Alexander Makarenko
4379821787 Sort packages before rendering in UI 2015-09-24 11:24:23 +03:00
Alex Kocharin
6b3617754c bump eslint to 1.x 2015-08-09 00:35:13 +03:00
James Newell
ee6cf0eb9f use encodeURIComponent and replace encoded '@' value with un-encoded value 2015-07-13 12:15:18 +10:00
Jakub Jirutka
fde2321222 Add workaround to handle URLs of scoped packages with unencoded /
Fixes https://github.com/rlidwka/sinopia/issues/104#issuecomment-66790574.
2015-07-12 01:26:18 +03:00
James Newell
a9c749995a only encode the / character to fix issue installing scoped packages from an upstream registry. @see #278. 2015-07-08 09:59:35 +10:00
Alex Kocharin
93245c0179 1.4.0 2015-06-07 08:04:01 +03:00
Alex Kocharin
a1843ffd34 update travis.yml 2015-06-07 07:54:38 +03:00
Alex Kocharin
0d266be965 up-storage: don't send X-Forwarded-For for proxies
It's not that important a feature to have, and it could break
some proxies.

ref https://github.com/rlidwka/sinopia/issues/254
2015-05-30 18:39:47 +03:00
Alex Kocharin
7c822d06f6 web: delay cb invocation to the next tick
ref #258
2015-05-30 18:27:18 +03:00
Alex Kocharin
d370e5a6a9 Merge pull request #253 from rlidwka/search
complete search rewrite
2015-05-30 18:17:49 +03:00
Alex Kocharin
3dd6442b94 update dependencies 2015-05-30 18:16:55 +03:00
Alex Kocharin
cac6e89870 Add readable-stream and JSONStream to shrinkwrap 2015-05-17 01:56:42 +03:00
Alex Kocharin
45edca2218 search: stream results from npmjs instead of buffering them 2015-05-17 01:29:16 +03:00
Alex Kocharin
46d710e079 1.3.1 2015-05-17 00:50:39 +03:00
Alex Kocharin
f1bb9f83e6 up-storage: make streaming requests without buffering 2015-05-16 19:33:06 +03:00
Alex Kocharin
6b6247b009 update dependencies 2015-05-16 17:40:08 +03:00
Alex Kocharin
8a68666096 1.3.0 2015-05-10 19:03:49 +03:00
Alex Kocharin
6d01bffc79 update dependencies 2015-05-10 18:39:53 +03:00
Alex Kocharin
c09d03b20d add dist-tags endpoints 2015-05-10 18:39:53 +03:00
Alex Kocharin
0da30ca089 test io.js 1.8 on travis 2015-05-10 18:39:53 +03:00
Alex Kocharin
b5c7895bb5 1.2.2 2015-04-22 02:46:05 +03:00
Alex Kocharin
97386397c2 add a code to bind on unix sockets
use "listen: 'unix:sinopia.sock'" for this
2015-04-22 02:38:25 +03:00
Alex Kocharin
137fd5978f fix access control
ref #238
2015-04-21 19:43:12 +03:00
Alex Kocharin
e8593c47cc 1.2.1 2015-04-11 23:21:03 +03:00
Alex Kocharin
74bb946a91 fix freeze in web interface on acl check 2015-04-11 23:19:45 +03:00
Alex Kocharin
82756c3968 1.2.0 2015-04-11 23:04:05 +03:00
Alex Kocharin
faaf1f537a add dotfiles to npmignore 2015-04-11 22:49:41 +03:00
Alex Kocharin
5383b2fccb update dependencies 2015-04-11 22:47:10 +03:00
Alex Kocharin
9294981d71 fix tests for 0.10 2015-04-11 20:14:49 +03:00
Alex Kocharin
17bdfd904a test: use promisified supertest-like asserts 2015-04-11 20:11:04 +03:00
Alex Kocharin
6cb257e51f add tests for auth plugins 2015-04-11 16:09:19 +03:00
Alex Kocharin
3c16e59a5c Refactor authorization plugins 2015-04-09 01:51:34 +03:00
Alex Kocharin
37f6591563 Merge branch 'feature/package-provider' of https://github.com/zipscene/sinopia into package-provider 2015-04-08 23:26:43 +03:00
Alex Kocharin
3e9932d866 update mocha to 2.2.3+, + options file 2015-04-07 22:51:05 +03:00
Chris Breneman
6954898da0 Merge remote-tracking branch 'upstream/master'
Conflicts:
	lib/auth.js
	lib/index.js
	lib/middleware.js
2015-03-31 09:47:20 -04:00
Alex Kocharin
a55dc65775 1.1.0 2015-03-30 23:55:25 +03:00
Alex Kocharin
66324d718a fix shrinkwrap formatting
workaround 'cause npm reformats everything as usual
2015-03-30 23:54:45 +03:00
Alex Kocharin
553e463b7e fix history + add missed item 2015-03-30 23:52:43 +03:00
Alex Kocharin
d7c95d6cd6 fix custom logo and url_prefix issues
use config.web.logo instead of config.web['logo-sm']
2015-03-29 23:01:34 +03:00
Kody J. Peterson
bf40ceb064 Fix custom logo
Custom logos are not loading as /-/static/-/logo-sm is not the endpoint it is /-/logo-sm
2015-03-29 23:01:18 +03:00
Alex Kocharin
554d31d119 should add via header for all requests 2015-03-28 22:26:36 +03:00
Alex Kocharin
8a3a03805e fix travis errors 2015-03-28 22:07:17 +03:00
Alex Kocharin
9047e28074 set up some linting (obvious errors only) 2015-03-28 21:25:53 +03:00
Alex Kocharin
2ee12c7293 update dependencies 2015-03-28 20:15:37 +03:00
Alex Kocharin
dc6b2f184a write a script to clean out shrinkwraps 2015-03-28 19:01:48 +03:00
Alex Kocharin
1a9596737e add test for parsing listen address 2015-03-28 18:03:36 +03:00
Alex Kocharin
89353f9312 add some docs for specifying listen addresses 2015-03-28 17:43:52 +03:00
Alex Kocharin
6d58d5920e add listening on ipv6 addresses 2015-03-28 17:43:50 +03:00
Alex Kocharin
dfef2b862f add a possibility to listen on multiple ports 2015-03-28 17:20:58 +03:00
Thomas Cort
61fff273fc cli.js: add https support. Issue #71. 2015-03-28 16:32:37 +03:00
Alex Kocharin
97c7c6814f Remove mentioning of 'always-auth'
It should be solved by using npm@2 anyway.
2015-03-28 15:14:01 +03:00
Rafael Cesar
025e23627f Added option to use a custom template for web UI 2015-03-28 14:15:07 +03:00
Alex Kocharin
0a38a704fe remove "from" and "resolved" from shrinkwrap
We should lock versions, but we shouldn't hardcode urls in it,
so people will be able to install this from private networks.

See #204 for details.
2015-03-08 05:01:02 +03:00
Alex Kocharin
798ba0d262 update dependencies 2015-03-08 04:58:12 +03:00
Alex Kocharin
24e3b5efba Merge pull request #210 from einfallstoll/patch-1
Add a comment how to listen on INADDR_ANY
2015-03-07 22:54:32 +03:00
Fabio Poloni
c00cde0aa3 added comment for INADDR_ANY 2015-03-05 11:14:54 +01:00
Chris Breneman
0af72f0c68 Pass allow_access calls that don't use the can() middleware through the plugin interface 2015-02-24 22:21:57 -05:00
Chris Breneman
82539add26 Change how package provider plugins are configured 2015-02-24 16:11:14 -05:00
Chris Breneman
76a1e8df80 Add package settings and authorization plugin system 2015-02-24 14:28:16 -05:00
Alex Kocharin
3fe188df21 travis: use container infrastructure 2015-02-18 17:59:19 +03:00
Alex Kocharin
4ed27c6eda travis: enable testing for io.js 2015-02-18 17:52:46 +03:00
Alex Kocharin
6ec76aa61f fix crash when using sinopia as a library 2015-02-18 17:52:17 +03:00
Alex Kocharin
4a46ffe3a0 update dependencies 2015-02-18 17:46:45 +03:00
Alex Kocharin
e47862be71 1.0.1 2015-02-12 14:59:58 +03:00
Alex Kocharin
04a9f14b3c fix test broken in 79e2ff2ee 2015-02-12 14:28:19 +03:00
Alex Kocharin
0aed9ee9f4 Merge pull request #200 from maxlaverse/master
Fixes NPE on POST _session when npm tries to authenticate
2015-02-12 14:26:15 +03:00
Alex Kocharin
79e2ff2ee8 change 403 error with 409 in adduser
Because npm does only accept 409, see #184.
2015-02-12 14:18:47 +03:00
maxlaverse
e5880841f3 Added missing argument
Added missing 'next' argument to the '/_session' route callback
2015-02-06 19:59:18 +01:00
Robin Persson
c49b0699c2 Fixed issue with not being able to disable the web interface 2015-01-26 14:27:32 +03:00
Alex Kocharin
bc91a05dd2 1.0.0 2015-01-26 14:06:06 +03:00
Alex Kocharin
5b5f56f51c fix test noise
TypeError: Cannot read property 'name' of undefined
    at log (/home/travis/build/rlidwka/sinopia/lib/middleware.js:185:32)
2015-01-26 14:00:37 +03:00
Alex Kocharin
f558c78d32 update dependencies 2015-01-26 13:55:34 +03:00
Alex Kocharin
ed3eb37c4f download packages using https
Or, in general, the same protocol as the registry;
this is what npm seem to be doing.

See issue #166 for details.
2014-12-23 00:29:26 +03:00
Alex Kocharin
09318d1943 fixed current folder reference in tests 2014-12-22 23:52:08 +03:00
Alex Kocharin
fad4f457ae move logger setup to index.js + tests 2014-12-22 20:58:25 +03:00
Alex Kocharin
fc3668885d 1.0.0-beta.2 2014-12-22 04:09:00 +03:00
Alex Kocharin
c385404e23 update dependencies 2014-12-22 03:57:36 +03:00
Tarun Garg
a0ff6dbc9e Checks whether process.env.HOME exists
Checks whether process.env.HOME exists before calling Path.join
Was not able to start sinopia on my fresh windows install due to this error.
2014-12-21 22:07:22 +03:00
Alex Kocharin
484ba9dc25 1.0.0-beta 2014-12-15 00:09:37 +03:00
Alex Kocharin
848d591f0a update dependencies 2014-12-15 00:03:42 +03:00
Alex Kocharin
c07d819ce9 add tests for scoped packages 2014-12-15 00:03:42 +03:00
Yannick Galatol
ea50f3d3ed Add a query parameter "local" to the search method of storage to allow listing only local packages 2014-12-15 00:03:19 +03:00
Alex Kocharin
76bdc74670 web: change header position from fixed to static
Mainly because anchors (`<a name=...>`) need to be offsetted to
account for the fixed header, and it ain't worth the hassle.
2014-12-10 09:36:54 +03:00
Alex Kocharin
4a4502967e disable source maps & rebuild 2014-12-10 09:30:02 +03:00
Alex Kocharin
5358d2b81b update dependencies 2014-12-10 09:29:52 +03:00
Alex Kocharin
0f6404f4ea fix path to helpers.less 2014-12-10 09:25:42 +03:00
Alex Kocharin
2b9bfcc0d9 marked -> render-readme 2014-12-10 09:14:31 +03:00
Alex Kocharin
950cfb2945 1.0.0-alpha.3 2014-12-05 09:23:57 +03:00
Alex Kocharin
06bb301bab fix dist.tarball replacement logic 2014-12-05 09:16:16 +03:00
Alex Kocharin
bad7aee4ec use /-/whoami endpoint instead of /whoami
Allows to get rid of referer magic, so it's a nice change to have.

See this issue for details:
https://github.com/npm/npm-registry-client/issues/88
2014-12-05 05:04:37 +03:00
Alex Kocharin
400288bb69 1.0.0-alpha.2 2014-11-30 12:05:51 +03:00
Alex Kocharin
c549466737 Add npm-shrinkwrap.json 2014-11-30 11:39:56 +03:00
Alex Kocharin
841f77d36a sort out dependencies needed to build static stuff 2014-11-30 11:22:24 +03:00
Alex Kocharin
e281b4e389 remove node_modules
Because of issues with native packages. Use shrinkwrap instead.
2014-11-30 11:12:53 +03:00
Alex Kocharin
64adb19b85 fs-ext@0.4.1 2014-11-25 04:25:42 +03:00
Alex Kocharin
b84bb568cd s/self/this/ in error message 2014-11-25 04:24:18 +03:00
Alex Kocharin
ecc5e9bfaf 1.0.0-alpha 2014-11-25 04:05:55 +03:00
Alex Kocharin
133bceb43f remove rimraf from bundled deps
added in bulk by mistake
2014-11-25 03:50:38 +03:00
Alex Kocharin
b338aefe45 bugfix: forgot "next" 2014-11-25 03:22:05 +03:00
Alex Kocharin
feae33499e make path to local-db if it doesn't exist 2014-11-25 03:16:58 +03:00
Alex Kocharin
40baedaa78 semver@4.1.0 2014-11-25 03:12:03 +03:00
Alex Kocharin
2b99b23eec fix es-shim dependency 2014-11-25 03:08:19 +03:00
Alex Kocharin
4592ec3657 bundle all the deps 2014-11-25 03:08:06 +03:00
Alex Kocharin
cf71b9dc9e encrypt user+pass instead of tokens for now 2014-11-24 22:53:05 +03:00
Alex Kocharin
691bdb3a92 web: encode version strings in urls 2014-11-17 21:18:07 +03:00
Alex Kocharin
ae1f67d31f reformat config file and http address messages 2014-11-16 21:44:32 +03:00
Alex Kocharin
a425c5e2ff add scoped packages draft 2014-11-16 20:46:01 +03:00
Alex Kocharin
972551e838 nitpick 2014-11-16 16:38:01 +03:00
Alex Kocharin
637d51cba0 create config in xdg_config_home by default 2014-11-16 16:33:03 +03:00
Alex Kocharin
c71e8dc829 enable web interface by default 2014-11-16 16:32:46 +03:00
Alex Kocharin
2f541130ab auth tokens draft 2014-11-16 15:37:50 +03:00
Alex Kocharin
322f64d517 install crypt3 from npm registry 2014-11-15 04:43:21 +03:00
Alex Kocharin
ca3cb6487d refactor log and etagify middlewares 2014-11-13 22:01:20 +03:00
Alex Kocharin
76ebb24b77 fix config file comment 2014-11-13 21:32:01 +03:00
Alex Kocharin
4c11a42d21 separate web and api routers to different files 2014-11-13 20:14:21 +03:00
Alex Kocharin
8259455ac5 switch to express 5
It's needed for the better separation between rest api and web interface.
2014-11-13 20:14:14 +03:00
Alex Kocharin
1fe0cedbd0 add access control for web ui 2014-11-13 18:52:13 +03:00
Alex Kocharin
09485451f7 fix res.sendfile callback 2014-11-13 18:38:49 +03:00
Alex Kocharin
c78390752a fix tests 2014-11-12 19:45:40 +03:00
Alex Kocharin
7687965219 handle 404 errors better
+ get rid of fs.exists
2014-11-12 19:26:27 +03:00
Alex Kocharin
100430227c display favicon properly 2014-11-12 19:26:24 +03:00
Alex Kocharin
148795918f move config file to separate folder
Make default config smaller, allow all users by default there.
2014-11-12 18:49:37 +03:00
Alex Kocharin
31bd3c9db7 warn about outdated npm in adduser
close #93
2014-11-12 18:01:59 +03:00
Alex Kocharin
037b3aea6a make use of es6-shim
It's mainly because of Object.setPrototypeOf()
2014-11-12 17:37:43 +03:00
Alex Kocharin
6a778e8c17 change code style to jshttp
close #155, see reasons there

This is a huge commit, so let me know if it will cause
any trouble, I might consider reverting it if it's the case.
2014-11-12 17:37:43 +03:00
Alex Kocharin
bac62f6969 Merge pull request #157 from vernak2539/webui-login-btn-placement
web-gui/placing login/logout btns. responsive
2014-11-11 03:54:04 +03:00
Alex Vernacchia
6a14a277b2 web-gui/placing login/logout btns. responsive 2014-11-05 14:04:13 +00:00
Alex Kocharin
67b4528643 add authentication to web interface 2014-11-04 17:51:45 +03:00
Alex Kocharin
7b34bb63c9 0.13.2 2014-11-01 23:50:49 +03:00
Alex Kocharin
a854d07a66 add a workaround for EPERM issue on windows
Derived from @korto's fix:
https://github.com/rlidwka/sinopia/issues/67#issuecomment-44782521
2014-11-01 23:48:40 +03:00
Alex Kocharin
7a7d794207 fix double on_open call 2014-11-01 22:57:36 +03:00
Alex Kocharin
4fb5acad48 0.13.0 2014-10-22 18:16:18 +04:00
Alex Kocharin
2e396e4d65 fix comment about users_file
ref #148
2014-10-20 22:40:33 +04:00
Alex Kocharin
55ddeb7aaa Merge pull request #145 from neuquino/master
added url_prefix to static resources
2014-10-20 09:57:53 +04:00
Facundo Chambó
907e1a8c43 use relative urls on browser side (ajax requests and image urls) 2014-10-19 22:44:02 -03:00
Facundo Chambó
8628c34b25 fixed npm image url 2014-10-19 22:34:57 -03:00
Facundo Chambó
9938a6bf4b removed trailing slash to avoid duplicate slashes 2014-10-19 22:03:40 -03:00
Facundo Chambó
f308bfb4bf added url_prefix to static resources 2014-10-17 12:25:55 -03:00
Alex Kocharin
c0793c575d Merge pull request #132 from vStone/patch-2
Fix max_users in auto generated config.yaml
2014-10-17 11:43:23 +04:00
Alex Kocharin
3c00c96b6a Merge pull request #144 from jondlm/patch-1
Wording changes in the test/README.md
2014-10-17 11:36:59 +04:00
Alex Kocharin
a1010be87a Merge pull request #143 from vernak2539/web-gui-proper-index-versions
web-gui/updating entries shown on index page
2014-10-17 11:30:04 +04:00
Jon de la Motte
4b20afbd15 Fix a typo in the test/README.md
Also made a couple wording changes and fixed a typo.
2014-10-13 12:51:35 -07:00
Alex Vernacchia
dc9859cd83 web-gui/updating entries shown on index page 2014-10-13 14:39:04 -04:00
Alex Kocharin
08f218724b Merge pull request #141 from vernak2539/web-gui
Web GUI update
2014-10-10 11:36:32 +04:00
Alex Vernacchia
daeb1eb2a3 web-gui/moving to bootstrap and finishing responsive layout 2014-10-07 14:46:16 -04:00
Alex Vernacchia
326c7355ed web-gui/updating display, layout, and styling 2014-10-06 17:37:23 -04:00
Alex Kocharin
d38606f409 0.12.1 2014-10-03 08:00:08 +04:00
Alex Kocharin
29c45e3ebc 0.12.1 2014-10-03 07:59:59 +04:00
Alex Kocharin
20f748be12 fix "offset out of bounds" issue 2014-10-03 07:51:56 +04:00
Alex Kocharin
1cb1e91ca1 update deps 2014-10-02 18:07:01 +04:00
Alex Kocharin
7fb119c9f3 Merge pull request #137 from vernak2539/update-readme-css
Updating Markdown CSS to be similar to github.com's
2014-10-02 11:44:08 +04:00
Alex Kocharin
eda8dfe9ca Validate package name when doing search
+ tests

fix #122
2014-10-02 11:14:59 +04:00
Romain Lai-King
4b3939ef4e Remove CDN dependencies
Use case: developers behind corporate firewall

close #133
2014-10-02 10:27:40 +04:00
Alex Vernacchia
cb0c79e79f Updating Markdown CSS to be similar to github.com's 2014-10-01 16:56:52 -04:00
Jan Vansteenkiste
e36638973f Fix wrongly documented max_users in auto generated config.yaml
See rlidwka/sinopia-htpasswd#3 and rlidwka/sinopia-htpasswd#6
2014-09-30 09:53:22 +02:00
Alex Kocharin
aef982d6e6 0.12.0 2014-09-25 08:08:23 +04:00
Alex Kocharin
4a588f6512 0.12.0 2014-09-25 08:07:42 +04:00
Alex Kocharin
b34cfb1fe0 remove tar.gz module
totally not needed, ref #100
2014-09-25 08:00:11 +04:00
Alex Kocharin
4f028a107b Save readme data that npm provides on publish
fix #100, fix #128
2014-09-25 07:56:31 +04:00
Alex Kocharin
91ee928f06 fix hostname for travis 2014-09-25 05:25:48 +04:00
Alex Kocharin
862b1eeed4 automated tests against fd leaks 2014-09-25 05:21:59 +04:00
Alex Kocharin
0786ec6108 set process.title 2014-09-25 05:13:03 +04:00
Alex Kocharin
2dca3114e9 update deps 2014-09-25 05:10:00 +04:00
Kalman Speier
3bf26bd89e config.logo -> config.web.logo 2014-09-24 23:52:39 +04:00
Alex Kocharin
ef074f8d6c 0.11.3 2014-09-18 03:17:46 +04:00
Alex Kocharin
4b46bff72f update to sinopia-htpasswd@0.4.3
fix possible fd leak, close #116
2014-09-18 03:16:58 +04:00
Alex Kocharin
765d3b47a7 0.11.2 2014-09-18 01:46:11 +04:00
Alex Kocharin
20c97527fb 0.11.2 2014-09-18 01:46:03 +04:00
Alex Kocharin
35b3efb058 sinopia-htpasswd@0.4.2
fix #121
2014-09-18 01:44:30 +04:00
Alex Kocharin
8d1781489b tag_version should return if tag is fresh 2014-09-18 00:04:15 +04:00
Alex Kocharin
92a33acf45 0.11.1 2014-09-15 18:55:11 +04:00
Alex Kocharin
48a35b6a01 mark crypt3 as optional
fix #119
2014-09-15 18:54:46 +04:00
Alex Kocharin
8db7c4355e fix history 2014-09-15 17:00:00 +04:00
Alex Kocharin
d0a1013131 0.11.0 2014-09-15 01:27:41 +04:00
Alex Kocharin
936c0b607b 0.11.0 2014-09-15 01:27:32 +04:00
Alex Kocharin
1123a440bc "users_file" -> "file" in config 2014-09-14 19:57:39 +04:00
Alex Kocharin
8f0039de92 '@all' -> '$all' (avoid yaml identifiers) 2014-09-14 19:49:15 +04:00
Alex Kocharin
f8b0ae6c6f 0.10.3 2014-09-14 19:28:57 +04:00
Alex Kocharin
188ab10871 0.10.3 2014-09-14 19:28:50 +04:00
Alex Kocharin
a016077bf3 fix auth system 2014-09-14 19:22:24 +04:00
Alex Kocharin
834c155da4 0.10.2 2014-09-14 19:06:39 +04:00
Alex Kocharin
19b1d76e98 hotfix: auth groups didn't work correctly 2014-09-14 19:06:27 +04:00
Alex Kocharin
c43846f14e 0.10.1 2014-09-14 18:49:36 +04:00
Alex Kocharin
fd8d481be4 added missing dependency - rimraf 2014-09-14 18:49:22 +04:00
Alex Kocharin
8576201cc1 0.10.0 2014-09-14 18:41:54 +04:00
Alex Kocharin
0837025a74 added nan dep for crypt3 2014-09-14 18:37:21 +04:00
Alex Kocharin
5468c4e767 0.10.0 2014-09-14 18:35:34 +04:00
Alex Kocharin
34c12de81c importing all bundled deps to git 2014-09-14 18:28:17 +04:00
Alex Kocharin
a5cd498f92 use http-errors package instead of custom stuff 2014-09-10 21:55:26 +04:00
Alex Kocharin
dbb588f031 Merge branch '0.9.x' of github.com:rlidwka/sinopia 2014-09-07 01:25:08 +04:00
Alex Kocharin
720b98b715 0.9.3 2014-09-07 01:23:35 +04:00
Alex Kocharin
efd921d876 0.9.3 2014-09-07 01:23:24 +04:00
Alex Kocharin
1e28c2d949 fix double fs.closing + EBADF error 2014-09-07 01:20:28 +04:00
Alex Kocharin
a0c861ec5a forgot to resolve path to storage 2014-09-07 00:57:40 +04:00
Alex Kocharin
8d841ea8e3 hide local-list.json to storage/.sinopia-db.json
It was always in the current folder, which is a bad thing.
2014-09-07 00:50:34 +04:00
Alex Kocharin
504430dcdd temporarily disable web interface in config
I want to release 0.10 soon, and web doesn't have auth system yet.

So we'll probably disable it for now, and re-enable when its ready.
2014-09-06 23:53:28 +04:00
Alex Kocharin
435ff1beeb Merge branch 'auth' 2014-09-06 13:07:52 +04:00
Alex Kocharin
6e247829a3 auth refactoring: part 2
auth plugins
2014-09-06 13:07:10 +04:00
Alex Kocharin
99795b747c undefined variable fix 2014-09-06 13:07:10 +04:00
Alex Kocharin
3a325a1e04 auth refactoring, part 1
moving stuff to auth.js basically + allowing usergroups
2014-09-06 13:07:10 +04:00
Alex Kocharin
734565dc05 auth refactoring: part 3
moving htpasswd out
2014-09-06 13:03:22 +04:00
Alex Kocharin
697d5f0892 Merge branch '0.9.x' of github.com:rlidwka/sinopia 2014-09-03 16:20:05 +04:00
Alex Kocharin
409d25739e fix "can't set headers" exception
see discussion in #113
2014-09-03 16:18:27 +04:00
Alex Kocharin
65a792ba53 Merge branch '0.9.x' of github.com:rlidwka/sinopia 2014-09-03 15:44:24 +04:00
Alex Kocharin
4239107ed2 0.9.2 2014-09-03 15:40:48 +04:00
Alex Kocharin
f512925fd4 0.9.2 2014-09-03 15:40:42 +04:00
Alex Kocharin
b740dd11cf remove outdated user existence check
doesn't work with htpasswd, and doesn't make sense with
future auth plugins

ref #115
2014-09-03 15:31:57 +04:00
Alex Kocharin
430a479113 auth refactoring: part 2
auth plugins
2014-09-02 04:27:04 +04:00
Alex Kocharin
86394b25ee undefined variable fix 2014-09-02 04:26:54 +04:00
Alex Kocharin
8086c6f0bf auth refactoring, part 1
moving stuff to auth.js basically + allowing usergroups
2014-09-02 03:09:08 +04:00
Alex Kocharin
7742e11cde Merge pull request #111 from jeremymoritz/master
fixed typo in README.md
2014-08-29 10:41:19 +04:00
Jeremy Moritz
2597fcae80 fixed typo in README.md 2014-08-28 14:40:14 -05:00
Alex Kocharin
5dd720cc21 add stuff for testing against fd leaks and memory leaks 2014-08-15 01:37:46 +04:00
Alex Kocharin
fdbde9eb87 add .editorconfig to fix indentation issues 2014-08-13 10:23:17 +04:00
Alex Kocharin
7e5227e4a1 allow "pretty" format for logging into files
ref #88
2014-08-11 08:46:20 +04:00
Alex Kocharin
60ae815c09 make tests more verbose for travis 2014-08-11 08:09:18 +04:00
Alex Kocharin
adecd99866 ignore errors for 0.11 branch (native modules are failing) 2014-08-11 07:16:15 +04:00
Alex Kocharin
932fa34edc travis + badges 2014-08-11 06:09:25 +04:00
Alex Kocharin
75fc0ae472 Merge remote-tracking branch 'origin/0.9.x'
Conflicts:
	package.yaml
2014-08-11 03:57:13 +04:00
Alex Kocharin
10433438d0 0.9.1 2014-08-11 03:55:43 +04:00
Alex Kocharin
c84bc05d10 0.9.1 2014-08-11 03:55:34 +04:00
Alex Kocharin
33412c4613 relax express versions for sinopia@0.9.x branch 2014-08-11 03:53:49 +04:00
Alex Kocharin
4bc965b45b WIP: review all dependency versions, add bundledDependencies 2014-08-11 03:52:09 +04:00
Alex Kocharin
6704abf988 bugfixes for webui 2014-08-08 06:17:05 +04:00
Alex Kocharin
d9f84677f8 fix tests broken by webui 2014-08-08 05:58:25 +04:00
Alex Kocharin
e1880ce19e Merge remote-tracking branch 'origin/0.9.x' 2014-08-08 05:26:05 +04:00
Alex Kocharin
478d75fec4 update history 2014-08-08 05:21:08 +04:00
Alex Kocharin
77700e72b1 Merge remote-tracking branch 'origin/0.9.x' 2014-08-08 05:15:22 +04:00
Alex Kocharin
5d4068d539 remove undefined variable 2014-08-08 05:14:27 +04:00
Alex Kocharin
57d6fe7150 Merge remote-tracking branch 'origin/0.9.x'
Conflicts:
	lib/local-storage.js
2014-08-08 05:12:42 +04:00
Alex Kocharin
a8dc1b2f7b option not to cache third party files
ref #85
2014-08-08 05:08:41 +04:00
John Gozde
060dcb49b4 Filter falsey _npmUser values. 2014-08-08 03:15:39 +04:00
Keyvan Fatehi
7981d55860 Point to the new docker image 2014-08-08 03:15:29 +04:00
Alex Kocharin
8aa6754875 don't receive future updates to express.js
... until this gets resolved:
https://github.com/strongloop/express/issues/2264

ref #92
2014-08-08 03:15:06 +04:00
Alex Kocharin
4660dd3b7f Merge pull request #95 from jgoz/null-npmUser
Filter falsey _npmUser values
2014-08-02 00:36:30 +04:00
John Gozde
58046a2ec7 Filter falsey _npmUser values. 2014-08-01 14:08:44 -06:00
Alex Kocharin
008423d49b Merge pull request #94 from keyvanfatehi/patch-2
Point to the new docker image
2014-08-01 17:45:36 +04:00
Keyvan Fatehi
3eb09b8b07 Point to the new docker image 2014-07-31 22:29:21 -05:00
Alex Kocharin
888cfe3f1e don't receive future updates to express.js
... until this gets resolved:
https://github.com/strongloop/express/issues/2264

ref #92
2014-07-31 18:12:26 +04:00
Alex Kocharin
4545076b0f ignore anything in bin/ folder 2014-07-26 21:27:32 +04:00
Alex Kocharin
854fd796ae auto-compile package.json on "npm install" 2014-07-26 21:27:10 +04:00
Alex Kocharin
77af6d4c17 remove package.json from git index on recompile 2014-07-26 21:00:43 +04:00
Alex Kocharin
21704c9f84 removing unused code 2014-07-26 20:46:17 +04:00
Alex Kocharin
4f913f2468 Merge https://github.com/bpeacock/sinopia.git
Conflicts:
	lib/config.js
	lib/config_def.yaml
	lib/index.js
	lib/local-storage.js
	lib/storage.js
	package.json
2014-07-26 20:36:22 +04:00
Alex Kocharin
859f1bb959 0.9.0 2014-07-26 17:23:41 +04:00
Alex Kocharin
8d8963d37a 0.9.0 2014-07-26 17:23:21 +04:00
Alex Kocharin
c2d3fe9929 add comments about registration 2014-07-26 17:14:06 +04:00
Alex Kocharin
3809d6eb32 add a bunch of tests for htpasswd 2014-07-23 01:45:28 +04:00
Alex Kocharin
490340fbb0 tweaking messages and status codes for user login 2014-07-23 01:44:06 +04:00
Alex Kocharin
a528811e35 bugfixes for htpasswd authentication 2014-07-22 23:48:15 +04:00
Alex Kocharin
9275b2cc85 "msg" -> "message"
former one created too much confusion
2014-07-22 23:31:01 +04:00
Alex Kocharin
48b7031074 fix mocha debug mode 2014-07-22 22:24:19 +04:00
Alex Kocharin
41d4997ea7 only close socket if it exists, fix #89 2014-07-21 19:06:44 +04:00
Alex Kocharin
ff8a5e99ec add user registration 2014-07-21 17:02:02 +04:00
Alex Kocharin
9a14a6e022 change event to postinstall (npm doesn't always run prepublish?) 2014-07-10 17:22:10 +00:00
Alex Kocharin
404acfae49 install -> prepublish, add version 2014-07-10 17:19:17 +00:00
Alex Kocharin
1e438d7dae make "npm install rlidwka/sinopia" possible 2014-07-10 17:10:40 +00:00
Alex Kocharin
e929e089d0 basic support for .htpasswd 2014-06-26 20:21:23 +04:00
Alex Kocharin
81486f412f make authentication function async 2014-06-26 19:23:21 +04:00
Alex Kocharin
5cc0187b67 fix tests 2014-06-24 06:57:54 +04:00
Alex Kocharin
d9accbb6a7 better access control for search
For each of the packages check if user has access to it and remove
package info from the result if he doesn't.

ref #65
2014-06-24 06:50:05 +04:00
Alex Kocharin
17d8ab7dcd use req.query instead of manually parsing querystring
ref #65
2014-06-24 06:48:58 +04:00
Alex Kocharin
3f746eb7c8 fix logging: in search "error" can be legitimate output (package named "error"), not an actual error
ref #65
2014-06-24 06:46:35 +04:00
Alex Kocharin
9816059485 Merge pull request #65 from yannickcr/npm-search
Add search functionality
2014-06-24 06:25:28 +04:00
Alex Kocharin
fe058b716b 0.8.2 2014-06-20 08:38:17 +04:00
Alex Kocharin
dc7d7dfd53 0.8.2 2014-06-20 08:38:00 +04:00
Alex Kocharin
3e514c2c3b add hyperlinks to issue references 2014-06-20 08:17:58 +04:00
Alex Kocharin
703adeaffc socket could've been closed twice, fix #80 2014-06-20 08:12:37 +04:00
Alex Kocharin
c4e5cb71b4 eslint version bump 2014-06-18 05:59:22 +04:00
Alex Kocharin
73572b0839 fix #77, config assert() has no effect 2014-06-18 04:52:07 +04:00
Alex Kocharin
88f3944f5a whitelist all allowed characters in filenames, see #75 2014-06-18 02:01:58 +04:00
Alex Kocharin
20524118ef Merge pull request #78 from josephg/master
Allow tarballs with @ in them
2014-06-18 01:57:34 +04:00
Joseph Gentle
68bf469ad4 Fixed unit tests 2014-05-15 16:17:48 -07:00
Joseph Gentle
21f7f03cf7 Fixed sinopia to allow old tarballs with @ in them 2014-05-15 16:07:33 -07:00
Brian Peacock
96126e5a2f Added back in package.json so that it can be installed from git repo 2014-05-13 17:14:25 -05:00
Brian Peacock
490b04f298 Removed 'root' config parameter 2014-05-13 17:12:21 -05:00
Brian Peacock
abacb64479 formatting 2014-05-12 10:49:45 -05:00
Brian Peacock
2a299c9201 Switched some tabs to spaces 2014-05-12 10:43:18 -05:00
Brian Peacock
39083b819f Switched back to package.yaml 2014-05-12 10:42:04 -05:00
Brian Peacock
228f1512d7 Typo 2014-05-12 09:37:18 -05:00
Brian Peacock
f5b9e0e0d5 Added default logo to the page 2014-05-12 09:35:53 -05:00
Brian Peacock
daaee2b971 Updated README to reflect changes 2014-05-12 09:29:29 -05:00
Brian Peacock
a38cf312b2 Updated the config 2014-05-12 09:21:07 -05:00
Brian Peacock
ab44907dac Fixed bug with creating users with hashed passwords 2014-05-09 13:26:47 -05:00
Brian Peacock
1246f12f5a Added CSS for no logo 2014-05-09 09:19:42 -05:00
Brian Peacock
99b8c31d3a Added the ability to add users 2014-05-08 19:24:41 -05:00
Brian Peacock
dbf3301ff9 Added favicon 2014-05-08 18:03:14 -05:00
Brian Peacock
a6fe4a1516 Added logo support 2014-05-08 17:58:13 -05:00
Brian Peacock
277f1023c9 Fixed unpublish 2014-05-08 16:48:15 -05:00
Brian Peacock
820ae3f27c Tabs to spaces 2014-05-08 16:35:06 -05:00
Brian Peacock
d52cfe8bac Fixed issue publishing unpublished modules 2014-05-08 16:34:16 -05:00
Brian Peacock
f74d7cbd3e Added fixed header 2014-05-08 15:41:59 -05:00
Brian Peacock
7e9b5944fd Removed logging 2014-05-08 14:47:54 -05:00
Brian Peacock
6fb0be9cc0 Fixed search UI bugs 2014-05-08 14:47:24 -05:00
Brian Peacock
1ddc1c68dc Added icons, smoother animations, search readme's 2014-05-08 11:13:39 -05:00
Brian Peacock
8b3dc9072f Added nifty transitions 2014-05-07 16:51:03 -05:00
Brian Peacock
112e551240 Fixed issue with no packages and the template 2014-05-07 15:53:24 -05:00
Brian Peacock
7b4bac1874 Added url config option 2014-05-07 15:43:22 -05:00
Brian Peacock
baa7f78e83 Nicer formamtting 2014-05-07 15:36:03 -05:00
Brian Peacock
45e4208218 Added cases for no local packages 2014-05-07 15:31:25 -05:00
Brian Peacock
1e48eacbf8 Changed the name back 2014-05-07 15:10:06 -05:00
Brian Peacock
9e062994fe Modified the search box 2014-05-07 15:00:36 -05:00
Brian Peacock
4f155f5004 Added highligting CSS 2014-05-07 14:56:48 -05:00
Brian Peacock
c0ee2db8a4 Added readme API with markdown support 2014-05-07 14:28:10 -05:00
Brian Peacock
5464b449ce Formatted entries 2014-05-07 13:36:48 -05:00
Brian Peacock
f2e433d52b Formatting 2014-05-07 13:26:44 -05:00
Brian Peacock
11fdc9340e Search works 2014-05-07 13:08:29 -05:00
Brian Peacock
1e71e2faa0 Fixed grunt build location 2014-05-07 11:29:04 -05:00
Brian Peacock
2806de2a5d Added static resources 2014-05-07 11:27:51 -05:00
Brian Peacock
27c032d53f Added a search class 2014-05-07 10:29:47 -05:00
Brian Peacock
c47f73f799 Added pack in user put 2014-05-07 10:11:48 -05:00
Brian Peacock
2901e619cb Fixed refresh issue 2014-05-07 10:10:59 -05:00
Brian Peacock
5a98db9589 Added Setup instructions 2014-05-06 17:48:15 -05:00
Brian Peacock
34b631fa12 Added listings 2014-05-06 17:40:21 -05:00
Brian Peacock
bb129c1151 Got handlebars templates working right 2014-05-06 17:04:03 -05:00
Brian Peacock
37afd429ec Added basic GUI 2014-05-06 16:34:48 -05:00
Brian Peacock
edee08a778 Fixed some things 2014-05-06 14:57:50 -05:00
Brian Peacock
0395d958cf Made it run... 2014-05-06 14:01:13 -05:00
Brian Peacock
9e51798295 Made it a regular npm module 2014-05-06 12:06:10 -05:00
Brian Peacock
cb6e2cd7f6 More specific express version to try to get old node working. 2014-05-06 11:34:09 -05:00
Alex Kocharin
a79fa68deb lint 2014-04-14 00:44:17 +00:00
Alex Kocharin
fabf3eff4c 0.8.1 2014-04-14 00:37:07 +00:00
Alex Kocharin
6a60650073 0.8.1 2014-04-14 00:36:57 +00:00
Alex Kocharin
6eada0f22e preserve tags when publishing something (ref #63) 2014-04-13 02:04:48 +00:00
Yannick Croissant
5241ddfe84 Add search functionality 2014-04-12 19:20:26 +02:00
Alex Kocharin
bea03619d7 add note about fs-ext 2014-04-01 00:21:51 +00:00
Alex Kocharin
8f03805697 update package.yaml, set publishConfig 2014-04-01 00:19:52 +00:00
Alex Kocharin
d0c2ac0f9d 0.8.0 2014-04-01 00:17:10 +00:00
Alex Kocharin
d820a030a1 0.8.0 2014-04-01 00:17:03 +00:00
Alex Kocharin
b4c0b89365 set ignore_latest_tag to false by default 2014-04-01 00:13:59 +00:00
Alex Kocharin
752d0f62f0 make fs-ext optional 2014-03-31 04:32:11 +00:00
Alex Kocharin
9404e811a5 accept gzip from uplinks, fix #54 2014-03-30 21:05:42 +00:00
Alex Kocharin
2102e71c10 removing outdated todo 2014-03-30 19:43:03 +00:00
Alex Kocharin
f023826a59 0.7.1 2014-03-29 06:16:07 +00:00
Alex Kocharin
f03512d32b 0.7.1 2014-03-29 06:15:59 +00:00
Alex Kocharin
7f56593ee3 document new config option 2014-03-29 06:15:43 +00:00
Alex Kocharin
2995858bb0 updating eslint config 2014-03-29 05:38:21 +00:00
Alex Kocharin
a8cdfcd7cd add err.status instead of checking for exact error message
it's done for consistency reasons
2014-03-29 04:32:05 +00:00
Alex Kocharin
4470cb7d55 making latest tag behaviour configurable 2014-03-29 02:31:34 +00:00
Alex Kocharin
4b06026d2e Revert "Make 404 responses compatible with CouchDB API"
This reverts commit dabf5e1c9a.

See discussion in #57
2014-03-29 01:08:28 +00:00
Alex Kocharin
7967d5857d Revert "fix logs and tests for #56"
This reverts commit df49fb84c1.
2014-03-29 01:08:00 +00:00
Alex Kocharin
4ea84e8b74 0.7.0 2014-03-13 19:48:42 +00:00
Alex Kocharin
bf63b9f738 0.7.0 2014-03-13 19:47:49 +00:00
Alex Kocharin
8af77df0e0 fix zero timeouts in config 2014-03-13 19:45:47 +00:00
Alex Kocharin
df49fb84c1 fix logs and tests for #56 2014-03-13 19:23:21 +00:00
Alex Kocharin
d657e180d5 fix tests 2014-03-13 19:19:02 +00:00
Alex Kocharin
0afd9d1bb3 Merge pull request #56 from strongloop/fix-unknown-package-name-response
Make 404 responses compatible with CouchDB API
2014-03-13 19:15:04 +00:00
Alex Kocharin
f839837f34 document new config options 2014-03-13 18:56:31 +00:00
Alex Kocharin
a030e1110d release 0.7.0 2014-03-13 18:56:14 +00:00
Miroslav Bajtoš
dabf5e1c9a Make 404 responses compatible with CouchDB API
The CouchDB REST API returns always `"error": "not_found"` in the body
of a 404 response:
  http://couchdb-13.readthedocs.org/en/latest/api-basics/#http-status-codes

The npm client depends on the magic string 'not_found' as can be seen
in requestDone() in npm-registry-client/lib/request.js.

Before this change, npm install of an unknown package was reporting
the Sinopia error string and a stack trace of npm.

After this change, npm install of an unknown package returns a nice
error saying "the package is not in the npm registry, bug the author"
2014-03-13 19:47:44 +01:00
Alex Kocharin
bb9612441c trim intervals 2014-03-08 04:38:37 +00:00
Alex Kocharin
48825a2e46 change intervals formatting to match nginx
see http://wiki.nginx.org/ConfigNotation
2014-03-08 04:37:16 +00:00
Alex Kocharin
6a2a463b76 count each failed request only once 2014-03-08 04:00:07 +00:00
Alex Kocharin
f1ec18dc4b implement nginx-like logic to avoid hitting upstream when it's down 2014-03-08 03:54:28 +00:00
Alex Kocharin
6b9001ef6c change interval formatting in config
All intervals are now in milliseconds. But you can add
multiples ("ms", "s", "m", "h", "d", "M", "y") to set
value using different units.

For example, value "1.5h" would mean 1.5 hours.
2014-03-08 03:49:59 +00:00
Alex Kocharin
9ff1203688 throw away incomplete downloads 2014-03-07 19:48:24 +00:00
Alex Kocharin
a891354a32 always return content-length for tarballs 2014-03-07 19:39:20 +00:00
Alex Kocharin
9c4c93695b fix crash in #52 2014-03-07 18:20:41 +00:00
Alex Kocharin
3b510437a8 Merge pull request #47 from samcday/maxage-support
Support maxage for uplinks
2014-03-06 07:11:07 +00:00
Alex Kocharin
ed1ec0c27a eslint version bump 2014-03-05 20:07:38 +00:00
Alex Kocharin
526f61b136 0.6.7 2014-03-05 19:16:47 +00:00
Alex Kocharin
568058d7d9 pin down express@3 with backward compat semver range
closes #49
2014-03-05 19:16:19 +00:00
Alex Kocharin
b77da91094 Merge branch 'master' of github.com:rlidwka/sinopia 2014-03-05 15:18:39 +00:00
Alex Kocharin
e2457e9e5c 0.6.6 2014-03-05 14:20:20 +00:00
Alex Kocharin
c6714cd591 pin express@3 dependency 2014-03-05 14:19:41 +00:00
Sam Day
58e98c7263 Support maxage for uplinks 2014-03-02 20:47:09 +11:00
Alex Kocharin
234deb4e7e Merge pull request #46 from BarthV/patch-1
adding Chef Cookbook to README
2014-03-01 08:48:06 +00:00
Barthélemy Vessemont
4466cf6be1 Chef Cookbook is out !
Hi !

http://community.opscode.com/cookbooks/sinopia - https://github.com/BarthV/sinopia-cookbook

I made a Chef cookbook, it provides a Sinopia server for ubuntu 12.04 (for the moment) and covers most of the config.yaml options. You can easily test this cookbook with Vbox & Vagrant.

I would be happy to help Sinopia, and reply to any questions/issues about my work.

Feel free to provides PR on sinopia-cookbook !

Thanks.
2014-02-28 22:49:08 +01:00
Alex Kocharin
cf5dddb174 republishing as 0.6.5 2014-02-28 11:30:08 +00:00
Alex Kocharin
70506d54ee 0.6.5 2014-02-28 11:29:08 +00:00
Alex Kocharin
09781df60a 0.6.4 2014-02-28 11:26:02 +00:00
Alex Kocharin
c730670711 version bump 2014-02-28 11:25:51 +00:00
Alex Kocharin
691d62f403 eslint update + style fixes 2014-02-23 21:20:50 +04:00
Alex Kocharin
a5d0094669 remove npmsslkeys
See commit in npmconf:
e0b4a4e5cc
2014-02-23 20:55:41 +04:00
Alex Kocharin
b65cf7afe3 move eslint config to yaml 2014-02-07 00:56:46 +04:00
Alex Kocharin
e2e2c39148 0.6.3 2014-02-03 00:54:22 +04:00
Alex Kocharin
de4dbd234a 0.6.3 2014-02-03 00:51:59 +04:00
Alex Kocharin
440f42415c adding test for invalid tags, fixes #40 2014-02-03 00:50:51 +04:00
Alex Kocharin
8840ac4c1f removing bad statement 2014-02-03 00:50:38 +04:00
Alex Kocharin
9f662a69e1 validate all url parameters better 2014-02-01 12:08:48 +04:00
Alex Kocharin
c098eb4661 Merge pull request #43 from saheba/link_to_puppet_module
added link to published puppet-sinopia module
2014-01-24 13:20:05 -08:00
saheba
2c28f3549e added link to published puppet-sinopia module
automates sinopia installations on unix machines with puppet.
2014-01-24 13:56:19 +01:00
Alex Kocharin
616ec7626a process.getuid doesn't always exist (fixes #41) 2014-01-24 06:36:03 +03:00
Alex Kocharin
9a915511b3 0.6.2 2014-01-18 23:07:50 +04:00
Alex Kocharin
0d37933259 version bump 2014-01-18 23:07:29 +04:00
Alex Kocharin
2add883370 adding config param to specify upload limits, fixes #39 2014-01-18 23:04:12 +04:00
Alex Kocharin
e522347667 making loose semver versions work, ref #38 2014-01-18 22:57:44 +04:00
Alex Kocharin
8987ee0b2a Merge branch 'master' of github.com:rlidwka/sinopia 2014-01-13 23:46:47 +04:00
Alex Kocharin
ceb9b5be1d 0.6.1 2014-01-13 23:41:23 +04:00
Alex Kocharin
6c258758c8 support setting different storage paths for different packages
fixes #35
2014-01-13 22:56:36 +04:00
Alex Kocharin
dadbf3a18b rename ChangeLog to History 2014-01-13 22:02:33 +04:00
Alex Kocharin
c18ac8015a update dependencies 2014-01-13 22:02:08 +04:00
Alex Kocharin
68d3cc7295 fs interface refactoring 2014-01-13 20:48:51 +04:00
Alex Kocharin
8ebe73d1f1 Merge pull request #34 from keyvanfatehi/patch-1
Add docker-sinopia link to README
2014-01-06 22:46:10 -08:00
Keyvan Fatehi
75ea41a55b Add docker-sinopia link to README 2014-01-06 19:09:33 -08:00
Alex Kocharin
d519e8e763 0.6.0 2013-12-30 12:32:20 +04:00
Alex Kocharin
bb6b31e7c0 v0.6.0 2013-12-30 12:29:54 +04:00
Alex Kocharin
5d19b66290 adding integration tests 2013-12-30 12:25:26 +04:00
Alex Kocharin
5422de642e tag support, closes #8 2013-12-29 10:41:31 +04:00
Alex Kocharin
02f8143097 tests for tags support 2013-12-29 10:40:47 +04:00
Alex Kocharin
3c7ae2f37d a few bugfixes 2013-12-29 04:58:48 +04:00
Alex Kocharin
6075034521 proxy_access -> proxy (since we're removing proxy_publish) 2013-12-29 04:54:46 +04:00
Alex Kocharin
d1a1a8f4cd bugfix 2013-12-29 04:54:39 +04:00
Alex Kocharin
54535893ab adding support for new npm behaviour, closes #31 2013-12-29 04:53:31 +04:00
Alex Kocharin
8c05cfe6a2 require heapdump if present on the system 2013-12-29 04:52:23 +04:00
Alex Kocharin
6c838c7947 remove all replication-like functionality
apparently it was a bad idea, it's simpler to just run a single
sinopia instance as a master

TODO: write some help in readme about it
2013-12-27 17:23:14 +04:00
Alex Kocharin
f3f4fdc4ac tests 2013-12-27 17:06:57 +04:00
Alex Kocharin
985d705ad2 working on tags / incomplete 2013-12-27 17:06:30 +04:00
Alex Kocharin
6234f8cba9 Merge branch 'master' of github.com:rlidwka/sinopia 2013-12-27 17:05:05 +04:00
Alex Kocharin
c0e34890e4 req.user -> req.remoteUser 2013-12-27 15:29:23 +04:00
Alex Kocharin
b42687d230 Merge tag 'v0.5.9' of github.com:rlidwka/sinopia
0.5.9
2013-12-26 23:46:08 +04:00
Alex Kocharin
4ce0142651 0.5.9 2013-12-26 23:25:27 +04:00
Alex Kocharin
b44255de3c set right Accept header for tarballs, closes #32 2013-12-26 23:25:19 +04:00
Alex Kocharin
b0fa7ee2d1 using eslint to check the code 2013-12-23 04:14:57 +04:00
Alex Kocharin
1c17291654 0.5.8 2013-12-21 16:31:25 +04:00
Alex Kocharin
3b2bd161b7 logging bad versions 2013-12-21 16:09:29 +04:00
Alex Kocharin
d29e22ed4b version bump 2013-12-21 13:20:25 +04:00
Alex Kocharin
1a0f577689 adding nice screenshot 2013-12-21 12:06:38 +03:00
Alex Kocharin
a3a59aa8f3 reorganize tests, and add new ones 2013-12-19 19:11:54 +04:00
Alex Kocharin
3a31064ded deal with js-yaml warning 2013-12-19 18:50:31 +04:00
Alex Kocharin
151136381e style fix 2013-12-19 07:18:45 +04:00
Alex Kocharin
503b60a45b catch bind errors 2013-12-19 07:17:52 +04:00
Alex Kocharin
225c2bb65f added a warning to not run it under root 2013-12-17 20:25:17 +04:00
Alex Kocharin
8ea985ff6b don't color multiline strings in logs output
stack traces should be easily recognizable
2013-12-16 03:07:19 +04:00
Alex Kocharin
0cec69ee1d Merge branch 'master' of github.com:rlidwka/sinopia 2013-12-16 00:58:09 +04:00
Alex Kocharin
d5d2a8bdcc moving unit tests to separate folder 2013-12-16 00:54:50 +04:00
Alex Kocharin
693aa576b4 safeguard against bad tarball names 2013-12-16 00:54:29 +04:00
Alex Kocharin
3abce5e2b6 better error message when publishing with bad auth header 2013-12-16 00:38:16 +04:00
Alex Kocharin
5c6d6301ef 0.5.7 2013-12-15 04:06:19 +04:00
Alex Kocharin
f43ec71ccb 0.5.7 2013-12-15 04:05:58 +04:00
Alex Kocharin
ef61515f28 try to fetch package from uplinks if user requested a tarball we don't know about
closes #29 + tests fix
2013-12-15 03:15:58 +04:00
Alex Kocharin
9094d0742f better error message for incorrect package names 2013-12-15 03:09:55 +04:00
Alex Kocharin
ede09b5c77 refactoring: move uplink.get_package() calls to it's own function 2013-12-13 20:50:41 +04:00
Alex Kocharin
19e4d78197 test suite for #29 (nothing is fixed yet) 2013-12-13 20:49:48 +04:00
Alex Kocharin
380ec1e917 always end response with a newline 2013-12-13 19:12:24 +04:00
Alex Kocharin
ee16b06b3a style fix 2013-12-13 18:00:46 +04:00
Alex Kocharin
8149f883b0 default express.js to production mode, not development 2013-12-12 03:19:40 +04:00
Alex Kocharin
c75249b5b0 commenting out tests that doesn't work yet 2013-12-12 02:02:20 +04:00
Alex Kocharin
942a7747ac fixing tests 2013-12-12 02:01:23 +04:00
Alex Kocharin
96b336acc3 if uplink doesn't answer, stop sending requests to it for a while (2 min default) 2013-12-12 02:00:26 +04:00
Alex Kocharin
520a3b0220 cleanup 2013-12-12 01:55:40 +04:00
Alex Kocharin
230750ff88 adding sinopia version to logs 2013-12-12 01:55:17 +04:00
Alex Kocharin
84a6e36d84 semver version bump 2013-12-12 01:54:36 +04:00
Alex Kocharin
203f0300e3 Merge branch '0.5.x' of github.com:rlidwka/sinopia
Conflicts:
	lib/storage.js
	test/start.sh
2013-12-12 01:44:29 +04:00
Alex Kocharin
1cb12fec35 0.5.6 2013-12-12 01:27:34 +04:00
Alex Kocharin
e5195ecfb5 version bump 2013-12-12 01:27:21 +04:00
Alex Kocharin
7ee2361700 better tests for various tags (including bad ones), ref #21 2013-12-12 01:22:35 +04:00
Alex Kocharin
47a92ff273 fail-safe semver parsing + tests, ref #25 2013-12-12 00:51:48 +04:00
Brett Trotter
fc99692219 Update semver.compare to semver.compareLoose to tolerate grunt and other packages 2013-12-12 00:35:07 +04:00
Alex Kocharin
bd7dde229a Merge branch 'master' of github.com:rlidwka/sinopia 2013-12-11 21:40:41 +04:00
Alex Kocharin
21a5c788e1 Merge pull request #26 from bretttrotter-uar/test-exit-status
exit with appropriate status code
2013-12-11 09:23:22 -08:00
Brett Trotter
0b286d1fe3 exit with appropriate status code 2013-12-11 09:04:35 -06:00
Alex Kocharin
987f540cde Merge pull request #25 from bretttrotter-uar/loosen-semver
Update semver.compare to semver.compareLoose to tolerate grunt and other...
2013-12-11 05:53:46 -08:00
Alex Kocharin
a11da1545a 0.5.5 2013-12-11 17:04:32 +04:00
Alex Kocharin
d149252a3b fixing crash on bad tags 2013-12-11 17:04:08 +04:00
Brett Trotter
ed3386f044 Update semver.compare to semver.compareLoose to tolerate grunt and other packages 2013-12-10 13:04:47 -06:00
Alex Kocharin
36fa1ba655 log fatal errors 2013-12-10 14:29:46 +04:00
Alex Kocharin
77182a755d expose sinopia's version in Server header for debugging purposes 2013-12-10 14:12:34 +04:00
Alex Kocharin
f05a4784fd fixing crash on bad tags 2013-12-10 13:55:35 +04:00
Alex Kocharin
7f7c056ecd various bugfixes, camelcase for http headers 2013-12-09 08:00:16 +04:00
Alex Kocharin
bbb402f762 detecting http loops 2013-12-09 07:59:31 +04:00
Alex Kocharin
4d70d8065e add via tag to prevent loops 2013-12-09 07:58:25 +04:00
Alex Kocharin
3617a91f87 0.5.4 2013-12-08 04:56:14 +04:00
Alex Kocharin
03445c4c49 version bump 2013-12-08 04:55:52 +04:00
Alex Kocharin
2f45649a2c improving tags support, ref #8 2013-12-08 02:55:50 +04:00
Alex Kocharin
08d1011433 use standard \033 escape sequence for vt esc codes 2013-12-08 02:54:26 +04:00
Alex Kocharin
6e71913c46 setting default timeout to 30 seconds, #18 2013-12-08 02:37:27 +04:00
Alex Kocharin
cc7165dd3c Merge branch 'master' of github.com:rlidwka/sinopia 2013-12-06 21:49:31 +04:00
Alex Kocharin
a257fc3962 swallow bad auth errors, fixes #17 2013-12-06 21:46:51 +04:00
Alex Kocharin
6a295ac196 don't send etags with errors 2013-12-06 21:46:11 +04:00
Alex Kocharin
ff52b00a7c Merge pull request #18 from iambrandonn/master
Adding a configurable timeout for each uplink
2013-12-05 09:31:35 -08:00
Alex Kocharin
9ee8d484db dealing with internal errors in express.js 2013-12-05 16:31:21 +04:00
Alex Kocharin
7b0ab14d4c dealing with express.js errors 2013-12-05 16:27:23 +04:00
Brandon Nicholls
1d109f8071 Adding a configurable timeout for each uplink 2013-12-04 12:39:29 -07:00
Alex Kocharin
75e78b4137 0.5.3 2013-11-24 21:19:02 +04:00
Alex Kocharin
2e4a4b1dc1 version 0.5.3 changelog 2013-11-24 21:18:18 +04:00
Alex Kocharin
98d2c3ae18 tests fix 2013-11-24 21:13:21 +04:00
Alex Kocharin
8f05e141c3 retrieving proxy values from environment if present 2013-11-24 21:08:20 +04:00
Alex Kocharin
1df6d53612 config examples for proxy 2013-11-24 21:07:54 +04:00
Alex Kocharin
13242c8237 add proxy support, fix #13 2013-11-24 21:07:18 +04:00
Alex Kocharin
fecffa2a39 return an appropriate X-Status-Cat header 2013-11-24 21:06:01 +04:00
Alex Kocharin
6a7226bb83 Merge branch 'master' of github.com:rlidwka/sinopia 2013-11-24 21:03:05 +04:00
Alex Kocharin
fbb98b1101 Merge pull request #12 from Qwerios/master
Updated documentation, #12
2013-11-19 22:44:36 -08:00
Mark Doeswijk
9f2871f381 Added listen all documentation 2013-11-19 13:26:32 +01:00
Mark Doeswijk
558598c6be Added information about how to add a new user 2013-11-19 11:56:38 +01:00
Mark Doeswijk
e6ce764750 Added documentation for how to setup sinopia on a linux server 2013-11-19 11:53:53 +01:00
Alex Kocharin
aca1dc57d7 taking $PATH into account when launching tests 2013-10-28 10:01:27 +04:00
Alex Kocharin
b9d5066f82 changing license to more (in)appropriate 2013-10-26 19:52:24 +04:00
Alex Kocharin
15d46e3280 log 'new packages directory' message at a warn level 2013-10-26 16:20:42 +04:00
Alex Kocharin
b2f6128e9c style fix 2013-10-26 16:18:36 +04:00
Alex Kocharin
aba48ee6dd 0.5.2 2013-10-26 15:43:59 +04:00
Alex Kocharin
5da3c8ba58 transaction stub 2013-10-26 15:43:42 +04:00
Alex Kocharin
3f8166fa19 0.5.2 2013-10-26 15:43:15 +04:00
Alex Kocharin
dafcf8647c basic support for unpublishing individual versions (local only) 2013-10-23 10:15:17 +04:00
Alex Kocharin
6ae26226eb removing all tarballs on unpublish --force 2013-10-22 17:33:39 +04:00
Alex Kocharin
3a407396b8 better error logging 2013-10-22 13:37:28 +04:00
Alex Kocharin
782abbb86d honor etags when making requests 2013-10-22 13:31:48 +04:00
Alex Kocharin
ec26083e81 added a maxage option for uplinks 2013-10-22 12:34:07 +04:00
Alex Kocharin
fea98dfa59 unlink directory when package is unpublished 2013-10-22 11:53:59 +04:00
Alex Kocharin
78f856cf81 shasum check for uploaded tarballs 2013-10-22 11:45:19 +04:00
Alex Kocharin
61658cfbdc added utils.is_object function for convenience 2013-10-22 11:29:57 +04:00
Alex Kocharin
4c2c4b87c2 calculating sha1sum of uploaded tarballs 2013-10-22 11:12:06 +04:00
Alex Kocharin
5622b2283d added couchdb-like revisions 2013-10-22 11:00:04 +04:00
Alex Kocharin
8b314040d9 fixing race conditions when updating package data 2013-10-22 09:10:25 +04:00
Alex Kocharin
e35c02f8f1 0.5.1 2013-10-20 23:17:31 +04:00
Alex Kocharin
f1d26652f3 0.5.1 2013-10-20 23:15:06 +04:00
Alex Kocharin
acc2e571ff logging didn't work on chunked output 2013-10-19 01:53:27 +04:00
Alex Kocharin
fa51797602 return 500 instead of 404 if local storage is corrupted 2013-10-19 01:35:41 +04:00
Alex Kocharin
3ca3ac2dae logging fs errors 2013-10-19 01:17:53 +04:00
Alex Kocharin
012892600b handing JSON.parse errors 2013-10-19 00:46:13 +04:00
Alex Kocharin
674f944942 tweaking fs logger color 2013-10-19 00:45:36 +04:00
Alex Kocharin
8a2a91c1a7 0.5.0 2013-10-12 21:28:09 +04:00
Alex Kocharin
473d3d5c2f 0.5.0 release 2013-10-12 21:28:01 +04:00
Alex Kocharin
5b6f22c0b9 changelog added 2013-10-12 18:38:21 +04:00
Alex Kocharin
1a58a0f8d8 hide authorization header in logs 2013-10-12 18:37:47 +04:00
Alex Kocharin
fcebeea3ee don't print colors if output stream is not tty 2013-10-12 11:57:59 +04:00
Alex Kocharin
015623f9ae making various test cases work 2013-10-11 13:50:41 +04:00
Alex Kocharin
957f915d42 fixing a next(err) function called twice 2013-10-11 13:49:00 +04:00
Alex Kocharin
70f717a295 using undefined instead of anonymous for non-logged in users 2013-10-11 09:53:54 +04:00
Alex Kocharin
3596a12eb7 logging caused failure in case of errors 2013-10-11 09:46:37 +04:00
Alex Kocharin
9ee525b317 logging engine added, much better logs now 2013-10-11 09:32:59 +04:00
Alex Kocharin
b6082f1216 some work towards unpublishing packages 2013-10-11 09:32:12 +04:00
Alex Kocharin
0aa687624d only create config if we're asked to (+ nice help) 2013-10-09 20:22:29 +04:00
Alex Kocharin
a76a443994 moved bin/sinopia to lib/cli.js 2013-10-09 19:47:55 +04:00
Alex Kocharin
94d37441ae error not raised when updating versions - fix #10 2013-10-08 22:55:32 +04:00
Alex Kocharin
7a3255d1ab very basic support for unpublishing a package 2013-10-06 12:27:50 +04:00
Alex Kocharin
b9cb60da64 replacing connect.bodyParser with connect.json 2013-10-06 11:26:05 +04:00
Alex Kocharin
566f2c4896 0.4.3 2013-10-05 20:39:31 +04:00
Alex Kocharin
459002128c ignore tarballs 2013-10-05 20:37:44 +04:00
Alex Kocharin
bffc6db38f better error message, ref #9 2013-10-05 18:49:08 +04:00
Alex Kocharin
57dc1c64d9 Merge branch 'master' of github.com:rlidwka/sinopia 2013-10-05 18:29:05 +04:00
Alex Kocharin
8dcb735626 rename tarball from uplink when download is finished
closes #11
2013-10-05 18:26:51 +04:00
Alex Kocharin
d59bdb0c37 Update README.md 2013-10-02 22:56:06 +04:00
Alex Kocharin
2f15d1c0fa fd leak 2013-10-02 22:54:46 +04:00
Alex Kocharin
16730bb7b4 fix fetching tarballs from npmjs registry 2013-10-02 22:48:32 +04:00
Alex Kocharin
f38a897fa2 improving tags support (read-only for now) 2013-10-02 22:26:20 +04:00
Alex Kocharin
1556ce195a npm star/unstar calls now return proper error 2013-10-02 22:01:18 +04:00
Alex Kocharin
2675196672 jshinting 2013-10-01 22:02:23 +04:00
Alex Kocharin
77fccaa1e5 readme: typo fix 2013-09-29 08:22:50 +04:00
Alex Kocharin
e95d22abf6 typo 2013-09-29 08:22:04 +04:00
Alex Kocharin
cd9f0d7044 disabling loop tests for a while 2013-09-29 08:05:20 +04:00
Alex Kocharin
38340fe194 0.4.2 2013-09-29 08:04:14 +04:00
Alex Kocharin
8aa60a93c5 Merge branch 'master' of github.com:rlidwka/sinopia 2013-09-29 08:02:39 +04:00
Alex Kocharin
f5b542724b update uplink code 2013-09-28 21:31:58 +04:00
Alex Kocharin
a503bce863 tab -> space 2013-09-28 20:59:18 +04:00
Alex Kocharin
21ad8945d9 update 2013-09-28 20:46:55 +04:00
Alex Kocharin
908f212b6f Update README.md 2013-09-28 19:48:31 +04:00
Alex Kocharin
5efaa637e5 Update README.md 2013-09-28 19:48:05 +04:00
Alex Kocharin
835841707a readme 2013-09-28 19:22:05 +04:00
Alex Kocharin
0b29b3d140 updating readme 2013-09-28 19:17:15 +04:00
Alex Kocharin
69eae02eae package.yaml update 2013-09-28 17:55:19 +04:00
Alex Kocharin
8cf27005c5 0.4.1 2013-09-28 16:43:04 +04:00
Alex Kocharin
c3de74ebfd error handling when uploading tarballs 2013-09-28 16:37:24 +04:00
Alex Kocharin
8c5628bfae uploading tarballs bugfixes 2013-09-28 16:19:40 +04:00
Alex Kocharin
f92a839a7f test stuff 2013-09-28 15:14:51 +04:00
Alex Kocharin
c8bb68a4aa adding package version to remote repository 2013-09-28 15:08:38 +04:00
Alex Kocharin
0173c55ead publishing package to all relevant uplinks 2013-09-28 14:59:05 +04:00
Alex Kocharin
57b34a7637 config files splitting + fwd 2013-09-27 16:36:10 +04:00
Alex Kocharin
5dbc825892 through -> streams2 migrate - final 2013-09-27 15:31:28 +04:00
Alex Kocharin
1570cc348c through -> streams2 transition for local-fs 2013-09-27 13:54:43 +04:00
Alex Kocharin
ac2ea00b2b adding __proto__ to package name blacklist 2013-09-27 13:20:38 +04:00
Alex Kocharin
361d653613 testing fetching files from uplinks 2013-09-27 12:56:44 +04:00
Alex Kocharin
8fe23d3393 moving readable stream interfaces from through to streams2 2013-09-27 12:56:13 +04:00
Alex Kocharin
9f80a0046e showing expected content type in errors 2013-09-27 12:55:42 +04:00
Alex Kocharin
64a0ab8be7 wrong file ignored 2013-09-27 12:54:56 +04:00
Alex Kocharin
b7e4bfdd14 migrating from through to streams2 2013-09-27 12:54:16 +04:00
Alex Kocharin
35235db7a0 adding test stuff to gitignore 2013-09-27 12:51:36 +04:00
Alex Kocharin
89f232b5ec fixing bugs preventing access to upstream 2013-09-27 11:48:01 +04:00
Alex Kocharin
102b125f14 moving to mocha for tests 2013-09-27 10:16:46 +04:00
Alex Kocharin
683f4657e4 forking 2 servers for tests 2013-09-27 08:57:09 +04:00
Alex Kocharin
6dc6f31579 better tests 2013-09-27 06:27:11 +04:00
Alex Kocharin
ff4778e3c6 adding comments 2013-09-27 04:26:15 +04:00
Alex Kocharin
7dbe61b8d5 0.4.0 2013-09-27 04:19:27 +04:00
Alex Kocharin
9b57669bbe keywords 2013-09-27 04:19:16 +04:00
Alex Kocharin
709ffe8e39 tests 2013-09-25 14:01:55 +04:00
Alex Kocharin
11bf03f130 config file error handling 2013-09-25 13:29:39 +04:00
Alex Kocharin
e01d30adb9 unimplemented functions now throw 2013-09-25 13:18:38 +04:00
Alex Kocharin
4791d0e707 inline documentation 2013-09-25 13:12:33 +04:00
Alex Kocharin
34a52f09a2 renaming storage libraries 2013-09-25 12:54:57 +04:00
Alex Kocharin
5f9e3aebc5 better error handling 2013-09-25 12:49:06 +04:00
Alex Kocharin
fa1c4f30ee fixing bug with error handling 2013-09-25 12:10:59 +04:00
Alex Kocharin
9374872a64 numeric port in config file 2013-09-25 12:10:40 +04:00
Alex Kocharin
6c78876dcd starting to write tests 2013-09-25 12:10:12 +04:00
Alex Kocharin
bdd748c6e2 cleanup incomplete upload requests 2013-09-24 10:28:26 +04:00
Alex Kocharin
2ee792633c fixing config file handling 2013-09-24 10:27:27 +04:00
Alex Kocharin
e351559dd2 adding listen address to config 2013-09-24 08:40:46 +04:00
Alex Kocharin
764dde38f1 more comments in config file 2013-09-24 08:36:43 +04:00
Alex Kocharin
d91bef5ca0 change config file format + comments 2013-09-24 08:27:47 +04:00
Alex Kocharin
c84413517f making storage path relative to config path 2013-09-24 08:15:13 +04:00
Alex Kocharin
d4ddf4a3e3 0.3.2 2013-07-12 23:52:18 +04:00
Alex Kocharin
f19f523e1a fixing bugs with streams 2013-07-12 23:51:04 +04:00
Alex Kocharin
593e6195c7 0.3.1 2013-07-04 06:09:06 +04:00
Alex Kocharin
86f02bda7d add md5 etags for json 2013-07-03 05:49:24 +04:00
Alex Kocharin
27844cd358 fixing some race conditions 2013-06-22 04:19:46 +04:00
Alex Kocharin
61727a771e bug: it's not really listening on localhost by default 2013-06-20 22:40:04 +04:00
Alex Kocharin
693f222dc2 0.3.0 2013-06-20 21:13:37 +04:00
Alex Kocharin
5eee99622f adding dep 2013-06-20 21:13:13 +04:00
Alex Kocharin
1eda590bfc use compression if somebody asked for it 2013-06-20 21:10:33 +04:00
Alex Kocharin
b4688d140f caching tarballs 2013-06-20 20:54:50 +04:00
Alex Kocharin
dfd0459c03 turning uplink requests into streams 2013-06-20 17:41:07 +04:00
Alex Kocharin
d7eb7c9ef8 working on streams 2013-06-20 17:07:34 +04:00
Alex Kocharin
ad29d7fac7 0.2.0 2013-06-19 21:40:54 +04:00
Alex Kocharin
c425cdab27 working on proxying tarball requests 2013-06-19 20:58:16 +04:00
Alex Kocharin
5aa335cd91 a lot of work done... 2013-06-18 22:14:55 +04:00
Alex Kocharin
26e3e21121 using minimatch instead of regexps 2013-06-14 13:27:08 +04:00
Alex Kocharin
4070eb7eb4 update package info 2013-06-14 13:08:29 +04:00
206 changed files with 21134 additions and 1085 deletions

27
.babelrc Normal file
View File

@@ -0,0 +1,27 @@
{
"presets": [
"react",
["env",{
"targets": {
"browsers": ["last 5 versions", "safari >= 11"],
"loose": true
}
}],
"stage-2",
"stage-3"
],
"plugins": [
"react-hot-loader/babel",
"transform-runtime",
"transform-object-rest-spread",
"transform-decorators-legacy"
],
"env": {
"development": {
"presets": ["flow"],
"plugins": [
"flow-runtime"
]
}
}
}

32
.dockerignore Normal file
View File

@@ -0,0 +1,32 @@
# we try to aoid adding files to the docker images that change often
# or that are not needed for running the docker image
# tis greatly reduces the amount of times we need to rerun `npm install` when building image locally
# https://codefresh.io/blog/not-ignore-dockerignore/
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
# consider them hidden
.*
# you can add exceptions like in .gitignore to maintain a whitelist:
# e.g.
!.babelrc
!.eslintrc
!.eslintignore
!.stylelintrc
# not going to run tests inside the docker container
test/
# do not copy over node_modules we will run `npm install` anyway
node_modules
# output from test runs and similar things
*.log
coverage/
# IDE config files
jsconfig.json
*.iml
# let's not get to recursive ;)
Dockerfile*
docker-compose*.yaml

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# 2 space indentation
[{.,}*.{js,yml,yaml}]
indent_style = space
indent_size = 2

2
.env Normal file
View File

@@ -0,0 +1,2 @@
# default values for docker-compose
PORT=4873

4
.eslintignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
coverage/
wiki/
static/

80
.eslintrc Normal file
View File

@@ -0,0 +1,80 @@
{
"plugins": [
"react",
"flowtype"
],
"extends": [
"eslint:recommended",
"google",
"plugin:react/recommended",
"plugin:flowtype/recommended"
],
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 7,
"ecmaFeatures": {
"impliedStrict": true,
"jsx": true
}
},
"env": {
"browser": true,
"node": true,
"es6": true
},
"rules": {
"no-tabs": 0,
"keyword-spacing": 0,
"padded-blocks": 0,
"no-useless-escape": 0,
"handle-callback-err": 2,
"no-debugger": 2,
"no-fallthrough": 2,
"curly": 2,
"eol-last": 1,
"no-irregular-whitespace": 1,
"no-mixed-spaces-and-tabs": [
1,
"smart-tabs"
],
"no-trailing-spaces": 1,
"no-new-require": 2,
"no-undef": 2,
"no-unreachable": 2,
"no-unused-vars": [
2,
{
"vars": "all",
"args": "none"
}
],
"max-len": [
1,
160
],
"semi": [
2,
"always"
],
"camelcase": 0,
"require-jsdoc": 2,
"valid-jsdoc": 2,
"prefer-spread": 1,
"prefer-rest-params": 1,
"no-var": 2,
"no-constant-condition": 2,
"no-empty": 2,
"guard-for-in": 2,
"no-invalid-this": 2,
"new-cap": 2,
"one-var": 2,
"no-console": [
2,
{
"allow": [
"warn"
]
}
]
}
}

41
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,41 @@
#### My reason:
<!--
a brief explanation of the issue, suggestion, feature
-->
#### Steps to reproduce:
<!--
(if it applies)
how can I do in order to reproduce it? environment?
-->
#### App Version:
<!--
Define which version the issue happens and whether previous version the behaviour is correct
-->
#### Config file:
<!--
Provide your config file might be really helpful. Please be aware to hide sensisive data before post.
-->
#### Additional information:
<!--
provide the following information would be helpful
-->
- `$ set DEBUG=express:* verdaccio` enable extreme verdaccio debug mode
- `$ npm --verbose` prints:
- `$ npm config get registry` prints:
- Verdaccio terminal output
- Windows, OS X/macOS, or Linux?:
- Verdaccio configuration file, eg: `cat ~/.config/verdaccio/config.yaml`
<!-- Remove those does not apply for you -->
- Container:
- I use local environment
- I develop / deploy using Docker.
- I deploy to a PaaS.
#### Additional verbose log:

16
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,16 @@
**Type:** bug / feature
The following has been addressed in the PR:
<!-- Remove the sections that your PR does not apply -->
* There is a related issue
* Unit or Functional tests are included in the PR
<!--
Our bots should ensure:
* The PR passes CI testing
-->
**Description:**
Resolves #???

33
.gitignore vendored
View File

@@ -1,5 +1,30 @@
node_modules
package.json
npm-debug.log
bin/storage
bin/config.yaml
verdaccio-*.tgz
.DS_Store
###
!bin/verdaccio
test-storage*
.verdaccio_test_env
node_modules
package-lock.json
# Istanbul
coverage/
.nyc*
# Visual Studio Code
.vscode/*
.idea/
# React
bundle.js
bundle.js.map
__tests__
__snapshots__
# Compiled script
static/

10
.npmignore Normal file
View File

@@ -0,0 +1,10 @@
node_modules
npm-debug.log
coverage/
verdaccio-*.tgz
test-storage*
scripts/
wiki/
src/webui
tools/
/.*

8
.stylelintrc Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": "stylelint-config-standard",
"rules": {
"selector-pseudo-class-no-unknown": [true, {
"ignorePseudoClasses": ["/global/"]
}]
}
}

11
.travis.yml Normal file
View File

@@ -0,0 +1,11 @@
language: node_js
node_js:
- '4'
- '6'
- '7'
- '8'
sudo: false
before_script:
- npm install -g npm
script: npm install && npm run build:webui && npm run test-travis
after_success: npm run coverage:codecov

74
AUTHORS Normal file
View File

@@ -0,0 +1,74 @@
030 <chocolatey030@gmail.com>
Alex Kocharin <alex@kocharin.ru>
Alex Kocharin <rlidwka@kocharin.ru>
Alex Vernacchia <avernacchia@exacttarget.com>
Alexander Makarenko <estliberitas@gmail.com>
Alexandre-io <Alexandre-io@users.noreply.github.com>
Aram Drevekenin <grimsniffer@gmail.com>
Bart Dubois <dubcio@o2.pl>
Barthélemy Vessemont <bvessemont@gmail.com>
Brandon Nicholls <brandon.nicholls@gmail.com>
Bren Norris <bnorris@enterrasolutions.com>
Brett Trotter <brett.trotter@webfilings.com>
Brian Peacock <bpeacock@fastfig.com>
Cedric Darne <cdarne@hibernum.com>
Chad Killingsworth <chad.killingsworth@banno.com>
Chris Breneman <crispy@cluenet.org>
Cody Droz <cody-geest@uiowa.edu>
Daniel Rodríguez Rivero <rdanielo@gmail.com>
Denis Babineau <denis.babineau@gmail.com>
Emmanuel Narh <narhe@advisory.com>
Fabio Poloni <fabio@APP-roved.com>
Facundo Chambó <fchambo@despegar.com>
Guilherme Bernal <dev@lbguilherme.com>
Jakub Jirutka <jakub@jirutka.cz>
James Newell <j.newell@nib.com.au>
Jan Vansteenkiste <jan@vstone.eu>
Jannis Achstetter <jannis.achstetter@schneider-electric.com>
Jeremy Moritz <jeremy@jeremymoritz.com>
John Gozde <johng@pandell.com>
Jon de la Motte <jondlm@gmail.com>
Joseph Gentle <me@josephg.com>
José De Paz <josedepaz@users.noreply.github.com>
Juan Carlos Picado <juan@encuestame.org>
Juan Carlos Picado <juanpicado19@gmail.com>
Juan Picado <juanpicado19@gmail.com>
Juan Picado @jotadeveloper <juanpicado19@gmail.com>
Kalman Speier <kalman.speier@gmail.com>
Keyvan Fatehi <keyvanfatehi@gmail.com>
Kody J. Peterson <kodypeterson@users.noreply.github.com>
Madison Grubb <madison.grubb@itential.com>
Manuel de Brito Fontes <aledbf@gmail.com>
Mark Doeswijk <mark.doeswijk@marviq.com>
Meeeeow <i@aka.mn>
Meeeeow <me@async.sh>
Michael Arnel <michael.arnel@gmail.com>
Michael Crowe <michael@developrise.com>
Miguel Mejias <miguelangelmejias@dorna.com>
Miroslav Bajtoš <miroslav@strongloop.com>
Nate Ziarek <natez@OSX12-L-NATEZ.local>
Nick <nick.edelenbos@trimm.nl>
Piotr Synowiec <psynowiec@gmail.com>
Rafael Cesar <rafa.cesar@gmail.com>
Robert Ewald <r3wald@gmail.com>
Robert Groh <robert.groh@medesso.de>
Robin Persson <rprssn@gmail.com>
Romain Lai-King <romain.laiking@opentrust.com>
Ryan Graham <r.m.graham@gmail.com>
Ryan Graham <ryan@codingintrigue.co.uk>
Sam Day <sday@atlassian.com>
Tarun Garg <tarun1793@users.noreply.github.com>
Thomas Cort <thomasc@ssimicro.com>
Tom Vincent <git@tlvince.com>
Trent Earl <trent@trentearl.com>
Yannick Croissant <yannick.croissant@gmail.com>
Yannick Galatol <ygalatol@teads.tv>
cklein <trancesilken@gmail.com>
danielo515 <rdanielo@gmail.com>
jmwilkinson <j.wilkinson@f5.com>
jotadeveloper <juanpicado19@gmail.com>
jotadeveloper <juanpicado@users.noreply.github.com>
maxlaverse <max@laverse.net>
saheba <saheba@users.noreply.github.com>
steve-p-com <github@steve-p.com>
trent.earl <trent.earl@malauzai.com>

505
CHANGELOG.md Normal file
View File

@@ -0,0 +1,505 @@
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="2.4.0"></a>
# [2.4.0](https://github.com/verdaccio/verdaccio/compare/v2.3.6...v2.4.0) (2017-09-23)
### Bug Fixes
* :bug: check error code to prevent data loss ([5d73dca](https://github.com/verdaccio/verdaccio/commit/5d73dca))
* :bug: check error code to prevent data loss ([93aae05](https://github.com/verdaccio/verdaccio/commit/93aae05))
* :bug: Package metadata cache not work ([4d6a447](https://github.com/verdaccio/verdaccio/commit/4d6a447))
### Features
* Update node alpine version to 8.4.0 ([3f96ce3](https://github.com/verdaccio/verdaccio/commit/3f96ce3))
<a name="2.3.6"></a>
## [2.3.6](https://github.com/verdaccio/verdaccio/compare/v2.3.5...v2.3.6) (2017-08-17)
### Bug Fixes
* link was broken ([a9481cc](https://github.com/verdaccio/verdaccio/commit/a9481cc))
* Correct accept header set for registry requests ([#295](https://github.com/verdaccio/verdaccio/pull/295))
* Update SSL documentation ([#296](https://github.com/verdaccio/verdaccio/pull/296))
* Fix auth process to check against username also and not just groups ([#293](https://github.com/verdaccio/verdaccio/pull/293))
<a name="2.3.5"></a>
## [2.3.5](https://github.com/verdaccio/verdaccio/compare/v2.3.4...v2.3.5) (2017-08-14)
### Bug Fixes
* configuration files inconsistencies, add unit test ([644c098](https://github.com/verdaccio/verdaccio/commit/644c098))
* Remove accept header that seems cause issues [#285](https://github.com/verdaccio/verdaccio/issues/285) [#289](https://github.com/verdaccio/verdaccio/issues/289) and npm search fails ([fab8391](https://github.com/verdaccio/verdaccio/commit/fab8391))
<a name="2.3.4"></a>
## [2.3.4](https://github.com/verdaccio/verdaccio/compare/v2.3.3...v2.3.4) (2017-07-29)
### Bug Fixes
* Docker image fails due lock file localhost references ([901a7be](https://github.com/verdaccio/verdaccio/commit/901a7be))
<a name="2.3.3"></a>
## [2.3.3](https://github.com/verdaccio/verdaccio/compare/v2.3.2...v2.3.3) (2017-07-29)
### Bug Fixes
* refactor [#268](https://github.com/verdaccio/verdaccio/issues/268) in a better way, amended to elegant way ([94fb6ad](https://github.com/verdaccio/verdaccio/commit/94fb6ad))
<a name="2.3.2"></a>
## [2.3.2](https://github.com/verdaccio/verdaccio/compare/v2.3.0...v2.3.2) (2017-07-28)
### Bug Fixes
* :bug: detail page can't handle scoped package ([1c9fbfc](https://github.com/verdaccio/verdaccio/commit/1c9fbfc))
* [#268](https://github.com/verdaccio/verdaccio/issues/268) remove the accept header that avoids request with some regiestries ([e7dcf3c](https://github.com/verdaccio/verdaccio/commit/e7dcf3c))
* [#78](https://github.com/verdaccio/verdaccio/issues/78) add new setting to allow publish when uplinks are offline ([430425c](https://github.com/verdaccio/verdaccio/commit/430425c))
* broken link ([9fb0e14](https://github.com/verdaccio/verdaccio/commit/9fb0e14))
* lint warning ([d0afe78](https://github.com/verdaccio/verdaccio/commit/d0afe78))
* Param web.title from config.yaml does not work on docker image [#265](https://github.com/verdaccio/verdaccio/issues/265) ([b1a396d](https://github.com/verdaccio/verdaccio/commit/b1a396d))
* undefined check ([ff96d2e](https://github.com/verdaccio/verdaccio/commit/ff96d2e))
## 2.3.1 (July 25, 2017)
- bug: Detail page can't handle scoped package - [#261](https://github.com/verdaccio/verdaccio/pull/261)
- bug: can't publish a private package to verdaccio while offline - [#223](https://github.com/verdaccio/verdaccio/pull/223)
- refactor: use light version of syntax highlighter - [#260](https://github.com/verdaccio/verdaccio/pull/260)
## 2.3.0 (July 22, 2017)
- feature: Refactor User Interface - [#220](https://github.com/verdaccio/verdaccio/pull/220)
## 2.2.7 (July 18, 2017)
- bug: fix running behind of loadbalancer with TLS termination - [#254](https://github.com/verdaccio/verdaccio/pull/254)
## 2.2.6 (July 13, 2017)
- build: update node version due security update announcement - [#251](https://github.com/verdaccio/verdaccio/pull/251)
## 2.2.5 (July 4, 2017)
- Fixed adding the verdaccio user into the group - [#241](https://github.com/verdaccio/verdaccio/pull/241)
## 2.2.3 (July 4, 2017)
- Updated Dockerfile & added proper signal handling - [#239](https://github.com/verdaccio/verdaccio/pull/239)
## 2.2.2 (July 2, 2017)
- Improve Docker Build - [#181](https://github.com/verdaccio/verdaccio/pull/181)
- Bugfix #73 `npm-latest` support - [#228](https://github.com/verdaccio/verdaccio/pull/228)
- Add [documentation](https://github.com/verdaccio/verdaccio/tree/master/wiki) - [#229](https://github.com/verdaccio/verdaccio/pull/229)
## 2.2.1 (June 17, 2017)
- config section moved up, some keywords added - [#211](https://github.com/verdaccio/verdaccio/pull/211)
- docs: update docs with behind reverse proxy - [#214](https://github.com/verdaccio/verdaccio/pull/214)
- Add remote ip to request log - [#216](https://github.com/verdaccio/verdaccio/pull/216)
## 2.2.0 (June 8, 2017)
- Allow url_prefix to be only the path - ([@BartDubois ]((https://github.com/BartDubois))) in [#197](https://github.com/verdaccio/verdaccio/pull/197)
- Apache reverse proxy configuration - ([@mysiar ]((https://github.com/mysiar))) in [#198](https://github.com/verdaccio/verdaccio/pull/198)
- don't blindly clobber local dist-tags - ([@rmg ]((https://github.com/rmg))) in [#206](https://github.com/verdaccio/verdaccio/pull/206)
- Adds cache option to uplinks - ([@silkentrance ]((https://github.com/silkentrance))) in [#132](https://github.com/verdaccio/verdaccio/pull/132)
## 2.1.7 (May 14, 2017)
- Fixed publish fail in YARN - ([@W1U02]((https://github.com/W1U02)) in [#183](https://github.com/verdaccio/verdaccio/pull/183)
## 2.1.6 (May 12, 2017)
- Fix https certificates safety check - ([@juanpicado]((https://github.com/juanpicado))) in [#189](https://github.com/verdaccio/verdaccio/pull/189)
- Fix upstream search not work with gzip - ([@Meeeeow](https://github.com/Meeeeow) in [#170](https://github.com/verdaccio/verdaccio/pull/170))
- Add additional requirement to output message - ([@marnel ](https://github.com/marnel) in [#184](https://github.com/verdaccio/verdaccio/pull/184))
- Implement npm ping endpoint - ([@juanpicado]((https://github.com/juanpicado))) in [#179](https://github.com/verdaccio/verdaccio/pull/179)
- Add support for multiple notification endpoints to existing webhook - ([@ryan-codingintrigue]((https://github.com/ryan-codingintrigue)))
in [#108](https://github.com/verdaccio/verdaccio/pull/108)
## 2.1.5 (April 22, 2017)
- fix upstream search - ([@Meeeeow](https://github.com/Meeeeow) in [#166](https://github.com/verdaccio/verdaccio/pull/166))
- Fix search feature - ([@Meeeeow](https://github.com/Meeeeow) in [#163](https://github.com/verdaccio/verdaccio/pull/163))
- add docs about run behind proxy - ([@Meeeeow](https://github.com/Meeeeow) in [#160](https://github.com/verdaccio/verdaccio/pull/160))
## 2.1.4 (April 13, 2017)
- Added Nexus Repository OSS as similar existing software - ([@nedelenbos030](https://github.com/nedelenbos) in [#147](https://github.com/verdaccio/verdaccio/pull/147))
- Increase verbose on notify request - ([@juanpicado](https://github.com/juanpicado) in [#153](https://github.com/verdaccio/verdaccio/pull/153))
- Add fallback support to previous config files - ([@juanpicado](https://github.com/juanpicado) in [#155](https://github.com/verdaccio/verdaccio/pull/155))
- Allows retrieval of all local package contents via http://server/-/search/* - ([@Verikon](https://github.com/Verikon) in [#152](https://github.com/verdaccio/verdaccio/pull/155))
## 2.1.3 (March 29, 2017)
- [GH-83] create systemd service - ([@030](https://github.com/030) in [#89](https://github.com/verdaccio/verdaccio/pull/89))
- optional scope in the readme package name. - ([@psychocode](https://github.com/psychocode) in [#136](https://github.com/verdaccio/verdaccio/pull/136))
- Added docker image for rpi - ([@danielo515](https://github.com/danielo515) in [#137](https://github.com/verdaccio/verdaccio/pull/137))
- Allow configuring a tagline that is displayed on the webpage between. ([@jachstet-sea](https://github.com/jachstet-sea) in [#143](https://github.com/verdaccio/verdaccio/pull/143))
## 2.1.2 (March 9, 2017)
- Contribute guidelines - ([@juanpicado](https://github.com/juanpicado) in [#133](https://github.com/verdaccio/verdaccio/pull/133))
- fix(plugin-loader): plugins verdaccio-* overwrite by sinopia- ([@Alexandre-io](https://github.com/Alexandre-io) in [#129](https://github.com/verdaccio/verdaccio/pull/129))
## 2.1.1 (February 7, 2017)
- [GH-86] updated readme to point to new chef cookbook ([@kgrubb](https://github.com/kgrubb) in [#117](https://github.com/verdaccio/verdaccio/pull/117))
- [GH-88] rename to Verdaccio instead of Sinopia ([@kgrubb](https://github.com/kgrubb) in [#93](https://github.com/verdaccio/verdaccio/pull/93))
- Unit testing coverage ([@juanpicado](https://github.com/juanpicado) in [#116](https://github.com/verdaccio/verdaccio/issues/116))
- Allow htpasswd-created users to log in [@imsnif](https://github.com/imsnif) in [#112](https://github.com/verdaccio/verdaccio/issues/112))
- remove travis io.js support ([@juanpicado](https://github.com/juanpicado) in [#115](https://github.com/verdaccio/verdaccio/issues/115))
- rename clean up ([@juanpicado](https://github.com/juanpicado) in [#114](https://github.com/verdaccio/verdaccio/issues/114))
- _npmUser / author not showing up ([@juanpicado](https://github.com/juanpicado) in [#65](https://github.com/verdaccio/verdaccio/issues/65))
- Docs: correct config attribute `proxy_access` ([@robertgroh](https://github.com/robertgroh) in [#96](https://github.com/verdaccio/verdaccio/pull/96))
- Problem with docker.yaml ([@josedepaz](https://github.com/josedepaz) in [#72](https://github.com/verdaccio/verdaccio/pull/72))
- Prevent logging of user and password ([@tlvince](https://github.com/tlvince) in [#94](https://github.com/verdaccio/verdaccio/pull/94))
- Updated README.md to reflect the availability of the docker image ([@jmwilkinson](https://github.com/jmwilkinson)) in [#71](https://github.com/verdaccio/verdaccio/pull/71))
## 2.1.0 (October 11, 2016)
- Use __dirname to resolve local plugins ([@aledbf](https://github.com/aledbf) in [#25](https://github.com/verdaccio/verdaccio/pull/25))
- Fix npm cli logout ([@plitex](https://github.com/plitex) in [#47](https://github.com/verdaccio/verdaccio/pull/47))
- Add log format: pretty-timestamped ([@jachstet-sea](https://github.com/jachstet-sea) in [#68](https://github.com/verdaccio/verdaccio/pull/68))
- Allow adding/overriding HTTP headers of uplinks via config ([@jachstet-sea](https://github.com/jachstet-sea) in [#67](https://github.com/verdaccio/verdaccio/pull/67))
- Update Dockerfile to fix failed start ([@denisbabineau](https://github.com/denisbabineau) in [#62](https://github.com/verdaccio/verdaccio/pull/62))
- Update the configs to fully support proxying scoped packages ([@ChadKillingsworth](https://github.com/ChadKillingsworth) in [#60](https://github.com/verdaccio/verdaccio/pull/60))
- Prevent the server from crashing if a repo is accessed that the user does not have access to ([@crowebird](https://github.com/crowebird) in [#58](https://github.com/verdaccio/verdaccio/pull/58))
- Hook system, for integration into things like slack
- Register entry partial even if custom template is provided ([@plitex](https://github.com/plitex) in [#46](https://github.com/verdaccio/verdaccio/pull/46))
- Rename process to verdaccio ([@juanpicado](https://github.com/juanpicado) in [#57](https://github.com/verdaccio/verdaccio/pull/57))
## 7 Jun 2015, version 1.4.0
- avoid sending X-Forwarded-For through proxies (issues [#19](https://github.com/rlidwka/sinopia/issues/19), [#254](https://github.com/rlidwka/sinopia/issues/254))
- fix multiple issues in search (issues [#239](https://github.com/rlidwka/sinopia/issues/239), [#253](https://github.com/rlidwka/sinopia/pull/253))
- fix "maximum stack trace exceeded" errors in auth (issue [#258](https://github.com/rlidwka/sinopia/issues/258))
## 10 May 2015, version 1.3.0
- add dist-tags endpoints (issue [#211](https://github.com/rlidwka/sinopia/issues/211))
## 22 Apr 2015, version 1.2.2
- fix access control regression in `1.2.1` (issue [#238](https://github.com/rlidwka/sinopia/issues/238))
- add a possibility to bind on unix sockets (issue [#237](https://github.com/rlidwka/sinopia/issues/237))
## 11 Apr 2015, version 1.2.1
- added more precise authorization control to auth plugins (issue [#207](https://github.com/rlidwka/sinopia/pull/207))
## 29 Mar 2015, version 1.1.0
- add a possibility to listen on multiple ports (issue [#172](https://github.com/rlidwka/sinopia/issues/172))
- added https support (issues [#71](https://github.com/rlidwka/sinopia/issues/71), [#166](https://github.com/rlidwka/sinopia/issues/166))
- added an option to use a custom template for web UI (issue [#208](https://github.com/rlidwka/sinopia/pull/208))
- remove "from" and "resolved" fields from shrinkwrap (issue [#204](https://github.com/rlidwka/sinopia/issues/204))
- fix hanging when rendering readme (issue [#206](https://github.com/rlidwka/sinopia/issues/206))
- fix logger-related crash when using sinopia as a library
- all requests to uplinks should now have proper headers
## 12 Feb 2015, version 1.0.1
- fixed issue with `max_users` option (issue [#184](https://github.com/rlidwka/sinopia/issues/184))
- fixed issue with not being able to disable the web interface (issue [#195](https://github.com/rlidwka/sinopia/pull/195))
- fixed 500 error while logging in with npm (issue [#200](https://github.com/rlidwka/sinopia/pull/200))
## 26 Jan 2015, version 1.0.0
- switch markdown parser from `remarkable` to `markdown-it`
- update `npm-shrinkwrap.json`
- now downloading tarballs from upstream using the same protocol as for metadata (issue [#166](https://github.com/rlidwka/sinopia/issues/166))
## 22 Dec 2014, version 1.0.0-beta.2
- fix windows behavior when `$HOME` isn't set (issue [#177](https://github.com/rlidwka/sinopia/issues/177))
- fix sanitization for highlighted code blocks in readme (issue [render-readme/#1](https://github.com/rlidwka/render-readme/issues/1))
## 15 Dec 2014, version 1.0.0-beta
- Markdown rendering is now a lot safer (switched to remarkable+sanitizer).
- Header in web interface is now static instead of fixed.
- `GET /-/all?local` now returns list of all local packages (issue [#179](https://github.com/rlidwka/sinopia/pull/179))
## 5 Dec 2014, version 1.0.0-alpha.3
- Fixed an issue with scoped packages in tarballs
## 25 Nov 2014, version 1.0.0-alpha
- Config file is now created in `$XDG_CONFIG_HOME` instead of current directory.
It is printed to stdout each time sinopia starts, so you hopefully won't have any trouble locating it.
The change is made so sinopia will pick up the same config no matter which directory it is started from.
- Default config file is now a lot shorter, and it is very permissive by default. You could use sinopia without modifying it on your own computer, but definitely should change it on production.
- Added auth tokens. For now, auth token is just a username+password encrypted for security reasons, so it isn't much different from basic auth, but allows to avoid "always-auth" npm setting.
- Added scoped packages.
Please note that default `*` mask won't apply to them. You have to use masks like `@scope/*` to match scoped packages, or `**` to match everything.
- Enabled web interface by default. Wow, it looks almost ready now!
- All dependencies are bundled now, so uncompatible changes in 3rd party stuff in the future won't ruin the day.
## 1 Nov 2014, version 0.13.2
- fix `EPERM`-related crashes on windows (issue [#67](https://github.com/rlidwka/sinopia/issues/67))
## 22 Oct 2014, version 0.13.0
- web interface:
- web page layout improved (issue [#141](https://github.com/rlidwka/sinopia/pull/141))
- latest version is now displayed correctly (issues [#120](https://github.com/rlidwka/sinopia/issues/120), [#123](https://github.com/rlidwka/sinopia/issues/123), [#143](https://github.com/rlidwka/sinopia/pull/143))
- fixed web interface working behind reverse proxy (issues [#145](https://github.com/rlidwka/sinopia/issues/145), [#147](https://github.com/rlidwka/sinopia/issues/147))
## 2 Oct 2014, version 0.12.1
- web interface:
- update markdown CSS (issue [#137](https://github.com/rlidwka/sinopia/pull/137))
- jquery is now served locally (issue [#133](https://github.com/rlidwka/sinopia/pull/133))
- bugfixes:
- fix "offset out of bounds" issues (issue [sinopia-htpasswd/#2](https://github.com/rlidwka/sinopia-htpasswd/issues/2))
- "max_users" in htpasswd plugin now work correctly (issue [sinopia-htpasswd/#3](https://github.com/rlidwka/sinopia-htpasswd/issues/3))
- fix `ENOTDIR, open '.sinopia-db.json'` error in npm search (issue [#122](https://github.com/rlidwka/sinopia/issues/122))
## 25 Sep 2014, version 0.12.0
- set process title to `sinopia`
- web interface bugfixes:
- save README data for each package (issue [#100](https://github.com/rlidwka/sinopia/issues/100))
- fix crashes related to READMEs (issue [#128](https://github.com/rlidwka/sinopia/issues/128))
## 18 Sep 2014, version 0.11.3
- fix 500 error in adduser function in sinopia-htpasswd (issue [#121](https://github.com/rlidwka/sinopia/issues/121))
- fix fd leak in authenticate function in sinopia-htpasswd (issue [#116](https://github.com/rlidwka/sinopia/issues/116))
## 15 Sep 2014, version 0.11.1
- mark crypt3 as optional (issue [#119](https://github.com/rlidwka/sinopia/issues/119))
## 15 Sep 2014, version 0.11.0
- Added auth plugins (issue [#99](https://github.com/rlidwka/sinopia/pull/99))
Now you can create your own auth plugin based on [sinopia-htpasswd](https://github.com/rlidwka/sinopia-htpasswd) package.
- WIP: web interface (issue [#73](https://github.com/rlidwka/sinopia/pull/73))
It is disabled by default, and not ready for production yet. Use at your own risk. We will enable it in the next major release.
- Some modules are now bundled by default, so users won't have to install stuff from git. We'll see what issues it causes, maybe all modules will be bundled in the future like in npm.
## 14 Sep 2014, version 0.10.x
*A bunch of development releases that are broken in various ways. Please use 0.11.x instead.*
## 7 Sep 2014, version 0.9.3
- fix several bugs that could cause "can't set headers" exception
## 3 Sep 2014, version 0.9.2
- allow "pretty" format for logging into files (issue [#88](https://github.com/rlidwka/sinopia/pull/88))
- remove outdated user existence check (issue [#115](https://github.com/rlidwka/sinopia/pull/115))
## 11 Aug 2014, version 0.9.1
- filter falsey _npmUser values (issue [#95](https://github.com/rlidwka/sinopia/pull/95))
- option not to cache third-party files (issue [#85](https://github.com/rlidwka/sinopia/issues/85))
## 26 Jul 2014, version 0.9.0
- new features:
- add search functionality (issue [#65](https://github.com/rlidwka/sinopia/pull/65))
- allow users to authenticate using .htpasswd (issue [#44](https://github.com/rlidwka/sinopia/issues/44))
- allow user registration with "npm adduser" (issue [#44](https://github.com/rlidwka/sinopia/issues/44))
- bugfixes:
- avoid crashing when res.socket is null (issue [#89](https://github.com/rlidwka/sinopia/issues/89))
## 20 Jun 2014, version 0.8.2
- allow '@' in package/tarball names (issue [#75](https://github.com/rlidwka/sinopia/issues/75))
- other minor fixes (issues [#77](https://github.com/rlidwka/sinopia/issues/77), [#80](https://github.com/rlidwka/sinopia/issues/80))
## 14 Apr 2014, version 0.8.1
- "latest" tag is now always present in any package (issue [#63](https://github.com/rlidwka/sinopia/issues/63))
- tags created with new npm versions (>= 1.3.19) can now be published correctly
## 1 Apr 2014, version 0.8.0
- use gzip compression whenever possible (issue [#54](https://github.com/rlidwka/sinopia/issues/54))
- set `ignore_latest_tag` to false, it should now be more compatible with npm registry
- make `fs-ext` optional (issue [#61](https://github.com/rlidwka/sinopia/issues/61))
## 29 Mar 2014, version 0.7.1
- added `ignore_latest_tag` config param (issues [#55](https://github.com/rlidwka/sinopia/issues/55), [#59](https://github.com/rlidwka/sinopia/issues/59))
- reverted PR [#56](https://github.com/rlidwka/sinopia/issues/56) (see discussion in [#57](https://github.com/rlidwka/sinopia/issues/57))
## 13 Mar 2014, version 0.7.0
- config changes:
- breaking change: all time intervals are now specified in *seconds* instead of *milliseconds* for the sake of consistency. Change `timeout` if you have one!
- all time intervals now can be specified in [nginx notation](http://wiki.nginx.org/ConfigNotation), for example `1m 30s` will specify a 90 seconds timeout
- added `maxage` option to avoid asking public registry for the same data too often (issue [#47](https://github.com/rlidwka/sinopia/issues/47))
- added `max_fails` and `fail_timeout` options to reduce amount of requests to public registry when it's down (issue [#7](https://github.com/rlidwka/sinopia/issues/7))
- bug fixes:
- fix crash when headers are sent twice (issue [#52](https://github.com/rlidwka/sinopia/issues/52))
- all tarballs are returned with `Content-Length`, which allows [yapm](https://github.com/rlidwka/yapm) to estimate download time
- when connection to public registry is interrupted when downloading a tarball, we no longer save incomplete tarball to the disk
- other changes:
- 404 errors are returned in couchdb-like manner (issue [#56](https://github.com/rlidwka/sinopia/issues/56))
## 5 Mar 2014, version 0.6.7
- pin down express@3 version, since sinopia doesn't yet work with express@4
## 28 Feb 2014, version 0.6.5
- old SSL keys for npm are removed, solves `SELF_SIGNED_CERT_IN_CHAIN` error
## 3 Feb 2014, version 0.6.3
- validate tags and versions (issue [#40](https://github.com/rlidwka/sinopia/issues/40))
- don't crash when process.getuid doesn't exist (issue [#41](https://github.com/rlidwka/sinopia/issues/41))
## 18 Jan 2014, version 0.6.2
- adding config param to specify upload limits (issue [#39](https://github.com/rlidwka/sinopia/issues/39))
- making loose semver versions work (issue [#38](https://github.com/rlidwka/sinopia/issues/38))
## 13 Jan 2014, version 0.6.1
- support setting different storage paths for different packages (issue [#35](https://github.com/rlidwka/sinopia/issues/35))
## 30 Dec 2013, version 0.6.0
- tag support (issue [#8](https://github.com/rlidwka/sinopia/issues/8))
- adding support for npm 1.3.19+ behaviour (issue [#31](https://github.com/rlidwka/sinopia/issues/31))
- removing all support for proxying publish requests to uplink (too complex)
## 26 Dec 2013, version 0.5.9
- fixing bug with bad Accept header (issue [#32](https://github.com/rlidwka/sinopia/issues/32))
## 20 Dec 2013, version 0.5.8
- fixed a warning from js-yaml
- don't color multiline strings in logs output
- better error messages in various cases
- test format changed
## 15 Dec 2013, version 0.5.7
- try to fetch package from uplinks if user requested a tarball we don't know about (issue [#29](https://github.com/rlidwka/sinopia/issues/29))
- security fix: set express.js to production mode so we won't return stack traces to the user in case of errors
## 11 Dec 2013, version 0.5.6
- fixing a few crashes related to tags
## 8 Dec 2013, version 0.5.4
- latest tag always shows highest version available (issue [#8](https://github.com/rlidwka/sinopia/issues/8))
- added a configurable timeout for requests to uplinks (issue [#18](https://github.com/rlidwka/sinopia/issues/18))
- users with bad authentication header are considered not logged in (issue [#17](https://github.com/rlidwka/sinopia/issues/17))
## 24 Nov 2013, version 0.5.3
- added proxy support for requests to uplinks (issue [#13](https://github.com/rlidwka/sinopia/issues/13))
- changed license from default BSD to WTFPL
## 26 Oct 2013, version 0.5.2
- server now supports unpublishing local packages
- added fs-ext dependency (flock)
- fixed a few face conditions
## 20 Oct 2013, version 0.5.1
- fixed a few errors related to logging
## 12 Oct 2013, version 0.5.0
- using bunyan as a log engine
- pretty-formatting colored logs to stdout by default
- ask user before creating any config files
## 5 Oct 2013, version 0.4.3
- basic tags support for npm (read-only)
- npm star/unstar calls now return proper error
## 29 Sep 2013, version 0.4.2
## 28 Sep 2013, version 0.4.1
- using mocha for tests now
- making use of streams2 api, doesn't work on 0.8 anymore
- basic support for uploading packages to other registries
## 27 Sep 2013, version 0.4.0
- basic test suite
- storage path in config is now relative to config file location, not cwd
- proper cleanup for temporary files
## 12 Jul 2013, version 0.3.2
## 4 Jul 2013, version 0.3.1
- using ETag header for all json output, based on md5
## 20 Jun 2013, version 0.3.0
- compression for http responses
- requests for files to uplinks are now streams (no buffering)
- tarballs are now cached locally
## 19 Jun 2013, version 0.2.0
- config file changed, packages is now specified with minimatch
- ability to retrieve all packages from another registry (i.e. npmjs)
## 14 Jun 2013, version 0.1.1
- config is now autogenerated
- tarballs are now read/written from fs using streams (no buffering)
## 9 Jun 2013, version 0.1.0
- first npm version
- ability to publish packages and retrieve them locally
- basic authentication/access control
## 22 May 2013, version 0.0.0
- first commits

46
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at verdaccio.npm@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

209
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,209 @@
# Contributing to Verdaccio
We are happy you wish to contribute this project, for that reason we want to board you with this guide.
## How I contribute?
### Ways to contribute
There are many ways to contribute to the Verdaccio Project. Heres a list of technical contributions with increasing levels of involvement and required knowledge of Verdaccio's code and operations.
* [Reporting a Bug](CONTRIBUTING.md#reporting-a-bug)
* [Request Features](CONTRIBUTING.md#request-features)
* [Plugins](CONTRIBUTING.md#plugins)
* [Improve the Documentation](wiki/README.md)
Please read carefully this document. It will guide you to provide maintainers and readers valuable information to boots the process solve the issue or evaluate your proposal.
## Reporting a Bug
We welcome clear bug reports. If you've found a bug in Verdaccio that isn't a security risk, please file a report in our [issue tracker](https://github.com/verdaccio/verdaccio/issues). Before you file your issue, search to see if it has already been reported. If so, up-vote (using GitHub reactions) or add additional helpful details to the existing issue to show that it's affecting multiple people.
### Check if there's a simple solution in the wiki.
Some of the most popular topics can be found in our [wiki](https://github.com/verdaccio/verdaccio/wiki), that would be the first place to look at the topic you are interested.
### Questions & Chat
We have tagged questions for easy follow up under the tag [questions](https://github.com/verdaccio/verdaccio/labels/question). Additionaly, I'd recommend to deliver questions in the new chat as **#questions/#development** channels at [gitter](https://gitter.im/verdaccio/).
### Look at the past
* Verdaccio is a fork of `sinopia@1.4.0`, thereforce, there is a huge [database of tickets](https://github.com/rlidwka/sinopia/issues) in the original projet. It's a good place to find answers.
* Questions under the tag of [sinopia](http://stackoverflow.com/questions/tagged/sinopia) or [verdaccio](http://stackoverflow.com/search?q=verdaccio) at Stackoverflow might be helpful.
### Using the issue tracker
The issue tracker is a channel were mostly users/developers post.
#### I want to report a bug
We considere a bug a feature that is not working as is described in the documentation. Before reporte a bug follow the next steps:
1. Use the GitHub issue search — check if the issue has already been reported.
2. Check if the issue has been fixed — try to reproduce it using the latest master or development branch in the repository.
Verdaccio still does not support all npm commands due either in the initial design were not considered important or nobody has request it yet.
## Request Features
A new feature is always welcome, thus, analyse whether you ir idea fits in the scope of the project and elaborate your request providing enough context, for instance:
* A wide description the advantages of your request.
* It's compatible with `npm` and `yarn`?
* You might implement your feature and provide a forked repository as example.
* Whatever you have on mind 🤓.
### Submitting a Pull Request
The following are the general steps you should follow in creating a pull request. Subsequent pull requests only need
to follow step 3 and beyond:
1. Fork the repository on GitHub
2. Clone the forked repository to your machine
3. Create a "feature" branch in your local repository
4. Make your changes and commit them to your local repository
5. Rebase and push your commits to your GitHub remote fork/repository
6. Issue a Pull Request to the official repository
7. Your Pull Request is reviewed by a committer and merged into the repository
*Note*: While there are other ways to accomplish the steps using other tools, the examples here will assume the most
actions will be performed via the `git` command line.
### 1. Fork the Repository
When logged in to your GitHub account, and you are viewing one of the main repositories, you will see the *Fork* button.
Clicking this button will show you which repositories you can fork to. Choose your own account. Once the process
finishes, you will have your own repository that is "forked" from the official one.
Forking is a GitHub term and not a git term. Git is a wholly distributed source control system and simply worries
about local and remote repositories and allows you to manage your code against them. GitHub then adds this additional
layer of structure of how repositories can relate to each other.
### 2. Clone the Forked Repository
Once you have successfully forked your repository, you will need to clone it locally to your machine:
```bash
$ git clone --recursive git@github.com:username/verdaccio.git verdaccio
```
This will clone your fork to your current path in a directory named `verdaccio`.
You should also set up the `upstream` repository. This will allow you to take changes from the "master" repository
and merge them into your local clone and then push them to your GitHub fork:
```bash
$ cd verdaccio
$ git remote add upstream git@github.com:verdaccio/verdaccio.git
$ git fetch upstream
```
Then you can retrieve upstream changes and rebase on them into your code like this:
```bash
$ git pull --rebase upstream master
```
For more information on maintaining a fork, please see the GitHub Help article [Fork a Repo](https://help.github.com/articles/fork-a-repo/) and information on
[rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) from git.
### 3. Create a Branch
The easiest workflow is to keep your master branch in sync with the upstream branch and do not locate any of your own
commits in that branch. When you want to work on a new feature, you then ensure you are on the master branch and create
a new branch from there. While the name of the branch can be anything, it can often be easy to use the issue number
you might be working on (if an issue was opened prior to opening a pull request). For example:
```bash
$ git checkout -b issue-12345 master
Switched to a new branch 'issue-12345'
```
You will then be on the feature branch. You can verify what branch you are on like this:
```bash
$ git status
# On branch issue-12345
nothing to commit, working directory clean
```
### 4. Make Changes and Commit
#### Before commit
At this point you have ready your changes, your new feature it's ready to be shipped, but, to avoid delays to merge, please be aware the build must past.
Before commit, run the test command:
```bash
npm test
```
It won't have **eslint** errors and **all test must past**. Then, and only then, you should push and ship your **PR**.
*At the moment of this writing, there are plenty of warning to clean, but please warnings are not fails, but try to don't commit code with warnings*
#### After testing your changes
Now you just need to make your changes. Once you have finished your changes (and tested them) you need to commit them
to your local repository (assuming you have staged your changes for committing):
```bash
$ git status
# On branch issue-12345
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: somefile.js
#
$ git commit -m "Corrects some defect, fixes #12345, refs #12346"
[t12345 0000000] Corrects some defect, fixes #12345, refs #12346
1 file changed, 2 insertions(+), 2 deletions(-)
```
### 5. Rebase and Push Changes
If you have been working on your contribution for a while, the upstream repository may have changed. You may want to
ensure your work is on top of the latest changes so your pull request can be applied cleanly:
```bash
$ git pull --rebase upstream master
```
When you are ready to push your commit to your GitHub repository for the first time on this branch you would do the
following:
```bash
$ git push -u origin issue-12345
```
After the first time, you simply need to do:
```bash
$ git push
```
### 6. Issue a Pull Request
In order to have your commits merged into the main repository, you need to create a pull request. The instructions for
this can be found in the GitHub Help Article [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/). Essentially you do the following:
1. Go to the site for your repository.
2. Click the Pull Request button.
3. Select the feature branch from your repository.
4. Enter a title and description of your pull request in the description.
5. Review the commit and files changed tabs.
6. Click `Send Pull Request`
You will get notified about the status of your pull request based on your GitHub settings.
## Plugins
Plugins are Add-ons that extend the functionality of the application. Whether you want develop your own plugin I'd suggest do the following:
1. Check whether there is a legacy sinopia plugin for the feature that you need at [npmjs](https://www.npmjs.com/search?q=sinopia).
2. There is a [life-cycle to load a plugin](https://github.com/verdaccio/verdaccio/blob/master/lib/plugin-loader.js#L22) you should keep on mind.
3. You are free to host your plugin in your repository, whether you want to host within in our organization, feel free to ask, we'll happy to host it.
4. Try a describe widely your plugin to provide a deeply understanding to your users.

43
Dockerfile Normal file
View File

@@ -0,0 +1,43 @@
FROM node:8.4.0-alpine
LABEL maintainer="https://github.com/verdaccio/verdaccio"
RUN apk --no-cache add openssl && \
wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 && \
chmod +x /usr/local/bin/dumb-init && \
apk del openssl
ENV APPDIR /usr/local/app
WORKDIR $APPDIR
ADD . $APPDIR
ENV NODE_ENV=production
RUN npm config set registry http://registry.npmjs.org/ && \
npm install -g -s --no-progress yarn --pure-lockfile && \
yarn install --production=false && \
yarn run build:webui && \
yarn cache clean && \
yarn install --production=true --pure-lockfile
RUN mkdir -p /verdaccio/storage /verdaccio/conf
ADD conf/docker.yaml /verdaccio/conf/config.yaml
RUN addgroup -S verdaccio && adduser -S -G verdaccio verdaccio && \
chown -R verdaccio:verdaccio "$APPDIR" && \
chown -R verdaccio:verdaccio /verdaccio
USER verdaccio
ENV PORT 4873
ENV PROTOCOL http
EXPOSE $PORT
VOLUME ["/verdaccio"]
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD $APPDIR/bin/verdaccio --config /verdaccio/conf/config.yaml --listen $PROTOCOL://0.0.0.0:${PORT}

13
Dockerfile.rpi Normal file
View File

@@ -0,0 +1,13 @@
FROM hypriot/rpi-node:6-onbuild
RUN mkdir -p /verdaccio/storage /verdaccio/conf
WORKDIR /verdaccio
ADD conf/docker.yaml /verdaccio/conf/config.yaml
EXPOSE 4873
VOLUME ["/verdaccio/conf", "/verdaccio/storage"]
CMD ["/usr/src/app/bin/verdaccio", "--config", "/verdaccio/conf/config.yaml", "--listen", "0.0.0.0:4873"]

229
README.md
View File

@@ -1,89 +1,230 @@
**This thing doesn't work yet, come back in a few weeks**
# Verdaccio
## Installation
### A lightweight private npm proxy registry
`verdaccio` is a fork of `sinopia`. It aims to keep backwards compatibility with `sinopia`, while keeping up with npm changes.
### Logo
We are looking for a new logo, ⚡⚡⚡ [please 🙏🙏 I encorage you to contribute helping us to decide](https://github.com/verdaccio/verdaccio/issues/237) ⚡⚡⚡
[![CircleCI](https://circleci.com/gh/verdaccio/verdaccio/tree/master.svg?style=svg)](https://circleci.com/gh/verdaccio/verdaccio/tree/master)
[![npm version badge](https://img.shields.io/npm/v/verdaccio.svg)](https://www.npmjs.org/package/verdaccio)
[![downloads badge](http://img.shields.io/npm/dm/verdaccio.svg)](https://www.npmjs.org/package/verdaccio)
[![codecov](https://codecov.io/gh/verdaccio/verdaccio/branch/master/graph/badge.svg)](https://codecov.io/gh/verdaccio/verdaccio)
[![Gitter chat](https://badges.gitter.im/verdaccio/questions.png)](https://gitter.im/verdaccio/)
<p align="center"><img src="https://firebasestorage.googleapis.com/v0/b/jotadeveloper-website.appspot.com/o/verdaccio_long_video2.gif?alt=media&token=4d20cad1-f700-4803-be14-4b641c651b41"></p>
It allows you to have a **local npm private registry with zero configuration**. You don't have to install and replicate an entire database. Verdaccio keeps its own small database and, if a package doesn't exist there, **it asks any other registry** (npmjs.org) for it keeping only those packages you use.
## Introduction
### Use private packages
If you want to use all benefits of npm package system in your company without sending all code to the public, and use your private packages just as easy as public ones.
### Cache npmjs.org registry
If you have more than one server you want to install packages on, you might want to use this to decrease latency
(presumably "slow" npmjs.org will be connected to only once per package/version) and provide limited failover (if npmjs.org is down, we might still find something useful in the cache) or avoid issues like *[How one developer just broke Node, Babel and thousands of projects in 11 lines of JavaScript](https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/)*.
### Override public packages
If you want to use a modified version of some 3rd-party package (for example, you found a bug, but maintainer didn't accept pull request yet), you can publish your version locally under the same name.
See in detail each of these [use cases](https://github.com/verdaccio/verdaccio/tree/master/wiki/use-cases.md).
## Get Started
**Help? Don't miss the [documentation section](wiki/README.md)**
### Prerequisites
* Node.js >= 4.6.1
* `npm` or `yarn`
Installation and starting (application will create default config in config.yaml you can edit later)
```bash
# installation and starting
$ npm install -g sinopia
$ sinopia
npm install --global verdaccio
```
# npm configuration
$ npm set registry http://localhost:4387/
Run in your terminal
# if you have any restricted packages (that's the point of having private
# registry anyway), you should add this:
$ npm set always-auth true
```bash
verdaccio
```
# if you use HTTPS (you probably should), add an appropriate CA information
After **npm 5.2** you can use npx which install and launch verdaccio with the same command
```bash
npx verdaccio
```
You would need set some npm configuration, this is optional.
```bash
$ npm set registry http://localhost:4873/
# if you use HTTPS, add an appropriate CA information
# ("null" means get CA list from OS)
$ npm set ca null
```
## Goals
Now you can navigate to [http://localhost:4873/](http://localhost:4873/) where your local packages will be listed and can be searched.
We want to create a private/caching npm repository server. The idea of it to be as simple as it could possibly be, which means "just download and run it". As I recall, there're no such things available now, is there?
> Warning: Verdaccio current is not support PM2's cluster mode, run it with cluster mode may cause unknown behavior
There's two obvious use-cases:
#### Beta
1. Private repository. If you want to use all benefits of npm package system in your company without sending all code to the public, you'll want that.
If you are an adventurous developer you can use and install the latest beta version, this is a non stable version, I'd recommend only use for testing purporses.
2. Caching. If you have more than one server you want to install packages on, you might want to use this to decrease latency (presumably "slow" npmjs.org will be connected to only once per package/version) and provide limited failover (if npmjs.org is down, we might still find something useful in the cache).
```bash
$ npm install -g verdaccio@beta
```
## Disclaimer
## Publishing Private Packages
I don't know the internal npm stuff yet, so if npm repository heavily depends on some complex CouchDB functions, this unive^W project is doomed.
## Name of a project
Now it's "npmrepod" for "npm repository daemon". Better name suggestions are very much welcome. :)
#### Create an user and log in
By the way, is it called "repository" or "registry" anyway?
```bash
npm adduser --registry http://localhost:4873
```
## Configuration
#### Publish your package
It should be able to work without any configuration, just install and run it.
```bash
npm publish --registry http://localhost:4873
```
Of course for some advanced usage a configuration file would be necessary. So it'll probably be a javascript or yaml config. We would want to include custom functions there as plugins, so... yeah, it's probably javascript file.
This will prompt you for user credentials which will be saved on the `verdaccio` server.
## Using public packages from npm.js / caching
## Server Side Configuration
If some package doesn't exist in the storage, server would forward requests to npmjs.org. If npmjs.org is down, we would serve packages from cache pretending that no other packages exist. We would download only what's needed (= requested by clients), and this information would be cached forever.
When you start a server, it auto-creates a config file. For instructions on how to run Verdaccio as a service, with a nice URL or behind a proxy have a look at the [server-side configure document](https://github.com/verdaccio/verdaccio/tree/master/wiki/server.md).
Example: if you successfully request express@3.0.1 from this server once, you'll able to do that again (with all it's dependencies) anytime even if npmjs.org is down. But say express@3.0.0 will not be downloaded until it's actually needed by somebody. And if npmjs.org is offline, this server would say that only express@3.0.1 (= only what's in the cache) is published, but nothing else.
## Docker
Open question: can we track package changes on npmjs.org without replicating their entire database?
[![dockeri.co](http://dockeri.co/image/verdaccio/verdaccio)](https://hub.docker.com/r/verdaccio/verdaccio/)
## Features
Below are the most commony needed informations,
every aspect of Docker and verdaccio is [documented separately](https://github.com/verdaccio/verdaccio/tree/master/wiki/docker.md)
For now I'm planning to make `npm publish` and `npm install` work with this repository. Advanced features like `npm search` are so to speak not a priority.
### Prebuilt images
## Access control
To pull the latest pre-built [docker image](https://hub.docker.com/r/verdaccio/verdaccio/):
It is supposed to be private repository. We can't allow just anybody to see/download any package as it is in npmjs.org. So it's an open question how access control should be implemented.
```bash
docker pull verdaccio/verdaccio
```
Maybe configuration would be simular to gitolite with working groups and such.
Since version 2 images for every versions are availabel as [tags](https://hub.docker.com/r/verdaccio/verdaccio/tags/).
Should we allow anybody to publish any package by default? Should it be configurable? Shall we use users from npmjs.org or use our own user management? Well... those questions are up.
### Running verdaccio using Docker
To run the docker container:
```bash
docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio
```
### Using docker-compose
1. Get the latest version of [docker-compose](https://github.com/docker/compose).
2. Build and run the container:
```bash
$ docker-compose up --build
```
Docker examples are available [in this repository](https://github.com/verdaccio/docker-examples).
* Docker + Nginx
* Docker + Kubernetes
* Docker + Apache
### Advanced Infrastructure Management Tools
#### Ansible
A Verdaccio playbook [is available at galaxy](https://galaxy.ansible.com/030/verdaccio)
Source: [https://github.com/verdaccio/ansible-verdaccio](https://github.com/verdaccio/ansible-verdaccio)
Maintainer: [@030](https://github.com/030)
#### Chef
The Verdaccio Chef cookbook [is available via the chef supermarket](https://supermarket.chef.io/cookbooks/verdaccio).
Source: [https://github.com/verdaccio/verdaccio-cookbook](https://github.com/verdaccio/verdaccio-cookbook).
Maintainer: [@kgrubb](https://github.com/kgrubb)
#### Puppet
Source: [https://github.com/verdaccio/puppet-verdaccio](https://github.com/verdaccio/puppet-verdaccio).
Maintainer: *No asigned yet*
## Compatibility
Verdaccio aims to support all features of a standard npm client that make sense to support in private repository. Unfortunately, it isn't always possible.
### Basic features
- Installing packages (npm install, npm upgrade, etc.) - **supported**
- Publishing packages (npm publish) - **supported**
### Advanced package control
- Unpublishing packages (npm unpublish) - **supported**
- Tagging (npm tag) - **supported**
- Deprecation (npm deprecate) - not supported
### User management
- Registering new users (npm adduser {newuser}) - **supported**
- Transferring ownership (npm owner add {user} {pkg}) - not supported, verdaccio uses its own acl management system
### Misc stuff
- Searching (npm search) - **supported** (cli / browser)
- Starring (npm star, npm unstar) - not supported, doesn't make sense in private registry
- Ping (npm ping) - **supported**
## Storage
No CouchDB. It is supposed to work with zero configuration, so filesystem would be used for storage by default.
No CouchDB here. **This application is supposed to work with zero configuration**, so filesystem is used as a storage.
But our company would want to use MongoDB+GridFS for ourselves, because we have several servers with MongoDB replication set up.
If you want to use a database instead, ask for it, we'll come up with some kind of a plugin system.
So, we would implement some kind of plugin system. There would be at least two plugins with the package (filesystem as a default, mongodb), but if someone wants to use CouchDB or whatever he could write a plugin himself.
About the storage there is a running discussion [here](https://github.com/verdaccio/verdaccio/issues?q=is%3Aissue+is%3Aopen+label%3Astorage).
## Plugins
- storage (filesystem, mongo)
- logging (bunyan interface?)
## Existing things
## Similar existing things
- npm + git (I mean, using git+ssh:// dependencies) - most people seem to use this, but it's a terrible idea... *npm update* doesn't work, can't use git subdirectories this way, etc.
- [reggie](https://github.com/mbrevoort/node-reggie) - this looks very interesting indeed... I might borrow some code there.
- [shadow-npm](https://github.com/dominictarr/shadow-npm), [public service](http://shadow-npm.net/) - it uses the same code as npmjs.org + service is dead
- [gemfury](http://www.gemfury.com/l/npm-registry) and others - those are closed-source cloud services, and I'm not in a mood to trust my private code to somebody (security through obscurity yeah!)
- npm-registry-proxy, npm-delegate, npm-proxy - those are just proxies...
- [nexus-repository-oss](https://www.sonatype.com/nexus-repository-oss) - Repository manager that handles more than just NPM dependencies
- Is there something else?
- [codebox-npm](https://github.com/craftship/codebox-npm) - Serverless private npm registry using
- [local-npm](https://github.com/nolanlawson/local-npm) - Local and offline-first npm mirror
Anything else?
## FAQ / Contact / Troubleshoot
If you have any issue you can try the following options, do no desist to ask or check our issues database, perhaps someone has asked already what you are looking for.
* [Documentation](wiki/README.md)
* [Most common questions](https://github.com/verdaccio/verdaccio/issues?q=is%3Aissue+is%3Aopen+label%3Aquestion)
* [Reporting a bug](https://github.com/verdaccio/verdaccio/blob/master/CONTRIBUTING.md#reporting-a-bug)
* [Running discussions](https://github.com/verdaccio/verdaccio/issues?q=is%3Aissue+is%3Aopen+label%3Adiscuss)
* [Chat Room](https://gitter.im/verdaccio/)

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env node
var pkg_file = '../package.yaml';
var fs = require('fs');
var yaml = require('js-yaml');
var commander = require('commander');
var server = require('../lib/index');
var crypto = require('crypto');
var pkg = require(pkg_file);
commander
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)', '4873')
.option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)')
.version(pkg.version)
.parse(process.argv);
var config;
if (commander.config) {
config = yaml.safeLoad(fs.readFileSync(commander.config, 'utf8'));
} else {
try {
config = yaml.safeLoad(fs.readFileSync('./config.yaml', 'utf8'));
} catch(err) {
var created_config = require('../lib/config_gen')();
config = yaml.safeLoad(created_config.yaml);
console.log('starting with default config, use user: "%s", pass: "%s" to authenticate', created_config.user, created_config.pass);
fs.writeFileSync('./config.yaml', created_config.yaml);
}
}
if (!config.user_agent) config.user_agent = 'Sinopia/'+pkg.version;
var hostport = commander.listen.split(':');
if (hostport.length < 2) {
hostport = [undefined, hostport[0]];
}
server(config).listen(hostport[1], hostport[0]);
console.log('Server is listening on http://%s:%s/', hostport[0] || 'localhost', hostport[1]);

3
bin/verdaccio Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env node
require('../src/lib/cli');

31
circle.yml Normal file
View File

@@ -0,0 +1,31 @@
machine:
environment:
YARN_VERSION: 0.27.5
PATH: "${PATH}:${HOME}/.yarn/bin:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
node:
version: 8
dependencies:
pre:
- |
if [[ ! -e ~/.yarn/bin/yarn || $(yarn --version) != "${YARN_VERSION}" ]]; then
curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version $YARN_VERSION
fi
- nvm install 4
- nvm install 6
cache_directories:
- ~/.yarn
- ~/.cache/yarn
- "node_modules"
override:
- yarn install --no-progress
test:
override:
- yarn run pre:ci
- nvm alias default 6
- yarn run test:ci
- nvm alias default 4
- yarn run test:ci
- nvm alias default 8
- yarn run test:ci
- yarn run coverage:publish

1
conf/README.md Normal file
View File

@@ -0,0 +1 @@
This directory is for config examples.

50
conf/default.yaml Normal file
View File

@@ -0,0 +1,50 @@
#
# This is the default config file. It allows all users to do anything,
# so don't use it on production systems.
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/master/conf
#
# path to a directory with all packages
storage: ./storage
auth:
htpasswd:
file: ./htpasswd
# Maximum amount of users allowed to register, defaults to "+inf".
# You can set this to -1 to disable registration.
#max_users: 1000
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'@*/*':
# scoped packages
access: $all
publish: $authenticated
proxy: npmjs
'**':
# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
access: $all
# allow all known users to publish packages
# (anyone can register by default, remember?)
publish: $authenticated
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs
# log settings
logs:
- {type: stdout, format: pretty, level: http}
#- {type: file, path: verdaccio.log, level: info}

53
conf/docker.yaml Normal file
View File

@@ -0,0 +1,53 @@
#
# This is the config file used for the docker images.
# It allows all users to do anything, so don't use it on production systems.
#
# Do not configure host and port under `listen` in this file
# as it will be ignored when using docker.
# see https://github.com/verdaccio/verdaccio/blob/master/wiki/docker.md#docker-and-custom-port-configuration
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/master/conf
#
# path to a directory with all packages
storage: /verdaccio/storage
auth:
htpasswd:
file: /verdaccio/conf/htpasswd
# Maximum amount of users allowed to register, defaults to "+infinity".
# You can set this to -1 to disable registration.
#max_users: 1000
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'@*/*':
# scoped packages
access: $all
publish: $authenticated
proxy: npmjs
'**':
# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
access: $all
# allow all known users to publish packages
# (anyone can register by default, remember?)
publish: $authenticated
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs
# log settings
logs:
- {type: stdout, format: pretty, level: http}
#- {type: file, path: verdaccio.log, level: info}

188
conf/full.yaml Normal file
View File

@@ -0,0 +1,188 @@
# path to a directory with all packages
storage: ./storage
# a list of users
#
# This is deprecated, use auth plugins instead (see htpasswd below).
# users:
# admin:
# crypto.createHash('sha1').update(pass).digest('hex')
# password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
web:
# WebUI is enabled as default, if you want disable it, just uncomment this line
#enable: false
title: Verdaccio
# logo: logo.png
auth:
htpasswd:
file: ./htpasswd
# Maximum amount of users allowed to register, defaults to "+infinity".
# You can set this to -1 to disable registration.
#max_users: 1000
# a list of other known repositories we can talk to
uplinks:
npmjs:
url: https://registry.npmjs.org/
# amount of time to wait for repository to respond
# before giving up and use the local cached copy
#timeout: 30s
# maximum time in which data is considered up to date
#
# default is 2 minutes, so server won't request the same data from
# uplink if a similar request was made less than 2 minutes ago
#maxage: 2m
# if two subsequent requests fail, no further requests will be sent to
# this uplink for five minutes
#max_fails: 2
#fail_timeout: 5m
# timeouts are defined in the same way as nginx, see:
# http://wiki.nginx.org/ConfigNotation
# add/override HTTP headers sent to the uplink server
# this allows for HTTP Basic auth for example:
#headers:
# authorization: "Basic YourBase64EncodedCredentials=="
# set this to false to prevent tarballs from this upstream
# to be stored in the local storage (defaults to true)
#cache: false
packages:
'@*/*':
# scoped packages
access: $all
publish: $authenticated
proxy: npmjs
# uncomment this for packages with "local-" prefix to be available
# for admin only, it's a recommended way of handling private packages
#'local-*':
# access: admin
# publish: admin
# # you can override storage directory for a group of packages this way:
# storage: 'local_storage'
'**':
# allow all users to read packages (including non-authenticated users)
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
access: $all
# allow 'admin' to publish packages
publish: $authenticated
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: npmjs
#####################################################################
# Advanced settings
#####################################################################
## Special packages publish configurations
#publish:
## This will allow the publisher to publish packages even if any uplink is down.
# allow_offline: true
# if you use nginx with custom path, use this to override links
#url_prefix: https://dev.company.local/verdaccio/
# You can specify listen address (or simply a port).
# If you add multiple values, verdaccio will listen on all of them.
#
# Examples:
#
#listen:
# - localhost:4873 # default value
# - http://localhost:4873 # same thing
# - 0.0.0.0:4873 # listen on all addresses (INADDR_ANY)
# - https://example.org:4873 # if you want to use https
# - [::1]:4873 # ipv6
# - unix:/tmp/verdaccio.sock # unix socket
# Configure HTTPS, it is required if you use "https" protocol above.
#https:
# key: path/to/server.key
# cert: path/to/server.crt
# ca: path/to/server.pem
# type: file | stdout | stderr
# level: trace | debug | info | http (default) | warn | error | fatal
#
# parameters for file: name is filename
# {type: 'file', path: 'verdaccio.log', level: 'debug'},
#
# parameters for stdout and stderr: format: json | pretty | pretty-timestamped
# {type: 'stdout', format: 'pretty', level: 'debug'},
logs:
- {type: stdout, format: pretty, level: http}
#- {type: file, path: verdaccio.log, level: info}
# you can specify proxy used with all requests in wget-like manner here
# (or set up ENV variables with the same name)
#http_proxy: http://something.local/
#https_proxy: https://something.local/
#no_proxy: localhost,127.0.0.1
# maximum size of uploaded json document
# increase it if you have "request entity too large" errors
#max_body_size: 1mb
# Notify Settings
# Notify was built primarily to use with Slack's Incoming
# webhooks, but will also deliver a simple payload to
# any endpoint. Currently only active for publish / create
# commands.
notify:
# Choose a method. Technically this will accept any HTTP
# request method, but probably stick to GET or POST
method: POST
# Only run this notification if the package name matches the regular
# expression
packagePattern: ^example-package$
# Any flags to be used with the regular expression
packagePatternFlags: i
# If this endpoint requires specific headers, set them here
# as an array of key: value objects.
headers: [{'Content-type': 'application/x-www-form-urlencoded'}]
# set the URL endpoint for this call
endpoint: https://hooks.slack.com/...
# Finally, the content you will be sending in the body.
# This data will first be run through Handlebars to parse
# any Handlebar expressions. All data housed in the metadata object
# is available for use within the expressions.
content: ' {{ handlebar-expression }}'
# For Slack, follow the following format:
# content: '{ "text": "Package *{{ name }}* published to version *{{ dist-tags.latest }}*", "username": "Verdaccio", "icon_emoji": ":package:" }'
# Multiple notification endpoints can be created by specifying a collection
'example-package-1':
method: POST
# Only run this notification if the package name matches the regular
# expression
packagePattern: ^example-package-regex$
# Any flags to be used with the regular expression
# since verdaccio 2.2.2 this property has been disabled read #108
# it will be re-enabled after 2.5.0
# packagePatternFlags: i
# If this endpoint requires specific headers, set them here
# as an array of key: value objects.
# headers supports as well a literal object
headers: {'Content-type': 'application/x-www-form-urlencoded'}
# set the URL endpoint for this call
endpoint: https://hooks.slack.com/...
# Finally, the content you will be sending in the body.
# This data will first be run through Handlebars to parse
# any Handlebar expressions. All data housed in the metadata object
# is available for use within the expressions.
content: ' {{ handlebar-expression }}'
# For Slack, follow the following format:
# content: '{ "text": "Package *{{ name }}* published to version *{{ dist-tags.latest }}*", "username": "Verdaccio", "icon_emoji": ":package:" }'

14
docker-compose.yaml Normal file
View File

@@ -0,0 +1,14 @@
version: '2.1'
services:
verdaccio:
build: .
container_name: verdaccio
environment:
- PORT
ports:
- $PORT:$PORT
volumes:
- verdaccio:/verdaccio
volumes:
verdaccio:
driver: local

9
index.js Normal file
View File

@@ -0,0 +1,9 @@
module.exports = require('./src/api/index');
/** package
{ "name": "verdaccio",
"version": "0.0.0",
"dependencies": {"js-yaml": "*"},
"scripts": {"postinstall": "js-yaml package.yaml > package.json ; npm install"}
}
**/

View File

@@ -1,128 +0,0 @@
var assert = require('assert');
var crypto = require('crypto');
// [[a, [b, c]], d] -> [a, b, c, d]
function flatten(array) {
var result = [];
for (var i=0; i<array.length; i++) {
if (Array.isArray(array[i])) {
result.push.apply(result, flatten(array[i]));
} else {
result.push(array[i]);
}
}
return result;
}
function Config(config) {
if (!(this instanceof Config)) return new Config(config);
for (var i in config) {
if (this[i] == null) this[i] = config[i];
}
var users = {all:true};
var check_user_or_uplink = function(arg) {
assert(arg !== 'all' || arg !== 'owner' || arg !== 'anonymous', 'CONFIG: reserved user/uplink name: ' + arg);
assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg);
assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg);
users[arg] = true;
};
['users', 'uplinks', 'packages'].forEach(function(x) {
if (this[x] == null) this[x] = {};
assert(
typeof(this[x]) === 'object' &&
!Array.isArray(this[x])
, 'CONFIG: bad "'+x+'" value (object expected)');
});
for (var i in this.users) check_user_or_uplink(i);
for (var i in this.uplinks) check_user_or_uplink(i);
for (var i in this.users) {
assert(this.users[i].password, 'CONFIG: no password for user: ' + i);
assert(
typeof(this.users[i].password) === 'string' &&
this.users[i].password.match(/^[a-f0-9]{40}$/)
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected');
}
for (var i in this.uplinks) {
assert(this.uplinks[i].url, 'CONFIG: no url for uplink: ' + i);
assert(
typeof(this.uplinks[i].url) === 'string'
, 'CONFIG: wrong url format for uplink: ' + i);
this.uplinks[i].url = this.uplinks[i].url.replace(/\/$/, '');
}
for (var i in this.packages) {
var check_userlist = function(i, hash, action) {
if (hash[action] == null) hash[action] = [];
// if it's a string, split it to array
if (typeof(hash[action]) === 'string') {
hash[action] = hash[action].split(/\s+/);
}
assert(
typeof(hash[action]) === 'object' &&
Array.isArray(hash[action])
, 'CONFIG: bad "'+i+'" package '+action+' description (array or string expected)');
hash[action] = flatten(hash[action]);
hash[action].forEach(function(user) {
assert(
users[user] != null
, 'CONFIG: "'+i+'" package: user "'+user+'" doesn\'t exist');
});
}
assert(
typeof(this.packages[i]) === 'object' &&
!Array.isArray(this.packages[i])
, 'CONFIG: bad "'+i+'" package description (object expected)');
check_userlist(i, this.packages[i], 'access');
check_userlist(i, this.packages[i], 'proxy');
check_userlist(i, this.packages[i], 'publish');
}
return this;
}
function allow_action(package, who, action) {
for (var i in this.packages) {
var match_package = i == package;
var m = i.match(/^\/(.*)\/$/);
if (m && (new RegExp(m[1])).exec(package)) {
match_package = true;
}
if (match_package) {
return this.packages[i][action].reduce(function(prev, curr) {
if (curr === who || curr === 'all') return true;
return prev;
}, false);
}
}
return false;
}
Config.prototype.allow_access = function(package, user) {
return allow_action.call(this, package, user, 'access');
}
Config.prototype.allow_publish = function(package, user) {
return allow_action.call(this, package, user, 'publish');
}
Config.prototype.allow_proxy = function(package, uplink) {
return allow_action.call(this, package, uplink, 'proxy');
}
Config.prototype.authenticate = function(user, password) {
if (this.users[user] == null) return false;
return crypto.createHash('sha1').update(password).digest('hex') === this.users[user].password;
}
module.exports = Config;

View File

@@ -1,18 +0,0 @@
# path to a directory with all packages
storage: ./storage
users:
admin:
# crypto.createHash('sha1').update(pass).digest('hex')
password: __PASSWORD__
uplinks:
npmjs:
url: https://registry.npmjs.org/
packages:
'/.*/':
publish: admin
access: all
proxy: npmjs

View File

@@ -1,16 +0,0 @@
var fs = require('fs');
var crypto = require('crypto');
module.exports = function create_config() {
var pass = crypto.randomBytes(8).toString('base64').replace(/[=+\/]/g, '');
var pass_digest = crypto.createHash('sha1').update(pass).digest('hex');
var config = fs.readFileSync(require.resolve('./config_def.yaml'), 'utf8');
config = config.replace('__PASSWORD__', pass_digest);
return {
yaml: config,
user: 'admin',
pass: pass,
};
}

View File

@@ -1,51 +0,0 @@
var util = require('util');
function parse_error_params(params, status, msg) {
if (typeof(params) === 'string') {
return {
msg: params,
status: status,
};
} else if (typeof(params) === 'number') {
return {
msg: msg,
status: params,
};
} else if (typeof(params) === 'object' && params != null) {
if (params.msg == null) params.msg = msg;
if (params.status == null) params.status = status;
return params;
} else {
return {
msg: msg,
status: status,
};
}
}
/*
* Errors caused by malfunctioned code
*/
var AppError = function(params, constr) {
Error.captureStackTrace(this, constr || this);
params = parse_error_params(params, 500, 'Internal server error');
this.msg = params.msg;
this.status = params.status;
};
util.inherits(AppError, Error);
AppError.prototype.name = 'Application Error';
/*
* Errors caused by wrong request
*/
var UserError = function(params, constr) {
params = parse_error_params(params, 404, 'The requested resource was not found');
this.msg = params.msg;
this.status = params.status;
};
util.inherits(UserError, Error);
UserError.prototype.name = 'User Error';
module.exports.AppError = AppError;
module.exports.UserError = UserError;

View File

@@ -1,90 +0,0 @@
var fs = require('fs');
var Path = require('path');
function make_directories(dest, cb) {
var dir = Path.dirname(dest);
if (dir === '.' || dir === '..') return cb();
fs.mkdir(dir, function(err) {
if (err && err.code === 'ENOENT') {
make_directories(dir, function() {
fs.mkdir(dir, cb);
})
} else {
cb();
}
});
}
function write_file(dest, data, cb) {
var safe_write = function(cb) {
fs.writeFile(dest, data, cb);
}
safe_write(function(err) {
if (err && err.code === 'ENOENT') {
make_directories(dest, function() {
safe_write(cb);
})
} else {
cb(err);
}
});
}
function create(name, contents, callback) {
fs.exists(name, function(exists) {
if (exists) return callback(new Error({code: 'EEXISTS'}));
write_file(name, contents, callback);
});
}
function update(name, contents, callback) {
fs.exists(name, function(exists) {
if (!exists) return callback(new Error({code: 'ENOENT'}));
write_file(name, contents, callback);
});
}
function read(name, callback) {
fs.readFile(name, callback);
}
function Storage(path) {
this.path = path;
try {
fs.mkdirSync(path);
console.log('created new packages directory: ', path);
} catch(err) {
if (err.code !== 'EEXIST') throw new Error(err);
}
}
Storage.prototype.read = function(name, cb) {
read(this.path + '/' + name, cb);
}
Storage.prototype.read_json = function(name, cb) {
read(this.path + '/' + name, function(err, res) {
if (err) return cb(err);
cb(null, JSON.parse(res));
});
}
Storage.prototype.create = function(name, value, cb) {
create(this.path + '/' + name, value, cb);
}
Storage.prototype.create_json = function(name, value, cb) {
create(this.path + '/' + name, JSON.stringify(value), cb);
}
Storage.prototype.update = function(name, value, cb) {
update(this.path + '/' + name, value, cb);
}
Storage.prototype.update_json = function(name, value, cb) {
update(this.path + '/' + name, JSON.stringify(value), cb);
}
module.exports = Storage;

View File

@@ -1,198 +0,0 @@
var express = require('express');
var cookies = require('cookies');
var utils = require('./utils');
var Storage = require('./storage');
var Config = require('./config');
var UError = require('./error').UserError;
var basic_auth = require('./middleware').basic_auth;
var validate_name = require('./middleware').validate_name;
var media = require('./middleware').media;
var expect_json = require('./middleware').expect_json;
module.exports = function(config_hash) {
var config = new Config(config_hash);
var storage = new Storage(config);
var can = function(action) {
return function(req, res, next) {
if (config['allow_'+action](req.params.package, req.remoteUser)) {
next();
} else {
next(new UError({
status: 403,
msg: 'user '+req.remoteUser+' not allowed to '+action+' it'
}));
}
};
};
var app = express();
app.use(express.logger());
app.use(basic_auth(function(user, pass) {
return config.authenticate(user, pass);
}));
app.use(express.bodyParser());
app.param('package', validate_name);
app.param('filename', validate_name);
/* app.get('/', function(req, res) {
res.send({
error: 'unimplemented'
});
});*/
/* app.get('/-/all', function(req, res) {
var https = require('https');
var JSONStream = require('JSONStream');
var request = require('request')({
url: 'https://registry.npmjs.org/-/all',
ca: require('./npmsslkeys'),
})
.pipe(JSONStream.parse('*'))
.on('data', function(d) {
console.log(d);
});
});*/
// TODO: anonymous user?
app.get('/:package/:version?', can('access'), function(req, res, next) {
storage.get_package(req.params.package, function(err, info) {
if (err) return next(err);
// XXX: in some cases npm calls for /:package and for some cases
// for /:package/:version - should investigate that
if (req.params.version) {
if (info.versions[req.params.version] != null) {
info = info.versions[req.params.version];
} else {
return next(new UError({
status: 404,
msg: 'version not found: ' + req.params.version
}));
}
}
res.send(info);
});
});
app.get('/:package/-/:filename', can('access'), function(req, res, next) {
storage.get_tarball(req.params.package, req.params.filename, function(err, stream) {
if (err) return next(err);
if (!stream) {
return next(new UError({
status: 404,
msg: 'package not found'
}));
}
res.header('content-type', 'application/octet-stream');
res.send(stream);
});
});
//app.get('/*', function(req, res) {
// proxy.request(req, res);
//});
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
app.post('/_session', cookies.express(), function(req, res) {
res.cookies.set('AuthSession', String(Math.random()), {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + 10*60*60*1000)
});
res.send({"ok":true,"name":"somebody","roles":[]});
});
app.get('/-/user/:argument', function(req, res, next) {
// can't put 'org.couchdb.user' in route address for some reason
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route');
res.status(200);
return res.send({
ok: 'you are authenticated as "' + req.user + '"',
});
});
app.put('/-/user/:argument', function(req, res, next) {
// can't put 'org.couchdb.user' in route address for some reason
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route');
res.status(409);
return res.send({
error: 'registration is not implemented',
});
});
app.put('/-/user/:argument/-rev/*', function(req, res, next) {
// can't put 'org.couchdb.user' in route address for some reason
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route');
res.status(201);
return res.send({
ok: 'you are authenticated as "' + req.user + '"',
});
});
// publishing a package
app.put('/:package', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package;
try {
var metadata = utils.validate_metadata(req.body, name);
} catch(err) {
return next(new UError({
status: 422,
msg: 'bad incoming package data',
}));
}
storage.add_package(name, metadata, function(err) {
if (err) return next(err);
res.status(201);
return res.send({
ok: 'created new package'
});
});
});
// uploading package tarball
app.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) {
var name = req.params.package;
storage.add_tarball(name, req.params.filename, req, function(err) {
if (err) return next(err);
res.status(201);
return res.send({
ok: 'tarball uploaded successfully'
});
});
});
// adding a version
app.put('/:package/:version/-tag/:tag', can('publish'), media('application/json'), expect_json, function(req, res, next) {
var name = req.params.package;
var version = req.params.version;
var tag = req.params.tag;
storage.add_version(name, version, req.body, tag, function(err) {
if (err) return next(err);
res.status(201);
return res.send({
ok: 'package published'
});
});
});
app.use(app.router);
app.use(function(err, req, res, next) {
if (err.status && err.msg && err.status >= 400 && err.status < 600) {
res.status(err.status);
res.send({error: err.msg});
} else {
console.log(err);
console.log(err.stack);
res.status(500);
res.send({error: 'internal server error'});
}
});
return app;
};

View File

@@ -1,79 +0,0 @@
var utils = require('./utils');
var UError = require('./error').UserError;
module.exports.validate_name = function validate_name(req, res, next, value, name) {
if (utils.validate_name(req.params.package)) {
req.params.package = String(req.params.package);
next();
} else {
next(new UError({
status: 403,
msg: 'invalid package name',
}));
}
};
module.exports.media = function media(expect) {
return function(req, res, next) {
if (req.headers['content-type'] !== expect) {
next(new UError({
status: 415,
msg: 'wrong content-type, we expect '+expect,
}));
} else {
next();
}
}
}
module.exports.expect_json = function expect_json(req, res, next) {
if (typeof(req.body) !== 'object') {
return next({
status: 400,
msg: 'can\'t parse incoming json',
});
}
next();
}
module.exports.basic_auth = function basic_auth(callback) {
return function(req, res, next) {
var authorization = req.headers.authorization;
if (req.user) return next();
if (authorization == null) {
req.user = req.remoteUser = 'anonymous';
return next();
}
var parts = authorization.split(' ');
if (parts.length !== 2) return next({
status: 400,
msg: 'bad authorization header',
});
var scheme = parts[0]
, credentials = new Buffer(parts[1], 'base64').toString()
, index = credentials.indexOf(':');
if ('Basic' != scheme || index < 0) return next({
status: 400,
msg: 'bad authorization header',
});
var user = credentials.slice(0, index)
, pass = credentials.slice(index + 1);
if (callback(user, pass)) {
req.user = req.remoteUser = user;
next();
} else {
next({
status: 403,
msg: 'bad username/password, access denied',
});
}
}
};

View File

@@ -1,62 +0,0 @@
//
// Get this thingy from `npmconf` package if it ever changes...
//
module.exports = // the npm CA certificate.
[ "-----BEGIN CERTIFICATE-----\n"+
"MIIChzCCAfACCQDauvz/KHp8ejANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMC\n"+
"VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMQwwCgYDVQQKEwNucG0x\n"+
"IjAgBgNVBAsTGW5wbSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxDjAMBgNVBAMTBW5w\n"+
"bUNBMRcwFQYJKoZIhvcNAQkBFghpQGl6cy5tZTAeFw0xMTA5MDUwMTQ3MTdaFw0y\n"+
"MTA5MDIwMTQ3MTdaMIGHMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEDAOBgNV\n"+
"BAcTB09ha2xhbmQxDDAKBgNVBAoTA25wbTEiMCAGA1UECxMZbnBtIENlcnRpZmlj\n"+
"YXRlIEF1dGhvcml0eTEOMAwGA1UEAxMFbnBtQ0ExFzAVBgkqhkiG9w0BCQEWCGlA\n"+
"aXpzLm1lMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLI4tIqPpRW+ACw9GE\n"+
"OgBlJZwK5f8nnKCLK629Pv5yJpQKs3DENExAyOgDcyaF0HD0zk8zTp+ZsLaNdKOz\n"+
"Gn2U181KGprGKAXP6DU6ByOJDWmTlY6+Ad1laYT0m64fERSpHw/hjD3D+iX4aMOl\n"+
"y0HdbT5m1ZGh6SJz3ZqxavhHLQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAC4ySDbC\n"+
"l7W1WpLmtLGEQ/yuMLUf6Jy/vr+CRp4h+UzL+IQpCv8FfxsYE7dhf/bmWTEupBkv\n"+
"yNL18lipt2jSvR3v6oAHAReotvdjqhxddpe5Holns6EQd1/xEZ7sB1YhQKJtvUrl\n"+
"ZNufy1Jf1r0ldEGeA+0ISck7s+xSh9rQD2Op\n"+
"-----END CERTIFICATE-----\n",
// "GlobalSign Root CA"
"-----BEGIN CERTIFICATE-----\n"+
"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx\n"+
"GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds\n"+
"b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV\n"+
"BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD\n"+
"VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa\n"+
"DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc\n"+
"THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb\n"+
"Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP\n"+
"c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX\n"+
"gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"+
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF\n"+
"AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj\n"+
"Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG\n"+
"j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH\n"+
"hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC\n"+
"X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n"+
"-----END CERTIFICATE-----\n",
// "GlobalSign Root CA - R2"
"-----BEGIN CERTIFICATE-----\n"+
"MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv\n"+
"YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh\n"+
"bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT\n"+
"aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln\n"+
"bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6\n"+
"ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp\n"+
"s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN\n"+
"S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL\n"+
"TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C\n"+
"ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\n"+
"FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i\n"+
"YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN\n"+
"BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp\n"+
"9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu\n"+
"01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7\n"+
"9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7\n"+
"TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==\n"+
"-----END CERTIFICATE-----\n" ]

View File

@@ -1,24 +0,0 @@
var https = require('https');
module.exports.request = function(req, resp) {
https.get({
host: 'registry.npmjs.org',
path: req.url,
ca: require('./npmsslkeys'),
headers: {
'User-Agent': 'sinopia/0.0.0',
},
}, function(res) {
resp.writeHead(res.statusCode, res.headers);
res.on('data', function(d) {
resp.write(d);
});
res.on('end', function() {
resp.end();
});
}).on('error', function(err) {
console.error(err);
resp.send(500);
});
}

View File

@@ -1,97 +0,0 @@
var fs_storage = require('./fs-storage');
var UError = require('./error').UserError;
var info_file = 'package.json';
var fs = require('fs');
function Storage(config) {
if (!(this instanceof Storage)) return new Storage(config);
this.config = config;
this.storage = new fs_storage(this.config.storage);
return this;
}
Storage.prototype.add_package = function(name, metadata, callback) {
this.storage.create_json(name + '/' + info_file, metadata, function(err) {
if (err && err.code === 'EEXISTS') {
return callback(new UError({
status: 409,
msg: 'this package is already present'
}));
}
callback();
});
}
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
var self = this;
self.storage.read_json(name + '/' + info_file, function(err, data) {
// TODO: race condition
if (err) return callback(err);
if (data.versions[version] != null) {
return callback(new UError({
status: 409,
msg: 'this version already present'
}));
}
data.versions[version] = metadata;
data['dist-tags'][tag] = version;
self.storage.update_json(name + '/' + info_file, data, callback);
});
}
Storage.prototype.add_tarball = function(name, filename, stream, callback) {
var self = this;
if (name === info_file) {
return callback(new UError({
status: 403,
msg: 'can\'t use this filename'
}));
}
var data = new Buffer(0);
stream.on('data', function(d) {
var tmp = data;
data = new Buffer(tmp.length+d.length);
tmp.copy(data, 0);
d.copy(data, tmp.length);
});
stream.on('end', function(d) {
self.storage.create(name + '/' + filename, data, function(err) {
if (err && err.code === 'EEXISTS') {
return callback(new UError({
status: 409,
msg: 'this tarball is already present'
}));
}
callback.apply(null, arguments);
});
});
}
Storage.prototype.get_tarball = function(name, filename, callback) {
this.storage.read(name + '/' + filename, function(err) {
if (err && err.code === 'ENOENT') {
return callback(new UError({
status: 404,
msg: 'no such package available'
}));
}
callback.apply(null, arguments);
});
}
Storage.prototype.get_package = function(name, callback) {
this.storage.read_json(name + '/' + info_file, function(err) {
if (err && err.code === 'ENOENT') {
return callback(new UError({
status: 404,
msg: 'no such package available'
}));
}
callback.apply(null, arguments);
});
}
module.exports = Storage;

View File

@@ -1,41 +0,0 @@
var request = require('request');
var UError = require('./error').UserError;
var URL = require('url');
function Storage(name, config) {
if (!(this instanceof Storage)) return new Storage(config);
this.config = config;
this.name = name;
this.ca;
if (URL.parse(this.config.uplinks[this.name].url).hostname === 'registry.npmjs.org') {
this.ca = require('./npmsslkeys');
}
return this;
}
Storage.prototype.get_package = function(name, callback) {
request({
url: this.config.uplinks[this.name].url + '/' + name,
json: true,
headers: {
'User-Agent': this.config.user_agent,
},
ca: this.ca,
}, function(err, res, body) {
if (err) return callback(err);
if (res.statusCode === 404) {
return callback(new UError({
msg: 'package doesn\'t exist on uplink',
status: 404,
}));
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return callback(new Error('bad status code: ' + res.statusCode));
}
callback(null, body);
});
}
module.exports = Storage;

View File

@@ -1,131 +0,0 @@
var async = require('async');
var semver = require('semver');
var UError = require('./error').UserError;
var Local = require('./st-local');
var Proxy = require('./st-proxy');
var utils = require('./utils');
function Storage(config) {
if (!(this instanceof Storage)) return new Storage(config);
this.config = config;
this.uplinks = {};
for (var p in config.uplinks) {
this.uplinks[p] = new Proxy(p, config);
}
this.local = new Local(config);
return this;
}
Storage.prototype.add_package = function(name, metadata, callback) {
var self = this;
var uplinks = [];
for (var i in this.uplinks) {
if (this.config.allow_proxy(name, i)) {
uplinks.push(this.uplinks[i]);
}
}
async.map(uplinks, function(up, cb) {
up.get_package(name, function(err, res) {
cb(null, [err, res]);
});
}, function(err, results) {
for (var i=0; i<results.length; i++) {
// checking error
// if uplink fails with a status other than 404, we report failure
if (results[i][0] != null) {
if (results[i][0].status !== 404) {
return callback(new UError({
status: 503,
msg: 'one of the uplinks is down, refuse to publish'
}));
}
}
// checking package
if (results[i][1] != null) {
return callback(new UError({
status: 409,
msg: 'this package is already present'
}));
}
}
self.local.add_package(name, metadata, callback);
});
}
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
this.local.add_version(name, version, metadata, tag, callback);
}
Storage.prototype.add_tarball = function(name, filename, stream, callback) {
this.local.add_tarball(name, filename, stream, callback);
}
Storage.prototype.get_tarball = function(name, filename, callback) {
this.local.get_tarball(name, filename, callback);
}
Storage.prototype.get_package = function(name, callback) {
var uplinks = [this.local];
for (var i in this.uplinks) {
if (this.config.allow_proxy(name, i)) {
uplinks.push(this.uplinks[i]);
}
}
var result = {
name: name,
versions: {},
'dist-tags': {},
};
var exists = false;
var latest;
async.map(uplinks, function(up, cb) {
up.get_package(name, function(err, up_res) {
if (err) return cb();
try {
utils.validate_metadata(up_res, name);
} catch(err) {
return cb();
}
var this_version = up_res['dist-tags'].latest;
if (!semver.gt(latest, this_version) && this_version) {
latest = this_version;
var is_latest = true;
}
['versions', 'dist-tags'].forEach(function(key) {
for (var i in up_res[key]) {
if (!result[key][i] || is_latest) {
result[key][i] = up_res[key][i];
}
}
});
// if we got to this point, assume that the correct package exists
// on the uplink
exists = true;
cb();
});
}, function(err) {
if (err) return callback(err);
if (!exists) {
return callback(new UError({
status: 404,
msg: 'no such package available'
}));
}
callback(null, result);
});
}
module.exports = Storage;

View File

@@ -1,37 +0,0 @@
var assert = require('assert');
// from normalize-package-data/lib/fixer.js
module.exports.validate_name = function(name) {
if (
name.charAt(0) === "." ||
name.match(/[\/@\s\+%:]/) ||
name !== encodeURIComponent(name) ||
name.toLowerCase() === "node_modules" ||
name.toLowerCase() === "favicon.ico"
) {
return false;
} else {
return true;
}
}
function is_object(obj) {
return typeof(obj) === 'object' && !Array.isArray(obj);
}
module.exports.validate_metadata = function(object, name) {
assert(is_object(object));
assert.equal(object._id, name);
assert.equal(object.name, name);
if (!is_object(object['dist-tags'])) {
object['dist-tags'] = {};
}
if (!is_object(object['versions'])) {
object['versions'] = {};
}
return object;
}

153
package.json Normal file
View File

@@ -0,0 +1,153 @@
{
"name": "verdaccio",
"version": "2.4.0",
"description": "Private npm repository server",
"author": {
"name": "Alex Kocharin",
"email": "alex@kocharin.ru"
},
"repository": {
"type": "git",
"url": "git://github.com/verdaccio/verdaccio"
},
"main": "index.js",
"bin": {
"verdaccio": "./bin/verdaccio"
},
"dependencies": {
"@verdaccio/file-locking": "^0.0.3",
"@verdaccio/streams": "^0.0.2",
"JSONStream": "^1.1.1",
"apache-md5": "^1.1.2",
"async": "^2.0.1",
"body-parser": "^1.15.0",
"bunyan": "^1.8.0",
"chalk": "^2.0.1",
"commander": "^2.11.0",
"compression": "1.6.2",
"cookies": "^0.7.0",
"cors": "^2.8.3",
"express": "4.15.3",
"global": "^4.3.2",
"handlebars": "4.0.5",
"http-errors": "^1.4.0",
"js-string-escape": "1.0.1",
"js-yaml": "^3.6.0",
"jsonwebtoken": "^7.4.1",
"lockfile": "^1.0.1",
"lodash": "4.17.4",
"lunr": "^0.7.0",
"marked": "0.3.6",
"mime": "^1.3.6",
"minimatch": "^3.0.2",
"mkdirp": "^0.5.1",
"pkginfo": "^0.4.0",
"request": "^2.72.0",
"semver": "^5.1.0",
"unix-crypt-td-js": "^1.0.0"
},
"devDependencies": {
"axios": "0.16.2",
"babel-cli": "6.24.1",
"babel-core": "6.25.0",
"babel-eslint": "7.2.3",
"babel-loader": "7.1.1",
"babel-plugin-flow-runtime": "0.11.1",
"babel-plugin-transform-decorators-legacy": "1.3.4",
"babel-plugin-transform-runtime": "6.23.0",
"babel-preset-env": "1.5.2",
"babel-preset-flow": "6.23.0",
"babel-preset-react": "6.24.1",
"babel-preset-stage-2": "6.24.1",
"babel-preset-stage-3": "6.24.1",
"babel-runtime": "6.23.0",
"codacy-coverage": "2.0.2",
"codecov": "2.2.0",
"coveralls": "2.13.1",
"css-loader": "0.28.4",
"element-react": "1.0.16",
"element-theme-default": "1.3.7",
"eslint": "4.2.0",
"eslint-config-google": "0.8.0",
"eslint-loader": "1.8.0",
"eslint-plugin-babel": "4.1.1",
"eslint-plugin-flowtype": "2.35.0",
"eslint-plugin-import": "2.6.1",
"eslint-plugin-react": "7.1.0",
"extract-text-webpack-plugin": "3.0.0",
"file-loader": "0.11.2",
"flow-runtime": "0.13.0",
"friendly-errors-webpack-plugin": "1.6.1",
"fs-extra": "4.0.1",
"github-markdown-css": "2.8.0",
"html-webpack-plugin": "2.29.0",
"in-publish": "2.0.0",
"localstorage-memory": "1.0.2",
"mocha": "3.4.2",
"mocha-lcov-reporter": "1.3.0",
"node-sass": "4.5.3",
"normalize.css": "7.0.0",
"nyc": "11.0.3",
"ora": "1.3.0",
"prop-types": "15.5.10",
"react": "15.6.1",
"react-dom": "15.6.1",
"react-hot-loader": "3.0.0-beta.7",
"react-router-dom": "4.1.1",
"react-syntax-highlighter": "5.6.2",
"rimraf": "2.6.1",
"sass-loader": "6.0.6",
"source-map-loader": "0.2.1",
"standard-version": "4.2.0",
"style-loader": "0.18.2",
"stylelint": "7.13.0",
"stylelint-config-standard": "16.0.0",
"stylelint-webpack-plugin": "0.8.0",
"url-loader": "0.5.8",
"webpack": "3.2.0",
"webpack-dev-server": "2.5.0",
"webpack-merge": "4.1.0"
},
"keywords": [
"private",
"package",
"repository",
"registry",
"enterprise",
"modules",
"proxy",
"server"
],
"scripts": {
"release": "standard-version -a -s",
"prepublish": "in-publish && npm run build:webui || not-in-publish",
"test": "mocha ./test/functional ./test/unit --reporter=spec --full-trace",
"pre:ci": "npm run build:webui",
"test:ci": "npm run test:coverage",
"test:only": "mocha ./test/functional ./test/unit",
"test:coverage": "nyc npm t",
"coverage:html": "nyc report --reporter=html",
"coverage:publish": "nyc report --reporter=lcov | codecov",
"lint": "eslint .",
"lint:css": "stylelint 'src/**/*.scss' --syntax scss",
"pre:webpack": "npm run lint && rimraf static/*",
"dev:webui": "babel-node tools/dev.server.js",
"build:webui": "npm run pre:webpack && webpack --config tools/webpack.prod.config.babel.js",
"build:docker": "docker build -t verdaccio . --no-cache",
"build:docker:rpi": "docker build -f Dockerfile.rpi -t verdaccio:rpi ."
},
"jest": {
"snapshotSerializers": [
"jest-serializer-enzyme"
]
},
"engines": {
"node": ">=4.6.1",
"npm": ">=2.15.9"
},
"preferGlobal": true,
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"license": "WTFPL"
}

View File

@@ -1,26 +0,0 @@
name: sinopia
version: 0.1.1
description: Private npm repository server
author:
name: Alex Kocharin
email: alex@kocharin.ru
main: index.js
bin:
sinopia: ./bin/sinopia
dependencies:
express: '>= 3.2.5'
commander: '>= 1.1.1'
js-yaml: '>= 2.0.5'
cookies: '>= 0.3.6'
request: '*'
async: '*'
semver: '*'
preferGlobal: true
license: BSD

6
scripts/generate_authors.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
git shortlog -se \
| perl -spe 's/^\s+\d+\s+//' \
| sed -e '/^CommitSyncScript.*$/d' \
> AUTHORS

5
src/api/.eslintrc Normal file
View File

@@ -0,0 +1,5 @@
{
"rules": {
"no-useless-escape": 0
}
}

View File

@@ -0,0 +1,94 @@
'use strict';
const Middleware = require('../../web/middleware');
const mime = require('mime');
const _ = require('lodash');
const media = Middleware.media;
const expect_json = Middleware.expect_json;
module.exports = function(route, auth, storage) {
const can = Middleware.allow(auth);
const tag_package_version = function(req, res, next) {
if (_.isString(req.body) === false) {
return next('route');
}
let tags = {};
tags[req.params.tag] = req.body;
storage.merge_tags(req.params.package, tags, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({ok: 'package tagged'});
});
};
// tagging a package
route.put('/:package/:tag',
can('publish'), media(mime.lookup('json')), tag_package_version);
route.post('/-/package/:package/dist-tags/:tag',
can('publish'), media(mime.lookup('json')), tag_package_version);
route.put('/-/package/:package/dist-tags/:tag',
can('publish'), media(mime.lookup('json')), tag_package_version);
route.delete('/-/package/:package/dist-tags/:tag', can('publish'), function(req, res, next) {
const tags = {};
tags[req.params.tag] = null;
storage.merge_tags(req.params.package, tags, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({
ok: 'tag removed',
});
});
});
route.get('/-/package/:package/dist-tags', can('access'), function(req, res, next) {
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) {
return next(err);
}
next(info['dist-tags']);
});
});
route.post('/-/package/:package/dist-tags', can('publish'), media(mime.lookup('json')), expect_json,
function(req, res, next) {
storage.merge_tags(req.params.package, req.body, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({ok: 'tags updated'});
});
});
route.put('/-/package/:package/dist-tags', can('publish'), media(mime.lookup('json')), expect_json,
function(req, res, next) {
storage.replace_tags(req.params.package, req.body, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({ok: 'tags updated'});
});
});
route.delete('/-/package/:package/dist-tags', can('publish'), media(mime.lookup('json')),
function(req, res, next) {
storage.replace_tags(req.params.package, {}, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({ok: 'tags removed'});
});
});
};

View File

@@ -0,0 +1,54 @@
'use strict';
const _ = require('lodash');
const createError = require('http-errors');
const Middleware = require('../../web/middleware');
const Utils = require('../../../lib/utils');
module.exports = function(route, auth, storage, config) {
const can = Middleware.allow(auth);
// TODO: anonymous user?
route.get('/:package/:version?', can('access'), function(req, res, next) {
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) {
return next(err);
}
info = Utils.filter_tarball_urls(info, req, config);
let queryVersion = req.params.version;
if (_.isNil(queryVersion)) {
return next(info);
}
let t = Utils.get_version(info, queryVersion);
if (_.isNil(t) === false) {
return next(t);
}
if (_.isNil(info['dist-tags']) === false) {
if (_.isNil(info['dist-tags'][queryVersion]) === false) {
queryVersion = info['dist-tags'][queryVersion];
t = Utils.get_version(info, queryVersion);
if (_.isNil(t)) {
return next(t);
}
}
}
return next( createError[404]('version not found: ' + req.params.version) );
});
});
route.get('/:package/-/:filename', can('access'), function(req, res) {
const stream = storage.get_tarball(req.params.package, req.params.filename);
stream.on('content-length', function(content) {
res.header('Content-Length', content);
});
stream.on('error', function(err) {
return res.report_error(err);
});
res.header('Content-Type', 'application/octet-stream');
stream.pipe(res);
});
};

View File

@@ -0,0 +1,7 @@
'use strict';
module.exports = function(route) {
route.get('/-/ping', function(req, res, next) {
next({});
});
};

View File

@@ -0,0 +1,188 @@
'use strict';
const _ = require('lodash');
const Path = require('path');
const createError = require('http-errors');
const Middleware = require('../../web/middleware');
const Notify = require('../../../lib/notify');
const Utils = require('../../../lib/utils');
const mime = require('mime');
const media = Middleware.media;
const expect_json = Middleware.expect_json;
const notify = Notify.notify;
module.exports = function(router, auth, storage, config) {
const can = Middleware.allow(auth);
// publishing a package
router.put('/:package/:_rev?/:revision?', can('publish'), media(mime.lookup('json')), expect_json, function(req, res, next) {
const name = req.params.package;
let metadata;
const create_tarball = function(filename, data, cb) {
let stream = storage.add_tarball(name, filename);
stream.on('error', function(err) {
cb(err);
});
stream.on('success', function() {
cb();
});
// this is dumb and memory-consuming, but what choices do we have?
stream.end(new Buffer(data.data, 'base64'));
stream.done();
};
const create_version = function(version, data, cb) {
storage.add_version(name, version, data, null, cb);
};
const add_tags = function(tags, cb) {
storage.merge_tags(name, tags, cb);
};
const after_change = function(err, ok_message) {
// old npm behaviour
if (_.isNil(metadata._attachments)) {
if (err) return next(err);
res.status(201);
return next({
ok: ok_message,
success: true,
});
}
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue https://github.com/rlidwka/sinopia/issues/31, dealing with it here:
if (typeof(metadata._attachments) !== 'object'
|| Object.keys(metadata._attachments).length !== 1
|| typeof(metadata.versions) !== 'object'
|| Object.keys(metadata.versions).length !== 1) {
// npm is doing something strange again
// if this happens in normal circumstances, report it as a bug
return next( createError[400]('unsupported registry call') );
}
if (err && err.status != 409) {
return next(err);
}
// at this point document is either created or existed before
const t1 = Object.keys(metadata._attachments)[0];
create_tarball(Path.basename(t1), metadata._attachments[t1], function(err) {
if (err) {
return next(err);
}
const t2 = Object.keys(metadata.versions)[0];
metadata.versions[t2].readme = _.isNil(metadata.readme) === false ? String(metadata.readme) : '';
create_version(t2, metadata.versions[t2], function(err) {
if (err) {
return next(err);
}
add_tags(metadata['dist-tags'], function(err) {
if (err) {
return next(err);
}
notify(metadata, config);
res.status(201);
return next({ok: ok_message, success: true});
});
});
});
};
if (Object.keys(req.body).length === 1 && Utils.is_object(req.body.users)) {
// 501 status is more meaningful, but npm doesn't show error message for 5xx
return next( createError[404]('npm star|unstar calls are not implemented') );
}
try {
metadata = Utils.validate_metadata(req.body, name);
} catch(err) {
return next( createError[422]('bad incoming package data') );
}
if (req.params._rev) {
storage.change_package(name, metadata, req.params.revision, function(err) {
after_change(err, 'package changed');
});
} else {
storage.addPackage(name, metadata, function(err) {
after_change(err, 'created new package');
});
}
});
// unpublishing an entire package
router.delete('/:package/-rev/*', can('publish'), function(req, res, next) {
storage.remove_package(req.params.package, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({ok: 'package removed'});
});
});
// removing a tarball
router.delete('/:package/-/:filename/-rev/:revision', can('publish'), function(req, res, next) {
storage.remove_tarball(req.params.package, req.params.filename, req.params.revision, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({ok: 'tarball removed'});
});
});
// uploading package tarball
router.put('/:package/-/:filename/*', can('publish'), media('application/octet-stream'), function(req, res, next) {
const name = req.params.package;
const stream = storage.add_tarball(name, req.params.filename);
req.pipe(stream);
// checking if end event came before closing
let complete = false;
req.on('end', function() {
complete = true;
stream.done();
});
req.on('close', function() {
if (!complete) {
stream.abort();
}
});
stream.on('error', function(err) {
return res.report_error(err);
});
stream.on('success', function() {
res.status(201);
return next({
ok: 'tarball uploaded successfully',
});
});
});
// adding a version
router.put('/:package/:version/-tag/:tag', can('publish'), media(mime.lookup('json')), expect_json, function(req, res, next) {
let name = req.params.package;
let version = req.params.version;
let tag = req.params.tag;
storage.add_version(name, version, req.body, tag, function(err) {
if (err) {
return next(err);
}
res.status(201);
return next({
ok: 'package published',
});
});
});
};

View File

@@ -0,0 +1,99 @@
'use strict';
module.exports = function(route, auth, storage) {
// searching packages
route.get('/-/all(\/since)?', function(req, res) {
let received_end = false;
let response_finished = false;
let processing_pkgs = 0;
let firstPackage = true;
res.status(200);
/*
* Offical NPM registry (registry.npmjs.org) no longer return whole database,
* They only return packages matched with keyword in `referer: search pkg-name`,
* And NPM client will request server in every search.
*
* The magic number 99999 was sent by NPM registry. Modify it may caused strange
* behaviour in the future.
*
* BTW: NPM will not return result if user-agent does not contain string 'npm',
* See: method 'request' in up-storage.js
*
* If there is no cache in local, NPM will request /-/all, then get response with
* _updated: 99999, 'Date' in response header was Mon, 10 Oct 1983 00:12:48 GMT,
* this will make NPM always query from server
*
* Data structure also different, whel request /-/all, response is an object, but
* when request /-/all/since, response is an array
*/
const respShouldBeArray = req.path.endsWith('/since');
res.set('Date', 'Mon, 10 Oct 1983 00:12:48 GMT');
const check_finish = function() {
if (!received_end) {
return;
}
if (processing_pkgs) {
return;
}
if (response_finished) {
return;
}
response_finished = true;
if (respShouldBeArray) {
res.end(']\n');
} else {
res.end('}\n');
}
};
if (respShouldBeArray) {
res.write('[');
} else {
res.write('{"_updated":' + 99999);
}
let stream = storage.search(req.query.startkey || 0, {req: req});
stream.on('data', function each(pkg) {
processing_pkgs++;
auth.allow_access(pkg.name, req.remote_user, function(err, allowed) {
processing_pkgs--;
if (err) {
if (err.status && String(err.status).match(/^4\d\d$/)) {
// auth plugin returns 4xx user error,
// that's equivalent of !allowed basically
allowed = false;
} else {
stream.abort(err);
}
}
if (allowed) {
if (respShouldBeArray) {
res.write(`${firstPackage ? '' : ','}${JSON.stringify(pkg)}\n`);
if (firstPackage) {
firstPackage = false;
}
} else {
res.write(',\n' + JSON.stringify(pkg.name) + ':' + JSON.stringify(pkg));
}
}
check_finish();
});
});
stream.on('error', function(_err) {
res.socket.destroy();
});
stream.on('end', function() {
received_end = true;
check_finish();
});
});
};

View File

@@ -0,0 +1,70 @@
'use strict';
const _ = require('lodash');
const Cookies = require('cookies');
const createError = require('http-errors');
module.exports = function(route, auth) {
route.get('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(200);
next({
ok: 'you are authenticated as "' + req.remote_user.name + '"',
});
});
route.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
let token = (req.body.name && req.body.password)
? auth.aes_encrypt(req.body.name + ':' + req.body.password).toString('base64')
: undefined;
if (_.isNil(req.remote_user.name) === false) {
res.status(201);
return next({
ok: 'you are authenticated as \'' + req.remote_user.name + '\'',
// token: auth.issue_token(req.remote_user),
token: token,
});
} else {
auth.add_user(req.body.name, req.body.password, function(err, user) {
if (err) {
if (err.status >= 400 && err.status < 500) {
// With npm registering is the same as logging in,
// and npm accepts only an 409 error.
// So, changing status code here.
return next( createError[409](err.message) );
}
return next(err);
}
req.remote_user = user;
res.status(201);
return next({
ok: 'user \'' + req.body.name + '\' created',
// token: auth.issue_token(req.remote_user),
token: token,
});
});
}
});
route.delete('/-/user/token/*', function(req, res, next) {
res.status(200);
next({
ok: 'Logged out',
});
});
// placeholder 'cause npm require to be authenticated to publish
// we do not do any real authentication yet
route.post('/_session', Cookies.express(), function(req, res, next) {
res.cookies.set('AuthSession', String(Math.random()), {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + 10 * 60 * 60 * 1000),
});
next({
ok: true,
name: 'somebody',
roles: [],
});
});
};

View File

@@ -0,0 +1,15 @@
'use strict';
module.exports = function(route) {
route.get('/whoami', function(req, res, next) {
if (req.headers.referer === 'whoami') {
next({username: req.remote_user.name});
} else {
next('route');
}
});
route.get('/-/whoami', function(req, res, next) {
next({username: req.remote_user.name});
});
};

63
src/api/endpoint/index.js Normal file
View File

@@ -0,0 +1,63 @@
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const Middleware = require('../web/middleware');
const match = Middleware.match;
const validate_name = Middleware.validate_name;
const validate_pkg = Middleware.validate_package;
const encodeScopePackage = Middleware.encodeScopePackage;
const whoami = require('./api/whoami');
const ping = require('./api/ping');
const user = require('./api/user');
const distTags = require('./api/dist-tags');
const publish = require('./api/publish');
const search = require('./api/search');
const pkg = require('./api/package');
module.exports = function(config, auth, storage) {
/* eslint new-cap:off */
const app = express.Router();
/* eslint new-cap:off */
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
app.param('package', validate_pkg);
app.param('filename', validate_name);
app.param('tag', validate_name);
app.param('version', validate_name);
app.param('revision', validate_name);
app.param('token', validate_name);
// these can't be safely put into express url for some reason
// TODO: For some reason? what reason?
app.param('_rev', match(/^-rev$/));
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/));
app.param('anything', match(/.*/));
app.use(auth.basic_middleware());
// app.use(auth.bearer_middleware())
app.use(bodyParser.json({strict: false, limit: config.max_body_size || '10mb'}));
app.use(Middleware.anti_loop(config));
// encode / in a scoped package name to be matched as a single parameter in routes
app.use(encodeScopePackage);
// for "npm whoami"
whoami(app);
pkg(app, auth, storage, config);
search(app, auth, storage);
user(app, auth);
distTags(app, auth, storage);
publish(app, auth, storage, config);
ping(app);
return app;
};

123
src/api/index.js Normal file
View File

@@ -0,0 +1,123 @@
'use strict';
const express = require('express');
const Error = require('http-errors');
const compression = require('compression');
const Auth = require('../lib/auth');
const Logger = require('../lib/logger');
const Config = require('../lib/config');
const Middleware = require('./web/middleware');
const Cats = require('../lib/status-cats');
const Storage = require('../lib/storage');
const _ = require('lodash');
const cors = require('cors');
module.exports = function(config_hash) {
// Config
Logger.setup(config_hash.logs);
const config = new Config(config_hash);
const storage = new Storage(config);
const auth = new Auth(config);
const app = express();
// run in production mode by default, just in case
// it shouldn't make any difference anyway
app.set('env', process.env.NODE_ENV || 'production');
app.use(cors());
// Middleware
const error_reporting_middleware = function(req, res, next) {
res.report_error = res.report_error || function(err) {
if (err.status && err.status >= 400 && err.status < 600) {
if (_.isNil(res.headersSent) === false) {
res.status(err.status);
next({error: err.message || 'unknown error'});
}
} else {
Logger.logger.error( {err: err}
, 'unexpected error: @{!err.message}\n@{err.stack}');
if (!res.status || !res.send) {
Logger.logger.error('this is an error in express.js, please report this');
res.destroy();
} else if (!res.headersSent) {
res.status(500);
next({error: 'internal server error'});
} else {
// socket should be already closed
}
}
};
next();
};
// Router setup
app.use(Middleware.log);
app.use(error_reporting_middleware);
app.use(function(req, res, next) {
res.setHeader('X-Powered-By', config.user_agent);
next();
});
app.use(Cats.middleware);
app.use(compression());
app.get('/favicon.ico', function(req, res, next) {
req.url = '/-/static/favicon.png';
next();
});
// Hook for tests only
if (config._debug) {
app.get('/-/_debug', function(req, res, next) {
const do_gc = _.isNil(global.gc) === false;
if (do_gc) {
global.gc();
}
next({
pid: process.pid,
main: process.mainModule.filename,
conf: config.self_path,
mem: process.memoryUsage(),
gc: do_gc,
});
});
}
// For npm request
app.use(require('./endpoint')(config, auth, storage));
// For WebUI & WebUI API
if (_.get(config, 'web.enable', true)) {
app.use('/', require('./web')(config, auth, storage));
app.use('/-/verdaccio/', require('./web/api')(config, auth, storage));
} else {
app.get('/', function(req, res, next) {
next(Error[404]('Web interface is disabled in the config file'));
});
}
// Catch 404
app.get('/*', function(req, res, next) {
next(Error[404]('File not found'));
});
app.use(function(err, req, res, next) {
if (_.isError(err)) {
if (err.code === 'ECONNABORT' && res.statusCode === 304) {
return next();
}
if (_.isFunction(res.report_error) === false) {
// in case of very early error this middleware may not be loaded before error is generated
// fixing that
error_reporting_middleware(req, res, _.noop);
}
res.report_error(err);
} else {
// Fall to Middleware.final
return next(err);
}
});
app.use(Middleware.final);
return app;
};

147
src/api/web/api.js Normal file
View File

@@ -0,0 +1,147 @@
'use strict';
const bodyParser = require('body-parser');
const express = require('express');
const marked = require('marked');
const Search = require('../../lib/search');
const Middleware = require('./middleware');
const match = Middleware.match;
const validateName = Middleware.validate_name;
const validatePkg = Middleware.validate_package;
const securityIframe = Middleware.securityIframe;
const route = express.Router(); // eslint-disable-line
const async = require('async');
const HTTPError = require('http-errors');
const Utils = require('../../lib/utils');
/*
This file include all verdaccio only API(Web UI), for npm API please see ../endpoint/
*/
module.exports = function(config, auth, storage) {
Search.configureStorage(storage);
const can = Middleware.allow(auth);
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
route.param('package', validatePkg);
route.param('filename', validateName);
route.param('version', validateName);
route.param('anything', match(/.*/));
route.use(bodyParser.urlencoded({extended: false}));
route.use(auth.jwtMiddleware());
route.use(securityIframe);
// Get list of all visible package
route.get('/packages', function(req, res, next) {
storage.get_local(function(err, packages) {
if (err) {
// that function shouldn't produce any
throw err;
}
async.filterSeries(
packages,
function(pkg, cb) {
auth.allow_access(pkg.name, req.remote_user, function(err, allowed) {
setImmediate(function() {
if (err) {
cb(null, false);
} else {
cb(err, allowed);
}
});
});
},
function(err, packages) {
if (err) throw err;
packages.sort(function(a, b) {
if (a.name < b.name) {
return -1;
} else {
return 1;
}
});
next(packages);
}
);
});
});
// Get package readme
route.get('/package/readme(/@:scope?)?/:package/:version?', can('access'), function(req, res, next) {
let packageName = req.params.package;
if (req.params.scope) {
packageName = `@${req.params.scope}/${packageName}`;
}
storage.get_package(packageName, {req: req}, function(err, info) {
if (err) {
return next(err);
}
res.set('Content-Type', 'text/plain');
next(marked(info.readme || 'ERROR: No README data found!'));
});
});
// Search package
route.get('/search/:anything', function(req, res, next) {
const results = Search.query(req.params.anything);
const packages = [];
const getPackageInfo = function(i) {
storage.get_package(results[i].ref, (err, entry) => {
if (!err && entry) {
auth.allow_access(entry.name, req.remote_user, function(err, allowed) {
if (err || !allowed) {
return;
}
packages.push(entry.versions[entry['dist-tags'].latest]);
});
}
if (i >= results.length - 1) {
next(packages);
} else {
getPackageInfo(i + 1);
}
});
};
if (results.length) {
getPackageInfo(0);
} else {
next([]);
}
});
route.post('/login', function(req, res, next) {
auth.authenticate(req.body.username, req.body.password, (err, user) => {
if (!err) {
req.remote_user = user;
next({
token: auth.issue_token(user, '24h'),
username: req.remote_user.name,
});
} else {
next(HTTPError[err.message ? 401 : 500](err.message));
}
});
});
route.post('/-/logout', function(req, res, next) {
let base = Utils.combineBaseUrl(Utils.getWebProtocol(req), req.get('host'), config.url_prefix);
res.cookies.set('token', '');
res.redirect(base);
});
// What are you looking for? logout? client side will remove token when user click logout,
// or it will auto expire after 24 hours.
// This token is different with the token send to npm client.
// We will/may replace current token with JWT in next major release, and it will not expire at all(configurable).
return route;
};

53
src/api/web/index.js Normal file
View File

@@ -0,0 +1,53 @@
'use strict';
const express = require('express');
const Search = require('../../lib/search');
const Middleware = require('./middleware');
const Utils = require('../../lib/utils');
/* eslint new-cap:off */
const router = express.Router();
const _ = require('lodash');
const env = require('../../config/env');
const fs = require('fs');
const template = fs.readFileSync(`${env.DIST_PATH}/index.html`).toString();
module.exports = function(config, auth, storage) {
Search.configureStorage(storage);
router.use(auth.jwtMiddleware());
router.use(Middleware.securityIframe);
// Static
router.get('/-/static/:filename', function(req, res, next) {
const file = `${env.APP_ROOT}/static/${req.params.filename}`;
res.sendFile(file, function(err) {
if (!err) {
return;
}
if (err.status === 404) {
next();
} else {
next(err);
}
});
});
router.get('/-/verdaccio/logo', function(req, res) {
res.send(_.get(config, 'web.logo') || '/-/static/logo.png');
});
router.get('/', function(req, res) {
const base = Utils.combineBaseUrl(Utils.getWebProtocol(req), req.get('host'), config.url_prefix);
const defaultTitle = 'Verdaccio';
let webPage = template
.replace(/ToReplaceByVerdaccio/g, base)
.replace(/ToReplaceByTitle/g, _.get(config, 'web.title') ? config.web.title : defaultTitle)
.replace(/(main.*\.js|style.*\.css)/g, `${base}/-/static/$1`);
res.setHeader('Content-Type', 'text/html');
res.send(webPage);
});
return router;
};

253
src/api/web/middleware.js Normal file
View File

@@ -0,0 +1,253 @@
/* eslint prefer-rest-params: "off" */
'use strict';
const crypto = require('crypto');
const _ = require('lodash');
const createError = require('http-errors');
const utils = require('../../lib/utils');
const Logger = require('../../lib/logger');
module.exports.match = function match(regexp) {
return function(req, res, next, value) {
if (regexp.exec(value)) {
next();
} else {
next('route');
}
};
};
module.exports.securityIframe = function securityIframe(req, res, next) {
// disable loading in frames (clickjacking, etc.)
res.header('X-Frame-Options', 'deny');
next();
};
module.exports.validate_name = function validate_name(req, res, next, value, name) {
if (value.charAt(0) === '-') {
// special case in couchdb usually
next('route');
} else if (utils.validate_name(value)) {
next();
} else {
next( createError[403]('invalid ' + name) );
}
};
module.exports.validate_package = function validate_package(req, res, next, value, name) {
if (value.charAt(0) === '-') {
// special case in couchdb usually
next('route');
} else if (utils.validate_package(value)) {
next();
} else {
next( createError[403]('invalid ' + name) );
}
};
module.exports.media = function media(expect) {
return function(req, res, next) {
if (req.headers['content-type'] !== expect) {
next( createError[415]('wrong content-type, expect: ' + expect
+ ', got: '+req.headers['content-type']) );
} else {
next();
}
};
};
module.exports.encodeScopePackage = function(req, res, next) {
if (req.url.indexOf('@') !== -1) {
// e.g.: /@org/pkg/1.2.3 -> /@org%2Fpkg/1.2.3, /@org%2Fpkg/1.2.3 -> /@org%2Fpkg/1.2.3
req.url = req.url.replace(/^(\/@[^\/%]+)\/(?!$)/, '$1%2F');
}
next();
};
module.exports.expect_json = function expect_json(req, res, next) {
if (!utils.is_object(req.body)) {
return next( createError[400]('can\'t parse incoming json') );
}
next();
};
module.exports.anti_loop = function(config) {
return function(req, res, next) {
if (req.headers.via != null) {
let arr = req.headers.via.split(',');
for (let i=0; i<arr.length; i++) {
let m = arr[i].match(/\s*(\S+)\s+(\S+)/);
if (m && m[2] === config.server_id) {
return next( createError[508]('loop detected') );
}
}
}
next();
};
};
/**
* Express doesn't do etags with requests <= 1024b
* we use md5 here, it works well on 1k+ bytes, but sucks with fewer data
* could improve performance using crc32 after benchmarks.
* @param {Object} data
* @return {String}
*/
function md5sum(data) {
return crypto.createHash('md5').update(data).digest('hex');
}
module.exports.allow = function(auth) {
return function(action) {
return function(req, res, next) {
req.pause();
let packageName = req.params.package;
if (req.params.scope) {
packageName = `@${req.params.scope}/${packageName}`;
}
auth['allow_' + action](packageName, req.remote_user, function(error, allowed) {
req.resume();
if (error) {
next(error);
} else if (allowed) {
next();
} else {
// last plugin (that's our built-in one) returns either
// cb(err) or cb(null, true), so this should never happen
throw createError('bug in the auth plugin system');
}
});
};
};
};
module.exports.final = function(body, req, res, next) {
if (res.statusCode === 401 && !res.getHeader('WWW-Authenticate')) {
// they say it's required for 401, so...
res.header('WWW-Authenticate', 'Basic, Bearer');
}
try {
if (_.isString(body) || _.isObject(body)) {
if (!res.getHeader('Content-type')) {
res.header('Content-type', 'application/json');
}
if (typeof(body) === 'object' && _.isNil(body) === false) {
if (typeof(body.error) === 'string') {
res._verdaccio_error = body.error;
}
body = JSON.stringify(body, undefined, ' ') + '\n';
}
// don't send etags with errors
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
res.header('ETag', '"' + md5sum(body) + '"');
}
} else {
// send(null), send(204), etc.
}
} catch(err) {
// if verdaccio sends headers first, and then calls res.send()
// as an error handler, we can't report error properly,
// and should just close socket
if (err.message.match(/set headers after they are sent/)) {
if (_.isNil(res.socket) === false) {
res.socket.destroy();
}
return;
} else {
throw err;
}
}
res.send(body);
};
module.exports.log = function(req, res, next) {
// logger
req.log = Logger.logger.child({sub: 'in'});
let _auth = req.headers.authorization;
if (_.isNil(_auth) === false) {
req.headers.authorization = '<Classified>';
}
let _cookie = req.headers.cookie;
if (_.isNil(_cookie) === false) {
req.headers.cookie = '<Classified>';
}
req.url = req.originalUrl;
req.log.info( {req: req, ip: req.ip}
, '@{ip} requested \'@{req.method} @{req.url}\'' );
req.originalUrl = req.url;
if (_.isNil(_auth) === false) {
req.headers.authorization = _auth;
}
if (_.isNil(_cookie) === false) {
req.headers.cookie = _cookie;
}
let bytesin = 0;
req.on('data', function(chunk) {
bytesin += chunk.length;
});
let bytesout = 0;
let _write = res.write;
res.write = function(buf) {
bytesout += buf.length;
_write.apply(res, arguments);
};
const log = function() {
let forwardedFor = req.headers['x-forwarded-for'];
let remoteAddress = req.connection.remoteAddress;
let remoteIP = forwardedFor ? `${forwardedFor} via ${remoteAddress}` : remoteAddress;
let message = '@{status}, user: @{user}(@{remoteIP}), req: \'@{request.method} @{request.url}\'';
if (res._verdaccio_error) {
message += ', error: @{!error}';
} else {
message += ', bytes: @{bytes.in}/@{bytes.out}';
}
req.url = req.originalUrl;
req.log.warn({
request: {
method: req.method,
url: req.url,
},
level: 35, // http
user: req.remote_user && req.remote_user.name,
remoteIP,
status: res.statusCode,
error: res._verdaccio_error,
bytes: {
in: bytesin,
out: bytesout,
},
}, message);
req.originalUrl = req.url;
};
req.on('close', function() {
log(true);
});
const _end = res.end;
res.end = function(buf) {
if (buf) {
bytesout += buf.length;
}
_end.apply(res, arguments);
log();
};
next();
};

9
src/config/env.js Normal file
View File

@@ -0,0 +1,9 @@
const path = require('path');
const APP_ROOT = path.resolve(__dirname, '../../');
module.exports = {
APP_ROOT,
SRC_ROOT: path.resolve(APP_ROOT, 'src/'),
DIST_PATH: path.resolve(APP_ROOT, 'static/'),
};

5
src/lib/.eslintrc Normal file
View File

@@ -0,0 +1,5 @@
{
"rules": {
"no-useless-escape": 0
}
}

446
src/lib/auth.js Normal file
View File

@@ -0,0 +1,446 @@
/* eslint prefer-spread: "off" */
/* eslint prefer-rest-params: "off" */
'use strict';
const Crypto = require('crypto');
const Error = require('http-errors');
const Logger = require('./logger');
const load_plugins = require('./plugin-loader').load_plugins;
const pkgJson = require('../../package.json');
const jwt = require('jsonwebtoken');
/**
* Handles the authentification, load auth plugins.
*/
class Auth {
/**
* @param {*} config config reference
*/
constructor(config) {
this.config = config;
this.logger = Logger.logger.child({sub: 'auth'});
this.secret = config.secret;
const plugin_params = {
config: config,
logger: this.logger,
};
if (config.users_file) {
if (!config.auth || !config.auth.htpasswd) {
// b/w compat
config.auth = config.auth || {};
config.auth.htpasswd = {file: config.users_file};
}
}
this.plugins = load_plugins(config, config.auth, plugin_params, function(p) {
return p.authenticate || p.allow_access || p.allow_publish;
});
this.plugins.unshift({
verdaccio_version: pkgJson.version,
authenticate: function(user, password, cb) {
if (config.users != null
&& config.users[user] != null
&& (Crypto.createHash('sha1').update(password).digest('hex')
=== config.users[user].password)
) {
return cb(null, [user]);
}
return cb();
},
adduser: function(user, password, cb) {
if (config.users && config.users[user]) {
return cb(Error[403]('this user already exists'));
}
return cb();
},
});
const allow_action = function(action) {
return function(user, pkg, cb) {
let ok = pkg[action].reduce(function(prev, curr) {
if (user.name === curr || user.groups.indexOf(curr) !== -1) return true;
return prev;
}, false);
if (ok) return cb(null, true);
if (user.name) {
cb( Error[403]('user ' + user.name + ' is not allowed to ' + action + ' package ' + pkg.name) );
} else {
cb( Error[403]('unregistered users are not allowed to ' + action + ' package ' + pkg.name) );
}
};
};
this.plugins.push({
authenticate: function(user, password, cb) {
return cb( Error[403]('bad username/password, access denied') );
},
add_user: function(user, password, cb) {
return cb( Error[409]('registration is disabled') );
},
allow_access: allow_action('access'),
allow_publish: allow_action('publish'),
});
}
/**
* Authenticate an user.
* @param {*} user
* @param {*} password
* @param {*} cb
*/
authenticate(user, password, cb) {
const plugins = this.plugins.slice(0)
;(function next() {
let p = plugins.shift();
if (typeof(p.authenticate) !== 'function') {
return next();
}
p.authenticate(user, password, function(err, groups) {
if (err) {
return cb(err);
}
if (groups != null && groups != false) {
return cb(err, authenticatedUser(user, groups));
}
next();
});
})();
}
/**
* Add a new user.
* @param {*} user
* @param {*} password
* @param {*} cb
*/
add_user(user, password, cb) {
let self = this;
let plugins = this.plugins.slice(0)
;(function next() {
let p = plugins.shift();
let n = 'adduser';
if (typeof(p[n]) !== 'function') {
n = 'add_user';
}
if (typeof(p[n]) !== 'function') {
next();
} else {
p[n](user, password, function(err, ok) {
if (err) return cb(err);
if (ok) return self.authenticate(user, password, cb);
next();
});
}
})();
}
/**
* Allow user to access a package.
* @param {*} package_name
* @param {*} user
* @param {*} callback
*/
allow_access(package_name, user, callback) {
let plugins = this.plugins.slice(0);
let pkg = Object.assign({name: package_name},
this.config.getMatchedPackagesSpec(package_name))
;(function next() {
let p = plugins.shift();
if (typeof(p.allow_access) !== 'function') {
return next();
}
p.allow_access(user, pkg, function(err, ok) {
if (err) return callback(err);
if (ok) return callback(null, ok);
next(); // cb(null, false) causes next plugin to roll
});
})();
}
/**
* Allow user to publish a package.
* @param {*} package_name
* @param {*} user
* @param {*} callback
*/
allow_publish(package_name, user, callback) {
let plugins = this.plugins.slice(0);
let pkg = Object.assign({name: package_name},
this.config.getMatchedPackagesSpec(package_name))
;(function next() {
let p = plugins.shift();
if (typeof(p.allow_publish) !== 'function') {
return next();
}
p.allow_publish(user, pkg, function(err, ok) {
if (err) return callback(err);
if (ok) return callback(null, ok);
next(); // cb(null, false) causes next plugin to roll
});
})();
}
/**
* Set up a basic middleware.
* @return {Function}
*/
basic_middleware() {
let self = this;
let credentials;
return function(req, res, _next) {
req.pause();
const next = function(err) {
req.resume();
// uncomment this to reject users with bad auth headers
// return _next.apply(null, arguments)
// swallow error, user remains unauthorized
// set remoteUserError to indicate that user was attempting authentication
if (err) {
req.remote_user.error = err.message;
}
return _next();
};
if (req.remote_user != null && req.remote_user.name !== undefined) {
return next();
}
req.remote_user = buildAnonymousUser();
let authorization = req.headers.authorization;
if (authorization == null) return next();
let parts = authorization.split(' ');
if (parts.length !== 2) {
return next( Error[400]('bad authorization header') );
}
const scheme = parts[0];
if (scheme === 'Basic') {
credentials = new Buffer(parts[1], 'base64').toString();
} else if (scheme === 'Bearer') {
credentials = self.aes_decrypt(new Buffer(parts[1], 'base64')).toString('utf8');
if (!credentials) {
return next();
}
} else {
return next();
}
const index = credentials.indexOf(':');
if (index < 0) {
return next();
}
const user = credentials.slice(0, index);
const pass = credentials.slice(index + 1);
self.authenticate(user, pass, function(err, user) {
if (!err) {
req.remote_user = user;
next();
} else {
req.remote_user = buildAnonymousUser();
next(err);
}
});
};
}
/**
* Set up the bearer middleware.
* @return {Function}
*/
bearer_middleware() {
let self = this;
return function(req, res, _next) {
req.pause();
const next = function(_err) {
req.resume();
return _next.apply(null, arguments);
};
if (req.remote_user != null && req.remote_user.name !== undefined) {
return next();
}
req.remote_user = buildAnonymousUser();
let authorization = req.headers.authorization;
if (authorization == null) {
return next();
}
let parts = authorization.split(' ');
if (parts.length !== 2) {
return next( Error[400]('bad authorization header') );
}
let scheme = parts[0];
let token = parts[1];
if (scheme !== 'Bearer') {
return next();
}
let user;
try {
user = self.decode_token(token);
} catch(err) {
return next(err);
}
req.remote_user = authenticatedUser(user.u, user.g);
req.remote_user.token = token;
next();
};
}
/**
* JWT middleware for WebUI
* @return {Function}
*/
jwtMiddleware() {
return (req, res, _next) => {
if (req.remote_user !== null && req.remote_user.name !== undefined) return _next();
req.pause();
const next = function(_err) {
req.resume();
return _next();
};
req.remote_user = buildAnonymousUser();
let token = (req.headers.authorization || '').replace('Bearer ', '');
if (!token) return next();
let decoded;
try {
decoded = this.decode_token(token);
} catch (err) {/**/}
if (decoded) {
req.remote_user = authenticatedUser(decoded.user, decoded.group);
}
next();
};
}
/**
* Generates the token.
* @param {object} user
* @param {string} expire_time
* @return {string}
*/
issue_token(user, expire_time) {
return jwt.sign(
{
user: user.name,
group: user.real_groups && user.real_groups.length ? user.real_groups : undefined,
},
this.secret,
{
notBefore: '1000', // Make sure the time will not rollback :)
expiresIn: expire_time || '7d',
}
);
}
/**
* Decodes the token.
* @param {*} token
* @return {Object}
*/
decode_token(token) {
let decoded;
try {
decoded = jwt.verify(token, this.secret);
} catch (err) {
throw Error[401](err.message);
}
return decoded;
}
/**
* Encrypt a string.
* @param {String} buf
* @return {Buffer}
*/
aes_encrypt(buf) {
const c = Crypto.createCipher('aes192', this.secret);
const b1 = c.update(buf);
const b2 = c.final();
return Buffer.concat([b1, b2]);
}
/**
* Dencrypt a string.
* @param {String} buf
* @return {Buffer}
*/
aes_decrypt(buf) {
try {
const c = Crypto.createDecipher('aes192', this.secret);
const b1 = c.update(buf);
const b2 = c.final();
return Buffer.concat([b1, b2]);
} catch(_) {
return new Buffer(0);
}
}
}
/**
* Builds an anonymous user in case none is logged in.
* @return {Object} { name: xx, groups: [], real_groups: [] }
*/
function buildAnonymousUser() {
return {
name: undefined,
// groups without '$' are going to be deprecated eventually
groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'],
real_groups: [],
};
}
/**
* Authenticate an user.
* @param {*} name
* @param {*} groups
* @return {Object} { name: xx, groups: [], real_groups: [] }
*/
function authenticatedUser(name, groups) {
let _groups = (groups || []).concat(['$all', '$authenticated', '@all', '@authenticated', 'all']);
return {
name: name,
groups: _groups,
real_groups: groups,
};
}
module.exports = Auth;

199
src/lib/cli.js Normal file
View File

@@ -0,0 +1,199 @@
#!/usr/bin/env node
/* eslint no-sync:0 */
/* eslint no-empty:0 */
'use strict';
const _ = require('lodash');
if (process.getuid && process.getuid() === 0) {
global.console.error('Verdaccio doesn\'t need superuser privileges. Don\'t run it under root.');
}
process.title = 'verdaccio';
try {
// for debugging memory leaks
// totally optional
require('heapdump');
} catch(err) { }
const logger = require('./logger');
logger.setup(); // default setup
const commander = require('commander');
const constants = require('constants');
const fs = require('fs');
const http = require('http');
const https = require('https');
const Path = require('path');
const URL = require('url');
const server = require('../api/index');
const Utils = require('./utils');
const pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
const pkgVersion = module.exports.version;
const pkgName = module.exports.name;
commander
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
.option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)')
.version(pkgVersion)
.parse(process.argv);
if (commander.args.length == 1 && !commander.config) {
// handling "verdaccio [config]" case if "-c" is missing in commandline
commander.config = commander.args.pop();
}
if (commander.args.length != 0) {
commander.help();
}
let config;
let config_path;
try {
if (commander.config) {
config_path = Path.resolve(commander.config);
} else {
config_path = require('./config-path')();
}
config = Utils.parseConfigFile(config_path);
logger.logger.warn({file: config_path}, 'config file - @{file}');
} catch (err) {
logger.logger.fatal({file: config_path, err: err}, 'cannot open config file @{file}: @{!err.message}');
process.exit(1);
}
afterConfigLoad();
/**
* Retrieve all addresses defined in the config file.
* Verdaccio is able to listen multiple ports
* eg:
* listen:
- localhost:5555
- localhost:5557
@return {Array}
*/
function get_listen_addresses() {
// command line || config file || default
let addresses;
if (commander.listen) {
addresses = [commander.listen];
} else if (Array.isArray(config.listen)) {
addresses = config.listen;
} else if (config.listen) {
addresses = [config.listen];
} else {
addresses = ['4873'];
}
addresses = addresses.map(function(addr) {
let parsed_addr = Utils.parse_address(addr);
if (!parsed_addr) {
logger.logger.warn({addr: addr},
'invalid address - @{addr}, we expect a port (e.g. "4873"),'
+ ' host:port (e.g. "localhost:4873") or full url'
+ ' (e.g. "http://localhost:4873/")');
}
return parsed_addr;
}).filter(Boolean);
return addresses;
}
/**
* Trigger the server after configuration has been loaded.
*/
function afterConfigLoad() {
if (!config.self_path) {
config.self_path = Path.resolve(config_path);
}
if (!config.https) {
config.https = {enable: false};
}
const app = server(config);
get_listen_addresses().forEach(function(addr) {
let webServer;
if (addr.proto === 'https') { // https
if (!config.https || !config.https.key || !config.https.cert || !config.https.ca) {
let conf_path = function(file) {
if (!file) return config_path;
return Path.resolve(Path.dirname(config_path), file);
};
logger.logger.fatal([
'You need to specify "https.key", "https.cert" and "https.ca" to run https server',
'',
// commands are borrowed from node.js docs
'To quickly create self-signed certificate, use:',
' $ openssl genrsa -out ' + conf_path('verdaccio-key.pem') + ' 2048',
' $ openssl req -new -sha256 -key ' + conf_path('verdaccio-key.pem') + ' -out ' + conf_path('verdaccio-csr.pem'),
' $ openssl x509 -req -in ' + conf_path('verdaccio-csr.pem') +
' -signkey ' + conf_path('verdaccio-key.pem') + ' -out ' + conf_path('verdaccio-cert.pem'),
'',
'And then add to config file (' + conf_path() + '):',
' https:',
' key: verdaccio-key.pem',
' cert: verdaccio-cert.pem',
' ca: verdaccio-cert.pem',
].join('\n'));
process.exit(2);
}
try {
webServer = https.createServer({
secureProtocol: 'SSLv23_method', // disable insecure SSLv2 and SSLv3
secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3,
key: fs.readFileSync(config.https.key),
cert: fs.readFileSync(config.https.cert),
ca: fs.readFileSync(config.https.ca),
}, app);
} catch (err) { // catch errors related to certificate loading
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2);
}
} else { // http
webServer = http.createServer(app);
}
webServer
.listen(addr.port || addr.path, addr.host)
.on('error', function(err) {
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}');
process.exit(2);
});
logger.logger.warn({
addr: ( addr.path
? URL.format({
protocol: 'unix',
pathname: addr.path,
})
: URL.format({
protocol: addr.proto,
hostname: addr.host,
port: addr.port,
pathname: '/',
})
),
version: pkgName + '/' + pkgVersion,
}, 'http address - @{addr} - @{version}');
});
// undocumented stuff for tests
if (_.isFunction(process.send)) {
process.send({
verdaccio_started: true,
});
}
}
process.on('uncaughtException', function(err) {
logger.logger.fatal( {
err: err,
},
'uncaught exception, please report this\n@{err.stack}' );
process.exit(255);
});

110
src/lib/config-path.js Normal file
View File

@@ -0,0 +1,110 @@
'use strict';
const fs = require('fs');
const Path = require('path');
const logger = require('./logger');
const CONFIG_FILE = 'config.yaml';
const pkgJson = require('../../package.json');
/**
* Find and get the first config file that match.
* @return {String} the config file path
*/
function find_config_file() {
const paths = get_paths();
for (let i=0; i<paths.length; i++) {
if (file_exists(paths[i].path)) return paths[i].path;
}
create_config_file(paths[0]);
return paths[0].path;
}
/**
* Create a default config file in your system.
* @param {String} config_path
*/
function create_config_file(config_path) {
require('mkdirp').sync(Path.dirname(config_path.path));
logger.logger.info({file: config_path.path}, 'Creating default config file in @{file}');
let created_config = fs.readFileSync(require.resolve('../../conf/default.yaml'), 'utf8');
if (config_path.type === 'xdg') {
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored,
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
let data_dir = process.env.XDG_DATA_HOME|| Path.join(process.env.HOME, '.local', 'share');
if (folder_exists(data_dir)) {
data_dir = Path.resolve(Path.join(data_dir, pkgJson.name, 'storage'));
created_config = created_config.replace(/^storage: .\/storage$/m, `storage: ${data_dir}`);
}
}
fs.writeFileSync(config_path.path, created_config);
}
/**
* Retrieve a list of possible config file locations.
* @return {Array}
*/
function get_paths() {
let try_paths = [];
let xdg_config = process.env.XDG_CONFIG_HOME
|| process.env.HOME && Path.join(process.env.HOME, '.config');
if (xdg_config && folder_exists(xdg_config)) {
try_paths.push({
path: Path.join(xdg_config, pkgJson.name, CONFIG_FILE),
type: 'xdg',
});
}
if (process.platform === 'win32' && process.env.APPDATA && folder_exists(process.env.APPDATA)) {
try_paths.push({
path: Path.resolve(Path.join(process.env.APPDATA, pkgJson.name, CONFIG_FILE)),
type: 'win',
});
}
try_paths.push({
path: Path.resolve(Path.join('.', pkgJson.name, CONFIG_FILE)),
type: 'def',
});
// backward compatibility
try_paths.push({
path: Path.resolve(Path.join('.', CONFIG_FILE)),
type: 'old',
});
return try_paths;
}
/**
* Check whether the path already exist.
* @param {String} path
* @return {Boolean}
*/
function folder_exists(path) {
try {
const stat = fs.statSync(path);
return stat.isDirectory();
} catch(_) {
return false;
}
}
/**
* Check whether the file already exist.
* @param {String} path
* @return {Boolean}
*/
function file_exists(path) {
try {
const stat = fs.statSync(path);
return stat.isFile();
} catch(_) {
return false;
}
}
module.exports = find_config_file;

225
src/lib/config.js Normal file
View File

@@ -0,0 +1,225 @@
/* eslint prefer-rest-params: "off" */
/* eslint prefer-spread: "off" */
'use strict';
const assert = require('assert');
const _ = require('lodash');
const Error = require('http-errors');
const Crypto = require('crypto');
const minimatch = require('minimatch');
const Utils = require('./utils');
const pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
const pkgVersion = module.exports.version;
const pkgName = module.exports.name;
/**
* [[a, [b, c]], d] -> [a, b, c, d]
* @param {*} array
* @return {Array}
*/
function flatten(array) {
let result = [];
for (let i=0; i<array.length; i++) {
if (Array.isArray(array[i])) {
result.push.apply(result, flatten(array[i]));
} else {
result.push(array[i]);
}
}
return result;
}
/**
* Coordinates the application configuration
*/
class Config {
/**
* @param {*} config config the content
*/
constructor(config) {
const self = this;
for (let i in config) {
if (self[i] == null) {
self[i] = config[i];
}
}
if (!self.user_agent) {
self.user_agent = `${pkgName}/${pkgVersion}`;
}
// some weird shell scripts are valid yaml files parsed as string
assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file');
assert(self.storage, 'CONFIG: storage path not defined');
const users = {
'all': true,
'anonymous': true,
'undefined': true,
'owner': true,
'none': true,
};
const check_user_or_uplink = function(arg) {
assert(arg !== 'all' && arg !== 'owner'
&& arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg);
assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg);
assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg);
users[arg] = true;
};
// sanity check for strategic config properties
['users', 'uplinks', 'packages'].forEach(function(x) {
if (self[x] == null) self[x] = {};
assert(Utils.is_object(self[x]), `CONFIG: bad "${x}" value (object expected)`);
});
// sanity check for users
for (let i in self.users) {
if (Object.prototype.hasOwnProperty.call(self.users, i)) {
check_user_or_uplink(i);
}
}
// sanity check for uplinks
for (let i in self.uplinks) {
if (self.uplinks[i].cache == null) {
self.uplinks[i].cache = true;
}
if (Object.prototype.hasOwnProperty.call(self.uplinks, i)) {
check_user_or_uplink(i);
}
}
for (let i in self.users) {
if (Object.prototype.hasOwnProperty.call(self.users, i)) {
assert(self.users[i].password, 'CONFIG: no password for user: ' + i);
assert(typeof(self.users[i].password) === 'string' &&
self.users[i].password.match(/^[a-f0-9]{40}$/)
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected');
}
}
for (let i in self.uplinks) {
if (Object.prototype.hasOwnProperty.call(self.uplinks, i)) {
assert(self.uplinks[i].url, 'CONFIG: no url for uplink: ' + i);
assert( typeof(self.uplinks[i].url) === 'string'
, 'CONFIG: wrong url format for uplink: ' + i);
self.uplinks[i].url = self.uplinks[i].url.replace(/\/$/, '');
}
}
/**
* Normalise user list.
* @return {Array}
*/
function normalize_userlist() {
let result = [];
for (let i=0; i<arguments.length; i++) {
if (arguments[i] == null) continue;
// if it's a string, split it to array
if (typeof(arguments[i]) === 'string') {
result.push(arguments[i].split(/\s+/));
} else if (Array.isArray(arguments[i])) {
result.push(arguments[i]);
} else {
throw Error('CONFIG: bad package acl (array or string expected): ' + JSON.stringify(arguments[i]));
}
}
return flatten(result);
}
// add a default rule for all packages to make writing plugins easier
if (self.packages['**'] == null) {
self.packages['**'] = {};
}
for (let i in self.packages) {
if (Object.prototype.hasOwnProperty.call(self.packages, i)) {
assert(
typeof(self.packages[i]) === 'object' &&
!Array.isArray(self.packages[i])
, 'CONFIG: bad "'+i+'" package description (object expected)');
self.packages[i].access = normalize_userlist(
self.packages[i].allow_access,
self.packages[i].access
);
delete self.packages[i].allow_access;
self.packages[i].publish = normalize_userlist(
self.packages[i].allow_publish,
self.packages[i].publish
);
delete self.packages[i].allow_publish;
self.packages[i].proxy = normalize_userlist(
self.packages[i].proxy_access,
self.packages[i].proxy
);
delete self.packages[i].proxy_access;
}
}
// loading these from ENV if aren't in config
['http_proxy', 'https_proxy', 'no_proxy'].forEach((function(v) {
if (!(v in self)) {
self[v] = process.env[v] || process.env[v.toUpperCase()];
}
}));
// unique identifier of self server (or a cluster), used to avoid loops
if (!self.server_id) {
self.server_id = Crypto.pseudoRandomBytes(6).toString('hex');
}
}
/**
* Check whether an uplink can proxy
* @param {String} pkg package anem
* @param {*} upLink
* @return {Boolean}
*/
hasProxyTo(pkg, upLink) {
return (this.getMatchedPackagesSpec(pkg).proxy || []).reduce(function(prev, curr) {
if (upLink === curr) {
return true;
}
return prev;
}, false);
}
/**
* Check for package spec
* @param {String} pkg package name
* @return {Object}
*/
getMatchedPackagesSpec(pkg) {
for (let i in this.packages) {
if (minimatch.makeRe(i).exec(pkg)) {
return this.packages[i];
}
}
return {};
}
/**
* Store or create whether recieve a secret key
* @param {String} secret
* @return {String}
*/
checkSecretKey(secret) {
if (_.isNil(secret) === false) {
this.secret = secret;
return secret;
}
// it generates a secret key
// FUTURE: this might be an external secret key, perhaps whitin config file?
this.secret = Crypto.pseudoRandomBytes(32).toString('hex');
return this.secret;
}
}
module.exports = Config;

196
src/lib/logger.js Normal file
View File

@@ -0,0 +1,196 @@
'use strict';
const Logger = require('bunyan');
const Error = require('http-errors');
const Stream = require('stream');
const chalk = require('chalk');
const Utils = require('./utils');
const pkgJSON = require('../../package.json');
/**
* Match the level based on buyan severity scale
* @param {*} x severity level
* @return {String} security level
*/
function getlvl(x) {
switch(true) {
case x < 15 : return 'trace';
case x < 25 : return 'debug';
case x < 35 : return 'info';
case x == 35 : return 'http';
case x < 45 : return 'warn';
case x < 55 : return 'error';
default : return 'fatal';
}
}
/**
* Setup the Buyan logger
* @param {*} logs list of log configuration
*/
function setup(logs) {
let streams = [];
if (logs == null) {
logs = [{type: 'stdout', format: 'pretty', level: 'http'}];
}
logs.forEach(function(target) {
// create a stream for each log configuration
const stream = new Stream();
stream.writable = true;
if (target.type === 'stdout' || target.type === 'stderr') {
// destination stream
const dest = target.type === 'stdout' ? process.stdout : process.stderr;
if (target.format === 'pretty') {
// making fake stream for prettypritting
stream.write = function(obj) {
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n');
};
} else if (target.format === 'pretty-timestamped') {
// making fake stream for prettypritting
stream.write = function(obj) {
dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n');
};
} else {
stream.write = function(obj) {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n');
};
}
} else if (target.type === 'file') {
const dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'});
dest.on('error', function(err) {
Logger.emit('error', err);
});
stream.write = function(obj) {
if (target.format === 'pretty') {
dest.write(print(obj.level, obj.msg, obj, false) + '\n');
} else {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n');
}
};
} else {
throw Error('wrong target type for a log');
}
if (target.level === 'http') target.level = 35;
streams.push({
type: 'raw',
level: target.level || 35,
stream: stream,
});
});
// buyan default configuration
const logger = new Logger({
name: pkgJSON.name,
streams: streams,
serializers: {
err: Logger.stdSerializers.err,
req: Logger.stdSerializers.req,
res: Logger.stdSerializers.res,
},
});
module.exports.logger = logger;
}
// adopted from socket.io
// this part was converted to coffee-script and back again over the years,
// so it might look weird
// level to color
const levels = {
fatal: chalk.red,
error: chalk.red,
warn: chalk.yellow,
http: chalk.magenta,
info: chalk.cyan,
debug: chalk.green,
trace: chalk.white,
};
let max = 0;
for (let l in levels) {
if (Object.prototype.hasOwnProperty.call(levels, l)) {
max = Math.max(max, l.length);
}
}
/**
* Apply whitespaces based on the length
* @param {*} str the log message
* @return {String}
*/
function pad(str) {
if (str.length < max) {
return str + ' '.repeat(max - str.length);
}
return str;
}
/**
* Apply colors to a string based on level parameters.
* @param {*} type
* @param {*} msg
* @param {*} obj
* @param {*} colors
* @return {String}
*/
function print(type, msg, obj, colors) {
if (typeof type === 'number') {
type = getlvl(type);
}
let finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) {
let str = obj;
let is_error;
if (name[0] === '!') {
name = name.substr(1);
is_error = true;
}
let _ref = name.split('.');
for (let _i = 0; _i < _ref.length; _i++) {
let id = _ref[_i];
if (Utils.is_object(str) || Array.isArray(str)) {
str = str[id];
} else {
str = undefined;
}
}
if (typeof(str) === 'string') {
if (!colors || str.includes('\n')) {
return str;
} else if (is_error) {
return chalk.red(str);
} else {
return chalk.green(str);
}
} else {
return require('util').inspect(str, null, null, colors);
}
});
const subsystems = [{
in: chalk.green('<--'),
out: chalk.yellow('-->'),
fs: chalk.black('-=-'),
default: chalk.blue('---'),
}, {
in: '<--',
out: '-->',
fs: '-=-',
default: '---',
}];
const sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default;
if (colors) {
return ` ${levels[type]((pad(type)))}${chalk.white(`${sub} ${finalmsg}`)}`;
} else {
return ` ${(pad(type))}${sub} ${finalmsg}`;
}
}
module.exports.setup = setup;

81
src/lib/notify.js Normal file
View File

@@ -0,0 +1,81 @@
'use strict';
const Handlebars = require('handlebars');
const request = require('request');
const _ = require('lodash');
const logger = require('./logger');
const handleNotify = function(metadata, notifyEntry) {
let regex;
if (metadata.name && notifyEntry.packagePattern) {
// FUTURE: comment out due https://github.com/verdaccio/verdaccio/pull/108#issuecomment-312421052
// regex = new RegExp(notifyEntry.packagePattern, notifyEntry.packagePatternFlags || '');
regex = new RegExp(notifyEntry.packagePattern);
if (!regex.test(metadata.name)) {
return;
}
}
const template = Handlebars.compile(notifyEntry.content);
const content = template( metadata );
const options = {
body: content,
};
// provides fallback support, it's accept an Object {} and Array of {}
if (notifyEntry.headers && _.isArray(notifyEntry.headers)) {
const header = {};
notifyEntry.headers.map(function(item) {
if (Object.is(item, item)) {
for (const key in item) {
if (item.hasOwnProperty(key)) {
header[key] = item[key];
}
}
}
});
options.headers = header;
} else if (Object.is(notifyEntry.headers, notifyEntry.headers)) {
options.headers = notifyEntry.headers;
}
options.method = notifyEntry.method;
if ( notifyEntry.endpoint ) {
options.url = notifyEntry.endpoint;
}
return new Promise(( resolve, reject) => {
request(options, function(err, response, body) {
if (err || response.statusCode >= 400) {
const errorMessage = _.isNil(err) ? response.statusMessage : err;
logger.logger.error({err: errorMessage}, ' notify error: @{err.message}' );
reject(errorMessage);
} else {
logger.logger.info({content: content}, 'A notification has been shipped: @{content}');
if (body) {
logger.logger.debug({body: body}, ' body: @{body}' );
}
resolve(_.isNil(body) === false ? body : null);
}
});
});
};
const notify = function(metadata, config) {
if (config.notify) {
if (config.notify.content) {
return handleNotify(metadata, config.notify);
} else {
// multiple notifications endpoints PR #108
for (const key in config.notify) {
if (config.notify.hasOwnProperty(key)) {
return handleNotify(metadata, config.notify[key]);
}
}
}
}
};
module.exports.notify = notify;

77
src/lib/plugin-loader.js Normal file
View File

@@ -0,0 +1,77 @@
'use strict';
const Path = require('path');
/**
* Requires a module.
* @param {*} path the module's path
* @return {Object}
*/
function try_load(path) {
try {
return require(path);
} catch(err) {
if (err.code === 'MODULE_NOT_FOUND') {
return null;
}
throw err;
}
}
/**
* Load a plugin following the rules
* - First try to load from the internal directory plugins (which will disappear soon or later).
* - A seccond attempt from node_modules, in case to have multiple match as for instance verdaccio-ldap
* and sinopia-ldap. All verdaccio prefix will have preferences.
* @param {*} config a reference of the configuration settings
* @param {*} plugin_configs
* @param {*} params a set of params to initialise the plugin
* @param {*} sanity_check callback that check the shape that should fulfill the plugin
* @return {Array} list of plugins
*/
function load_plugins(config, plugin_configs, params, sanity_check) {
let plugins = Object.keys(plugin_configs || {}).map(function(p) {
let plugin;
// try local plugins first
plugin = try_load(Path.resolve(__dirname + '/..//plugins', p));
// npm package
if (plugin === null && p.match(/^[^\.\/]/)) {
plugin = try_load(`verdaccio-${p}`);
// compatibility for old sinopia plugins
if (!plugin) {
plugin = try_load(`sinopia-${p}`);
}
}
if (plugin === null) {
plugin = try_load(p);
}
// relative to config path
if (plugin === null && p.match(/^\.\.?($|\/)/)) {
plugin = try_load(Path.resolve(Path.dirname(config.self_path), p));
}
if (plugin === null) {
throw Error('"' + p + '" plugin not found\ntry "npm install verdaccio-' + p + '"');
}
if (typeof(plugin) !== 'function') {
throw Error('"' + p + '" doesn\'t look like a valid plugin');
}
plugin = plugin(plugin_configs[p], params);
if (plugin === null || !sanity_check(plugin)) {
throw Error('"' + p + '" doesn\'t look like a valid plugin');
}
return plugin;
});
return plugins;
}
exports.load_plugins = load_plugins;

83
src/lib/search.js Normal file
View File

@@ -0,0 +1,83 @@
/* eslint no-invalid-this: "off" */
'use strict';
const lunr = require('lunr');
/**
* Handle the search Indexer.
*/
class Search {
/**
* Constructor.
*/
constructor() {
this.index = lunr(function() {
this.field('name', {boost: 10});
this.field('description', {boost: 4});
this.field('author', {boost: 6});
this.field('readme');
});
}
/**
* Performs a query to the indexer.
* If the keyword is a * it returns all local elements
* otherwise performs a search
* @param {*} q the keyword
* @return {Array} list of results.
*/
query(q) {
return q === '*'
? this.storage.localStorage.localList.get().map( function( pkg ) {
return {ref: pkg, score: 1};
}) : this.index.search(q);
}
/**
* Add a new element to index
* @param {*} pkg the package
*/
add(pkg) {
this.index.add({
id: pkg.name,
name: pkg.name,
description: pkg.description,
author: pkg._npmUser ? pkg._npmUser.name : '???',
});
}
/**
* Remove an element from the index.
* @param {*} name the id element
*/
remove(name) {
this.index.remove({id: name});
}
/**
* Force a reindex.
*/
reindex() {
let self = this;
this.storage.get_local(function(err, packages) {
if (err) throw err; // that function shouldn't produce any
let i = packages.length;
while (i--) {
self.add(packages[i]);
}
});
}
/**
* Set up the {Storage}
* @param {*} storage An storage reference.
*/
configureStorage(storage) {
this.storage = storage;
this.reindex();
}
}
module.exports = new Search();

77
src/lib/status-cats.js Normal file
View File

@@ -0,0 +1,77 @@
/* eslint prefer-rest-params: "off" */
'use strict';
// see https://secure.flickr.com/photos/girliemac/sets/72157628409467125
const images = {
100: 'aVvDhR', // '6512768893', // 100 - Continue
101: 'aXXExP', // '6540479029', // 101 - Switching Protocols
200: 'aVuVsF', // '6512628175', // 200 - OK
201: 'aXWm1Z', // '6540221577', // 201 - Created
202: 'aXXEyF', // '6540479079', // 202 - Accepted
204: 'aYyJ7B', // '6547319943', // 204 - No Content
206: 'aVEnUP', // '6514473163', // 206 - Partial Content
207: 'aVEnRD', // '6514472979', // 207 - Multi-Status
300: 'aW7mac', // '6519540181', // 300 - Multiple Choices
301: 'aW7mb4', // '6519540231', // 301 - Moved Permanently
302: 'aV6jKp', // '6508023829', // 302 - Found
303: 'aVxtaK', // '6513125065', // 303 - See Other
304: 'aXY3dH', // '6540551929', // 304 - Not Modified
305: 'aXX5LK', // '6540365403', // 305 - Use Proxy
307: 'aVwQnk', // '6513001269', // 307 - Temporary Redirect
400: 'aXYDeT', // '6540669737', // 400 - Bad Request
401: 'aV6jwe', // '6508023065', // 401 - Unauthorized
402: 'aVwQoe', // '6513001321', // 402 - Payment Required
403: 'aV6jFK', // '6508023617', // 403 - Forbidden
404: 'aV6juR', // '6508022985', // 404 - Not Found
405: 'aV6jE8', // '6508023523', // 405 - Method Not Allowed
406: 'aV6jxa', // '6508023119', // 406 - Not Acceptable
408: 'aV6jyc', // '6508023179', // 408 - Request Timeout
409: 'aV6jzz', // '6508023259', // 409 - Conflict
410: 'aVES2H', // '6514567755', // 410 - Gone
411: 'aXYVpT', // '6540724141', // 411 - Length Required
413: 'aV6jHZ', // '6508023747', // 413 - Request Entity Too Large
414: 'aV6jBa', // '6508023351', // 414 - Request-URI Too Long
416: 'aVxQvr', // '6513196851', // 416 - Requested Range Not Satisfiable
417: 'aV6jGP', // '6508023679', // 417 - Expectation Failed
418: 'aV6J7c', // '6508102407', // 418 - I'm a teapot
422: 'aVEnTt', // '6514473085', // 422 - Unprocessable Entity
423: 'aVEyVZ', // '6514510235', // 423 - Locked
424: 'aVEWZ6', // '6514584423', // 424 - Failed Dependency
425: 'aXYdzH', // '6540586787', // 425 - Unordered Collection
426: 'aVdo4M', // '6509400771', // 426 - Upgrade Required
429: 'aVdo8F', // '6509400997', // 429 - Too Many Requests
431: 'aVdo3n', // '6509400689', // 431 - Request Header Fields Too Large
444: 'aVdo1P', // '6509400599', // 444 - No Response
450: 'aVxtbK', // '6513125123', // 450 - Blocked by Windows Parental Controls
451: 'eTiGQd', // '9113233540', // 451 - Unavailable for Legal Reasons
500: 'aVdo6e', // '6509400855', // 500 - Internal Server Error
502: 'aV6jCv', // '6508023429', // 502 - Bad Gateway
503: 'aXYvop', // '6540643319', // 503 - Service Unavailable
506: 'aXYvnH', // '6540643279', // 506 - Variant Also Negotiates
507: 'aVdnZa', // '6509400503', // 507 - Insufficient Storage
508: 'aVdnYa', // '6509400445', // 508 - Loop Detected
509: 'aXXg1V', // '6540399865', // 509 - Bandwidth Limit Exceeded
599: 'aVdo7v', // '6509400929', // 599 - Network connect timeout error
};
module.exports.get_image = function(status) {
if (status in images) {
return 'http://flic.kr/p/' + images[status];
// return 'https://secure.flickr.com/photos/girliemac/'+images[status]+'/in/set-72157628409467125/lightbox/'
}
};
module.exports.middleware = function(req, res, next) {
let _writeHead = res.writeHead;
res.writeHead = function(status) {
if (status in images) {
res.setHeader('X-Status-Cat', module.exports.get_image(status));
}
_writeHead.apply(res, arguments);
};
next();
};

671
src/lib/storage.js Normal file
View File

@@ -0,0 +1,671 @@
'use strict';
const _ = require('lodash');
const assert = require('assert');
const async = require('async');
const Error = require('http-errors');
const semver = require('semver');
const Stream = require('stream');
const Search = require('./search');
const LocalStorage = require('./storage/local/local-storage');
const Logger = require('./logger');
const MyStreams = require('@verdaccio/streams');
const Proxy = require('./storage/up-storage');
const Utils = require('./utils');
const WHITELIST = ['_rev', 'name', 'versions', 'dist-tags', 'readme', 'time'];
const getDefaultMetadata = (name) => {
return {
'name': name,
'versions': {},
'dist-tags': {},
'_uplinks': {},
};
};
/**
* Implements Storage interface
* (same for storage.js, local-storage.js, up-storage.js).
*/
class Storage {
/**
* @param {*} config
*/
constructor(config) {
this.config = config;
this._setupUpLinks(this.config);
this.localStorage = new LocalStorage(config, Logger.logger, Utils);
this.localStorage.localList.data.secret = this.config.checkSecretKey(this.localStorage.localList.data.secret);
this.localStorage.localList.sync();
// an instance for local storage
this.logger = Logger.logger.child();
}
/**
* Add a {name} package to a system
Function checks if package with the same name is available from uplinks.
If it isn't, we create package locally
Used storages: local (write) && uplinks
* @param {*} name
* @param {*} metadata
* @param {*} callback
*/
addPackage(name, metadata, callback) {
const self = this;
/**
* Check whether a package it is already a local package
* @return {Promise}
*/
const checkPackageLocal = () => {
return new Promise((resolve, reject) => {
this.localStorage.getPackageMetadata(name, {}, (err, results) => {
if (!_.isNil(err) && err.status !== 404) {
return reject(err);
}
if (results) {
return reject(Error[409]('this package is already present'));
}
return resolve();
});
});
};
/**
* Check whether a package exist in any of the uplinks.
* @return {Promise}
*/
const checkPackageRemote = () => {
return new Promise((resolve, reject) => {
self._syncUplinksMetadata(name, null, {}, (err, results, err_results) => {
// something weird
if (err && err.status !== 404) {
return reject(err);
}
// checking package
if (results) {
return reject(Error[409]('this package is already present'));
}
for (let i = 0; i < err_results.length; i++) {
// checking error
// if uplink fails with a status other than 404, we report failure
if (_.isNil(err_results[i][0]) === false) {
if (err_results[i][0].status !== 404) {
if (_.isNil(this.config.publish) === false &&
_.isBoolean(this.config.publish.allow_offline) &&
this.config.publish.allow_offline) {
return resolve();
}
return reject(Error[503]('one of the uplinks is down, refuse to publish'));
}
}
}
return resolve();
});
});
};
/**
* Add a package to the local database
* @return {Promise}
*/
const publishPackage = () => {
return new Promise((resolve, reject) => {
self.localStorage.addPackage(name, metadata, (err, latest) => {
if (!_.isNull(err)) {
return reject(err);
} else if (!_.isUndefined(latest)) {
Search.add(latest);
}
return resolve();
});
});
};
// NOTE:
// - when we checking package for existance, we ask ALL uplinks
// - when we publishing package, we only publish it to some of them
// so all requests are necessary
checkPackageLocal()
.then(() => {
return checkPackageRemote().then(() => {
return publishPackage().then(() => {
callback();
}, (err) => callback(err));
}, (err) => callback(err));
}, (err) => callback(err));
}
/**
* Add a new version of package {name} to a system
Used storages: local (write)
* @param {*} name
* @param {*} version
* @param {*} metadata
* @param {*} tag
* @param {*} callback
*/
add_version(name, version, metadata, tag, callback) {
this.localStorage.addVersion(name, version, metadata, tag, callback);
}
/**
* Tags a package version with a provided tag
Used storages: local (write)
* @param {*} name
* @param {*} tag_hash
* @param {*} callback
*/
merge_tags(name, tag_hash, callback) {
this.localStorage.mergeTags(name, tag_hash, callback);
}
/**
* Tags a package version with a provided tag
Used storages: local (write)
* @param {*} name
* @param {*} tag_hash
* @param {*} callback
*/
replace_tags(name, tag_hash, callback) {
this.localStorage.replaceTags(name, tag_hash, callback);
}
/**
* Change an existing package (i.e. unpublish one version)
Function changes a package info from local storage and all uplinks with write access./
Used storages: local (write)
* @param {*} name
* @param {*} metadata
* @param {*} revision
* @param {*} callback
*/
change_package(name, metadata, revision, callback) {
this.localStorage.changePackage(name, metadata, revision, callback);
}
/**
* Remove a package from a system
Function removes a package from local storage
Used storages: local (write)
* @param {*} name
* @param {*} callback
*/
remove_package(name, callback) {
this.localStorage.removePackage(name, callback);
// update the indexer
Search.remove(name);
}
/**
Remove a tarball from a system
Function removes a tarball from local storage.
Tarball in question should not be linked to in any existing
versions, i.e. package version should be unpublished first.
Used storage: local (write)
* @param {*} name
* @param {*} filename
* @param {*} revision
* @param {*} callback
*/
remove_tarball(name, filename, revision, callback) {
this.localStorage.removeTarball(name, filename, revision, callback);
}
/**
* Upload a tarball for {name} package
Function is syncronous and returns a WritableStream
Used storages: local (write)
* @param {*} name
* @param {*} filename
* @return {Stream}
*/
add_tarball(name, filename) {
return this.localStorage.addTarball(name, filename);
}
/**
Get a tarball from a storage for {name} package
Function is syncronous and returns a ReadableStream
Function tries to read tarball locally, if it fails then it reads package
information in order to figure out where we can get this tarball from
Used storages: local || uplink (just one)
* @param {*} name
* @param {*} filename
* @return {Stream}
*/
get_tarball(name, filename) {
let readStream = new MyStreams.ReadTarball();
readStream.abort = function() {};
let self = this;
// if someone requesting tarball, it means that we should already have some
// information about it, so fetching package info is unnecessary
// trying local first
let rstream = self.localStorage.getTarball(name, filename);
let is_open = false;
rstream.on('error', function(err) {
if (is_open || err.status !== 404) {
return readStream.emit('error', err);
}
// local reported 404
let err404 = err;
rstream.abort();
rstream = null; // gc
self.localStorage.getPackageMetadata(name, (err, info) => {
if (_.isNil(err) && info._distfiles && _.isNil(info._distfiles[filename]) === false) {
// information about this file exists locally
serveFile(info._distfiles[filename]);
} else {
// we know nothing about this file, trying to get information elsewhere
self._syncUplinksMetadata(name, info, {}, (err, info) => {
if (_.isNil(err) === false) {
return readStream.emit('error', err);
}
if (_.isNil(info._distfiles) || _.isNil(info._distfiles[filename])) {
return readStream.emit('error', err404);
}
serveFile(info._distfiles[filename]);
});
}
});
});
rstream.on('content-length', function(v) {
readStream.emit('content-length', v);
});
rstream.on('open', function() {
is_open = true;
rstream.pipe(readStream);
});
return readStream;
/**
* Fetch and cache local/remote packages.
* @param {Object} file define the package shape
*/
function serveFile(file) {
let uplink = null;
for (let p in self.uplinks) {
if (self.uplinks[p].isUplinkValid(file.url)) {
uplink = self.uplinks[p];
}
}
if (uplink == null) {
uplink = new Proxy({
url: file.url,
cache: true,
_autogenerated: true,
}, self.config);
}
let savestream = null;
if (uplink.config.cache) {
savestream = self.localStorage.addTarball(name, filename);
}
let on_open = function() {
// prevent it from being called twice
on_open = function() {};
let rstream2 = uplink.fetchTarball(file.url);
rstream2.on('error', function(err) {
if (savestream) {
savestream.abort();
}
savestream = null;
readStream.emit('error', err);
});
rstream2.on('end', function() {
if (savestream) {
savestream.done();
}
});
rstream2.on('content-length', function(v) {
readStream.emit('content-length', v);
if (savestream) {
savestream.emit('content-length', v);
}
});
rstream2.pipe(readStream);
if (savestream) {
rstream2.pipe(savestream);
}
};
if (savestream) {
savestream.on('open', function() {
on_open();
});
savestream.on('error', function(err) {
self.logger.warn( {err: err}
, 'error saving file: @{err.message}\n@{err.stack}' );
if (savestream) {
savestream.abort();
}
savestream = null;
on_open();
});
} else {
on_open();
}
}
}
/**
Retrieve a package metadata for {name} package
Function invokes localStorage.getPackage and uplink.get_package for every
uplink with proxy_access rights against {name} and combines results
into one json object
Used storages: local && uplink (proxy_access)
* @param {*} name
* @param {*} options
* @param {*} callback
*/
get_package(name, options, callback) {
if (_.isFunction(options)) {
callback = options, options = {};
}
this.localStorage.getPackageMetadata(name, options, (err, data) => {
if (err && (!err.status || err.status >= 500)) {
// report internal errors right away
return callback(err);
}
this._syncUplinksMetadata(name, data, options, function(err, result, uplink_errors) {
if (err) {
return callback(err);
}
for (let i in result) {
if (WHITELIST.indexOf(i) === -1) {
delete result[i];
}
}
Utils.normalize_dist_tags(result);
// npm can throw if this field doesn't exist
result._attachments = {};
callback(null, result, uplink_errors);
});
});
}
/**
Retrieve remote and local packages more recent than {startkey}
Function streams all packages from all uplinks first, and then
local packages.
Note that local packages could override registry ones just because
they appear in JSON last. That's a trade-off we make to avoid
memory issues.
Used storages: local && uplink (proxy_access)
* @param {*} startkey
* @param {*} options
* @return {Stream}
*/
search(startkey, options) {
let self = this;
// stream to write a tarball
let stream = new Stream.PassThrough({objectMode: true});
async.eachSeries(Object.keys(this.uplinks), function(up_name, cb) {
// shortcut: if `local=1` is supplied, don't call uplinks
if (options.req.query.local !== undefined) {
return cb();
}
// search by keyword for each uplink
let lstream = self.uplinks[up_name].search(options);
// join streams
lstream.pipe(stream, {end: false});
lstream.on('error', function(err) {
self.logger.error({err: err}, 'uplink error: @{err.message}');
cb(), cb = function() {};
});
lstream.on('end', function() {
cb(), cb = function() {};
});
stream.abort = function() {
if (lstream.abort) {
lstream.abort();
}
cb(), cb = function() {};
};
},
// executed after all series
function() {
// attach a local search results
let lstream = self.localStorage.search(startkey, options);
stream.abort = function() {
lstream.abort();
};
lstream.pipe(stream, {end: true});
lstream.on('error', function(err) {
self.logger.error({err: err}, 'search error: @{err.message}');
stream.end();
});
});
return stream;
}
/**
* Retrieve only private local packages
* @param {*} callback
*/
get_local(callback) {
let self = this;
let locals = this.localStorage.localList.get();
let packages = [];
const getPackage = function(i) {
self.localStorage.getPackageMetadata(locals[i], function(err, info) {
if (_.isNil(err)) {
const latest = info['dist-tags'].latest;
if (latest && info.versions[latest]) {
packages.push(info.versions[latest]);
} else {
self.logger.warn( {package: locals[i]}, 'package @{package} does not have a "latest" tag?' );
}
}
if (i >= locals.length - 1) {
callback(null, packages);
} else {
getPackage(i + 1);
}
});
};
if (locals.length) {
getPackage(0);
} else {
callback(null, []);
}
}
/**
* Function fetches package metadata from uplinks and synchronizes it with local data
if package is available locally, it MUST be provided in pkginfo
returns callback(err, result, uplink_errors)
* @param {*} name
* @param {*} packageInfo
* @param {*} options
* @param {*} callback
*/
_syncUplinksMetadata(name, packageInfo, options, callback) {
let exists = false;
const self = this;
const upLinks = [];
if (_.isNil(packageInfo)) {
exists = false;
packageInfo = getDefaultMetadata(name);
} else {
exists = true;
}
for (let up in this.uplinks) {
if (this.config.hasProxyTo(name, up)) {
upLinks.push(this.uplinks[up]);
}
}
async.map(upLinks, (upLink, cb) => {
const _options = Object.assign({}, options);
let upLinkMeta = packageInfo._uplinks[upLink.upname];
if (Utils.is_object(upLinkMeta)) {
const fetched = upLinkMeta.fetched;
if (fetched && (Date.now() - fetched) < upLink.maxage) {
return cb();
}
_options.etag = upLinkMeta.etag;
}
upLink.getRemoteMetadata(name, _options, (err, upLinkResponse, eTag) => {
if (err && err.remoteStatus === 304) {
upLinkMeta.fetched = Date.now();
}
if (err || !upLinkResponse) {
return cb(null, [err || Error('no data')]);
}
try {
Utils.validate_metadata(upLinkResponse, name);
} catch(err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json validating error @{!err.message}\n@{err.stack}');
return cb(null, [err]);
}
packageInfo._uplinks[upLink.upname] = {
etag: eTag,
fetched: Date.now(),
};
// added to fix verdaccio#73
if ('time' in upLinkResponse) {
packageInfo['time'] = upLinkResponse.time;
}
this._updateVersionsHiddenUpLink(upLinkResponse.versions, upLink);
try {
Storage._merge_versions(packageInfo, upLinkResponse, self.config);
} catch(err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json parsing error @{!err.message}\n@{err.stack}');
return cb(null, [err]);
}
// if we got to this point, assume that the correct package exists
// on the uplink
exists = true;
cb();
});
}, (err, upLinksErrors) => {
assert(!err && Array.isArray(upLinksErrors));
if (!exists) {
return callback( Error[404]('no such package available')
, null
, upLinksErrors );
}
self.localStorage.updateVersions(name, packageInfo, function(err, packageJsonLocal) {
if (err) {
return callback(err);
}
return callback(null, packageJsonLocal, upLinksErrors);
});
});
}
/**
* Set a hidden value for each version.
* @param {Array} versions list of version
* @param {String} upLink uplink name
* @private
*/
_updateVersionsHiddenUpLink(versions, upLink) {
for (let i in versions) {
if (Object.prototype.hasOwnProperty.call(versions, i)) {
const version = versions[i];
// holds a "hidden" value to be used by the package storage.
version[Symbol.for('__verdaccio_uplink')] = upLink.upname;
}
}
}
/**
* Set up the Up Storage for each link.
* @param {Object} config
* @private
*/
_setupUpLinks(config) {
// we support a number of uplinks, but only one local storage
// Proxy and Local classes should have similar API interfaces
this.uplinks = {};
for (let p in config.uplinks) {
if (Object.prototype.hasOwnProperty.call(config.uplinks, p)) {
// instance for each up-link definition
this.uplinks[p] = new Proxy(config.uplinks[p], config);
this.uplinks[p].upname = p;
}
}
}
/**
* Function gets a local info and an info from uplinks and tries to merge it
exported for unit tests only.
* @param {*} local
* @param {*} up
* @param {*} config
* @static
*/
static _merge_versions(local, up, config) {
// copy new versions to a cache
// NOTE: if a certain version was updated, we can't refresh it reliably
for (let i in up.versions) {
if (_.isNil(local.versions[i])) {
local.versions[i] = up.versions[i];
}
}
// refresh dist-tags
for (let i in up['dist-tags']) {
if (local['dist-tags'][i] !== up['dist-tags'][i]) {
if (!local['dist-tags'][i] || semver.lte(local['dist-tags'][i], up['dist-tags'][i])) {
local['dist-tags'][i] = up['dist-tags'][i];
}
if (i === 'latest' && local['dist-tags'][i] === up['dist-tags'][i]) {
// if remote has more fresh package, we should borrow its readme
local.readme = up.readme;
}
}
}
}
}
module.exports = Storage;

View File

@@ -0,0 +1,135 @@
'use strict';
const fs = require('fs');
const Path = require('path');
const logger = require('../../logger');
/**
* Handle local database.
* FUTURE: must be a plugin.
*/
class LocalData {
/**
* Load an parse the local json database.
* @param {*} path the database path
*/
constructor(path) {
this.path = path;
// Prevent any write action, wait admin to check what happened during startup
this.locked = false;
this.data = this._fetchLocalPackages();
}
/**
* Fetch local packages.
* @private
* @return {Object}
*/
_fetchLocalPackages() {
const emptyDatabase = {list: []};
try {
const dbFile = fs.readFileSync(this.path, 'utf8');
if (!dbFile) { // readFileSync is platform specific, FreeBSD might return null
return emptyDatabase;
}
const db = this._parseDatabase(dbFile);
if(!db) {
return emptyDatabase;
}
return db;
} catch (err) {
// readFileSync is platform specific, macOS, Linux and Windows thrown an error
// Only recreate if file not found to prevent data loss
if (err.code !== 'ENOENT') {
this.locked = true;
logger.logger.error(
'Failed to read package database file, please check the error printed below:\n',
`File Path: ${this.path}\n\n`,
err
);
}
return emptyDatabase;
}
}
/**
* Parse the local database.
* @param {Object} dbFile
* @private
* @return {Object}
*/
_parseDatabase(dbFile) {
try {
return JSON.parse(dbFile);
} catch(err) {
logger.logger.error(`Package database file corrupted (invalid JSON), please check the error printed below.\nFile Path: ${this.path}`, err);
this.locked = true;
}
}
/**
* Add a new element.
* @param {*} name
* @return {Error|*}
*/
add(name) {
if (this.data.list.indexOf(name) === -1) {
this.data.list.push(name);
return this.sync();
}
}
/**
* Remove an element from the database.
* @param {*} name
* @return {Error|*}
*/
remove(name) {
const i = this.data.list.indexOf(name);
if (i !== -1) {
this.data.list.splice(i, 1);
}
return this.sync();
}
/**
* Return all database elements.
* @return {Array}
*/
get() {
return this.data.list;
}
/**
* Syncronize {create} database whether does not exist.
* @return {Error|*}
*/
sync() {
if (this.locked) {
logger.logger.error('Database is locked, please check error message printed during startup to prevent data loss.');
return new Error('Verdaccio database is locked, please contact your administrator to checkout logs during verdaccio startup.');
}
// Uses sync to prevent ugly race condition
try {
require('mkdirp').sync(Path.dirname(this.path));
} catch(err) {
// perhaps a logger instance?
/* eslint no-empty:off */
}
try {
fs.writeFileSync(this.path, JSON.stringify(this.data));
} catch (err) {
return err;
}
}
}
module.exports = LocalData;

View File

@@ -0,0 +1,256 @@
/* eslint prefer-spread: "off" */
'use strict';
const fs = require('fs');
const path = require('path');
const createError = require('http-errors');
const mkdirp = require('mkdirp');
const MyStream = require('@verdaccio/streams');
const locker = require('@verdaccio/file-locking');
const fileExist = 'EEXISTS';
const noSuchFile = 'ENOENT';
const fSError = function(code) {
const err = createError(code);
err.code = code;
return err;
};
const readFile = function(name) {
return new Promise((resolve, reject) => {
fs.readFile(name, (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};
const tempFile = function(str) {
return `${str}.tmp${String(Math.random()).substr(2)}`;
};
const renameTmp = function(src, dst, _cb) {
const cb = function(err) {
if (err) {
fs.unlink(src, function() {});
}
_cb(err);
};
if (process.platform !== 'win32') {
return fs.rename(src, dst, cb);
}
// windows can't remove opened file,
// but it seem to be able to rename it
const tmp = tempFile(dst);
fs.rename(dst, tmp, function(err) {
fs.rename(src, dst, cb);
if (!err) {
fs.unlink(tmp, () => {});
}
});
};
const writeFile = function(dest, data, cb) {
const createTempFile = function(cb) {
const tempFilePath = tempFile(dest);
fs.writeFile(tempFilePath, data, function(err) {
if (err) {
return cb(err);
}
renameTmp(tempFilePath, dest, cb);
});
};
createTempFile(function(err) {
if (err && err.code === noSuchFile) {
mkdirp(path.dirname(dest), function(err) {
if (err) {
return cb(err);
}
createTempFile(cb);
});
} else {
cb(err);
}
});
};
const createWriteStream = function(name) {
const uploadStream = new MyStream.UploadTarball();
let _ended = 0;
uploadStream.on('end', function() {
_ended = 1;
});
fs.exists(name, function(exists) {
if (exists) {
return uploadStream.emit('error', fSError(fileExist));
}
const temporalName = `${name}.tmp-${String(Math.random()).replace(/^0\./, '')}`;
const file = fs.createWriteStream(temporalName);
let opened = false;
uploadStream.pipe(file);
uploadStream.done = function() {
const onend = function() {
file.on('close', function() {
renameTmp(temporalName, name, function(err) {
if (err) {
uploadStream.emit('error', err);
} else {
uploadStream.emit('success');
}
});
});
file.destroySoon();
};
if (_ended) {
onend();
} else {
uploadStream.on('end', onend);
}
};
uploadStream.abort = function() {
if (opened) {
opened = false;
file.on('close', function() {
fs.unlink(temporalName, function() {});
});
}
file.destroySoon();
};
file.on('open', function() {
opened = true;
// re-emitting open because it's handled in storage.js
uploadStream.emit('open');
});
file.on('error', function(err) {
uploadStream.emit('error', err);
});
});
return uploadStream;
};
const createReadStream = function(name, readTarballStream, callback) {
let readStream = fs.createReadStream(name);
readStream.on('error', function(err) {
readTarballStream.emit('error', err);
});
readStream.on('open', function(fd) {
fs.fstat(fd, function(err, stats) {
if (err) return readTarballStream.emit('error', err);
readTarballStream.emit('content-length', stats.size);
readTarballStream.emit('open');
readStream.pipe(readTarballStream);
});
});
readTarballStream = new MyStream.ReadTarball();
readTarballStream.abort = function() {
readStream.close();
};
return readTarballStream;
};
const createFile = function(name, contents, callback) {
fs.exists(name, function(exists) {
if (exists) {
return callback( fSError(fileExist) );
}
writeFile(name, contents, callback);
});
};
const updateFile = function(name, contents, callback) {
fs.exists(name, function(exists) {
if (!exists) {
return callback( fSError(noSuchFile) );
}
writeFile(name, contents, callback);
});
};
const readJSON = function(name, cb) {
readFile(name).then(function(res) {
let args = [];
try {
args = [null, JSON.parse(res.toString('utf8'))];
} catch(err) {
args = [err];
}
cb.apply(null, args);
}, function(err) {
return cb(err);
});
};
const lock_and_read = function(name, cb) {
locker.readFile(name, {lock: true}, function(err, res) {
if (err) {
return cb(err);
}
return cb(null, res);
});
};
const lockAndReadJSON = function(name, cb) {
locker.readFile(name, {
lock: true,
parse: true,
}, function(err, res) {
if (err) {
return cb(err);
}
return cb(null, res);
});
};
const unlock_file = function(name, cb) {
locker.unlockFile(name, cb);
};
const createJSON = function(name, value, cb) {
createFile(name, JSON.stringify(value, null, '\t'), cb);
};
const updateJSON = function(name, value, cb) {
updateFile(name, JSON.stringify(value, null, '\t'), cb);
};
const writeJSON = function(name, value, cb) {
writeFile(name, JSON.stringify(value, null, '\t'), cb);
};
// fs
module.exports.unlink = fs.unlink;
module.exports.rmdir = fs.rmdir;
// streams
module.exports.createWriteStream = createWriteStream;
module.exports.createReadStream = createReadStream;
// io
module.exports.read = readFile;
module.exports.write = writeFile;
module.exports.update = updateFile;
module.exports.create = createFile;
// json
module.exports.readJSON = readJSON;
module.exports.lockAndReadJSON = lockAndReadJSON;
module.exports.writeJSON = writeJSON;
module.exports.updateJSON = updateJSON;
module.exports.createJSON = createJSON;
// lock
module.exports.unlock_file = unlock_file;
module.exports.lock_and_read = lock_and_read;

View File

@@ -0,0 +1,977 @@
/* eslint prefer-rest-params: "off" */
/* eslint prefer-spread: "off" */
'use strict';
const assert = require('assert');
const Crypto = require('crypto');
const fs = require('fs');
const Path = require('path');
const Stream = require('stream');
const URL = require('url');
const async = require('async');
const _ = require('lodash');
const fsStorage = require('./local-fs');
const LocalData = require('./local-data');
const customStream = require('@verdaccio/streams');
const pkgFileName = 'package.json';
const fileExist = 'EEXISTS';
const noSuchFile = 'ENOENT';
const resourceNotAvailable = 'EAGAIN';
const generatePackageTemplate = function(name) {
return {
// standard things
'name': name,
'versions': {},
'dist-tags': {},
'time': {},
// our own object
'_distfiles': {},
'_attachments': {},
'_uplinks': {},
};
};
/**
* Implements Storage interface (same for storage.js, local-storage.js, up-storage.js).
*/
class LocalStorage {
/**
* Constructor
* @param {Object} config config list of properties
* @param {Object} logger reference
* @param {Object} utils package utilities
*/
constructor(config, logger, utils) {
this.config = config;
this.utils = utils;
this.localList = new LocalData(this._buildStoragePath(this.config));
this.logger = logger.child({sub: 'fs'});
}
/**
* Build the local database path.
* @param {Object} config
* @return {string|String|*}
* @private
*/
_buildStoragePath(config) {
// FUTURE: the database might be parameterizable from config.yaml
return Path.join(Path.resolve(Path.dirname(config.self_path || ''),
config.storage,
'.sinopia-db.json'
));
}
/**
* Add a package.
* @param {*} name
* @param {*} info
* @param {*} callback
* @return {Function}
*/
addPackage(name, info, callback) {
const storage = this._getLocalStorage(name);
if (!storage) {
return callback( this.utils.ErrorCode.get404('this package cannot be added'));
}
storage.createJSON(pkgFileName, generatePackageTemplate(name), (err) => {
if (err && err.code === fileExist) {
return callback( this.utils.ErrorCode.get409());
}
const latest = this.utils.getLatestVersion(info);
if (_.isNil(latest) === false && info.versions[latest]) {
return callback(null, info.versions[latest]);
}
return callback();
});
}
/**
* Remove package.
* @param {*} name
* @param {*} callback
* @return {Function}
*/
removePackage(name, callback) {
this.logger.info( {name: name}, 'unpublishing @{name} (all)');
let storage = this._getLocalStorage(name);
if (!storage) {
return callback( this.utils.ErrorCode.get404());
}
storage.readJSON(pkgFileName, (err, data) => {
if (err) {
if (err.code === noSuchFile) {
return callback( this.utils.ErrorCode.get404());
} else {
return callback(err);
}
}
this._normalizePackage(data);
let removeFailed = this.localList.remove(name);
if (removeFailed) {
// This will happen when database is locked
return callback(this.utils.ErrorCode.get422(removeFailed.message));
}
storage.unlink(pkgFileName, function(err) {
if (err) {
return callback(err);
}
const files = Object.keys(data._attachments);
const unlinkNext = function(cb) {
if (files.length === 0) {
return cb();
}
let file = files.shift();
storage.unlink(file, function() {
unlinkNext(cb);
});
};
unlinkNext(function() {
// try to unlink the directory, but ignore errors because it can fail
storage.rmdir('.', function(err) {
callback(err);
});
});
});
});
}
/**
* Synchronize remote package info with the local one
* @param {*} name
* @param {*} packageInfo
* @param {*} callback
*/
updateVersions(name, packageInfo, callback) {
this._readCreatePackage(name, (err, packageLocalJson) => {
if (err) {
return callback(err);
}
let change = false;
for (let versionId in packageInfo.versions) {
if (_.isNil(packageLocalJson.versions[versionId])) {
const version = packageInfo.versions[versionId];
// we don't keep readmes for package versions,
// only one readme per package
delete version.readme;
change = true;
packageLocalJson.versions[versionId] = version;
if (version.dist && version.dist.tarball) {
let filename = URL.parse(version.dist.tarball).pathname.replace(/^.*\//, '');
// we do NOT overwrite any existing records
if (_.isNil(packageLocalJson._distfiles[filename])) {
let hash = packageLocalJson._distfiles[filename] = {
url: version.dist.tarball,
sha: version.dist.shasum,
};
const upLink = version[Symbol.for('__verdaccio_uplink')];
if (_.isNil(upLink) === false) {
hash = this._updateUplinkToRemoteProtocol(hash, upLink);
}
}
}
}
}
for (let tag in packageInfo['dist-tags']) {
if (!packageLocalJson['dist-tags'][tag] || packageLocalJson['dist-tags'][tag] !== packageInfo['dist-tags'][tag]) {
change = true;
packageLocalJson['dist-tags'][tag] = packageInfo['dist-tags'][tag];
}
}
for (let up in packageInfo._uplinks) {
if (Object.prototype.hasOwnProperty.call(packageInfo._uplinks, up)) {
const need_change = !this.utils.is_object(packageLocalJson._uplinks[up])
|| packageInfo._uplinks[up].etag !== packageLocalJson._uplinks[up].etag
|| packageInfo._uplinks[up].fetched !== packageLocalJson._uplinks[up].fetched;
if (need_change) {
change = true;
packageLocalJson._uplinks[up] = packageInfo._uplinks[up];
}
}
}
if (packageInfo.readme !== packageLocalJson.readme) {
packageLocalJson.readme = packageInfo.readme;
change = true;
}
if ('time' in packageInfo) {
packageLocalJson.time = packageInfo.time;
change = true;
}
if (change) {
this.logger.debug('updating package info');
this._writePackage(name, packageLocalJson, function(err) {
callback(err, packageLocalJson);
});
} else {
callback(null, packageLocalJson);
}
});
}
/**
* Ensure the dist file remains as the same protocol
* @param {Object} hash metadata
* @param {String} upLink registry key
* @private
*/
_updateUplinkToRemoteProtocol(hash, upLink) {
// if we got this information from a known registry,
// use the same protocol for the tarball
//
// see https://github.com/rlidwka/sinopia/issues/166
const tarballUrl = URL.parse(hash.url);
const uplinkUrl = URL.parse(this.config.uplinks[upLink].url);
if (uplinkUrl.host === tarballUrl.host) {
tarballUrl.protocol = uplinkUrl.protocol;
hash.registry = upLink;
hash.url = URL.format(tarballUrl);
}
}
/**
* Add a new version to a previous local package.
* @param {*} name
* @param {*} version
* @param {*} metadata
* @param {*} tag
* @param {*} callback
*/
addVersion(name, version, metadata, tag, callback) {
this._updatePackage(name, (data, cb) => {
// keep only one readme per package
data.readme = metadata.readme;
delete metadata.readme;
if (data.versions[version] != null) {
return cb( this.utils.ErrorCode.get409() );
}
// if uploaded tarball has a different shasum, it's very likely that we have some kind of error
if (this.utils.is_object(metadata.dist) && _.isString(metadata.dist.tarball)) {
let tarball = metadata.dist.tarball.replace(/.*\//, '');
if (this.utils.is_object(data._attachments[tarball])) {
if (_.isNil(data._attachments[tarball].shasum) === false && _.isNil(metadata.dist.shasum) === false) {
if (data._attachments[tarball].shasum != metadata.dist.shasum) {
const errorMessage = `shasum error, ${data._attachments[tarball].shasum} != ${metadata.dist.shasum}`;
return cb( this.utils.ErrorCode.get400(errorMessage) );
}
}
let currentDate = new Date().toISOString();
data.time['modified'] = currentDate;
if (('created' in data.time) === false) {
data.time.created = currentDate;
}
data.time[version] = currentDate;
data._attachments[tarball].version = version;
}
}
data.versions[version] = metadata;
this.utils.tag_version(data, version, tag);
let addFailed = this.localList.add(name);
if (addFailed) {
return cb(this.utils.ErrorCode.get422(addFailed.message));
}
cb();
}, callback);
}
/**
* Merge a new list of tags for a local packages with the existing one.
* @param {*} name
* @param {*} tags
* @param {*} callback
*/
mergeTags(name, tags, callback) {
this._updatePackage(name, (data, cb) => {
for (let t in tags) {
if (tags[t] === null) {
delete data['dist-tags'][t];
continue;
}
// be careful here with == (cast)
if (_.isNil(data.versions[tags[t]])) {
return cb( this._getVersionNotFound() );
}
this.utils.tag_version(data, tags[t], t);
}
cb();
}, callback);
}
/**
* Return version not found
* @return {String}
* @private
*/
_getVersionNotFound() {
return this.utils.ErrorCode.get404('this version doesn\'t exist');
}
/**
* Return file no available
* @return {String}
* @private
*/
_getFileNotAvailable() {
return this.utils.ErrorCode.get404('no such file available');
}
/**
* Replace the complete list of tags for a local package.
* @param {*} name
* @param {*} tags
* @param {*} callback
*/
replaceTags(name, tags, callback) {
this._updatePackage(name, (data, cb) => {
data['dist-tags'] = {};
for (let t in tags) {
if (_.isNull(tags[t])) {
delete data['dist-tags'][t];
continue;
}
if (_.isNil(data.versions[tags[t]])) {
return cb( this._getVersionNotFound() );
}
this.utils.tag_version(data, tags[t], t);
}
cb();
}, callback);
}
/**
* Update the package metadata, tags and attachments (tarballs).
* Note: Currently supports unpublishing only.
* @param {*} name
* @param {*} metadata
* @param {*} revision
* @param {*} callback
* @return {Function}
*/
changePackage(name, metadata, revision, callback) {
if (!this.utils.is_object(metadata.versions) || !this.utils.is_object(metadata['dist-tags'])) {
return callback( this.utils.ErrorCode.get422());
}
this._updatePackage(name, (data, cb) => {
for (let ver in data.versions) {
if (_.isNil(metadata.versions[ver])) {
this.logger.info( {name: name, version: ver},
'unpublishing @{name}@@{version}');
delete data.versions[ver];
for (let file in data._attachments) {
if (data._attachments[file].version === ver) {
delete data._attachments[file].version;
}
}
}
}
data['dist-tags'] = metadata['dist-tags'];
cb();
}, function(err) {
if (err) {
return callback(err);
}
callback();
});
}
/**
* Remove a tarball.
* @param {*} name
* @param {*} filename
* @param {*} revision
* @param {*} callback
*/
removeTarball(name, filename, revision, callback) {
assert(this.utils.validate_name(filename));
this._updatePackage(name, (data, cb) => {
if (data._attachments[filename]) {
delete data._attachments[filename];
cb();
} else {
cb(this._getFileNotAvailable());
}
}, (err) => {
if (err) {
return callback(err);
}
const storage = this._getLocalStorage(name);
if (storage) {
storage.unlink(filename, callback);
}
});
}
/**
* Add a tarball.
* @param {String} name
* @param {String} filename
* @return {Stream}
*/
addTarball(name, filename) {
assert(this.utils.validate_name(filename));
let length = 0;
const shaOneHash = Crypto.createHash('sha1');
const uploadStream = new customStream.UploadTarball();
const _transform = uploadStream._transform;
const storage = this._getLocalStorage(name);
uploadStream.abort = function() {};
uploadStream.done = function() {};
uploadStream._transform = function(data) {
shaOneHash.update(data);
// measure the length for validation reasons
length += data.length;
_transform.apply(uploadStream, arguments);
};
if (name === pkgFileName || name === '__proto__') {
process.nextTick(function() {
uploadStream.emit('error', this.utils.ErrorCode.get403());
});
return uploadStream;
}
if (!storage) {
process.nextTick(() => {
uploadStream.emit('error', ('can\'t upload this package'));
});
return uploadStream;
}
const writeStream = storage.createWriteStream(filename);
writeStream.on('error', (err) => {
if (err.code === fileExist) {
uploadStream.emit('error', this.utils.ErrorCode.get409());
} else if (err.code === noSuchFile) {
// check if package exists to throw an appropriate message
this.getPackageMetadata(name, function(_err, res) {
if (_err) {
uploadStream.emit('error', _err);
} else {
uploadStream.emit('error', err);
}
});
} else {
uploadStream.emit('error', err);
}
});
writeStream.on('open', function() {
// re-emitting open because it's handled in storage.js
uploadStream.emit('open');
});
writeStream.on('success', () => {
this._updatePackage(name, function updater(data, cb) {
data._attachments[filename] = {
shasum: shaOneHash.digest('hex'),
};
cb();
}, function(err) {
if (err) {
uploadStream.emit('error', err);
} else {
uploadStream.emit('success');
}
});
});
uploadStream.abort = function() {
writeStream.abort();
};
uploadStream.done = function() {
if (!length) {
uploadStream.emit('error', this.utils.ErrorCode.get422('refusing to accept zero-length file'));
writeStream.abort();
} else {
writeStream.done();
}
};
uploadStream.pipe(writeStream);
return uploadStream;
}
/**
* Get a tarball.
* @param {*} name
* @param {*} filename
* @return {ReadTarball}
*/
getTarball(name, filename) {
assert(this.utils.validate_name(filename));
const storage = this._getLocalStorage(name);
if (_.isNil(storage)) {
return this._createFailureStreamResponse();
}
return this._streamSuccessReadTarBall(storage, filename);
}
/**
* Return a stream that emits a read failure.
* @private
* @return {ReadTarball}
*/
_createFailureStreamResponse() {
const stream = new customStream.ReadTarball();
process.nextTick(() => {
stream.emit('error', this._getFileNotAvailable());
});
return stream;
}
/**
* Return a stream that emits the tarball data
* @param {Object} storage
* @param {String} filename
* @private
* @return {ReadTarball}
*/
_streamSuccessReadTarBall(storage, filename) {
const stream = new customStream.ReadTarball();
const readTarballStream = storage.createReadStream(filename);
const e404 = this.utils.ErrorCode.get404;
stream.abort = function() {
if (_.isNil(readTarballStream) === false) {
readTarballStream.abort();
}
};
readTarballStream.on('error', function(err) {
if (err && err.code === noSuchFile) {
stream.emit('error', e404('no such file available'));
} else {
stream.emit('error', err);
}
});
readTarballStream.on('content-length', function(v) {
stream.emit('content-length', v);
});
readTarballStream.on('open', function() {
// re-emitting open because it's handled in storage.js
stream.emit('open');
readTarballStream.pipe(stream);
});
return stream;
}
/**
* Retrieve a package by name.
* @param {*} name
* @param {*} options
* @param {*} callback
* @return {Function}
*/
getPackageMetadata(name, options, callback) {
if (_.isFunction(options)) {
callback = options || {};
}
const storage = this._getLocalStorage(name);
if (_.isNil(storage)) {
return callback( this.utils.ErrorCode.get404() );
}
this.readJSON(storage, callback);
}
/**
* Read a json file from storage.
* @param {Object} storage
* @param {Function} callback
*/
readJSON(storage, callback) {
storage.readJSON(pkgFileName, (err, result) => {
if (err) {
if (err.code === noSuchFile) {
return callback( this.utils.ErrorCode.get404() );
} else {
return callback(this._internalError(err, pkgFileName, 'error reading'));
}
}
this._normalizePackage(result);
callback(err, result);
});
}
/**
* Search a local package.
* @param {*} startKey
* @param {*} options
* @return {Function}
*/
search(startKey, options) {
const stream = new Stream.PassThrough({objectMode: true});
this._eachPackage((item, cb) => {
fs.stat(item.path, (err, stats) => {
if (err) {
return cb(err);
}
if (stats.mtime > startKey) {
this.getPackageMetadata(item.name, options, (err, data) => {
if (err) {
return cb(err);
}
const versions = this.utils.semver_sort(Object.keys(data.versions));
const latest = data['dist-tags'] && data['dist-tags'].latest ? data['dist-tags'].latest : versions.pop();
if (data.versions[latest]) {
const version = data.versions[latest];
stream.push({
'name': version.name,
'description': version.description,
'dist-tags': {latest: latest},
'maintainers': version.maintainers || [version.author].filter(Boolean),
'author': version.author,
'repository': version.repository,
'readmeFilename': version.readmeFilename || '',
'homepage': version.homepage,
'keywords': version.keywords,
'bugs': version.bugs,
'license': version.license,
'time': {
modified: item.time ? new Date(item.time).toISOString() : stats.mtime,
},
'versions': {[latest]: 'latest'},
});
}
cb();
});
} else {
cb();
}
});
}, function on_end(err) {
if (err) return stream.emit('error', err);
stream.end();
});
return stream;
}
/**
* Retrieve a wrapper that provide access to the package location.
* @param {Object} packageInfo package name.
* @return {Object}
*/
_getLocalStorage(packageInfo) {
const path = this.__getLocalStoragePath(this.config.getMatchedPackagesSpec(packageInfo).storage);
if (_.isNil(path) || path === false) {
this.logger.debug( {name: packageInfo}, 'this package has no storage defined: @{name}' );
return null;
}
return new PathWrapper(
Path.join(
Path.resolve(Path.dirname(this.config.self_path || ''), path),
packageInfo
)
);
}
/**
* Verify the right local storage location.
* @param {String} path
* @return {String}
* @private
*/
__getLocalStoragePath(path) {
if (_.isNil(path)) {
path = this.config.storage;
}
return path;
}
/**
* Walks through each package and calls `on_package` on them.
* @param {*} onPackage
* @param {*} on_end
*/
_eachPackage(onPackage, on_end) {
let storages = {};
let utils = this.utils;
storages[this.config.storage] = true;
if (this.config.packages) {
Object.keys(this.config.packages || {}).map( (pkg) => {
if (this.config.packages[pkg].storage) {
storages[this.config.packages[pkg].storage] = true;
}
});
}
const base = Path.dirname(this.config.self_path);
async.eachSeries(Object.keys(storages), function(storage, cb) {
fs.readdir(Path.resolve(base, storage), function(err, files) {
if (err) {
return cb(err);
}
async.eachSeries(files, function(file, cb) {
if (file.match(/^@/)) {
// scoped
fs.readdir(Path.resolve(base, storage, file), function(err, files) {
if (err) {
return cb(err);
}
async.eachSeries(files, (file2, cb) => {
if (utils.validate_name(file2)) {
onPackage({
name: `${file}/${file2}`,
path: Path.resolve(base, storage, file, file2),
}, cb);
} else {
cb();
}
}, cb);
});
} else if (utils.validate_name(file)) {
onPackage({
name: file,
path: Path.resolve(base, storage, file),
}, cb);
} else {
cb();
}
}, cb);
});
}, on_end);
}
/**
* Normalise package properties, tags, revision id.
* @param {Object} pkg package reference.
*/
_normalizePackage(pkg) {
const pkgProperties = ['versions', 'dist-tags', '_distfiles', '_attachments', '_uplinks', 'time'];
pkgProperties.forEach((key) => {
if (!this.utils.is_object(pkg[key])) {
pkg[key] = {};
}
});
if (_.isString(pkg._rev) === false) {
pkg._rev = '0-0000000000000000';
}
// normalize dist-tags
this.utils.normalize_dist_tags(pkg);
}
/**
* Retrieve either a previous created local package or a boilerplate.
* @param {*} name
* @param {*} callback
* @return {Function}
*/
_readCreatePackage(name, callback) {
const storage = this._getLocalStorage(name);
if (!storage) {
const data = generatePackageTemplate(name);
this._normalizePackage(data);
return callback(null, data);
}
storage.readJSON(pkgFileName, (err, data) => {
// TODO: race condition
if (err) {
if (err.code === noSuchFile) {
// if package doesn't exist, we create it here
data = generatePackageTemplate(name);
} else {
return callback(this._internalError(err, pkgFileName, 'error reading'));
}
}
this._normalizePackage(data);
callback(null, data);
});
}
/**
* Handle internal error
* @param {*} err
* @param {*} file
* @param {*} message
* @return {Object} Error instance
*/
_internalError(err, file, message) {
this.logger.error( {err: err, file: file},
message + ' @{file}: @{!err.message}' );
return this.utils.ErrorCode.get500();
}
/**
* This function allows to update the package thread-safely
Algorithm:
1. lock package.json for writing
2. read package.json
3. updateFn(pkg, cb), and wait for cb
4. write package.json.tmp
5. move package.json.tmp package.json
6. callback(err?)
* @param {*} name package name
* @param {*} updateFn function(package, cb) - update function
* @param {*} _callback callback that gets invoked after it's all updated
* @return {Function}
*/
_updatePackage(name, updateFn, _callback) {
const storage = this._getLocalStorage(name);
if (!storage) {
return _callback( this.utils.ErrorCode.get404() );
}
storage.lockAndReadJSON(pkgFileName, (err, json) => {
let locked = false;
// callback that cleans up lock first
const callback = function(err) {
let _args = arguments;
if (locked) {
storage.unlock_file(pkgFileName, function() {
// ignore any error from the unlock
_callback.apply(err, _args);
});
} else {
_callback.apply(null, _args);
}
};
if (!err) {
locked = true;
}
if (err) {
if (err.code === resourceNotAvailable) {
return callback( this.utils.ErrorCode.get503() );
} else if (err.code === noSuchFile) {
return callback( this.utils.ErrorCode.get404() );
} else {
return callback(err);
}
}
this._normalizePackage(json);
updateFn(json, (err) => {
if (err) {
return callback(err);
}
this._writePackage(name, json, callback);
});
});
}
/**
* Update the revision (_rev) string for a package.
* @param {*} name
* @param {*} json
* @param {*} callback
* @return {Function}
*/
_writePackage(name, json, callback) {
// calculate revision a la couchdb
if (typeof(json._rev) !== 'string') {
json._rev = '0-0000000000000000';
}
const rev = json._rev.split('-');
json._rev = ((+rev[0] || 0) + 1) + '-' + Crypto.pseudoRandomBytes(8).toString('hex');
let storage = this._getLocalStorage(name);
if (!storage) {
return callback();
}
storage.writeJSON(pkgFileName, json, callback);
}
}
const PathWrapper = (function() {
/**
* A wrapper adding paths to fs_storage methods.
*/
class Wrapper {
/**
* @param {*} path
*/
constructor(path) {
this.path = path;
}
}
const wrapLocalStorageMethods = function(method) {
return function() {
let args = Array.prototype.slice.apply(arguments);
/* eslint no-invalid-this: off */
args[0] = Path.join(this.path, args[0] || '');
return fsStorage[method].apply(null, args);
};
};
for (let i in fsStorage) {
if (fsStorage.hasOwnProperty(i)) {
Wrapper.prototype[i] = wrapLocalStorageMethods(i);
}
}
return Wrapper;
})();
module.exports = LocalStorage;

View File

@@ -0,0 +1,531 @@
'use strict';
const JSONStream = require('JSONStream');
const createError = require('http-errors');
const _ = require('lodash');
const request = require('request');
const Stream = require('stream');
const URL = require('url');
const Logger = require('../logger');
const MyStreams = require('@verdaccio/streams');
const Utils = require('../utils');
const zlib = require('zlib');
const encode = function(thing) {
return encodeURIComponent(thing).replace(/^%40/, '@');
};
const jsonContentType = 'application/json';
const contenTypeAccept = `${jsonContentType}; q=0.8, */*`;
/**
* Just a helper (`config[key] || default` doesn't work because of zeroes)
* @param {Object} config
* @param {Object} key
* @param {Object} def
* @return {Object}
*/
const setConfig = (config, key, def) => {
return _.isNil(config[key]) === false ? config[key] : def;
};
/**
* Implements Storage interface
* (same for storage.js, local-storage.js, up-storage.js)
*/
class ProxyStorage {
/**
* Constructor
* @param {*} config
* @param {*} mainConfig
*/
constructor(config, mainConfig) {
this.config = config;
this.failed_requests = 0;
this.userAgent = mainConfig.user_agent;
this.ca = config.ca;
this.logger = Logger.logger.child({sub: 'out'});
this.server_id = mainConfig.server_id;
this.url = URL.parse(this.config.url);
this._setupProxy(this.url.hostname, config, mainConfig, this.url.protocol === 'https:');
this.config.url = this.config.url.replace(/\/$/, '');
if (Number(this.config.timeout) >= 1000) {
this.logger.warn(['Too big timeout value: ' + this.config.timeout,
'We changed time format to nginx-like one',
'(see http://nginx.org/en/docs/syntax.html)',
'so please update your config accordingly'].join('\n'));
}
// a bunch of different configurable timers
this.maxage = Utils.parseInterval(setConfig(this.config, 'maxage', '2m' ));
this.timeout = Utils.parseInterval(setConfig(this.config, 'timeout', '30s'));
this.max_fails = Number(setConfig(this.config, 'max_fails', 2 ));
this.fail_timeout = Utils.parseInterval(setConfig(this.config, 'fail_timeout', '5m' ));
}
/**
* Fetch an asset.
* @param {*} options
* @param {*} cb
* @return {Request}
*/
request(options, cb) {
let json;
if (this._statusCheck() === false) {
let streamRead = new Stream.Readable();
process.nextTick(function() {
if (_.isFunction(cb)) {
cb(createError('uplink is offline'));
}
streamRead.emit('error', createError('uplink is offline'));
});
streamRead._read = function() {};
// preventing 'Uncaught, unspecified "error" event'
streamRead.on('error', function() {});
return streamRead;
}
let self = this;
let headers = this._setHeaders(options);
this._addProxyHeaders(options.req, headers);
this._overrideWithUplinkConfigHeaders(headers);
const method = options.method || 'GET';
const uri = options.uri_full || (this.config.url + options.uri);
self.logger.info({
method: method,
headers: headers,
uri: uri,
}, 'making request: \'@{method} @{uri}\'');
if (Utils.is_object(options.json)) {
json = JSON.stringify(options.json);
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
}
let requestCallback = cb ? (function(err, res, body) {
let error;
const responseLength = err ? 0 : body.length;
processBody(err, body);
logActivity();
cb(err, res, body);
/**
* Perform a decode.
*/
function processBody() {
if (err) {
error = err.message;
return;
}
if (options.json && res.statusCode < 300) {
try {
body = JSON.parse(body.toString('utf8'));
} catch(_err) {
body = {};
err = _err;
error = err.message;
}
}
if (!err && Utils.is_object(body)) {
if (_.isString(body.error)) {
error = body.error;
}
}
}
/**
* Perform a log.
*/
function logActivity() {
let message = '@{!status}, req: \'@{request.method} @{request.url}\'';
message += error
? ', error: @{!error}'
: ', bytes: @{bytes.in}/@{bytes.out}';
self.logger.warn({
err: err,
request: {method: method, url: uri},
level: 35, // http
status: res != null ? res.statusCode : 'ERR',
error: error,
bytes: {
in: json ? json.length : 0,
out: responseLength || 0,
},
}, message);
}
}) : undefined;
const req = request({
url: uri,
method: method,
headers: headers,
body: json,
ca: this.ca,
proxy: this.proxy,
encoding: null,
gzip: true,
timeout: this.timeout,
}, requestCallback);
let statusCalled = false;
req.on('response', function(res) {
if (!req._verdaccio_aborted && _.isNil(statusCalled) === false) {
statusCalled = true;
self._statusCheck(true);
}
if (_.isNil(requestCallback) === false) {
(function do_log() {
const message = '@{!status}, req: \'@{request.method} @{request.url}\' (streaming)';
self.logger.warn({
request: {
method: method,
url: uri,
},
level: 35, // http
status: _.isNull(res) === false ? res.statusCode : 'ERR',
}, message);
})();
}
});
req.on('error', function(_err) {
if (!req._verdaccio_aborted && !statusCalled) {
statusCalled = true;
self._statusCheck(false);
}
});
return req;
}
/**
* Set default headers.
* @param {Object} options
* @return {Object}
* @private
*/
_setHeaders(options) {
const headers = options.headers || {};
const accept = 'Accept';
const acceptEncoding = 'Accept-Encoding';
const userAgent = 'User-Agent';
headers[accept] = headers[accept] || contenTypeAccept;
headers[acceptEncoding] = headers[acceptEncoding] || 'gzip';
// registry.npmjs.org will only return search result if user-agent include string 'npm'
headers[userAgent] = headers[userAgent] || `npm (${this.userAgent})`;
return headers;
}
/**
* It will add or override specified headers from config file.
*
* Eg:
*
* uplinks:
npmjs:
url: https://registry.npmjs.org/
headers:
Accept: "application/vnd.npm.install-v2+json; q=1.0"
verdaccio-staging:
url: https://mycompany.com/npm
headers:
Accept: "application/json"
authorization: "Basic YourBase64EncodedCredentials=="
* @param {Object} headers
* @private
*/
_overrideWithUplinkConfigHeaders(headers) {
// add/override headers specified in the config
for (let key in this.config.headers) {
if (Object.prototype.hasOwnProperty.call(this.config.headers, key)) {
headers[key] = this.config.headers[key];
}
}
}
/**
* Determine whether can fetch from the provided URL.
* @param {*} url
* @return {Boolean}
*/
isUplinkValid(url) {
url = URL.parse(url);
return url.protocol === this.url.protocol && url.host === this.url.host && url.path.indexOf(this.url.path) === 0;
}
/**
* Get a remote package metadata
* @param {*} name package name
* @param {*} options request options, eg: eTag.
* @param {*} callback
*/
getRemoteMetadata(name, options, callback) {
const headers = {};
if (_.isNil(options.etag) === false) {
headers['If-None-Match'] = options.etag;
headers['Accept'] = contenTypeAccept;
}
this.request({
uri: `/${encode(name)}`,
json: true,
headers: headers,
req: options.req,
}, (err, res, body) => {
if (err) {
return callback(err);
}
if (res.statusCode === 404) {
return callback( createError[404]('package doesn\'t exist on uplink') );
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
const error = createError(`bad status code: ${res.statusCode}`);
error.remoteStatus = res.statusCode;
return callback(error);
}
callback(null, body, res.headers.etag);
});
}
/**
* Fetch a tarball from the uplink.
* @param {String} url
* @return {Stream}
*/
fetchTarball(url) {
const stream = new MyStreams.ReadTarball({});
let current_length = 0;
let expected_length;
stream.abort = () => {};
const readStream = this.request({
uri_full: url,
encoding: null,
headers: {
Accept: contenTypeAccept,
},
});
readStream.on('response', function(res) {
if (res.statusCode === 404) {
return stream.emit('error', createError[404]('file doesn\'t exist on uplink'));
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return stream.emit('error', createError('bad uplink status code: ' + res.statusCode));
}
if (res.headers['content-length']) {
expected_length = res.headers['content-length'];
stream.emit('content-length', res.headers['content-length']);
}
readStream.pipe(stream);
});
readStream.on('error', function(err) {
stream.emit('error', err);
});
readStream.on('data', function(data) {
current_length += data.length;
});
readStream.on('end', function(data) {
if (data) {
current_length += data.length;
}
if (expected_length && current_length != expected_length) {
stream.emit('error', createError('content length mismatch'));
}
});
return stream;
}
/**
* Perform a stream search.
* @param {*} options request options
* @return {Stream}
*/
search(options) {
const transformStream = new Stream.PassThrough({objectMode: true});
const requestStream = this.request({
uri: options.req.url,
req: options.req,
headers: {
referer: options.req.headers.referer,
},
});
let parsePackage = (pkg) => {
if (Utils.is_object(pkg)) {
transformStream.emit('data', pkg);
}
};
requestStream.on('response', (res) => {
if (!String(res.statusCode).match(/^2\d\d$/)) {
return transformStream.emit('error', createError(`bad status code ${res.statusCode} from uplink`));
}
// See https://github.com/request/request#requestoptions-callback
// Request library will not decode gzip stream.
let jsonStream;
if (res.headers['content-encoding'] === 'gzip') {
jsonStream = res.pipe(zlib.createUnzip());
} else {
jsonStream = res;
}
jsonStream.pipe(JSONStream.parse('*')).on('data', parsePackage);
jsonStream.on('end', () => {
transformStream.emit('end');
});
});
requestStream.on('error', (err) => {
transformStream.emit('error', err);
});
transformStream.abort = () => {
requestStream.abort();
transformStream.emit('end');
};
return transformStream;
}
/**
* Add proxy headers.
* @param {*} req the http request
* @param {*} headers the request headers
*/
_addProxyHeaders(req, headers) {
if (req) {
// Only submit X-Forwarded-For field if we don't have a proxy selected
// in the config file.
//
// Otherwise misconfigured proxy could return 407:
// https://github.com/rlidwka/sinopia/issues/254
//
if (this.proxy === false) {
headers['X-Forwarded-For'] = (
req && req.headers['x-forwarded-for']
? req.headers['x-forwarded-for'] + ', '
: ''
) + req.connection.remoteAddress;
}
}
// always attach Via header to avoid loops, even if we're not proxying
headers['Via'] =
req && req.headers['via']
? req.headers['via'] + ', '
: '';
headers['Via'] += '1.1 ' + this.server_id + ' (Verdaccio)';
}
/**
* Check whether the remote host is available.
* @param {*} alive
* @return {Boolean}
*/
_statusCheck(alive) {
if (arguments.length === 0) {
return this._ifRequestFailure() === false;
} else {
if (alive) {
if (this.failed_requests >= this.max_fails) {
this.logger.warn({
host: this.url.host,
}, 'host @{host} is back online');
}
this.failed_requests = 0;
} else {
this.failed_requests ++;
if (this.failed_requests === this.max_fails) {
this.logger.warn({
host: this.url.host,
}, 'host @{host} is now offline');
}
}
this.last_request_time = Date.now();
}
}
/**
* If the request failure.
* @return {boolean}
* @private
*/
_ifRequestFailure() {
return this.failed_requests >= this.max_fails && Math.abs(Date.now() - this.last_request_time) < this.fail_timeout;
}
/**
* Set up a proxy.
* @param {*} hostname
* @param {*} config
* @param {*} mainconfig
* @param {*} isHTTPS
*/
_setupProxy(hostname, config, mainconfig, isHTTPS) {
let noProxyList;
let proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy';
// get http_proxy and no_proxy configs
if (proxy_key in config) {
this.proxy = config[proxy_key];
} else if (proxy_key in mainconfig) {
this.proxy = mainconfig[proxy_key];
}
if ('no_proxy' in config) {
noProxyList = config.no_proxy;
} else if ('no_proxy' in mainconfig) {
noProxyList = mainconfig.no_proxy;
}
// use wget-like algorithm to determine if proxy shouldn't be used
if (hostname[0] !== '.') {
hostname = '.' + hostname;
}
if (_.isString(noProxyList) && noProxyList.length) {
noProxyList = noProxyList.split(',');
}
if (_.isArray(noProxyList)) {
for (let i = 0; i < noProxyList.length; i++) {
let noProxyItem = noProxyList[i];
if (noProxyItem[0] !== '.') noProxyItem = '.' + noProxyItem;
if (hostname.lastIndexOf(noProxyItem) === hostname.length - noProxyItem.length) {
if (this.proxy) {
this.logger.debug({url: this.url.href, rule: noProxyItem},
'not using proxy for @{url}, excluded by @{rule} rule');
this.proxy = false;
}
break;
}
}
}
// if it's non-string (i.e. "false"), don't use it
if (_.isString(this.proxy) === false) {
delete this.proxy;
} else {
this.logger.debug( {url: this.url.href, proxy: this.proxy}, 'using proxy @{proxy} for @{url}' );
}
}
}
module.exports = ProxyStorage;

370
src/lib/utils.js Normal file
View File

@@ -0,0 +1,370 @@
'use strict';
const assert = require('assert');
const semver = require('semver');
const YAML = require('js-yaml');
const URL = require('url');
const fs = require('fs');
const _ = require('lodash');
const Logger = require('./logger');
const createError = require('http-errors');
/**
* Validate a package.
* @param {*} name
* @return {Boolean} whether the package is valid or not
*/
function validate_package(name) {
name = name.split('/', 2);
if (name.length === 1) {
// normal package
return module.exports.validate_name(name[0]);
} else {
// scoped package
return name[0][0] === '@'
&& module.exports.validate_name(name[0].slice(1))
&& module.exports.validate_name(name[1]);
}
}
/**
* From normalize-package-data/lib/fixer.js
* @param {*} name the package name
* @return {Boolean} whether is valid or not
*/
function validate_name(name) {
if (_.isString(name) === false) {
return false;
}
name = name.toLowerCase();
// all URL-safe characters and "@" for issue #75
return !(!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/)
|| name.charAt(0) === '.' // ".bin", etc.
|| name.charAt(0) === '-' // "-" is reserved by couchdb
|| name === 'node_modules'
|| name === '__proto__'
|| name === 'package.json'
|| name === 'favicon.ico'
);
}
/**
* Check whether an element is an Object
* @param {*} obj the element
* @return {Boolean}
*/
function isObject(obj) {
return _.isObject(obj) && _.isNull(obj) === false && _.isArray(obj) === false;
}
/**
* Validate the package metadata, add additional properties whether are missing within
* the metadata properties.
* @param {*} object
* @param {*} name
* @return {Object} the object with additional properties as dist-tags ad versions
*/
function validate_metadata(object, name) {
assert(isObject(object), 'not a json object');
assert.equal(object.name, name);
if (!isObject(object['dist-tags'])) {
object['dist-tags'] = {};
}
if (!isObject(object['versions'])) {
object['versions'] = {};
}
return object;
}
/**
* Create base url for registry.
* @param {String} protocol
* @param {String} host
* @param {String} prefix
* @return {String} base registry url
*/
function combineBaseUrl(protocol, host, prefix) {
let result = `${protocol}://${host}`;
if (prefix) {
prefix = prefix.replace(/\/$/, '');
result = (prefix.indexOf('/') === 0)
? `${result}${prefix}`
: prefix;
}
return result;
}
/**
* Iterate a packages's versions and filter each original tarbal url.
* @param {*} pkg
* @param {*} req
* @param {*} config
* @return {String} a filtered package
*/
function filter_tarball_urls(pkg, req, config) {
/**
* Filter a tarball url.
* @param {*} _url
* @return {String} a parsed url
*/
const filter = function(_url) {
if (!req.headers.host) {
return _url;
}
const filename = URL.parse(_url).pathname.replace(/^.*\//, '');
const base = combineBaseUrl(getWebProtocol(req), req.headers.host, config.url_prefix);
return `${base}/${pkg.name.replace(/\//g, '%2f')}/-/${filename}`;
};
for (let ver in pkg.versions) {
if (Object.prototype.hasOwnProperty.call(pkg.versions, ver)) {
const dist = pkg.versions[ver].dist;
if (_.isNull(dist) === false && _.isNull(dist.tarball) === false) {
dist.tarball = filter(dist.tarball);
}
}
}
return pkg;
}
/**
* Create a tag for a package
* @param {*} data
* @param {*} version
* @param {*} tag
* @return {Boolean} whether a package has been tagged
*/
function tag_version(data, version, tag) {
if (tag) {
if (data['dist-tags'][tag] !== version) {
if (semver.parse(version, true)) {
// valid version - store
data['dist-tags'][tag] = version;
return true;
}
}
Logger.logger.warn({ver: version, tag: tag}, 'ignoring bad version @{ver} in @{tag}');
if (tag && data['dist-tags'][tag]) {
delete data['dist-tags'][tag];
}
}
return false;
}
/**
* Gets version from a package object taking into account semver weirdness.
* @param {*} object
* @param {*} version
* @return {String} return the semantic version of a package
*/
function get_version(object, version) {
// this condition must allow cast
if (object.versions[version] != null) {
return object.versions[version];
}
try {
version = semver.parse(version, true);
for (let k in object.versions) {
if (version.compare(semver.parse(k, true)) === 0) {
return object.versions[k];
}
}
} catch (err) {
return undefined;
}
}
/**
* Parse an internet address
* Allow:
- https:localhost:1234 - protocol + host + port
- localhost:1234 - host + port
- 1234 - port
- http::1234 - protocol + port
- https://localhost:443/ - full url + https
- http://[::1]:443/ - ipv6
- unix:/tmp/http.sock - unix sockets
- https://unix:/tmp/http.sock - unix sockets (https)
* @param {*} urlAddress the internet address definition
* @return {Object|Null} literal object that represent the address parsed
*/
function parse_address(urlAddress) {
//
// TODO: refactor it to something more reasonable?
//
// protocol : // ( host )|( ipv6 ): port /
let urlPattern = /^((https?):(\/\/)?)?((([^\/:]*)|\[([^\[\]]+)\]):)?(\d+)\/?$/.exec(urlAddress);
if (urlPattern) {
return {
proto: urlPattern[2] || 'http',
host: urlPattern[6] || urlPattern[7] || 'localhost',
port: urlPattern[8] || '4873',
};
}
urlPattern = /^((https?):(\/\/)?)?unix:(.*)$/.exec(urlAddress);
if (urlPattern) {
return {
proto: urlPattern[2] || 'http',
path: urlPattern[4],
};
}
return null;
}
/**
* Function filters out bad semver versions and sorts the array.
* @param {*} array
* @return {Array} sorted Array
*/
function semverSort(array) {
return array
.filter(function(x) {
if (!semver.parse(x, true)) {
Logger.logger.warn( {ver: x}, 'ignoring bad version @{ver}' );
return false;
}
return true;
})
.sort(semver.compareLoose)
.map(String);
}
/**
* Flatten arrays of tags.
* @param {*} data
*/
function normalize_dist_tags(data) {
let sorted;
if (!data['dist-tags'].latest) {
// overwrite latest with highest known version based on semver sort
sorted = semverSort(Object.keys(data.versions));
if (sorted && sorted.length) {
data['dist-tags'].latest = sorted.pop();
}
}
for (let tag in data['dist-tags']) {
if (_.isArray(data['dist-tags'][tag])) {
if (data['dist-tags'][tag].length) {
// sort array
sorted = semverSort(data['dist-tags'][tag]);
if (sorted.length) {
// use highest version based on semver sort
data['dist-tags'][tag] = sorted.pop();
}
} else {
delete data['dist-tags'][tag];
}
} else if (_.isString(data['dist-tags'][tag] )) {
if (!semver.parse(data['dist-tags'][tag], true)) {
// if the version is invalid, delete the dist-tag entry
delete data['dist-tags'][tag];
}
}
}
}
const parseIntervalTable = {
'': 1000,
'ms': 1,
's': 1000,
'm': 60*1000,
'h': 60*60*1000,
'd': 86400000,
'w': 7*86400000,
'M': 30*86400000,
'y': 365*86400000,
};
/**
* Parse an internal string to number
* @param {*} interval
* @return {Number}
*/
function parseInterval(interval) {
if (typeof(interval) === 'number') {
return interval * 1000;
}
let result = 0;
let last_suffix = Infinity;
interval.split(/\s+/).forEach(function(x) {
if (!x) return;
let m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/);
if (!m
|| parseIntervalTable[m[4]] >= last_suffix
|| (m[4] === '' && last_suffix !== Infinity)) {
throw Error('invalid interval: ' + interval);
}
last_suffix = parseIntervalTable[m[4]];
result += Number(m[1]) * parseIntervalTable[m[4]];
});
return result;
}
/**
* Detect running protocol (http or https)
* @param {*} req
* @return {String}
*/
function getWebProtocol(req) {
return req.get('X-Forwarded-Proto') || req.protocol;
}
const getLatestVersion = function(pkgInfo) {
return pkgInfo['dist-tags'].latest;
};
const ErrorCode = {
get409: () => {
return createError(409, 'this package is already present');
},
get422: (customMessage) => {
return createError(422, customMessage || 'bad data');
},
get400: (customMessage) => {
return createError(400, customMessage);
},
get500: () => {
return createError(500);
},
get403: () => {
return createError(403, 'can\'t use this filename');
},
get503: () => {
return createError(500, 'resource temporarily unavailable');
},
get404: (customMessage) => {
return createError(404, customMessage || 'no such package available');
},
};
const parseConfigFile = (config_path) => YAML.safeLoad(fs.readFileSync(config_path, 'utf8'));
module.exports.parseInterval = parseInterval;
module.exports.semver_sort = semverSort;
module.exports.parse_address = parse_address;
module.exports.get_version = get_version;
module.exports.normalize_dist_tags = normalize_dist_tags;
module.exports.tag_version = tag_version;
module.exports.combineBaseUrl = combineBaseUrl;
module.exports.filter_tarball_urls = filter_tarball_urls;
module.exports.validate_metadata = validate_metadata;
module.exports.is_object = isObject;
module.exports.validate_name = validate_name;
module.exports.validate_package = validate_package;
module.exports.getWebProtocol = getWebProtocol;
module.exports.getLatestVersion = getLatestVersion;
module.exports.ErrorCode = ErrorCode;
module.exports.parseConfigFile = parseConfigFile;

View File

@@ -0,0 +1,59 @@
/* eslint require-jsdoc: off */
'use strict';
/** Node.js Crypt(3) Library
Inspired by (and intended to be compatible with) sendanor/crypt3
see https://github.com/sendanor/node-crypt3
The key difference is the removal of the dependency on the unix crypt(3) function
which is not platform independent, and requires compilation. Instead, a pure
javascript version is used.
*/
const crypt = require('unix-crypt-td-js');
const crypto = require('crypto');
function createSalt(type) {
type = type || 'sha512';
switch (type) {
case 'md5':
return '$1$' + crypto.randomBytes(10).toString('base64');
case 'blowfish':
return '$2a$' + crypto.randomBytes(10).toString('base64');
case 'sha256':
return '$5$' + crypto.randomBytes(10).toString('base64');
case 'sha512':
return '$6$' + crypto.randomBytes(10).toString('base64');
default:
throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type);
}
}
function crypt3(key, salt) {
salt = salt || createSalt();
return crypt(key, salt);
}
/** Crypt(3) password and data encryption.
* @param {string} key user's typed password
* @param {string} salt Optional salt, for example SHA-512 use "$6$salt$".
* @returns {string} A generated hash in format $id$salt$encrypted
* @see https://en.wikipedia.org/wiki/Crypt_(C)
*/
module.exports = crypt3;
/** Create salt
* @param {string} type The type of salt: md5, blowfish (only some linux distros), sha256 or sha512. Default is sha512.
* @returns {string} Generated salt string
*/
module.exports.createSalt = createSalt;

View File

@@ -0,0 +1,137 @@
/* eslint require-jsdoc: off */
'use strict';
let fs = require('fs');
let Path = require('path');
let utils = require('./utils');
module.exports = HTPasswd;
function HTPasswd(config, stuff) {
let self = Object.create(HTPasswd.prototype);
self._users = {};
// config for this module
self._config = config;
// verdaccio logger
self._logger = stuff.logger;
// verdaccio main config object
self._verdaccio_config = stuff.config;
// all this "verdaccio_config" stuff is for b/w compatibility only
self._maxusers = self._config.max_users;
if (!self._maxusers) self._maxusers = self._verdaccio_config.max_users;
// set maxusers to Infinity if not specified
if (!self._maxusers) self._maxusers = Infinity;
self._last_time = null;
let file = self._config.file;
if (!file) file = self._verdaccio_config.users_file;
if (!file) throw new Error('should specify "file" in config');
self._path = Path.resolve(Path.dirname(self._verdaccio_config.self_path), file);
return self;
}
HTPasswd.prototype.authenticate = function(user, password, cb) {
let self = this;
self._reload(function(err) {
if (err) return cb(err.code === 'ENOENT' ? null : err);
if (!self._users[user]) return cb(null, false);
if (!utils.verify_password(user, password, self._users[user])) return cb(null, false);
// authentication succeeded!
// return all usergroups this user has access to;
// (this particular package has no concept of usergroups, so just return user herself)
return cb(null, [user]);
});
};
// hopefully race-condition-free way to add users:
// 1. lock file for writing (other processes can still read)
// 2. reload .htpasswd
// 3. write new data into .htpasswd.tmp
// 4. move .htpasswd.tmp to .htpasswd
// 5. reload .htpasswd
// 6. unlock file
HTPasswd.prototype.adduser = function(user, password, real_cb) {
let self = this;
function sanity_check() {
let err = null;
if (self._users[user]) {
err = Error('this user already exists');
} else if (Object.keys(self._users).length >= self._maxusers) {
err = Error('maximum amount of users reached');
}
if (err) err.status = 403;
return err;
}
// preliminary checks, just to ensure that file won't be reloaded if it's not needed
let s_err = sanity_check();
if (s_err) return real_cb(s_err, false);
utils.lock_and_read(self._path, function(err, res) {
let locked = false;
// callback that cleans up lock first
function cb(err) {
if (locked) {
utils.unlock_file(self._path, function() {
// ignore any error from the unlock
real_cb(err, !err);
});
} else {
real_cb(err, !err);
}
}
if (!err) {
locked = true;
}
// ignore ENOENT errors, we'll just create .htpasswd in that case
if (err && err.code !== 'ENOENT') return cb(err);
let body = (res || '').toString('utf8');
self._users = utils.parse_htpasswd(body);
// real checks, to prevent race conditions
let s_err = sanity_check();
if (s_err) return cb(s_err);
try {
body = utils.add_user_to_htpasswd(body, user, password);
} catch (err) {
return cb(err);
}
fs.writeFile(self._path, body, function(err) {
if (err) return cb(err);
self._reload(function() {
cb(null, true);
});
});
});
};
HTPasswd.prototype._reload = function(_callback) {
let self = this;
fs.stat(self._path, function(err, stats) {
if (err) return _callback(err);
if (self._last_time === stats.mtime) return _callback();
self._last_time = stats.mtime;
fs.readFile(self._path, 'utf8', function(err, buffer) {
if (err) return _callback(err);
self._users = utils.parse_htpasswd(buffer);
_callback();
});
});
};

View File

@@ -0,0 +1,72 @@
/* eslint require-jsdoc: off */
'use strict';
let crypto = require('crypto');
let crypt3 = require('./crypt3');
let md5 = require('apache-md5');
let locker = require('@verdaccio/file-locking');
// this function neither unlocks file nor closes it
// it'll have to be done manually later
function lock_and_read(name, cb) {
locker.readFile(name, {lock: true}, function(err, res) {
if (err) {
return cb(err);
}
return cb(null, res);
});
}
// close and unlock file
function unlock_file(name, cb) {
locker.unlockFile(name, cb);
}
function parse_htpasswd(input) {
let result = {};
input.split('\n').forEach(function(line) {
let args = line.split(':', 3);
if (args.length > 1) result[args[0]] = args[1];
});
return result;
}
function verify_password(user, passwd, hash) {
if (hash.indexOf('{PLAIN}') === 0) {
return passwd === hash.substr(7);
} else if (hash.indexOf('{SHA}') === 0) {
return crypto.createHash('sha1').update(passwd, 'binary').digest('base64') === hash.substr(5);
} else {
return (
// for backwards compatibility, first check md5 then check crypt3
md5(passwd, hash) === hash ||
crypt3(passwd, hash) === hash
);
}
}
function add_user_to_htpasswd(body, user, passwd) {
if (user !== encodeURIComponent(user)) {
let err = Error('username should not contain non-uri-safe characters');
err.status = 409;
throw err;
}
if (crypt3) {
passwd = crypt3(passwd);
} else {
passwd = '{SHA}' + crypto.createHash('sha1').update(passwd, 'binary').digest('base64');
}
let comment = 'autocreated ' + (new Date()).toJSON();
let newline = user + ':' + passwd + ':' + comment + '\n';
if (body.length && body[body.length-1] !== '\n') newline = '\n' + newline;
return body + newline;
}
module.exports.parse_htpasswd = parse_htpasswd;
module.exports.verify_password = verify_password;
module.exports.add_user_to_htpasswd = add_user_to_htpasswd;
module.exports.lock_and_read = lock_and_read;
module.exports.unlock_file = unlock_file;

34
src/webui/.eslintrc Normal file
View File

@@ -0,0 +1,34 @@
{
"env": {
"browser": true,
"node": true,
"jest": true,
"es6": true
},
"rules": {
"require-jsdoc": 0,
"no-console": [
1,
{
"allow": [
"log"
]
}
],
"no-unused-vars": [
2,
{
"vars": "all",
"args": "all"
}
],
"comma-dangle": 0,
"semi": 1,
"react/no-danger-with-children": 1,
"react/no-string-refs": 1,
"react/prefer-es6-class": [
2,
"always"
]
}
}

19
src/webui/src/app.js Normal file
View File

@@ -0,0 +1,19 @@
import React from 'react';
import 'element-theme-default';
import {i18n} from 'element-react';
import locale from 'element-react/src/locale/lang/en';
i18n.use(locale);
import Route from './router';
import './styles/global.scss';
import 'normalize.css';
export default class App extends React.Component {
render() {
return (
<Route/>
);
}
}

View File

@@ -0,0 +1,34 @@
@import '../../styles/variable';
.header {
display: flex;
height: 70px;
width: 100%;
align-items: center;
background: $primary-color;
figure {
margin: 0 0 0 10px;
font-size: 14px;
line-height: 18px;
padding: 8px 0;
color: #f9f2f4;
}
.headerWrap {
display: flex;
align-items: center;
@include container-size();
}
.logo {
height: 50px;
}
.welcome {
margin-left: auto;
color: white;
}
}

View File

@@ -0,0 +1,185 @@
import React from 'react';
import {Button, Dialog, Input, Alert} from 'element-react';
import isString from 'lodash/isString';
import get from 'lodash/get';
import isNumber from 'lodash/isNumber';
import {Link} from 'react-router-dom';
import API from '../../../utils/api';
import storage from '../../../utils/storage';
import classes from './header.scss';
import './logo.png';
export default class Header extends React.Component {
state = {
showLogin: false,
username: '',
password: '',
logo: '',
loginError: null
}
constructor(props) {
super(props);
this.toggleLoginModal = this.toggleLoginModal.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
toggleLoginModal() {
this.setState({
showLogin: !this.state.showLogin
});
this.setState({loginError: null});
}
handleInput(name, e) {
this.setState({
[name]: e
});
}
componentWillMount() {
API.get('logo')
.then((response) => {
this.setState({logo: response.data});
})
.catch((error) => {
throw new Error(error);
});
}
async handleSubmit() {
if (this.state.username === '' || this.state.password === '') {
return this.setState({loginError: {
title: 'Unable to login',
type: 'error',
description: 'Username or password can\'t be empty!'
}});
}
try {
let resp = await API.post(`login`, {
data: {
username: this.state.username,
password: this.state.password
}
});
storage.setItem('token', resp.data.token);
storage.setItem('username', resp.data.username);
location.reload();
} catch (e) {
const errorObj = {
title: 'Unable to login',
type: 'error'
};
if (get(e, 'response.status', 0) === 401) {
errorObj.description = e.response.data.error;
} else {
errorObj.description = e.message;
}
this.setState({loginError: errorObj});
}
}
get isTokenExpire() {
const token = storage.getItem('token');
if (!isString(token)) {
return true;
}
let payload = token.split('.')[1];
if (!payload) {
return true;
}
try {
payload = JSON.parse(atob(payload));
} catch (err) {
console.error('Invalid token:', err, token); // eslint-disable-line
return false;
}
if (!payload.exp || !isNumber(payload.exp)) {
return true;
}
const jsTimestamp = (payload.exp * 1000) - 30000; // Report as expire before (real expire time - 30s)
const expired = Date.now() >= jsTimestamp;
if (expired) {
storage.clear();
}
return expired;
}
handleLogout() {
storage.clear();
location.reload();
}
renderUserActionButton() {
if (!this.isTokenExpire) { // TODO: Check jwt token expire
return (
<div className={ classes.welcome }>
Hi, {storage.getItem('username')}
&nbsp;
<Button type="danger" onClick={this.handleLogout}>Logout</Button>
</div>
);
} else {
return <Button type="danger" style={ {marginLeft: 'auto'} } onClick={ this.toggleLoginModal }>Login</Button>;
}
}
render() {
return (
<header className={ classes.header }>
<div className={ classes.headerWrap }>
<Link to="/">
<img src={ this.state.logo } className={ classes.logo } />
</Link>
<figure>
npm set registry { location.origin }
<br/>
npm adduser --registry { location.origin }
</figure>
{this.renderUserActionButton()}
</div>
<Dialog
title="Login"
size="tiny"
visible={ this.state.showLogin }
onCancel={ () => this.toggleLoginModal() }
>
<Dialog.Body>
{ this.state.loginError &&
<Alert
title={this.state.loginError.title} type={this.state.loginError.type}
description={this.state.loginError.description} showIcon={true} closable={false}>
</Alert>
}
<br/>
<Input placeholder="Username" onChange={this.handleInput.bind(this, 'username')} />
<br/><br/>
<Input type="password" placeholder="Type your password" onChange={this.handleInput.bind(this, 'password')} />
</Dialog.Body>
<Dialog.Footer className="dialog-footer">
<Button onClick={ () => this.toggleLoginModal() }>
Cancel
</Button>
<Button type="primary" onClick={ this.handleSubmit }>
Login
</Button>
</Dialog.Footer>
</Dialog>
</header>
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -0,0 +1,26 @@
.help {
.noPkg {
display: flex;
flex-direction: column;
align-items: center;
padding: 30px 0;
font-size: 20px;
color: #9f9f9f;
.noPkgTitle {
margin: 1em 0;
padding: 0;
font-size: 24px;
}
p {
line-height: 1.5;
margin: 0 auto;
font-size: 14px;
}
code {
font-style: italic;
}
}
}

View File

@@ -0,0 +1,40 @@
import React from 'react';
import SyntaxHighlighter, {registerLanguage} from 'react-syntax-highlighter/dist/light';
import sunburst from 'react-syntax-highlighter/src/styles/sunburst';
import js from 'react-syntax-highlighter/dist/languages/javascript';
import classes from './help.scss';
registerLanguage('javascript', js);
const Help = () => {
return (
<div className={classes.help}>
<li className={classes.noPkg}>
<h1 className={classes.noPkgTitle}>
No Package Published Yet
</h1>
<p>
<div>
To publish your first package just:
</div>
<br/>
<strong>
1. Login
</strong>
<SyntaxHighlighter language='javascript' style={sunburst}>
{`npm adduser --registry ${location.origin}`}
</SyntaxHighlighter>
<strong>2. Publish</strong>
<SyntaxHighlighter language='javascript' style={sunburst}>
{`npm publish --registry ${location.origin}`}
</SyntaxHighlighter>
<strong>3. Refresh this page!</strong>
</p>
</li>
</div>
);
};
export default Help;

View File

@@ -0,0 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import classes from './noItems.scss';
const NoItems = (props) => {
return (
<div className={classes.noItems}>
<h2>{props.text}</h2>
</div>
);
};
NoItems.propTypes = {
text: PropTypes.string.isRequired
};
export default NoItems;

View File

@@ -0,0 +1,3 @@
.noItems {
margin: 5em 0;
}

View File

@@ -0,0 +1,14 @@
@import '../../styles/variable';
.notFound {
width: 100%;
font-size: 18px;
line-height: 30px;
border: none;
border-bottom: 1px solid lightgrey;
outline: none;
&:focus {
border-bottom: 1px solid grey;
}
}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import classes from './404.scss';
const NotFound = (props) => {
return (
<div className={classes.notFound}>
<h1>Error 404 - {props.pkg}</h1>
<hr/>
<p>
Oops, The package you are trying to access does not exist.
</p>
</div>
);
};
NotFound.propTypes = {
pkg: PropTypes.string.isRequired
};
export default NotFound;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Tag} from 'element-react';
import {Link} from 'react-router-dom';
import isNil from 'lodash/isNil';
import classes from './package.scss';
export default class Package extends React.Component {
static propTypes = {
package: PropTypes.object
}
render() {
const {
package: pkg
} = this.props;
return (
<Link to={`detail/${pkg.name}`} className={classes.package}>
<h1>{pkg.name}<Tag type="gray">v{pkg.version}</Tag></h1>
{this.renderAuthor(pkg)}
<p>{pkg.description}</p>
</Link>
);
}
renderAuthor(pkg) {
if (isNil(pkg.author) || isNil(pkg.author.name)) {
return;
}
return <span role="author" className={classes.author}>By: {pkg.author.name}</span>;
}
}

View File

@@ -0,0 +1,65 @@
@import '../../styles/variable';
.package {
display: block;
position: relative;
margin: 0;
padding: 10px 0;
cursor: pointer;
text-decoration: none;
h1 {
font-size: 18px;
color: $primary-color;
:global {
.el-tag {
margin-left: 5px;
}
}
}
// Author
.author {
position: absolute;
top: 10px;
right: 0;
color: lightgrey;
font-size: inherit;
word-wrap: break-word;
width: 100px;
text-align: right;
}
p {
margin-bottom: 0;
font-size: 14px;
color: darkgray;
}
&:hover {
&::before {
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(255, 255, 255, 0.7);
content: '';
}
&::after {
display: block;
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 0;
left: 0;
content: 'Click to view detail';
text-align: center;
color: $primary-color;
font-size: 18px;
}
}
}

View File

@@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import isNil from 'lodash/isNil';
import Readme from '../Readme';
import classes from './packageDetail.scss';
const PackageDetail = (props) => {
const displayState = (readMe) => {
if (isNil(readMe)) {
return;
}
return <Readme readMe={readMe}/>;
};
return (
<div className={classes.pkgDetail}>
<h1 className={ classes.title }>{ props.package }</h1>
<hr/>
<div className={classes.readme}>
{displayState(props.readMe)}
</div>
</div>
);
};
PackageDetail.propTypes = {
readMe: PropTypes.string,
package: PropTypes.string.isRequired
};
export default PackageDetail;

View File

@@ -0,0 +1,12 @@
@import '../../styles/variable';
.pkgDetail {
.title {
font-size: 28px;
color: $text-black;
}
.readme {
margin-bottom: 5em;
}
}

View File

@@ -0,0 +1,65 @@
import React from 'react';
import PropTypes from 'prop-types';
import isEmpty from 'lodash/isEmpty';
import Package from '../Package';
import Help from '../Help';
import NoItems from '../NoItems';
import classes from './packageList.scss';
export default class PackageList extends React.Component {
static propTypes = {
packages: PropTypes.array,
help: PropTypes.bool
}
render() {
return (
<div>
<div className={classes.pkgContainer}>
{this.renderTitle()}
{this.isTherePackages() ? this.renderList(): this.renderOptions()}
</div>
</div>
);
}
renderTitle() {
if (this.isTherePackages() === false) {
return;
}
return <h1 className={ classes.listTitle }>Available Packages</h1>;
}
renderList() {
return this.props.packages.map((pkg, i)=> (
<li key={i}><Package package={pkg} /></li>
));
}
renderOptions() {
if (this.isTherePackages() === false && this.props.help) {
return this.renderHelp();
} else {
return this.renderNoItems();
}
}
renderNoItems() {
return <NoItems text={'No items were found with that query'}/>;
}
renderHelp() {
if (this.props.help === false) {
return;
}
return <Help/>;
}
isTherePackages() {
return isEmpty(this.props.packages) === false;
}
}

View File

@@ -0,0 +1,16 @@
.pkgContainer {
margin: 0;
padding: 0;
li {
border-bottom: 1px solid #e4e8f1;
list-style: none;
}
.listTitle {
font-weight: normal;
font-size: 24px;
margin-top: 30px;
margin-bottom: 0;
}
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
import 'github-markdown-css';
const Readme = (props) => {
return <div className="markdown-body" dangerouslySetInnerHTML={{__html: props.readMe}}/>;
};
Readme.propTypes = {
readMe: PropTypes.string.isRequired
};
export default Readme;

View File

@@ -0,0 +1,14 @@
@import '../../styles/variable';
.searchBox {
width: 100%;
font-size: 18px;
line-height: 30px;
border: none;
border-bottom: 1px solid lightgrey;
outline: none;
&:focus {
border-bottom: 1px solid grey;
}
}

View File

@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import classes from './search.scss';
const Search = (props) => {
return (
<input
type="text"
placeholder={props.placeHolder}
className={ classes.searchBox }
onChange={ props.handleSearchInput }
/>
);
};
Search.defaultProps = {
placeHolder: 'Type to search...'
};
Search.propTypes = {
handleSearchInput: PropTypes.func.isRequired,
placeHolder: PropTypes.string,
};
export default Search;

View File

@@ -0,0 +1,14 @@
@import '../../styles/variable';
.searchBox {
width: 100%;
font-size: 18px;
line-height: 30px;
border: none;
border-bottom: 1px solid lightgrey;
outline: none;
&:focus {
border-bottom: 1px solid grey;
}
}

26
src/webui/src/index.js Normal file
View File

@@ -0,0 +1,26 @@
import '../utils/__setPublicPath__';
import React from 'react';
import ReactDOM from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import App from './app';
let rootNode = document.getElementById('root');
let renderApp = (Component) => {
ReactDOM.render(
<AppContainer>
<Component/>
</AppContainer>,
rootNode
);
};
renderApp(App);
if (module.hot) {
module.hot.accept('./app', () => {
renderApp(App);
});
}

View File

@@ -0,0 +1,2 @@
@import '../../styles/variable';

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