Compare commits

...

343 Commits

Author SHA1 Message Date
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
226 changed files with 33715 additions and 1739 deletions

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Tab indentation (no size specified)
[*.js]
indent_style = tab
# 2 space indentation
[{.,}*.y{a,}ml]
indent_style = space
indent_size = 2
# Indentation override for Gruntfile
[Gruntfile.js]
indent_style = space
indent_size = 4

275
.eslint.yaml Normal file
View File

@@ -0,0 +1,275 @@
env:
node: true
#
# 0 - disable
# Rules that more harmful than useful, or just buggy.
#
# 1 - warning
# Rules that we didn't encounter yet. You can safely ignore them,
# but I'd like to know any interesting use-cases they forbid.
#
# 2 - error
# Rules that have proven to be useful, please follow them.
#
rules:
# didn't understand what it does, but it fails a good code
block-scoped-var: 0
# fails where newlines are used to format pretty big "if":
# if (
# name.charAt(0) === "." ||
# name.match(/[\/@\s\+%:]/) ||
# name !== encodeURIComponent(name) ||
# name.toLowerCase() === "node_modules"
# ) {
brace-style: 1
# snake_case is more readable, what's up with you guys?
camelcase: 0
# if some functions are complex, they are for a good reason,
# ain't worth it
complexity: [0, 10]
# never saw it, but self is preferred
consistent-this: [1, self]
# fails good code
curly: [0, multi]
# fails good code, where this notation is used for consistency:
# something['foo-bar'] = 123
# something['blahblah'] = 234
dot-notation: 0
# pointless in many cases (like indexOf() == -1), harmful in a few
# cases (when you do want to ignore types), fails good code
eqeqeq: 0
# if someone is changing prototype and makes properties enumerable,
# it's their own fault
guard-for-in: 0
# if some functions are complex, they are for a good reason,
# ain't worth it
max-depth: [0, 4]
max-nested-callbacks: [0, 2]
# should it really throw for every long URL?
max-len: [0, 80, 4]
# that's obvious by just looking at the code, you don't need lint for that
max-params: [0, 3]
# if some functions are complex, they are for a good reason,
# ain't worth it
max-statements: [0, 10]
# that one makes sense
new-cap: 2
# I'm writing javascript, not some weird reduced version of it
no-bitwise: 0
# not working around IE bugs, sorry
no-catch-shadow: 0
# see above, IE is useful for downloading other browsers only
no-comma-dangle: 0
# good for removing debugging code
no-console: 2
# good for removing debugging code
no-debugger: 2
# why would anyone need to check against that?
no-else-return: 0
# sometimes empty statement contains useful comment
no-empty: 0
# stupid rule
# "x == null" is "x === null || x === undefined"
no-eq-null: 0
# fails good code, when parens are used for grouping:
# (req && req.headers['via']) ? req.headers['via'] + ', ' : ''
# not everyone remembers priority tables, you know
no-extra-parens: 0
# fails defensive semicolons:
# ;['foo', 'bar'].forEach(function(x) {})
no-extra-semi: 0
# fails good code:
# var fs = require('fs'),
# , open = fs.open
no-mixed-requires: [0, false]
# new Array(12) is used to pre-allocate arrays
no-new-array: 0
# fails good code:
# fs.open('/file', 0666, function(){})
no-octal: 0
# fails good code:
# console.log('\033[31m' + str + '\033[39m')
# also fails \0 which is not octal escape
no-octal-escape: 0
# I'm writing javascript, not some weird reduced version of it
no-plusplus: 0
# fails good code:
# if (a) {
# var x = 'foo'
# } else {
# var x = bar
# }
no-redeclare: 0
# sometimes useful, often isn't
# probably worth enforcing
no-shadow: 2
no-sync: 2
# I'm writing javascript, not some weird reduced version of it
no-ternary: 0
# the single most important rule in the entire ruleset
no-undef: 2
# it is failing our own underscores
no-underscore-dangle: 0
# fails function hoisting
no-unreachable: 0
# fails npm-style code, it's good once you get used to it:
# if (typeof(options) === 'function') callback = options, options = {}
no-unused-expressions: 0
# fails (function(_err) {}) where named argument is used to show what
# nth function argument means
no-unused-vars: [0, local]
# fails function hoisting
no-use-before-define: 0
# fails foobar( (function(){}).bind(this) )
# parens are added for readability
no-wrap-func: 0
# fails good code:
# var x
# if (something) {
# var y
one-var: 0
quote-props: 0
# fails situation when different quotes are used to avoid escaping
quotes: [2, single, avoid-escape]
# http:#blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding
semi: [2, never]
# fails good code where spaces are used for grouping:
# (x+y * y+z)
space-infix-ops: 0
# typeof(something) should have braces to look like a function
# a matter of taste I suppose
space-unary-word-ops: 0
# strict mode is just harmful,
# can I have a check to enforce not using it?
strict: 0
sort-vars: 0
no-path-concat: 0
func-names: 0
# how can you set a return code without process.exit?
no-process-exit: 0
# both styles are useful
func-style: [0, declaration]
# fails while(1) {...}
no-constant-condition: 0
# fails good code:
# https://github.com/rlidwka/jju/blob/eb52ee72e5f21d48963798f9bda8ac8d68082148/lib/parse.js#L732
no-ex-assign: 0
wrap-iife: [2, inside]
# doesn't always make sense
consistent-return: 0
no-sequences: 0
# fails defensive semicolons
no-space-before-semi: 0
new-parens: 1
no-alert: 1
no-array-constructor: 1
no-caller: 1
no-cond-assign: 1
no-control-regex: 1
no-delete-var: 1
no-div-regex: 1
no-dupe-keys: 1
no-empty-class: 1
no-empty-label: 1
no-eval: 1
no-extend-native: 1
no-extra-boolean-cast: 1
no-extra-strict: 1
no-fallthrough: 1
no-floating-decimal: 1
no-func-assign: 1
no-global-strict: 1
no-implied-eval: 1
no-invalid-regexp: 1
no-iterator: 1
no-labels: 1
no-label-var: 1
no-lone-blocks: 1
no-loop-func: 1
no-multi-str: 1
no-native-reassign: 1
no-negated-in-lhs: 1
no-nested-ternary: 1
no-new: 1
no-new-func: 1
no-new-object: 1
no-new-wrappers: 1
no-obj-calls: 1
no-octal: 1
no-proto: 1
no-regex-spaces: 1
no-return-assign: 1
no-script-url: 1
no-self-compare: 1
no-shadow: 1
no-shadow-restricted-names: 1
no-spaced-func: 1
no-sparse-arrays: 1
no-sync: 1
no-undef: 1
no-undef-init: 1
no-unreachable: 1
no-with: 1
no-yoda: 1
radix: 1
space-return-throw-case: 1
use-isnan: 1
valid-jsdoc: 1
wrap-regex: 1

24
.gitignore vendored
View File

@@ -1,9 +1,23 @@
node_modules
package.json
/package.json
npm-debug.log
sinopia-*.tgz
.DS_Store
###
bin/storage*
bin/*.yaml
test/test-storage*
bin/**
!bin/sinopia
test-storage*
# ignoring everything except bundled deps
/node_modules/*
!/node_modules/mkdirp
!/node_modules/unopinionate
!/node_modules/onclick
!/node_modules/onscroll
!/node_modules/transition-complete
!/node_modules/helpers.less
!/node_modules/tar.gz
!/node_modules/sinopia-htpasswd
!/node_modules/crypt3
!/node_modules/http-errors

11
.npmignore Normal file
View File

@@ -0,0 +1,11 @@
node_modules
package.json
npm-debug.log
sinopia-*.tgz
###
bin/**
!bin/sinopia
test-storage*
/.eslint*

9
.travis.yml Normal file
View File

@@ -0,0 +1,9 @@
language: node_js
node_js:
- '0.10'
- '0.11'
matrix:
allow_failures:
- node_js: "0.11"
fast_finish: true
script: npm install . && npm run test-travis

View File

@@ -1,73 +0,0 @@
24 Nov 2013, version 0.5.3
- added proxy support for requests to uplinks (issue #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

39
Gruntfile.js Normal file
View File

@@ -0,0 +1,39 @@
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readYAML('package.yaml'),
browserify: {
dist: {
files: {
'lib/static/main.js': ['lib/GUI/js/main.js']
},
options: {
debug: true,
transform: ['browserify-handlebars']
}
}
},
less: {
dist: {
files: {
'lib/static/main.css': ['lib/GUI/css/main.less']
},
options: {
sourceMap: true
}
}
},
watch: {
files: [ "lib/GUI/**/*"],
tasks: [ 'default' ]
}
});
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.registerTask('default', [
'browserify',
'less'
]);
};

239
History.md Normal file
View File

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

View File

@@ -1,7 +1,13 @@
Sinopia is a private/caching npm repository server.
`sinopia` - a private/caching npm repository server
[![npm version badge](https://img.shields.io/npm/v/sinopia.svg)](https://www.npmjs.org/package/sinopia)
[![travis badge](http://img.shields.io/travis/rlidwka/sinopia.svg)](https://travis-ci.org/rlidwka/sinopia)
[![downloads badge](http://img.shields.io/npm/dm/sinopia.svg)](https://www.npmjs.org/package/sinopia)
It allows you to have a local npm registry with zero configuration. You don't have to install and replicate an entire CouchDB database. Sinopia keeps its own small database and, if a package doesn't exist there, it asks npmjs.org for it keeping only those packages you use.
<p align="center"><img src="https://f.cloud.github.com/assets/999113/1795553/680177b2-6a1d-11e3-82e1-02193aa4e32e.png"></p>
## Use cases
1. Use private packages.
@@ -19,7 +25,7 @@ It allows you to have a local npm registry with zero configuration. You don't ha
3. 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 accepted pull request yet), you can publish your version locally under the same name.
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 [override public packages](#override-public-packages) section for details.
@@ -42,23 +48,31 @@ $ npm set always-auth true
$ npm set ca null
```
Now you can navigate to [http://localhost:4873/](http://localhost:4873/) where your local packages will be listed and can be searched.
### Docker
A Sinopia docker image [is available](https://registry.hub.docker.com/u/keyvanfatehi/sinopia/)
### Chef
A Sinopia Chef cookbook [is available at Opscode community](http://community.opscode.com/cookbooks/sinopia) source: https://github.com/BarthV/sinopia-cookbook
### Puppet
A Sinopia puppet module [is available at puppet forge](http://forge.puppetlabs.com/saheba/sinopia) source: https://github.com/saheba/puppet-sinopia
## Configuration
When you start a server, it auto-creates a config file that adds one user (password is printed to stdout only once).
When you start a server, it auto-creates a config file.
## Adding a new user
There is no utility to add a new user but you can at least use node on the command-line to generate a password. You will need to edit the config and add the user manually.
Start node and enter the following code replacing 'newpass' with the password you want to get the hash for.
```bash
$ node
> crypto.createHash('sha1').update('newpass').digest('hex')
'6c55803d6f1d7a177a0db3eb4b343b0d50f9c111'
> [CTRL-D]
npm adduser --registry http://localhost:4873/
```
This will prompt you for user credentials which will be saved on the Sinopia server.
## Using private packages
@@ -99,18 +113,18 @@ Basic features:
Advanced package control:
- Unpublishing packages (npm unpublish) - not yet supported, should be soon
- Unpublishing packages (npm unpublish) - supported
- Tagging (npm tag) - not yet supported, should be soon
- Deprecation (npm deprecate) - not supported
User management:
- Registering new users (npm adduser {newuser}) - not supported, sinopia uses its own acl management system
- Registering new users (npm adduser {newuser}) - supported
- Transferring ownership (npm owner add {user} {pkg}) - not supported, sinopia uses its own acl management system
Misc stuff:
- Searching (npm search) - not supported
- Searching (npm search) - supported in the browser client but not command line
- Starring (npm star, npm unstar) - not supported, doesn't make sense in private registry
## Storage

9
index.js Normal file
View File

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

6203
lib/GUI/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

56
lib/GUI/css/fontello.less Normal file
View File

@@ -0,0 +1,56 @@
@font-face {
font-family: 'fontello';
src: url('../static/fontello.eot?10872183');
src: url('../static/fontello.eot?10872183#iefix') format('embedded-opentype'),
url('../static/fontello.woff?10872183') format('woff'),
url('../static/fontello.ttf?10872183') format('truetype'),
url('../static/fontello.svg?10872183#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?10872183#fontello') format('svg');
}
}
*/
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-search:before { content: '\e801'; } /* '' */
.icon-cancel:before { content: '\e803'; } /* '' */
.icon-right-open:before { content: '\e802'; } /* '' */
.icon-angle-right:before { content: '\e800'; } /* '' */

View File

@@ -0,0 +1,153 @@
/*
Original style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org>
*/
.hljs {
display: block; padding: 0.5em;
background: #F0F0F0;
}
.hljs,
.hljs-subst,
.hljs-tag .hljs-title,
.lisp .hljs-title,
.clojure .hljs-built_in,
.nginx .hljs-title {
color: black;
}
.hljs-string,
.hljs-title,
.hljs-constant,
.hljs-parent,
.hljs-tag .hljs-value,
.hljs-rules .hljs-value,
.hljs-rules .hljs-value .hljs-number,
.hljs-preprocessor,
.hljs-pragma,
.haml .hljs-symbol,
.ruby .hljs-symbol,
.ruby .hljs-symbol .hljs-string,
.hljs-aggregate,
.hljs-template_tag,
.django .hljs-variable,
.smalltalk .hljs-class,
.hljs-addition,
.hljs-flow,
.hljs-stream,
.bash .hljs-variable,
.apache .hljs-tag,
.apache .hljs-cbracket,
.tex .hljs-command,
.tex .hljs-special,
.erlang_repl .hljs-function_or_atom,
.asciidoc .hljs-header,
.markdown .hljs-header,
.coffeescript .hljs-attribute {
color: #800;
}
.smartquote,
.hljs-comment,
.hljs-annotation,
.hljs-template_comment,
.diff .hljs-header,
.hljs-chunk,
.asciidoc .hljs-blockquote,
.markdown .hljs-blockquote {
color: #888;
}
.hljs-number,
.hljs-date,
.hljs-regexp,
.hljs-literal,
.hljs-hexcolor,
.smalltalk .hljs-symbol,
.smalltalk .hljs-char,
.go .hljs-constant,
.hljs-change,
.lasso .hljs-variable,
.makefile .hljs-variable,
.asciidoc .hljs-bullet,
.markdown .hljs-bullet,
.asciidoc .hljs-link_url,
.markdown .hljs-link_url {
color: #080;
}
.hljs-label,
.hljs-javadoc,
.ruby .hljs-string,
.hljs-decorator,
.hljs-filter .hljs-argument,
.hljs-localvars,
.hljs-array,
.hljs-attr_selector,
.hljs-important,
.hljs-pseudo,
.hljs-pi,
.haml .hljs-bullet,
.hljs-doctype,
.hljs-deletion,
.hljs-envvar,
.hljs-shebang,
.apache .hljs-sqbracket,
.nginx .hljs-built_in,
.tex .hljs-formula,
.erlang_repl .hljs-reserved,
.hljs-prompt,
.asciidoc .hljs-link_label,
.markdown .hljs-link_label,
.vhdl .hljs-attribute,
.clojure .hljs-attribute,
.asciidoc .hljs-attribute,
.lasso .hljs-attribute,
.coffeescript .hljs-property,
.hljs-phony {
color: #88F
}
.hljs-keyword,
.hljs-id,
.hljs-title,
.hljs-built_in,
.hljs-aggregate,
.css .hljs-tag,
.hljs-javadoctag,
.hljs-phpdoc,
.hljs-yardoctag,
.smalltalk .hljs-class,
.hljs-winutils,
.bash .hljs-variable,
.apache .hljs-tag,
.go .hljs-typename,
.tex .hljs-command,
.asciidoc .hljs-strong,
.markdown .hljs-strong,
.hljs-request,
.hljs-status {
font-weight: bold;
}
.asciidoc .hljs-emphasis,
.markdown .hljs-emphasis {
font-style: italic;
}
.nginx .hljs-built_in {
font-weight: normal;
}
.coffeescript .javascript,
.javascript .xml,
.lasso .markup,
.tex .hljs-formula,
.xml .javascript,
.xml .vbscript,
.xml .css,
.xml .hljs-cdata {
opacity: 0.5;
}

7
lib/GUI/css/main.less Normal file
View File

@@ -0,0 +1,7 @@
@import "../../../node_modules/helpers.less/helpers.less";
@import (less) "bootstrap.css";
@import "markdown.less";
@import "highlight.js.less";
@import "fontello.less";
@import "styles.less";
@import "responsive.less";

700
lib/GUI/css/markdown.less Normal file
View File

@@ -0,0 +1,700 @@
/*** Sourced from this Gist: https://github.com/sindresorhus/github-markdown-css ***/
@font-face {
font-family: octicons-anchor;
src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAYcAA0AAAAACjQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABMAAAABwAAAAca8vGTk9TLzIAAAFMAAAARAAAAFZG1VHVY21hcAAAAZAAAAA+AAABQgAP9AdjdnQgAAAB0AAAAAQAAAAEACICiGdhc3AAAAHUAAAACAAAAAj//wADZ2x5ZgAAAdwAAADRAAABEKyikaNoZWFkAAACsAAAAC0AAAA2AtXoA2hoZWEAAALgAAAAHAAAACQHngNFaG10eAAAAvwAAAAQAAAAEAwAACJsb2NhAAADDAAAAAoAAAAKALIAVG1heHAAAAMYAAAAHwAAACABEAB2bmFtZQAAAzgAAALBAAAFu3I9x/Nwb3N0AAAF/AAAAB0AAAAvaoFvbwAAAAEAAAAAzBdyYwAAAADP2IQvAAAAAM/bz7t4nGNgZGFgnMDAysDB1Ml0hoGBoR9CM75mMGLkYGBgYmBlZsAKAtJcUxgcPsR8iGF2+O/AEMPsznAYKMwIkgMA5REMOXicY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChED+h5j//yEk/3KoSgZGNgYYk4GRCUgwMaACRoZhDwCs7QgGAAAAIgKIAAAAAf//AAJ4nHWMMQrCQBBF/0zWrCCIKUQsTDCL2EXMohYGSSmorScInsRGL2DOYJe0Ntp7BK+gJ1BxF1stZvjz/v8DRghQzEc4kIgKwiAppcA9LtzKLSkdNhKFY3HF4lK69ExKslx7Xa+vPRVS43G98vG1DnkDMIBUgFN0MDXflU8tbaZOUkXUH0+U27RoRpOIyCKjbMCVejwypzJJG4jIwb43rfl6wbwanocrJm9XFYfskuVC5K/TPyczNU7b84CXcbxks1Un6H6tLH9vf2LRnn8Ax7A5WQAAAHicY2BkYGAA4teL1+yI57f5ysDNwgAC529f0kOmWRiYVgEpDgYmEA8AUzEKsQAAAHicY2BkYGB2+O/AEMPCAAJAkpEBFbAAADgKAe0EAAAiAAAAAAQAAAAEAAAAAAAAKgAqACoAiAAAeJxjYGRgYGBhsGFgYgABEMkFhAwM/xn0QAIAD6YBhwB4nI1Ty07cMBS9QwKlQapQW3VXySvEqDCZGbGaHULiIQ1FKgjWMxknMfLEke2A+IJu+wntrt/QbVf9gG75jK577Lg8K1qQPCfnnnt8fX1NRC/pmjrk/zprC+8D7tBy9DHgBXoWfQ44Av8t4Bj4Z8CLtBL9CniJluPXASf0Lm4CXqFX8Q84dOLnMB17N4c7tBo1AS/Qi+hTwBH4rwHHwN8DXqQ30XXAS7QaLwSc0Gn8NuAVWou/gFmnjLrEaEh9GmDdDGgL3B4JsrRPDU2hTOiMSuJUIdKQQayiAth69r6akSSFqIJuA19TrzCIaY8sIoxyrNIrL//pw7A2iMygkX5vDj+G+kuoLdX4GlGK/8Lnlz6/h9MpmoO9rafrz7ILXEHHaAx95s9lsI7AHNMBWEZHULnfAXwG9/ZqdzLI08iuwRloXE8kfhXYAvE23+23DU3t626rbs8/8adv+9DWknsHp3E17oCf+Z48rvEQNZ78paYM38qfk3v/u3l3u3GXN2Dmvmvpf1Srwk3pB/VSsp512bA/GG5i2WJ7wu430yQ5K3nFGiOqgtmSB5pJVSizwaacmUZzZhXLlZTq8qGGFY2YcSkqbth6aW1tRmlaCFs2016m5qn36SbJrqosG4uMV4aP2PHBmB3tjtmgN2izkGQyLWprekbIntJFing32a5rKWCN/SdSoga45EJykyQ7asZvHQ8PTm6cslIpwyeyjbVltNikc2HTR7YKh9LBl9DADC0U/jLcBZDKrMhUBfQBvXRzLtFtjU9eNHKin0x5InTqb8lNpfKv1s1xHzTXRqgKzek/mb7nB8RZTCDhGEX3kK/8Q75AmUM/eLkfA+0Hi908Kx4eNsMgudg5GLdRD7a84npi+YxNr5i5KIbW5izXas7cHXIMAau1OueZhfj+cOcP3P8MNIWLyYOBuxL6DRylJ4cAAAB4nGNgYoAALjDJyIAOWMCiTIxMLDmZedkABtIBygAAAA==) format('woff');
}
.readme {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
color: #333;
overflow: hidden;
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
font-size: 16px;
line-height: 1.6;
word-wrap: break-word;
a {
background: transparent;
&:active,
&:hover {
outline: 0;
}
}
strong {
font-weight: bold;
}
h1 {
text-align: left;
font-size: 2em;
margin: 0.67em 0;
}
img {
border: 0;
}
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
pre {
overflow: auto;
}
code,
kbd,
pre {
font-family: monospace, monospace;
font-size: 1em;
}
input {
color: inherit;
font: inherit;
margin: 0;
}
html input[disabled] {
cursor: default;
}
input {
line-height: normal;
}
input[type="checkbox"] {
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
* {
-moz-box-sizing: border-box;
box-sizing: border-box;
}
input {
font: 13px/1.4 Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
}
a {
color: #4183c4;
text-decoration: none;
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
hr {
height: 0;
margin: 15px 0;
overflow: hidden;
background: transparent;
border: 0;
border-bottom: 1px solid #ddd;
&:before {
display: table;
content: "";
}
&:after {
display: table;
clear: both;
content: "";
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 15px;
margin-bottom: 15px;
line-height: 1.1;
}
h1 {
font-size: 30px;
}
h2 {
font-size: 21px;
}
h3 {
font-size: 16px;
}
h4 {
font-size: 14px;
}
h5 {
font-size: 12px;
}
h6 {
font-size: 11px;
}
blockquote {
margin: 0;
}
ul,
ol {
padding: 0;
margin-top: 0;
margin-bottom: 0;
ol {
list-style-type: lower-roman;
ol {
list-style-type: lower-alpha;
}
}
}
ul {
ul {
ol {
list-style-type: lower-alpha;
}
}
}
ol {
ul {
ol {
list-style-type: lower-alpha;
}
}
}
dd {
margin-left: 0;
}
code {
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
pre {
margin-top: 0;
margin-bottom: 0;
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
kbd {
background-color: #e7e7e7;
background-image: -webkit-linear-gradient(#fefefe, #e7e7e7);
background-image: linear-gradient(#fefefe, #e7e7e7);
background-repeat: repeat-x;
border-radius: 2px;
border: 1px solid #cfcfcf;
color: #000;
padding: 3px 5px;
line-height: 10px;
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
display: inline-block;
}
>*:first-child {
margin-top: 0 !important;
}
>*:last-child {
margin-bottom: 0 !important;
}
.anchor {
position: absolute;
top: 0;
bottom: 0;
left: 0;
display: block;
padding-right: 6px;
padding-left: 30px;
margin-left: -30px;
}
.anchor:focus {
outline: none;
}
h1,
h2,
h3,
h4,
h5,
h6 {
position: relative;
margin-top: 1em;
margin-bottom: 16px;
font-weight: bold;
line-height: 1.4;
.octicon-link {
display: none;
color: #000;
vertical-align: middle;
}
&:hover {
.anchor {
height: 1em;
padding-left: 8px;
margin-left: -30px;
line-height: 1;
text-decoration: none;
.octicon-link {
display: inline-block;
}
}
}
}
h1 {
padding-bottom: 0.3em;
font-size: 2.25em;
line-height: 1.2;
border-bottom: 1px solid #eee;
}
h2 {
padding-bottom: 0.3em;
font-size: 1.75em;
line-height: 1.225;
border-bottom: 1px solid #eee;
}
h3 {
font-size: 1.5em;
line-height: 1.43;
}
h4 {
font-size: 1.25em;
}
h5 {
font-size: 1em;
}
h6 {
font-size: 1em;
color: #777;
}
p,
blockquote,
ul,
ol,
dl,
table,
pre {
margin-top: 0;
margin-bottom: 16px;
}
hr {
height: 4px;
padding: 0;
margin: 16px 0;
background-color: #e7e7e7;
border: 0 none;
}
ul,
ol {
padding-left: 2em;
ul, ol {
margin-top: 0;
margin-bottom: 0;
}
}
li {
>p {
margin-top: 16px;
}
}
dl {
padding: 0;
dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: bold;
}
dd {
padding: 0 16px;
margin-bottom: 16px;
}
}
blockquote {
padding: 0 15px;
color: #777;
border-left: 4px solid #ddd;
>:first-child {
margin-top: 0;
}
>:last-child {
margin-bottom: 0;
}
}
table {
display: block;
width: 100%;
overflow: auto;
word-break: normal;
word-break: keep-all;
th {
font-weight: bold;
}
th,
td {
padding: 6px 13px;
border: 1px solid #ddd;
}
tr {
background-color: #fff;
border-top: 1px solid #ccc;
&:nth-child(2n) {
background-color: #f8f8f8;
}
}
}
img {
max-width: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
code {
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
font-size: 85%;
background-color: rgba(0,0,0,0.04);
border-radius: 3px;
&:before,
&:after {
letter-spacing: -0.2em;
content: "\00a0";
}
}
pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
word-wrap: normal;
>code {
padding: 0;
margin: 0;
font-size: 100%;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
display: inline;
max-width: initial;
padding: 0;
margin: 0;
overflow: initial;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
&:before,
&:after {
content: normal;
}
}
}
.highlight {
margin-bottom: 16px;
background: #fff;
pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
margin-bottom: 0;
word-break: normal;
}
}
.highlight{
.mf,
.mh,
.mi,
.mo,
.il,
.m {
color: #945277;
}
.s,
.sb,
.sc,
.sd,
.s2,
.se,
.sh,
.si,
.sx,
.s1 {
color: #df5000;
}
.kc,
.kd,
.kn,
.kp,
.kr,
.kt,
.k,
.o {
font-weight: bold;
}
.kt {
color: #458;
}
.c,
.cm,
.c1 {
color: #998;
font-style: italic;
}
.cp,
.cs {
color: #999;
font-weight: bold;
}
.cs {
font-style: italic;
}
.n {
color: #333;
}
.na,
.nv,
.vc,
.vg,
.vi {
color: #008080;
}
.nb {
color: #0086B3;
}
.nc {
color: #458;
font-weight: bold;
}
.no {
color: #094e99;
}
.ni {
color: #800080;
}
.ne {
color: #990000;
font-weight: bold;
}
.nf {
color: #945277;
font-weight: bold;
}
.nn {
color: #555;
}
.nt {
color: #000080;
}
.err {
color: #a61717;
background-color: #e3d2d2;
}
.gd {
color: #000;
background-color: #fdd;
.x {
color: #000;
background-color: #faa;
}
}
.ge {
font-style: italic;
}
.gr {
color: #aa0000;
}
.gh {
color: #999;
}
.gi {
color: #000;
background-color: #dfd;
.x {
color: #000;
background-color: #afa;
}
}
.go {
color: #888;
}
.gp {
color: #555;
}
.gs {
font-weight: bold;
}
.gu {
color: #800080;
font-weight: bold;
}
.gt {
color: #aa0000;
}
.ow {
font-weight: bold;
}
.w {
color: #bbb;
}
.sr {
color: #017936;
}
.ss {
color: #8b467f;
}
.bp {
color: #999;
}
.gc {
color: #999;
background-color: #EAF2F5;
}
}
.octicon {
font: normal normal 16px octicons-anchor;
line-height: 1;
display: inline-block;
text-decoration: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.octicon-link {
&:before {
content: '\f05c';
}
}
.task-list-item {
list-style-type: none;
+.task-list-item {
margin-top: 3px;
}
input {
float: left;
margin: 0.3em 0 0.25em -1.6em;
vertical-align: middle;
}
}
}

View File

@@ -0,0 +1,33 @@
@media (max-width: 992px) {
.body {
.main-header {
.packages-header {
border-bottom: none;
}
}
}
}
@media (max-width: 768px) {
.body {
.content {
padding-top: @mainHeaderHeight + @packagesHeaderHeight + @smRegistryInfoHeight + 10;
.entry {
.title {
margin-bottom: 0;
}
.author {
float: none !important;
clear: both;
padding: 0 0 5px 18px;
}
}
}
.no-results {
margin: 10px 0 0;
}
}
}

189
lib/GUI/css/styles.less Normal file
View File

@@ -0,0 +1,189 @@
//vars
@npmRed: #cc3d33;
@white: #fff;
@entryBg: #F3F3F3;
@mainHeaderHeight: 50px;
@packagesHeaderHeight: 60px;
@headerBorderWidth: 2px;
@smRegistryInfoHeight: 25px;
/*** Main Styles ***/
.body {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left:0;
margin: 0;
padding: 0;
.main-header {
position: fixed;
top: 0;
left: 0;
right: 0;
background: @white;
z-index: 1;
.navbar {
margin-bottom: 0;
}
.npm-logo {
width: 79px;
height: @mainHeaderHeight;
background-image: url( -/logo-sm );
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE8AAAAoCAYAAAC/xadkAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMDA4QUE4NzQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMDA4QUE4ODQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMwMDhBQTg1NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMwMDhBQTg2NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ypkYZgAACelJREFUeNrsWmtsHFcZPXd2Zh9+r+31+5W4tSPSoKZUKgpIgdAWqorSULVFqJREKpVAAqRKhfIPKfCDtP2TKilpRRACUpBC+UlbQRG0RUmo0gBtWoyT2HHstR0/Yq/X9j5mh/PdmfX7te42tZyMdO2dmTt3vnvu+Z53lHOm9VcwQntRuvMq0qMKmTSgDNw4Fh4OG3Gxyh3E/l2GZOxVE0UtDShqLUbCLkb9o0D6KmAnbgA4D7cMcbMIXAUw+BoQ2QXEu5pMAjqB2q8Cl34BXDwC1D3IzkkgNUEAfTeAE+B8IWJhAl0vAKECoOa7wIVDEwbsaQUMA03fAMJbgJ5fAxlS1Crjg/Z1DhznbxbzB1l3+SWguBpo2cdzamd6wjBcdqXYeoHa+8m8T/PncSBJ5gUiHCB9nQLHefvLCRK1sPe3QNUtQAO1ElE2MWumcJHEg881iE43ULGbQIdJ0RNA5V1EuxWY7r++VFiAC5JlcRLqyp+olV8CSm938RGn4foDwyR2xowzcQy3Q8l24KYi6vVviHyc6ryTAPa5OLt/Nq9HddiCddTM94GxvwFbHwIKb6YpE+CEa9n5K2HewoOXMj1AAQfY9h2g8xi1mjpevYdATnoob+JQxEeHcOUNauYHQNtjVN0KFw8sgEppFyJQLmQTDWRmgP3LXQDPU+ejpK+/lNeTm5R9jlZEpEgQRbJse5z//ZxvdDFw7mHM5eHCe2wxtnqglfHfMEOZcJt7eVOyjzA4hGP0TaD8EZ7TWeKC5w+WfsBc1o7xVvpqCom+Dhi+KWCSIMbb+QLxzJnNCZ6iyo50w4l2kHRjCDYTHstw7eDi/ssxj51VGMOvnsK5b55Awc0RbwXObn6HQVW1Y2/DoMbu/PN+BBoreTm21LxJL0MtO5AiroKvMg0XY8fa/GGKIZ7AdCMzzatltEwJMkl4DsNZDJ7PoPOxYIQs/vZhuqsL9tRkbuwj5YNNTfCVlOhUJzM1hamLXbnPyW8huGWr+9uykIj2ITU8nD9NoJxmaSlVtYmWyeY71GpDk3mJtLGMN5kTM6ZJ5Rgan3gCwYZGYrB2m6eYxPQfP474++f072BjI5p/8EPK6qzZ78hzqZER9L34ol7U6YEBRO67D6W7djGDyo/9lXfE33sX/S8d50IVr7IoyrN5RSZ/rZLD2jbsiQlUP/wwAnX1OQs2+vpfMPbWmxTKD5MMrHrwodyJkUrj8pHDWuzk4ABKP/NZVD3wQF41dowL2/PcIQRqi9eGNxy1Ou/F9lFtk/396xIqk0jQa1nalgiL13Mk+nq9kJQpkeVnJHA17+ZOzIDi2Gu0BBKq5KdwlxocRCaVdGPNUAhWRcU6ihhpjnNF2x+HbDc5hq+gIOdx7PExpMfHKYihF91fVT0TVNjxONI0AbKQ8h4rEtELm7tXltGh8mJxz+3fh7fvuAOnb7sVnU8+uT52RaN454t348wX9uDUJ3dg+JVX1jVOz+HDOLnjFpzZvRv/2bsX9uTkzL0rf3wZJ7d/Au/cfRfO7Pk8ndfFnB2LDttsnZ7lx12Jatr0pPI/k0yuz2hnHGSmp8nglDthe30qLqYhMzkFm2NJmxvkZsT5UU5p2oQ4OWZLKpvKubltXmpNvqIi7eoFPF9haJ0xlqEdioCXIXi5q5M3TCCgZZGxfMXF85RLO62yMn1PwBMvmzt6oraGtnf5CZQcZ07L0zj4iMbJ0zuMDZdtCROyplht5FRQ6eh4w5SIxcMm6TREbRP0umK3Nmbdb149b4OsJW1cQVubdjhmRaW2WxuYefnztvk4AnV12Pn6X+es8Mau4Bj5CpI/kvragixnxfNVMqS8jLPYOq++vI4X8ftr61bIVdzUy6G9MoLzQxUjGOT15IciUqC+fsZDarWWKs1yYZOEIZRDZJay2twsxSoLu3GoNydhe/bwM9tw1hSjuhMx4RXWV07iTJjFJeh9/oiehBQJsgNI+qMsE+mxMZjhsBZoqqsLvUd/rstPkqolenrgr65m2hRb8T2Sr/a+cJTBcQYq4PdsswODk0+PjupSlDAl2FCvs4/U8NA8Wdx4sxDjb/2DC12r3y0LeumZpxn7BaH8FibOnkWQgIl9NcidS88+C7OyQsd7kx0d8NdUr4GNTtbmqZULV1IUMH0wykrR98tjFDYOwzTnsVLqdKGtrTr4FCESPd3ofOop9vNpMIPNzbAqIwR4fOX8mAB1HTigmSHsUToY5d+0rQPfUOtWLbevphZjb/wdg384MU+WbHYhwAkI8pycdx88qOWQUpq/shKBhgaXYYbCZRIi42UhIn9wSzMXZWIVAGeZt+wGUCbBRH1oElZVmGy4CrOonG3phN+OJXTLGvpATZM3G7LICripltQBVzCxFoUvaG/XoYrJxbLH4pxYcuaZ1FDMW3epcgdm37EoV3QY8swWSq2K2nkBciI6JJGGC0BpBCidZVQyOoJ03A+fOe3VLdVypFKzm96LeQCjKIRASx38RYNAMAJd2HcyOdt8J5FhTikfzJCJUk2WVRcm2PbMhBSZJQVPPb7YowQBLAkC4dA13KwTKNLwDfXAKG+nxgX1+fIlKdub4jwBZVIZVH55B8q3n2KHTwF1j7J7LDfwZD/AF8EHj/+MNuo0gi31SA0M6IqGZuHc9Mhwd6mUPwAf29T5KNqf/x4iX7mTxj96bcIWzXA6m77fQzlnYNSUecDEMb/a7ngZhiPfWBhz0CPSRg3/jUJ1H4WvSr5X2SfFHLaCHCchfUvd2hnBEhsorEsP9Hs2Zc5Y+hs4n1ZXfZ2q5yukkTfl+YlrFPM5btv6feL1O+B/zwHNj9DV1y7e/NYZhiXbZxmvWCDANQOxd4Gel4EIQSu+nzc6PPoa61CDKUxdGMDEQI/+YkvbkRWMcbJ/Qv+Pj/QgOTCqnweuFXhZAOnYCr8GlNO+d9L7N95DQt7GW5e8OSlPbXUZ3puQ2gIMvwZET/OBH5M0O9jnPS/9XW8KPIb6b9+J8J52WOFi1zuvpTI9EkPJ7S3Q38LhWsbx2Z1Ezrvyc7T1jUD3AaCK2hK5l7f6kN2OVE7nPf9F67fa9Nnl41xk2rUtBC4gmyBdEjp+SGGEsYydUAj3O8C1MkhqeYMuC/Bx7RdLwEzwJMC/8BMgRNmbvi4hO6E51mXCX8Yr9CqdT7sdt/3UEzgfwGWjoSGvrYcFH+dGu8z/snzEDbQ/QwAP0g4eAm76ERUxrJRz/t4hFLVUwLqVYcF+dpbv0CaxeC93qUTdWUD1jVQ2Ujn0XS2/TrtsA4P0UWpn4qR8rzhuYvRfHUhNtaG8OoZLjyk3FDFmH1ZzQpn5HwWpZZP42WtqWaHUjOxqaQeiVkv21Rr7L86gFoumlh5j7nnGRSLY5iDeX4jRf3b9X4ABADgctRMdEJWtAAAAAElFTkSuQmCC);
background-repeat: no-repeat;
background-position: center center;
>a {
display: block;
width: 100%;
height: 100%;
}
}
.setup {
line-height: 1.3em;
padding-top: 5px;
}
.packages-header {
border-bottom: @headerBorderWidth solid #e6e6e6;
.search-container {
top: 9px;
.search-icon {
background: #e6e6e6;
}
}
}
.sm-registry-info {
height: @smRegistryInfoHeight;
line-height: 1.7em;
}
}
.content {
padding-top: @mainHeaderHeight + @packagesHeaderHeight + 10;
.entry {
.transition(height .3s);
padding: 9px 10px;
overflow: hidden;
border-bottom: 1px solid #E7E7E7;
&:last-child {
border-bottom: none;
}
&:nth-child( even ) {
background: @entryBg;
}
.title {
margin: 0 0 5px 10px;
}
.description {
margin: 0 0 0 18px;
font-size: 13px;
}
.name:hover {
text-decoration: none;
}
.name:before {
margin: 0;
margin-left: -10px;
.transformTransition(.2s);
}
&.open .name:before {
.rotate(90deg);
}
.version {
color: #666;
}
.author {
color: #666;
}
.readme {
font-size: 14px;
margin-top: 10px;
background: @white;
padding: 10px 12px;
.border-radius(3px);
border: 1px solid darken( @entryBg, 10% );
}
}
}
}
.pkg-search-container {
display: none;
}
.packages-container {
.search-ajax {
display: block;
margin: 50px auto;
}
}
.no-results {
text-align: center;
margin: 50px 0;
color: #888;
big {
font-size: 38px;
margin-bottom: 8px;
}
code {
font-size: 1.2em;
}
}
.red {
color: @npmRed;
}
.light-red {
color: lighten( @npmRed, 10% );
}
.white {
color: @white;
}
.red-bg {
background: @npmRed;
}
.light-red-bg {
background: lighten( @npmRed, 10% );
}
.no-bg {
background: none;
}
.no-border {
border: none;
}
.no-rnd-cnr {
.border-radius( 0 );
}
.center {
text-align: center;
}

20
lib/GUI/entry.hbs Normal file
View File

@@ -0,0 +1,20 @@
<div class="entry" data-name="{{ name }}" data-version="{{ version }}">
<div class="row">
<div class="col-md-8 col-sm-8">
<h4 class="title">
<a class='name icon-angle-right red' href='javascript:void(0)'>{{ name }}</a>
<small class='version'>v{{ version }}</small>
</h4>
</div>
<div class="col-md-4 col-sm-4">
<div class="author pull-right">
<small>By: {{ _npmUser.name }}</small>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p class="description">{{ description }}</p>
</div>
</div>
</div>

65
lib/GUI/index.hbs Normal file
View File

@@ -0,0 +1,65 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title>{{ name }}</title>
<link rel="icon" type="image/ico" href="{{ baseUrl }}/-/static/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="{{ baseUrl }}/-/static/main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body class="body">
<header class="main-header">
<nav class="navbar navbar-default red-bg white no-border no-rnd-cnr" role="navigation">
<div class="container">
<div class="npm-logo navbar-left">
<a href="{{ baseUrl }}"></a>
</div>
<div class="navbar-right setup hidden-xs">
<code class="white no-bg">npm set registry {{ baseUrl }}</code><br>
<code class="white no-bg">npm adduser --registry {{ baseUrl }}</code>
</div>
</div>
</nav>
<header class="sm-registry-info light-red-bg center hidden-sm hidden-lg hidden-md">
<code class="white no-bg">{{ baseUrl }}</code><br>
</header>
<header class="packages-header container">
<div class="row">
<div class="col-md-5 hidden-xs hidden-sm">
<h2 class="title">Available Packages</h2>
</div>
<div class="col-md-4 col-md-offset-3 col-sm-12">
<form id='search-form'>
<div class="input-group input-group-lg search-container">
<input type="text" class="form-control" placeholder="Search for packages">
<span class="input-group-btn">
<button class="btn btn-default search-icon js-search-btn"><i class="icon-search"></i></button>
</span>
</div>
</form>
</div>
</div>
</header>
</header>
<section class="content container packages-container" id="all-packages">
{{#each packages}}
{{> entry}}
{{/each}}
{{#unless packages.length}}
<div class='no-results'>
<big>No Packages</big><br>
Use <code>npm publish</code>
</div>
{{/unless}}
</section>
<section class="content container pkg-search-container" id="search-results"></section>
<script src="{{ baseUrl }}/-/static/jquery.min.js"></script>
<script type='text/javascript' src='{{ baseUrl }}/-/static/main.js'></script>
</body>
</html>

71
lib/GUI/js/entry.js Normal file
View File

@@ -0,0 +1,71 @@
var $ = require('unopinionate').selector,
onClick = require('onclick'),
transitionComplete = require('transition-complete');
$(function() {
onClick('.entry .name', function() {
var $this = $(this),
$entry = $this.closest('.entry');
//Close entry
if($entry.hasClass('open')) {
$entry
.height($entry.outerHeight())
.removeClass('open');
setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px');
}, 0);
transitionComplete(function() {
$entry.find('.readme').remove();
$entry.css('height', 'auto');
});
}
//Open entry
else {
//Close open entries
$('.entry.open').each(function() {
var $entry = $(this);
$entry
.height($entry.outerHeight())
.removeClass('open');
setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px');
}, 0);
transitionComplete(function() {
$entry.find('.readme').remove();
$entry.css('height', 'auto');
});
});
//Add the open class
$entry.addClass('open');
//Explicitly set heights for transitions
var height = $entry.outerHeight();
$entry
.attr('data-height', height)
.css('height', height);
//Get the data
$.ajax({
url: '-/readme/'+$entry.attr('data-name')+'/'+$entry.attr('data-version'),
dataType: 'text',
success: function(html) {
var $readme = $("<div class='readme'>")
.html(html)
.appendTo($entry);
$entry.height(height + $readme.outerHeight());
transitionComplete(function() {
$entry.css('height', 'auto');
});
}
});
}
});
});

2
lib/GUI/js/main.js Normal file
View File

@@ -0,0 +1,2 @@
require('./search');
require('./entry');

81
lib/GUI/js/search.js Normal file
View File

@@ -0,0 +1,81 @@
var $ = require('unopinionate').selector,
template = require('../entry.hbs'),
onScroll = require('onscroll');
$(function() {
'use strict';
(function( window, document ) {
var $form = $('#search-form');
var $input = $form.find('input');
var $body = $('body');
var $clear = $form.find('.clear');
var $searchResults = $("#search-results");
var $pkgListing = $("#all-packages");
var $searchBtn = $('.js-search-btn');
var request;
var currentResults;
var toggle = function( validQuery ) {
$searchResults.toggleClass( 'show', validQuery );
$pkgListing.toggleClass( 'hide', validQuery );
$searchBtn.find('i').toggleClass( 'icon-cancel', validQuery );
$searchBtn.find('i').toggleClass( 'icon-search', !validQuery );
};
$form.bind('submit keyup', function(e) {
var q, qBool;
e.preventDefault();
q = $input.val();
qBool = q !== '';
toggle( qBool );
if( !qBool ) {
if( request && typeof request.abort === 'function' ) {
request.abort();
}
currentResults = null;
$searchResults.html('');
return;
}
if( request && typeof request.abort === 'function' ) {
request.abort();
}
if( !currentResults ) {
$searchResults.html( "<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>" );
}
request = $.getJSON('-/search/' + q, function( results ) {
currentResults = results;
if( results.length > 0 ) {
var html = '';
$.each(results, function( i, entry ) {
html += template( entry );
});
$searchResults.html(html);
} else {
$searchResults.html("<div class='no-results'><big>No Results</big></div>");
}
});
});
$( document ).on( 'click', '.icon-cancel', function( e ) {
e.preventDefault();
$input.val('');
$form.keyup();
});
})( window, window.document );
});

185
lib/auth.js Normal file
View File

@@ -0,0 +1,185 @@
var Path = require('path')
, crypto = require('crypto')
, Error = require('http-errors')
, Logger = require('./logger')
, assert = require('assert')
module.exports = Auth
function Auth(config) {
if (!(this instanceof Auth)) return new Auth(config)
this.config = config
this.logger = Logger.logger.child({sub: 'auth'})
var stuff = {
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 = Object.keys(config.auth || {}).map(function(p) {
var plugin, name
try {
name = 'sinopia-' + p
plugin = require(name)
} catch(x) {
try {
name = p
plugin = require(name)
} catch(x) {}
}
if (plugin == null) {
throw Error('"' + p + '" auth plugin not found\n'
+ 'try "npm install sinopia-' + p + '"')
}
if (typeof(plugin) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
plugin = plugin(config.auth[p], stuff)
if (plugin == null || typeof(plugin.authenticate) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
return plugin
})
this.plugins.unshift({
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()
},
})
this.plugins.push({
authenticate: function(user, password, cb) {
return cb(Error[403]('bad username/password, access denied'))
},
adduser: function(user, password, cb) {
return cb(Error[409]('registration is disabled'))
},
})
}
Auth.prototype.authenticate = function(user, password, cb) {
var plugins = this.plugins.slice(0)
!function next() {
var p = plugins.shift()
p.authenticate(user, password, function(err, groups) {
if (err || groups) return cb(err, groups)
next()
})
}()
}
Auth.prototype.add_user = function(user, password, cb) {
var plugins = this.plugins.slice(0)
!function next() {
var p = plugins.shift()
var 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 || ok) return cb(err, ok)
next()
})
}
}()
}
Auth.prototype.middleware = function() {
var self = this
return function(req, res, _next) {
req.pause()
function next(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) return next()
req.remote_user = AnonymousUser()
var authorization = req.headers.authorization
if (authorization == null) return next()
var parts = authorization.split(' ')
if (parts.length !== 2) return next({
status: 400,
message: 'bad authorization header',
})
var scheme = parts[0]
, credentials = new Buffer(parts[1], 'base64').toString()
, index = credentials.indexOf(':')
if (scheme !== 'Basic' || index < 0) return next({
status: 400,
message: 'bad authorization header',
})
var user = credentials.slice(0, index)
, pass = credentials.slice(index + 1)
self.authenticate(user, pass, function(err, groups) {
if (!err && groups != null && groups != false) {
req.remote_user = AuthenticatedUser(user, groups)
next()
} else {
req.remote_user = AnonymousUser()
next(err)
}
})
}
}
function AnonymousUser() {
return {
name: undefined,
// groups without '@' are going to be deprecated eventually
groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'],
}
}
function AuthenticatedUser(name, groups) {
groups = groups.concat(['$all', '$authenticated', '@all', '@authenticated', 'all'])
return {
name: name,
groups: groups,
}
}

View File

@@ -1,5 +1,19 @@
#!/usr/bin/env node
/*eslint no-sync:0*/
if (process.getuid && process.getuid() === 0) {
global.console.error("Sinopia doesn't need superuser privileges. Don't run it under root.")
}
process.title = 'sinopia'
try {
// for debugging memory leaks
// totally optional
require('heapdump')
} catch(err){}
var logger = require('./logger')
logger.setup() // default setup
@@ -9,7 +23,8 @@ var pkg_file = '../package.yaml'
, commander = require('commander')
, server = require('./index')
, crypto = require('crypto')
, pkg = require(pkg_file)
, Path = require('path')
, pkg = yaml.safeLoad(fs.readFileSync(__dirname + '/' + pkg_file, 'utf8'))
commander
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
@@ -39,7 +54,7 @@ try {
var readline = require('readline')
var rl = readline.createInterface(process.stdin, process.stdout)
var timeout = setTimeout(function() {
console.log('I got tired waiting for an answer. Exitting...')
global.console.log('I got tired waiting for an answer. Exitting...')
process.exit(1)
}, 20000)
@@ -57,7 +72,7 @@ try {
afterConfigLoad()
} else if (x[0] == 'N' || x[0] == 'n') {
rl.close()
console.log('So, you just accidentally run me in a wrong folder. Exitting...')
global.console.log('So, you just accidentally run me in a wrong folder. Exitting...')
process.exit(1)
} else {
askUser()
@@ -89,13 +104,19 @@ function get_hostport() {
function afterConfigLoad() {
if (!config.user_agent) config.user_agent = 'Sinopia/'+pkg.version
if (!config.self_path) config.self_path = config_path
if (!config.self_path) config.self_path = Path.resolve(config_path)
logger.setup(config.logs)
var hostport = get_hostport()
server(config).listen(hostport[1], hostport[0])
logger.logger.warn({addr: 'http://'+hostport[0]+':'+hostport[1]+'/'}, 'Server is listening on @{addr}')
server(config)
.listen(hostport[1], hostport[0])
.on('error', function(err) {
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}')
process.exit(2)
})
logger.logger.warn({addr: 'http://'+hostport[0]+':'+hostport[1]+'/', version: 'Sinopia/'+pkg.version}, 'Server is listening on @{addr}')
// undocumented stuff for tests
if (typeof(process.send) === 'function') {
@@ -105,17 +126,24 @@ function afterConfigLoad() {
function write_config_banner(def, config) {
var hostport = get_hostport()
console.log('===========================================================')
console.log(' Creating a new configuration file: "%s"', config_path)
console.log(' ')
console.log(' If you want to setup npm to work with this registry,')
console.log(' run following commands:')
console.log(' ')
console.log(' $ npm set registry http://%s:%s/', hostport[0], hostport[1])
console.log(' $ npm set always-auth true')
console.log(' $ npm adduser')
console.log(' Username: %s', def.user)
console.log(' Password: %s', def.pass)
console.log('===========================================================')
var log = global.console.log
log('===========================================================')
log(' Creating a new configuration file: "%s"', config_path)
log(' ')
log(' If you want to setup npm to work with this registry,')
log(' run following commands:')
log(' ')
log(' $ npm set registry http://%s:%s/', hostport[0], hostport[1])
log(' $ npm set always-auth true')
log(' $ npm adduser')
log(' Username: %s', def.user)
log(' Password: %s', def.pass)
log('===========================================================')
}
process.on('uncaughtException', function(err) {
logger.logger.fatal({err: err}, 'uncaught exception, please report this\n@{err.stack}')
process.exit(255)
})

View File

@@ -1,6 +1,9 @@
var assert = require('assert')
, crypto = require('crypto')
, Path = require('path')
, minimatch = require('minimatch')
, Error = require('http-errors')
, LocalList = require('./local-list')
, utils = require('./utils')
// [[a, [b, c]], d] -> [a, b, c, d]
@@ -26,11 +29,17 @@ function Config(config) {
assert.equal(typeof(config), 'object', 'CONFIG: this doesn\'t look like a valid config file')
assert(this.storage, 'CONFIG: storage path not defined')
this.localList = LocalList(
Path.join(
Path.resolve(Path.dirname(this.self_path), this.storage),
'.sinopia-db.json'
)
)
var users = {all:true, anonymous:true, 'undefined':true, owner:true, none:true}
var 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 !== '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
@@ -47,7 +56,7 @@ function Config(config) {
for (var i in this.users) {
assert(this.users[i].password, 'CONFIG: no password for user: ' + i)
assert(
typeof(this.users[i].password) === 'string' &&
typeof(this.users[i].password) === 'string' &&
this.users[i].password.match(/^[a-f0-9]{40}$/)
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected')
}
@@ -73,11 +82,6 @@ function Config(config) {
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')
})
}
for (var i in this.packages) {
@@ -104,27 +108,30 @@ function Config(config) {
}
}).bind(this))
// unique identifier of this server (or a cluster), used to avoid loops
if (!this.server_id) {
this.server_id = crypto.pseudoRandomBytes(6).toString('hex')
}
if (this.ignore_latest_tag == null) this.ignore_latest_tag = false
return this
}
function allow_action(package, who, action) {
for (var i in this.packages) {
if (minimatch.makeRe(i).exec(package)) {
return this.packages[i][action].reduce(function(prev, curr) {
if (curr === String(who) || curr === 'all') return true
return prev
}, false)
}
}
return false
return (this.get_package_setting(package, action) || []).reduce(function(prev, curr) {
if (typeof(who) === 'string' && curr === who) return true
if (Array.isArray(who) && who.indexOf(curr) !== -1) return true
return prev
}, false)
}
Config.prototype.allow_access = function(package, user) {
return allow_action.call(this, package, user, 'allow_access') || allow_action.call(this, package, user, 'access')
return allow_action.call(this, package, user.groups, 'allow_access') || allow_action.call(this, package, user.groups, 'access')
}
Config.prototype.allow_publish = function(package, user) {
return allow_action.call(this, package, user, 'allow_publish') || allow_action.call(this, package, user, 'publish')
return allow_action.call(this, package, user.groups, 'allow_publish') || allow_action.call(this, package, user.groups, 'publish')
}
Config.prototype.proxy_access = function(package, uplink) {
@@ -132,13 +139,49 @@ Config.prototype.proxy_access = function(package, uplink) {
}
Config.prototype.proxy_publish = function(package, uplink) {
return allow_action.call(this, package, uplink, 'proxy_publish')
throw new Error('deprecated')
//return allow_action.call(this, package, uplink, 'proxy_publish')
}
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
Config.prototype.get_package_setting = function(package, setting) {
for (var i in this.packages) {
if (minimatch.makeRe(i).exec(package)) {
return this.packages[i][setting]
}
}
return undefined
}
module.exports = Config
var parse_interval_table = {
'': 1000,
ms: 1,
s: 1000,
m: 60*1000,
h: 60*60*1000,
d: 86400000,
w: 7*86400000,
M: 30*86400000,
y: 365*86400000,
}
module.exports.parse_interval = function(interval) {
if (typeof(interval) === 'number') return interval * 1000
var result = 0
var last_suffix = Infinity
interval.split(/\s+/).forEach(function(x) {
if (!x) return
var m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/)
if (!m
|| parse_interval_table[m[4]] >= last_suffix
|| (m[4] === '' && last_suffix !== Infinity)) {
throw new Error('invalid interval: ' + interval)
}
last_suffix = parse_interval_table[m[4]]
result += Number(m[1]) * parse_interval_table[m[4]]
})
return result
}

View File

@@ -2,42 +2,75 @@
storage: ./storage
# a list of users
#
# This could be deprecated soon, use auth plugins instead (see htpasswd below).
users:
admin:
# crypto.createHash('sha1').update(pass).digest('hex')
password: __PASSWORD__
web:
# web interface is disabled by default in 0.x, will be enabled soon in 1.x
# when all its issues will be fixed
#
# set this to `true` if you want to experiment with web ui now;
# this has a lot of issues, e.g. no auth yet, so use at your own risk
#enable: true
title: Sinopia
# logo: logo.png
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/
# maximum time (in seconds) in which data is considered up to date
# 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: 120
#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
packages:
# uncomment this for packages with "local-" prefix to be available
# uncomment this for packages with "local-" prefix to be available
# for admin only, it's a recommended way of handling private packages
#'local-*':
# allow_access: admin
# allow_publish: admin
# # you can override storage directory for a group of packages this way:
# storage: 'local_storage'
'*':
# allow all users to read packages ('all' is a keyword)
# this includes non-authenticated users
allow_access: all
# 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"
allow_access: $all
# allow 'admin' to publish packages
allow_publish: admin
# if package is not available locally, proxy requests to 'npmjs' registry
proxy_access: npmjs
# when package is published locally, also push it to remote registry
#proxy_publish: none
proxy: npmjs
#####################################################################
# Advanced settings
@@ -67,3 +100,12 @@ logs:
#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
# Workaround for countless npm bugs. Must have for npm <1.14.x, but expect
# it to be turned off in future versions. If `true`, latest tag is ignored,
# and the highest semver is placed instead.
#ignore_latest_tag: false

View File

@@ -4,7 +4,10 @@ var fs = require('fs')
module.exports = function create_config() {
var pass = crypto.randomBytes(8).toString('base64').replace(/[=+\/]/g, '')
, pass_digest = crypto.createHash('sha1').update(pass).digest('hex')
, config = fs.readFileSync(require.resolve('./config_def.yaml'), 'utf8')
/*eslint no-sync:0*/
var config = fs.readFileSync(require.resolve('./config_def.yaml'), 'utf8')
config = config.replace('__PASSWORD__', pass_digest)
return {

View File

@@ -1,62 +0,0 @@
var util = require('util')
, utils = require('./utils')
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 (utils.is_object(params)) {
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'
/*
* Mimic filesystem errors
*/
var FSError = function(code) {
this.code = code
}
util.inherits(UserError, Error)
UserError.prototype.name = 'FS Error'
module.exports.AppError = AppError
module.exports.UserError = UserError
module.exports.FSError = FSError

85
lib/index-web.js Normal file
View File

@@ -0,0 +1,85 @@
var fs = require('fs')
var marked = require('marked')
var search = require('./search')
var Handlebars = require('handlebars')
var Error = require('http-errors')
module.exports = function(app, config, storage) {
search.configureStorage(storage)
Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8'));
var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'));
app.get('/', function(req, res, next) {
res.setHeader('Content-Type', 'text/html');
storage.get_local(function(err, packages) {
res.send(template({
name: config.web.title || "Sinopia",
packages: packages,
baseUrl: config.url_prefix || req.protocol + '://' + req.get('host')
}));
});
});
// Static
app.get('/-/static/:filename', function(req, res, next) {
var file = __dirname + '/static/' + req.params.filename
fs.exists(file, function(exists) {
if (exists) {
res.sendfile(file)
} else {
res.status(404);
res.send("File Not Found")
}
})
})
app.get('/-/logo', function(req, res, next) {
res.sendfile(config.web.logo ? config.web.logo : __dirname + "/static/logo.png")
})
app.get('/-/logo-sm', function(req, res, next) {
res.sendfile(config.web.logosm ? config.web.logosm : __dirname + "/static/logo-sm.png")
})
// Search
app.get('/-/search/:anything', function(req, res, next) {
var results = search.query(req.params.anything),
packages = []
var getData = function(i) {
storage.get_package(results[i].ref, function(err, entry) {
if (entry) {
packages.push(entry.versions[entry['dist-tags'].latest])
}
if (i >= results.length - 1) {
res.send(packages)
} else {
getData(i + 1)
}
})
}
if (results.length) {
getData(0);
} else {
res.send([])
}
})
// Readme
marked.setOptions({
highlight: function (code) {
return require('highlight.js').highlightAuto(code).value
}
})
app.get('/-/readme/:package/:version?', function(req, res, next) {
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err)
res.send(marked(info.readme || 'ERROR: No README data found!'))
})
})
}

View File

@@ -3,34 +3,46 @@ var express = require('express')
, utils = require('./utils')
, Storage = require('./storage')
, Config = require('./config')
, UError = require('./error').UserError
, Error = require('http-errors')
, Middleware = require('./middleware')
, Logger = require('./logger')
, Cats = require('./status-cats')
, basic_auth = Middleware.basic_auth
, validate_name = Middleware.validate_name
, media = Middleware.media
, expect_json = Middleware.expect_json
, fs = require('fs')
, Auth = require('./auth')
function match(regexp) {
return function(req, res, next, value, name) {
if (regexp.exec(value)) {
next()
} else {
next('route')
}
}
}
module.exports = function(config_hash) {
var config = new Config(config_hash)
, storage = new Storage(config)
, auth = new Auth(config)
var can = function(action) {
return function(req, res, next) {
if (config['allow_'+action](req.params.package, req.remoteUser)) {
if (config['allow_'+action](req.params.package, req.remote_user)) {
next()
} else {
if (!req.remoteUser) {
next(new UError({
status: 403,
msg: "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?",
}))
if (!req.remote_user.name) {
if (req.remote_user.error) {
var message = "can't "+action+' restricted package, ' + req.remote_user.error
} else {
var message = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
}
next(Error[403](message))
} else {
next(new UError({
status: 403,
msg: 'user '+req.remoteUser+' not allowed to '+action+' it'
}))
next(Error[403]('user ' + req.remote_user.name
+ ' not allowed to ' + action + ' it'))
}
}
}
@@ -38,55 +50,63 @@ module.exports = function(config_hash) {
var app = express()
app.use(function(req, res, next) {
var calls = 0
res.report_error = function(err) {
calls++
// run in production mode by default, just in case
// it shouldn't make any difference anyway
app.set('env', process.env.NODE_ENV || 'production')
function error_reporting_middleware(req, res, next) {
res.report_error = res.report_error || function(err) {
if (err.status && err.status >= 400 && err.status < 600) {
if (calls == 1) {
if (!res.headersSent) {
res.status(err.status)
res.send({error: err.msg || err.message || 'unknown error'})
res.send({error: err.message || 'unknown error'})
}
} else {
Logger.logger.error({err: err}, 'unexpected error: @{!err.message}\n@{err.stack}')
if (calls == 1) {
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)
res.send({error: 'internal server error'})
} else {
// socket should be already closed
}
}
}
next()
})
}
app.use(error_reporting_middleware)
app.use(Middleware.log_and_etagify)
app.use(function(req, res, next) {
res.setHeader('X-Powered-By', 'Sinopia')
res.setHeader('X-Powered-By', config.user_agent)
next()
})
app.use(Cats.middleware)
app.use(basic_auth(function(user, pass) {
return config.authenticate(user, pass)
}))
app.use(express.json({strict: false}))
// TODO: npm DO NOT support compression :(
app.use(auth.middleware())
app.use(express.json({strict: false, limit: config.max_body_size || '10mb'}))
app.use(express.compress())
app.use(Middleware.anti_loop(config))
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
app.param('package', validate_name)
app.param('filename', validate_name)
app.param('tag', validate_name)
app.param('version', validate_name)
app.param('revision', validate_name)
/* app.get('/', function(req, res) {
res.send({
error: 'unimplemented'
})
})*/
// these can't be safely put into express url for some reason
app.param('_rev', match(/^-rev$/))
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/))
app.param('anything', match(/.*/))
/* app.get('/-/all', function(req, res) {
/* 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) {
@@ -96,46 +116,60 @@ module.exports = function(config_hash) {
// TODO: anonymous user?
app.get('/:package/:version?', can('access'), function(req, res, next) {
storage.get_package(req.params.package, function(err, info) {
storage.get_package(req.params.package, {req: req}, function(err, info) {
if (err) return next(err)
info = utils.filter_tarball_urls(info, req, config)
var version = req.params.version
, t
if (!version) {
return res.send(info)
}
if (info.versions[version] != null) {
return res.send(info.versions[version])
if ((t = utils.get_version(info, version)) != null) {
return res.send(t)
}
if (info['dist-tags'] != null) {
if (info['dist-tags'][version] != null) {
version = info['dist-tags'][version]
if (info.versions[version] != null) {
return res.send(info.versions[version])
if ((t = utils.get_version(info, version)) != null) {
return res.send(t)
}
}
}
return next(new UError({
status: 404,
msg: 'version not found: ' + req.params.version
}))
return next(Error[404]('version not found: ' + req.params.version))
})
})
app.get('/:package/-/:filename', can('access'), function(req, res, next) {
var stream = storage.get_tarball(req.params.package, req.params.filename)
stream.on('content-length', function(v) {
res.header('Content-Length', v)
})
stream.on('error', function(err) {
return res.report_error(err)
})
res.header('content-type', 'application/octet-stream')
res.header('Content-Type', 'application/octet-stream')
stream.pipe(res)
})
// searching packages
app.get('/-/all/:anything?', function(req, res, next) {
storage.search(req.param.startkey || 0, {req: req}, function(err, result) {
if (err) return next(err)
for (var pkg in result) {
if (!config.allow_access(pkg, req.remote_user)) {
delete result[pkg]
}
}
return res.send(result)
})
})
//app.get('/*', function(req, res) {
// proxy.request(req, res)
// proxy.request(req, res)
//})
// placeholder 'cause npm require to be authenticated to publish
@@ -145,75 +179,153 @@ module.exports = function(config_hash) {
// npmjs.org sets 10h expire
expires: new Date(Date.now() + 10*60*60*1000)
})
res.send({"ok":true,"name":"somebody","roles":[]})
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')
app.get('/-/user/:org_couchdb_user', function(req, res, next) {
res.status(200)
return res.send({
ok: 'you are authenticated as "' + req.user + '"',
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
})
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/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
if (req.remote_user.name != null) {
res.status(201)
return res.send({
ok: 'you are authenticated as "' + req.remote_user.name + '"',
})
} else {
if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') {
return next(Error[400]('user/password is not found in request (npm issue?)'))
}
auth.add_user(req.body.name, req.body.password, function(err) {
if (err) {
if (err.status < 500 && err.message === 'this user already exists') {
// with npm registering is the same as logging in
// so we replace message in case of conflict
return next(Error[409]('bad username/password, access denied'))
}
return next(err)
}
res.status(201)
return res.send({
ok: 'user "' + req.body.name + '" created',
})
})
}
})
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 + '"',
// tagging a package
app.put('/:package/:tag', can('publish'), media('application/json'), function(req, res, next) {
if (typeof(req.body) !== 'string') return next('route')
var tags = {}
tags[req.params.tag] = req.body
storage.add_tags(req.params.package, tags, function(err) {
if (err) return next(err)
res.status(201)
return res.send({
ok: 'package tagged'
})
})
})
// publishing a package
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
if (req.params._rev != null && req.params._rev != '-rev') return next('route')
var name = req.params.package
if (Object.keys(req.body).length == 1 && utils.is_object(req.body.users)) {
return next(new UError({
// 501 status is more meaningful, but npm doesn't show error message for 5xx
status: 404,
msg: 'npm star|unstar calls are not implemented',
}))
// 501 status is more meaningful, but npm doesn't show error message for 5xx
return next(Error[404]('npm star|unstar calls are not implemented'))
}
try {
var metadata = utils.validate_metadata(req.body, name)
} catch(err) {
return next(new UError({
status: 422,
msg: 'bad incoming package data',
}))
return next(Error[422]('bad incoming package data'))
}
if (req.params._rev) {
storage.change_package(name, metadata, req.params.revision, function(err) {
if (err) return next(err)
res.status(201)
return res.send({
ok: 'package changed'
})
after_change(err, 'package changed')
})
} else {
storage.add_package(name, metadata, function(err) {
after_change(err, 'created new package')
})
}
function after_change(err, ok_message) {
// old npm behaviour
if (metadata._attachments == null) {
if (err) return next(err)
res.status(201)
return res.send({
ok: 'created new package'
ok: ok_message
})
}
// npm-registry-client 0.3+ embeds tarball into the json upload
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
// issue #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(Error[400]('unsupported registry call'))
}
if (err && err.status != 409) return next(err)
// at this point document is either created or existed before
var t1 = Object.keys(metadata._attachments)[0]
create_tarball(t1, metadata._attachments[t1], function(err) {
if (err) return next(err)
var t2 = Object.keys(metadata.versions)[0]
metadata.versions[t2].readme = metadata.readme != null ? 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)
res.status(201)
return res.send({
ok: ok_message
})
})
})
})
}
function create_tarball(filename, data, cb) {
var 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()
}
function create_version(version, data, cb) {
storage.add_version(name, version, data, null, cb)
}
function add_tags(tags, cb) {
storage.add_tags(name, tags, cb)
}
})
// unpublishing an entire package
@@ -283,11 +395,41 @@ module.exports = function(config_hash) {
})
})
// hook for tests only
if (config._debug) {
app.get('/-/_debug', function(req, res) {
var do_gc = typeof(global.gc) !== 'undefined'
if (do_gc) global.gc()
res.send({
pid: process.pid,
main: process.mainModule.filename,
conf: config.self_path,
mem: process.memoryUsage(),
gc: do_gc,
})
})
}
app.use(app.router)
app.use(function(err, req, res, next) {
if (typeof(res.report_error) !== 'function') {
// in case of very early error this middleware may not be loaded before error is generated
// fixing that
error_reporting_middleware(req, res, function(){})
}
res.report_error(err)
})
if (config.web && config.web.enable) {
require('./index-web')(app, config, storage)
} else {
app.get('/', function(req, res) {
res.send('Web interface is a work-in-progress right now, '
+ 'so it is disabled by default. If you want to play with it, '
+ 'you can enable it in the config file.')
})
}
return app
}

View File

@@ -1,22 +1,23 @@
var fs = require('fs')
, fsExt = require('fs-ext')
, Path = require('path')
, mkdirp = require('mkdirp')
, mystreams = require('./streams')
, Logger = require('./logger')
, FSError = require('./error').FSError
, Error = require('http-errors')
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 FSError(code) {
var err = Error(code)
err.code = code
return err
}
try {
var fsExt = require('fs-ext')
} catch(e) {
fsExt = {
flock: function() {
arguments[arguments.length-1]()
}
})
}
}
function write(dest, data, cb) {
@@ -30,7 +31,8 @@ function write(dest, data, cb) {
safe_write(function(err) {
if (err && err.code === 'ENOENT') {
make_directories(dest, function() {
mkdirp(Path.dirname(dest), function(err) {
if (err) return cb(err)
safe_write(cb)
})
} else {
@@ -48,7 +50,7 @@ function write_stream(name) {
})
fs.exists(name, function(exists) {
if (exists) return stream.emit('error', new FSError('EEXISTS'))
if (exists) return stream.emit('error', FSError('EEXISTS'))
var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '')
, file = fs.createWriteStream(tmpname)
@@ -59,8 +61,11 @@ function write_stream(name) {
function onend() {
file.on('close', function() {
fs.rename(tmpname, name, function(err) {
if (err) stream.emit('error', err)
stream.emit('success')
if (err) {
stream.emit('error', err)
} else {
stream.emit('success')
}
})
})
file.destroySoon()
@@ -93,19 +98,36 @@ function write_stream(name) {
}
function read_stream(name, stream, callback) {
return fs.createReadStream(name)
var rstream = fs.createReadStream(name)
rstream.on('error', function(err) {
stream.emit('error', err)
})
rstream.on('open', function(fd) {
fs.fstat(fd, function(err, stats) {
if (err) return stream.emit('error', err)
stream.emit('content-length', stats.size)
stream.emit('open')
rstream.pipe(stream)
})
})
var stream = new mystreams.ReadTarballStream()
stream.abort = function() {
rstream.close()
}
return stream
}
function create(name, contents, callback) {
fs.exists(name, function(exists) {
if (exists) return callback(new FSError('EEXISTS'))
if (exists) return callback(FSError('EEXISTS'))
write(name, contents, callback)
})
}
function update(name, contents, callback) {
fs.exists(name, function(exists) {
if (!exists) return callback(new FSError('ENOENT'))
if (!exists) return callback(FSError('ENOENT'))
write(name, contents, callback)
})
}
@@ -137,44 +159,45 @@ function open_flock(name, opmod, flmod, tries, backoff, cb) {
}
})
})
}
}
// this function neither unlocks file nor closes it
// it'll have to be done manually later
function lock_and_read(name, callback) {
function lock_and_read(name, _callback) {
open_flock(name, 'r', 'exnb', 4, 10, function(err, fd) {
function callback(err) {
if (err && fd) {
fs.close(fd, function(err2) {
_callback(err)
})
} else {
_callback.apply(null, arguments)
}
}
if (err) return callback(err, fd)
fs.fstat(fd, function(err, st) {
if (err) return callback(err, fd)
var buffer = new Buffer(st.size)
fs.read(fd, buffer, 0, st.size, null, function(err, bytesRead, buffer) {
if (st.size === 0) return onRead(null, 0, buffer)
fs.read(fd, buffer, 0, st.size, null, onRead)
function onRead(err, bytesRead, buffer) {
if (err) return callback(err, fd)
if (bytesRead != st.size) return callback(new Error('st.size != bytesRead'), fd)
callback(null, fd, buffer)
})
}
})
})
}
function Storage(path) {
this.path = path
this.logger = Logger.logger.child({sub: 'fs'})
try {
fs.mkdirSync(path)
this.logger.warn({path: path}, 'created new packages directory: @{path}')
} catch(err) {
if (err.code !== 'EEXIST') throw new Error(err)
}
}
module.exports.read = read
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) {
module.exports.read_json = function(name, cb) {
read(name, function(err, res) {
if (err) return cb(err)
var args = []
@@ -187,12 +210,10 @@ Storage.prototype.read_json = function(name, cb) {
})
}
Storage.prototype.lock_and_read = function(name, cb) {
lock_and_read(this.path + '/' + name, cb)
}
module.exports.lock_and_read = lock_and_read
Storage.prototype.lock_and_read_json = function(name, cb) {
lock_and_read(this.path + '/' + name, function(err, fd, res) {
module.exports.lock_and_read_json = function(name, cb) {
lock_and_read(name, function(err, fd, res) {
if (err) return cb(err, fd)
var args = []
@@ -205,49 +226,29 @@ Storage.prototype.lock_and_read_json = function(name, cb) {
})
}
Storage.prototype.path_to = function(file) {
return this.path + '/' + file
module.exports.create = create
module.exports.create_json = function(name, value, cb) {
create(name, JSON.stringify(value, null, '\t'), cb)
}
Storage.prototype.create = function(name, value, cb) {
create(this.path + '/' + name, value, cb)
module.exports.update = update
module.exports.update_json = function(name, value, cb) {
update(name, JSON.stringify(value, null, '\t'), cb)
}
Storage.prototype.create_json = function(name, value, cb) {
create(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb)
module.exports.write = write
module.exports.write_json = function(name, value, cb) {
write(name, JSON.stringify(value, null, '\t'), cb)
}
Storage.prototype.update = function(name, value, cb) {
update(this.path + '/' + name, value, cb)
}
module.exports.write_stream = write_stream
Storage.prototype.update_json = function(name, value, cb) {
update(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb)
}
module.exports.read_stream = read_stream
Storage.prototype.write = function(name, value, cb) {
write(this.path + '/' + name, value, cb)
}
module.exports.unlink = fs.unlink
Storage.prototype.write_json = function(name, value, cb) {
write(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb)
}
Storage.prototype.write_stream = function(name, value, cb) {
return write_stream(this.path + '/' + name, value, cb)
}
Storage.prototype.read_stream = function(name, cb) {
return read_stream(this.path + '/' + name, cb)
}
Storage.prototype.unlink = function(name, cb) {
fs.unlink(this.path + '/' + name, cb)
}
Storage.prototype.rmdir = function(name, cb) {
fs.rmdir(this.path + '/' + name, cb)
}
module.exports = Storage
module.exports.rmdir = fs.rmdir

38
lib/local-list.js Normal file
View File

@@ -0,0 +1,38 @@
var fs = require('fs')
function LocalList(path) {
var self = Object.create(LocalList.prototype)
self.path = path
try {
self.list = JSON.parse(fs.readFileSync(self.path, 'utf8')).list
} catch(_) {
self.list = []
}
return self
}
LocalList.prototype = {
add: function(name) {
if (this.list.indexOf(name) === -1) {
this.list.push(name)
this.sync()
}
},
remove: function(name) {
var i = this.list.indexOf(name)
if (i !== -1) {
this.list.splice(i, 1)
}
this.sync()
},
get: function() {
return this.list
},
sync: function() {
fs.writeFileSync(this.path, JSON.stringify({list: this.list})); //Uses sync to prevent ugly race condition
},
}
module.exports = LocalList

View File

@@ -1,13 +1,14 @@
var fs = require('fs')
, semver = require('semver')
, Path = require('path')
, crypto = require('crypto')
, assert = require('assert')
, fs_storage = require('./local-fs')
, UError = require('./error').UserError
, Error = require('http-errors')
, utils = require('./utils')
, mystreams = require('./streams')
, Logger = require('./logger')
, info_file = 'package.json'
, search = require('./search');
//
// Implements Storage interface
@@ -16,8 +17,6 @@ var fs = require('fs')
function Storage(config) {
if (!(this instanceof Storage)) return new Storage(config)
this.config = config
var path = Path.resolve(Path.dirname(this.config.self_path), this.config.storage)
this.storage = new fs_storage(path)
this.logger = Logger.logger.child({sub: 'fs'})
return this
}
@@ -37,23 +36,26 @@ function get_boilerplate(name) {
}
}
Storage.prototype._internal_error = function(err, file, msg) {
this.logger.error( {err: err, file: this.storage.path_to(file)}
, msg + ' @{file}: @{!err.message}'
Storage.prototype._internal_error = function(err, file, message) {
this.logger.error( {err: err, file: file}
, message + ' @{file}: @{!err.message}'
)
return new UError({
status: 500,
msg: 'internal server error'
})
return Error[500]()
}
Storage.prototype.add_package = function(name, metadata, callback) {
this.storage.create_json(name + '/' + info_file, get_boilerplate(name), function(err) {
Storage.prototype.add_package = function(name, info, callback) {
var self = this
var storage = this.storage(name)
if (!storage) return callback(Error[404]('this package cannot be added'))
storage.create_json(info_file, get_boilerplate(name), function(err) {
if (err && err.code === 'EEXISTS') {
return callback(new UError({
status: 409,
msg: 'this package is already present'
}))
return callback(Error[409]('this package is already present'))
}
var latest = info['dist-tags'].latest
if (latest && info.versions[latest]) {
search.add(info.versions[latest])
}
callback()
})
@@ -62,20 +64,21 @@ Storage.prototype.add_package = function(name, metadata, callback) {
Storage.prototype.remove_package = function(name, callback) {
var self = this
self.logger.info({name: name}, 'unpublishing @{name} (all)')
self.storage.read_json(name + '/' + info_file, function(err, data) {
var storage = self.storage(name)
if (!storage) return callback(Error[404]('no such package available'))
storage.read_json(info_file, function(err, data) {
if (err) {
if (err.code === 'ENOENT') {
return callback(new UError({
status: 404,
msg: 'no such package available',
}))
return callback(Error[404]('no such package available'))
} else {
return callback(err)
}
}
self._normalize_package(data)
self.storage.unlink(name + '/' + info_file, function(err) {
storage.unlink(info_file, function(err) {
if (err) return callback(err)
var files = Object.keys(data._attachments)
@@ -84,32 +87,40 @@ Storage.prototype.remove_package = function(name, callback) {
if (files.length === 0) return cb()
var file = files.shift()
self.storage.unlink(name + '/' + file, function() {
storage.unlink(file, function() {
unlinkNext(cb)
})
}
unlinkNext(function() {
// try to unlink the directory, but ignore errors because it can fail
self.storage.rmdir(name, function(err) {
callback()
})
})
})
})
storage.rmdir('.', function(err) {
callback(err)
});
});
});
});
search.remove(name);
this.config.localList.remove(name);
}
Storage.prototype._read_create_package = function(name, callback) {
var self = this
, file = name + '/' + info_file
self.storage.read_json(file, function(err, data) {
var storage = self.storage(name)
if (!storage) {
var data = get_boilerplate(name)
self._normalize_package(data)
return callback(null, data)
}
storage.read_json(info_file, function(err, data) {
// TODO: race condition
if (err) {
if (err.code === 'ENOENT') {
// if package doesn't exist, we create it here
data = get_boilerplate(name)
} else {
return callback(self._internal_error(err, file, 'error reading'))
return callback(self._internal_error(err, info_file, 'error reading'))
}
}
self._normalize_package(data)
@@ -129,7 +140,8 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
if (data.versions[ver] == null) {
var verdata = newdata.versions[ver]
// why does anyone need to keep that in database?
// we don't keep readmes for package versions,
// only one readme per package
delete verdata.readme
change = true
@@ -137,13 +149,13 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
if (verdata.dist && verdata.dist.tarball) {
var url = utils.parse_tarball_url(
verdata.dist.__sinopia_orig_tarball || verdata.dist.tarball
/*verdata.dist.__sinopia_orig_tarball ||*/ verdata.dist.tarball
)
// we do NOT overwrite any existing records
if (url != null && data._distfiles[url.filename] == null) {
data._distfiles[url.filename] = {
url: verdata.dist.__sinopia_orig_tarball || verdata.dist.tarball,
url: /*verdata.dist.__sinopia_orig_tarball ||*/ verdata.dist.tarball,
sha: verdata.dist.shasum,
}
}
@@ -151,10 +163,17 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
}
}
for (var tag in newdata['dist-tags']) {
// if tag is updated to reference latter version, that's fine
var need_change =
(data['dist-tags'][tag] == null) ||
(!semver.gte(newdata['dist-tags'][tag], data['dist-tags'][tag]))
if (!Array.isArray(data['dist-tags'][tag]) || data['dist-tags'][tag].length != newdata['dist-tags'][tag].length) {
// backward compat
var need_change = true
} else {
for (var i=0; i<data['dist-tags'][tag].length; i++) {
if (data['dist-tags'][tag][i] != newdata['dist-tags'][tag][i]) {
var need_change = true
break
}
}
}
if (need_change) {
change = true
@@ -162,20 +181,26 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
}
}
for (var up in newdata._uplinks) {
var need_change =
!utils.is_object(data._uplinks[up]) || (newdata._uplinks[up].etag !== data._uplinks[up].etag)
var need_change =
!utils.is_object(data._uplinks[up]) || (newdata._uplinks[up].etag !== data._uplinks[up].etag || (newdata._uplinks[up].fetched !== data._uplinks[up].fetched))
if (need_change) {
change = true
data._uplinks[up] = newdata._uplinks[up]
}
}
if (newdata.readme !== data.readme) {
data.readme = newdata.readme
change = true
}
if (change) {
self.logger.debug('updating package info')
self._write_package(name, data, callback)
self._write_package(name, data, function(err) {
callback(err, data)
})
} else {
callback()
callback(null, data)
}
})
}
@@ -183,14 +208,12 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
var self = this
self.update_package(name, function updater(data, cb) {
// why does anyone need to keep that in database?
// keep only one readme per package
data.readme = metadata.readme
delete metadata.readme
if (data.versions[version] != null) {
return cb(new UError({
status: 409,
msg: 'this version already present'
}))
return cb(Error[409]('this version already present'))
}
// if uploaded tarball has a different shasum, it's very likely that we have some kind of error
@@ -199,10 +222,9 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback)
if (utils.is_object(data._attachments[tarball])) {
if (data._attachments[tarball].shasum != null && metadata.dist.shasum != null) {
if (data._attachments[tarball].shasum != metadata.dist.shasum) {
return cb(new UError({
status: 400,
msg: 'shasum error, ' + data._attachments[tarball].shasum + ' != ' + metadata.dist.shasum,
}))
return cb(Error[400]('shasum error, '
+ data._attachments[tarball].shasum
+ ' != ' + metadata.dist.shasum))
}
}
@@ -211,20 +233,38 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback)
}
data.versions[version] = metadata
data['dist-tags'][tag] = version
utils.tag_version(data, version, tag, self.config)
self.config.localList.add(name)
cb()
}, callback)
}
Storage.prototype.add_tags = function(name, tags, callback) {
var self = this
self.update_package(name, function updater(data, cb) {
for (var t in tags) {
if (data.versions[tags[t]] == null) {
return cb(Error[404]("this version doesn't exist"))
}
utils.tag_version(data, tags[t], t, self.config)
}
cb()
}, callback)
}
// change package info to tag a specific version
function _add_tag(data, version, tag) {
data['dist-tags'][tag] = version
}
// currently supports unpublishing only
Storage.prototype.change_package = function(name, metadata, revision, callback) {
var self = this
if (!utils.is_object(metadata.versions) || !utils.is_object(metadata['dist-tags'])) {
return callback(new UError({
status: 422,
msg: 'bad data',
}))
return callback(Error[422]('bad data'))
}
self.update_package(name, function updater(data, cb) {
@@ -250,29 +290,32 @@ Storage.prototype.change_package = function(name, metadata, revision, callback)
Storage.prototype.remove_tarball = function(name, filename, revision, callback) {
var self = this
assert(utils.validate_name(filename))
self.update_package(name, function updater(data, cb) {
if (data._attachments[filename]) {
delete data._attachments[filename]
cb()
} else {
cb(new UError({
status: 404,
msg: 'no such file available',
}))
cb(Error[404]('no such file available'))
}
}, function(err) {
if (err) return callback(err)
self.storage.unlink(name + '/' + filename, callback)
var storage = self.storage(name)
if (storage) storage.unlink(filename, callback)
})
}
Storage.prototype.add_tarball = function(name, filename) {
assert(utils.validate_name(filename))
var stream = new mystreams.UploadTarballStream()
, _transform = stream._transform
, length = 0
, shasum = crypto.createHash('sha1')
stream.abort = stream.done = function(){}
stream._transform = function(data) {
shasum.update(data)
length += data.length
@@ -281,20 +324,25 @@ Storage.prototype.add_tarball = function(name, filename) {
var self = this
if (name === info_file || name === '__proto__') {
stream.emit('error', new UError({
status: 403,
msg: 'can\'t use this filename'
}))
process.nextTick(function() {
stream.emit('error', Error[403]("can't use this filename"))
})
return stream
}
var wstream = this.storage.write_stream(name + '/' + filename)
var storage = self.storage(name)
if (!storage) {
process.nextTick(function() {
stream.emit('error', Error[404]("can't upload this package"))
})
return stream
}
var wstream = storage.write_stream(filename)
wstream.on('error', function(err) {
if (err.code === 'EEXISTS') {
stream.emit('error', new UError({
status: 409,
msg: 'this tarball is already present'
}))
stream.emit('error', Error[409]('this tarball is already present'))
} else if (err.code === 'ENOENT') {
// check if package exists to throw an appropriate message
self.get_package(name, function(_err, res) {
@@ -332,10 +380,7 @@ Storage.prototype.add_tarball = function(name, filename) {
}
stream.done = function() {
if (!length) {
stream.emit('error', new UError({
status: 422,
msg: 'refusing to accept zero-length file'
}))
stream.emit('error', Error[422]('refusing to accept zero-length file'))
wstream.abort()
} else {
wstream.done()
@@ -347,22 +392,33 @@ Storage.prototype.add_tarball = function(name, filename) {
}
Storage.prototype.get_tarball = function(name, filename, callback) {
assert(utils.validate_name(filename))
var self = this
var stream = new mystreams.ReadTarballStream()
stream.abort = function() {
rstream.close()
if (rstream) rstream.abort()
}
var rstream = this.storage.read_stream(name + '/' + filename)
var storage = self.storage(name)
if (!storage) {
process.nextTick(function() {
stream.emit('error', Error[404]('no such file available'))
})
return stream
}
var rstream = storage.read_stream(filename)
rstream.on('error', function(err) {
if (err && err.code === 'ENOENT') {
stream.emit('error', new UError({
status: 404,
msg: 'no such file available',
}))
stream.emit('error', Error(404, 'no such file available'))
} else {
stream.emit('error', err)
}
})
rstream.on('content-length', function(v) {
stream.emit('content-length', v)
})
rstream.on('open', function() {
// re-emitting open because it's handled in storage.js
stream.emit('open')
@@ -371,19 +427,19 @@ Storage.prototype.get_tarball = function(name, filename, callback) {
return stream
}
Storage.prototype.get_package = function(name, callback) {
var self = this
, file = name + '/' + info_file
Storage.prototype.get_package = function(name, options, callback) {
if (typeof(options) === 'function') callback = options, options = {}
self.storage.read_json(file, function(err, result) {
var self = this
var storage = self.storage(name)
if (!storage) return callback(Error[404]('no such package available'))
storage.read_json(info_file, function(err, result) {
if (err) {
if (err.code === 'ENOENT') {
return callback(new UError({
status: 404,
msg: 'no such package available'
}))
return callback(Error[404]('no such package available'))
} else {
return callback(self._internal_error(err, file, 'error reading'))
return callback(self._internal_error(err, info_file, 'error reading'))
}
}
self._normalize_package(result)
@@ -391,6 +447,37 @@ Storage.prototype.get_package = function(name, callback) {
})
}
Storage.prototype.get_recent_packages = function(startkey, callback) {
var self = this
var i = 0
var list = []
var storage = self.storage('')
if (!storage) return callback(null, [])
fs.readdir(storage.path, function(err, files) {
if (err) return callback(null, [])
var filesL = files.length
files.forEach(function(file) {
fs.stat(storage.path, function(err, stats) {
if (err) return callback(err)
if (stats.mtime > startkey && utils.validate_name(file)) {
list.push({
time: stats.mtime,
name: file
})
}
if (++i !== filesL) {
return false
}
return callback(null, list)
})
})
})
}
//
// This function allows to update the package thread-safely
//
@@ -409,12 +496,10 @@ Storage.prototype.get_package = function(name, callback) {
//
Storage.prototype.update_package = function(name, updateFn, _callback) {
var self = this
, file = name + '/' + info_file
self.storage.lock_and_read_json(file, function(err, fd, json) {
self.logger.debug({file: file}, 'locking @{file}')
var storage = self.storage(name)
if (!storage) return _callback(Error[404]('no such package available'))
storage.lock_and_read_json(info_file, function(err, fd, json) {
function callback() {
self.logger.debug({file: file}, 'unlocking @{file}')
var _args = arguments
if (fd) {
fs.close(fd, function(err) {
@@ -428,15 +513,9 @@ Storage.prototype.update_package = function(name, updateFn, _callback) {
if (err) {
if (err.code === 'EAGAIN') {
return callback(new UError({
status: 503,
msg: 'resource temporarily unavailable'
}))
return callback(Error[503]('resource temporarily unavailable'))
} else if (err.code === 'ENOENT') {
return callback(new UError({
status: 404,
msg: 'no such package available',
}))
return callback(Error[404]('no such package available'))
} else {
return callback(err)
}
@@ -465,8 +544,48 @@ Storage.prototype._write_package = function(name, json, callback) {
var rev = json._rev.split('-')
json._rev = ((+rev[0] || 0) + 1) + '-' + crypto.pseudoRandomBytes(8).toString('hex')
this.storage.write_json(name + '/' + info_file, json, callback)
var storage = this.storage(name)
if (!storage) return callback()
storage.write_json(info_file, json, callback)
}
Storage.prototype.storage = function(package) {
var path = this.config.get_package_setting(package, 'storage')
if (path == null) path = this.config.storage
if (path == null || path === false) {
this.logger.debug({name: package}, 'this package has no storage defined: @{name}')
return null
}
return new Path_Wrapper(
Path.join(
Path.resolve(Path.dirname(this.config.self_path), path),
package
)
)
}
var Path_Wrapper = (function() {
// a wrapper adding paths to fs_storage methods
function Wrapper(path) {
this.path = path
}
for (var i in fs_storage) {
if (fs_storage.hasOwnProperty(i)) {
Wrapper.prototype[i] = wrapper(i)
}
}
function wrapper(method) {
return function(/*...*/) {
var args = Array.prototype.slice.apply(arguments)
args[0] = Path.join(this.path, args[0] || '')
return fs_storage[method].apply(null, args)
}
}
return Wrapper
})()
module.exports = Storage

View File

@@ -1,28 +1,23 @@
var Logger = require('bunyan')
, Error = require('http-errors')
, Stream = require('stream')
, utils = require('./utils')
function getlvl(x) {
if (x < 15) {
return 'trace'
} else if (x < 25) {
return 'debug'
} else if (x < 35) {
return 'info'
} else if (x == 35) {
return 'http'
} else if (x < 45) {
return 'warn'
} else if (x < 55) {
return 'error'
} else {
return 'fatal'
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'
}
}
module.exports.setup = function(logs) {
var streams = []
if (!logs) logs = [{ type: 'stdout', format: 'pretty', level: 'http' }]
if (logs == null) logs = [{ type: 'stdout', format: 'pretty', level: 'http' }]
logs.forEach(function(target) {
var stream = new Stream()
@@ -35,11 +30,11 @@ module.exports.setup = function(logs) {
if (target.format === 'pretty') {
// making fake stream for prettypritting
stream.write = function(obj) {
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + "\n")
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n')
}
} else {
stream.write = function(obj) {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + "\n")
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n')
}
}
} else if (target.type === 'file') {
@@ -48,15 +43,19 @@ module.exports.setup = function(logs) {
Logger.emit('error', err)
})
stream.write = function(obj) {
dest.write(JSON.stringify(obj, Logger.safeCycles()) + "\n")
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 new Error('wrong target type for a log')
throw Error('wrong target type for a log')
}
if (target.level === 'http') target.level = 35
streams.push({
type: "raw",
type: 'raw',
level: target.level || 35,
stream: stream,
})
@@ -101,10 +100,10 @@ function pad(str) {
}
var subsystems = [{
in: '\x1b[32m<--\x1b[39m',
out: '\x1b[33m-->\x1b[39m',
fs: '\x1b[90m-=-\x1b[39m',
default: '\x1b[34m---\x1b[39m',
in: '\033[32m<--\033[39m',
out: '\033[33m-->\033[39m',
fs: '\033[90m-=-\033[39m',
default: '\033[34m---\033[39m',
}, {
in: '<--',
out: '-->',
@@ -132,12 +131,12 @@ function print(type, msg, obj, colors) {
}
if (typeof(str) === 'string') {
if (!colors) {
if (!colors || ~str.indexOf('\n')) {
return str
} else if (is_error) {
return '\x1b[31m' + str + '\x1b[39m'
return '\033[31m' + str + '\033[39m'
} else {
return '\x1b[32m' + str + '\x1b[39m'
return '\033[32m' + str + '\033[39m'
}
} else {
return require('util').inspect(str, void 0, void 0, colors)
@@ -147,9 +146,9 @@ function print(type, msg, obj, colors) {
// ^^--- black magic... kidding, just "colors ? 0 : 1"
if (colors) {
return " \x1b[" + levels[type] + "m" + (pad(type)) + "\x1b[39m " + sub + " " + finalmsg
return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg
} else {
return " " + (pad(type)) + " " + sub + " " + finalmsg
return ' ' + (pad(type)) + ' ' + sub + ' ' + finalmsg
}
}

View File

@@ -1,27 +1,24 @@
var crypto = require('crypto')
, Error = require('http-errors')
, utils = require('./utils')
, UError = require('./error').UserError
, Logger = require('./logger')
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)
if (value.charAt(0) === '-') {
// special case in couchdb usually
next('route')
} else if (utils.validate_name(value)) {
next()
} else {
next(new UError({
status: 403,
msg: 'invalid package name',
}))
next(Error[403]('invalid ' + 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, expect: '+expect+', got: '+req.headers['content-type'],
}))
next(Error[415]('wrong content-type, expect: ' + expect
+ ', got: '+req.headers['content-type']))
} else {
next()
}
@@ -32,50 +29,24 @@ module.exports.expect_json = function expect_json(req, res, next) {
if (!utils.is_object(req.body)) {
return next({
status: 400,
msg: 'can\'t parse incoming json',
message: 'can\'t parse incoming json',
})
}
next()
}
module.exports.basic_auth = function basic_auth(callback) {
module.exports.anti_loop = function(config) {
return function(req, res, next) {
var authorization = req.headers.authorization
if (req.user) return next()
if (authorization == null) {
req.user = req.remoteUser = undefined
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',
})
if (req.headers.via != null) {
var arr = req.headers.via.split(',')
for (var i=0; i<arr.length; i++) {
var m = arr[i].match(/\s*(\S+)\s+(\S+)/)
if (m && m[2] === config.server_id) {
return next(Error[508]('loop detected'))
}
}
}
next()
}
}
@@ -96,23 +67,42 @@ module.exports.log_and_etagify = function(req, res, next) {
if (_auth) req.headers.authorization = _auth
var bytesin = 0
req.on('data', function(chunk){ bytesin += chunk.length })
req.on('data', function(chunk) {
bytesin += chunk.length
})
var _send = res.send
res.send = function(body) {
if (typeof(body) === 'string' || typeof(body) === 'object') {
res.header('Content-type', 'application/json')
if (typeof(body) === 'object' && body != null) {
if (body.error) {
res._sinopia_error = body.error
try {
if (typeof(body) === 'string' || typeof(body) === 'object') {
if (!res.getHeader('Content-type')) {
res.header('Content-type', 'application/json')
}
body = JSON.stringify(body, undefined, '\t')
}
res.header('ETag', '"' + md5sum(body) + '"')
} else {
// send(null), send(204), etc.
if (typeof(body) === 'object' && body != null) {
if (typeof(body.error) === 'string') {
res._sinopia_error = body.error
}
body = JSON.stringify(body, undefined, '\t') + '\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 sinopia 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 (res.socket != null) res.socket.destroy()
return
} else {
throw err
}
}
res.send = _send
@@ -127,23 +117,23 @@ module.exports.log_and_etagify = function(req, res, next) {
}
function log() {
var msg = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\''
var message = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\''
if (res._sinopia_error) {
msg += ', error: @{!error}'
message += ', error: @{!error}'
} else {
msg += ', bytes: @{bytes.in}/@{bytes.out}'
message += ', bytes: @{bytes.in}/@{bytes.out}'
}
req.log.warn({
request: {method: req.method, url: req.url},
level: 35, // http
user: req.user,
user: req.remote_user.name,
status: res.statusCode,
error: res._sinopia_error,
bytes: {
in: bytesin,
out: bytesout,
}
}, msg)
}, message)
}
req.on('close', function() {

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" ]

45
lib/search.js Normal file
View File

@@ -0,0 +1,45 @@
var lunr = require('lunr')
var Search = function() {
this.index = lunr(function () {
this.field('name', {boost: 10});
this.field('description', {boost: 4});
this.field('author', {boost: 6});
this.field('readme');
});
};
Search.prototype = {
query: function(q) {
return this.index.search(q);
},
add: function(package) {
this.index.add({
id: package.name,
name: package.name,
description: package.description,
author: package._npmUser ? package._npmUser.name : '???'
});
},
remove: function(name) {
this.index.remove({
id: name
});
},
reindex: function() {
var self = this;
this.storage.get_local(function(err, packages) {
var i = packages.length;
while(i--) {
self.add(packages[i]);
}
});
},
configureStorage: function(storage) {
this.storage = storage;
this.reindex();
}
};
module.exports = new Search();

BIN
lib/static/ajax.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
lib/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
lib/static/fontello.eot Normal file

Binary file not shown.

15
lib/static/fontello.svg Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2014 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="search" unicode="&#xe801;" d="m643 386q0 103-74 176t-176 74-177-74-73-176 73-177 177-73 176 73 74 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 152-31 126-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
<glyph glyph-name="cancel" unicode="&#xe803;" d="m724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
<glyph glyph-name="right-open" unicode="&#xe802;" d="m613 386q0-29-20-51l-364-363q-21-21-50-21t-51 21l-42 42q-21 21-21 50 0 30 21 51l271 271-271 270q-21 22-21 51 0 30 21 50l42 42q20 21 51 21t50-21l364-363q20-21 20-50z" horiz-adv-x="642.9" />
<glyph glyph-name="angle-right" unicode="&#xe800;" d="m332 314q0-7-6-13l-260-260q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l260-260q6-5 6-13z" horiz-adv-x="357.1" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
lib/static/fontello.ttf Normal file

Binary file not shown.

BIN
lib/static/fontello.woff Normal file

Binary file not shown.

4
lib/static/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
lib/static/logo-sm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
lib/static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

7252
lib/static/main.css Normal file

File diff suppressed because one or more lines are too long

947
lib/static/main.js Normal file
View File

@@ -0,0 +1,947 @@
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var templater = require("handlebars/runtime").default.template;module.exports = templater(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression;
buffer += "<div class=\"entry\" data-name=\"";
if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
buffer += escapeExpression(stack1)
+ "\" data-version=\"";
if (helper = helpers.version) { stack1 = helper.call(depth0, {hash:{},data:data}); }
else { helper = (depth0 && depth0.version); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
buffer += escapeExpression(stack1)
+ "\">\n <div class=\"row\">\n <div class=\"col-md-8 col-sm-8\">\n <h4 class=\"title\">\n <a class='name icon-angle-right red' href='javascript:void(0)'>";
if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
buffer += escapeExpression(stack1)
+ "</a>\n <small class='version'>v";
if (helper = helpers.version) { stack1 = helper.call(depth0, {hash:{},data:data}); }
else { helper = (depth0 && depth0.version); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
buffer += escapeExpression(stack1)
+ "</small>\n </h4>\n </div>\n <div class=\"col-md-4 col-sm-4\">\n <div class=\"author pull-right\">\n <small>By: "
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0._npmUser)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ "</small>\n </div>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n <p class=\"description\">";
if (helper = helpers.description) { stack1 = helper.call(depth0, {hash:{},data:data}); }
else { helper = (depth0 && depth0.description); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
buffer += escapeExpression(stack1)
+ "</p>\n </div>\n </div>\n</div>\n";
return buffer;
});
},{"handlebars/runtime":11}],2:[function(require,module,exports){
var $ = require('unopinionate').selector,
onClick = require('onclick'),
transitionComplete = require('transition-complete');
$(function() {
onClick('.entry .name', function() {
var $this = $(this),
$entry = $this.closest('.entry');
//Close entry
if($entry.hasClass('open')) {
$entry
.height($entry.outerHeight())
.removeClass('open');
setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px');
}, 0);
transitionComplete(function() {
$entry.find('.readme').remove();
$entry.css('height', 'auto');
});
}
//Open entry
else {
//Close open entries
$('.entry.open').each(function() {
var $entry = $(this);
$entry
.height($entry.outerHeight())
.removeClass('open');
setTimeout(function() {
$entry.css('height', $entry.attr('data-height') + 'px');
}, 0);
transitionComplete(function() {
$entry.find('.readme').remove();
$entry.css('height', 'auto');
});
});
//Add the open class
$entry.addClass('open');
//Explicitly set heights for transitions
var height = $entry.outerHeight();
$entry
.attr('data-height', height)
.css('height', height);
//Get the data
$.ajax({
url: '-/readme/'+$entry.attr('data-name')+'/'+$entry.attr('data-version'),
dataType: 'text',
success: function(html) {
var $readme = $("<div class='readme'>")
.html(html)
.appendTo($entry);
$entry.height(height + $readme.outerHeight());
transitionComplete(function() {
$entry.css('height', 'auto');
});
}
});
}
});
});
},{"onclick":12,"transition-complete":14,"unopinionate":15}],3:[function(require,module,exports){
require('./search');
require('./entry');
},{"./entry":2,"./search":4}],4:[function(require,module,exports){
var $ = require('unopinionate').selector,
template = require('../entry.hbs'),
onScroll = require('onscroll');
$(function() {
'use strict';
(function( window, document ) {
var $form = $('#search-form');
var $input = $form.find('input');
var $body = $('body');
var $clear = $form.find('.clear');
var $searchResults = $("#search-results");
var $pkgListing = $("#all-packages");
var $searchBtn = $('.js-search-btn');
var request;
var currentResults;
var toggle = function( validQuery ) {
$searchResults.toggleClass( 'show', validQuery );
$pkgListing.toggleClass( 'hide', validQuery );
$searchBtn.find('i').toggleClass( 'icon-cancel', validQuery );
$searchBtn.find('i').toggleClass( 'icon-search', !validQuery );
};
$form.bind('submit keyup', function(e) {
var q, qBool;
e.preventDefault();
q = $input.val();
qBool = q !== '';
toggle( qBool );
if( !qBool ) {
if( request && typeof request.abort === 'function' ) {
request.abort();
}
currentResults = null;
$searchResults.html('');
return;
}
if( request && typeof request.abort === 'function' ) {
request.abort();
}
if( !currentResults ) {
$searchResults.html( "<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>" );
}
request = $.getJSON('-/search/' + q, function( results ) {
currentResults = results;
if( results.length > 0 ) {
var html = '';
$.each(results, function( i, entry ) {
html += template( entry );
});
$searchResults.html(html);
} else {
$searchResults.html("<div class='no-results'><big>No Results</big></div>");
}
});
});
$( document ).on( 'click', '.icon-cancel', function( e ) {
e.preventDefault();
$input.val('');
$form.keyup();
});
})( window, window.document );
});
},{"../entry.hbs":1,"onscroll":13,"unopinionate":15}],5:[function(require,module,exports){
"use strict";
/*globals Handlebars: true */
var base = require("./handlebars/base");
// Each of these augment the Handlebars object. No need to setup here.
// (This is done to easily share code between commonjs and browse envs)
var SafeString = require("./handlebars/safe-string")["default"];
var Exception = require("./handlebars/exception")["default"];
var Utils = require("./handlebars/utils");
var runtime = require("./handlebars/runtime");
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
var create = function() {
var hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base);
hb.SafeString = SafeString;
hb.Exception = Exception;
hb.Utils = Utils;
hb.VM = runtime;
hb.template = function(spec) {
return runtime.template(spec, hb);
};
return hb;
};
var Handlebars = create();
Handlebars.create = create;
exports["default"] = Handlebars;
},{"./handlebars/base":6,"./handlebars/exception":7,"./handlebars/runtime":8,"./handlebars/safe-string":9,"./handlebars/utils":10}],6:[function(require,module,exports){
"use strict";
var Utils = require("./utils");
var Exception = require("./exception")["default"];
var VERSION = "1.3.0";
exports.VERSION = VERSION;var COMPILER_REVISION = 4;
exports.COMPILER_REVISION = COMPILER_REVISION;
var REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '>= 1.0.0'
};
exports.REVISION_CHANGES = REVISION_CHANGES;
var isArray = Utils.isArray,
isFunction = Utils.isFunction,
toString = Utils.toString,
objectType = '[object Object]';
function HandlebarsEnvironment(helpers, partials) {
this.helpers = helpers || {};
this.partials = partials || {};
registerDefaultHelpers(this);
}
exports.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
constructor: HandlebarsEnvironment,
logger: logger,
log: log,
registerHelper: function(name, fn, inverse) {
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
},
registerPartial: function(name, str) {
if (toString.call(name) === objectType) {
Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
}
};
function registerDefaultHelpers(instance) {
instance.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Exception("Missing helper: '" + arg + "'");
}
});
instance.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
if (isFunction(context)) { context = context.call(this); }
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if (isArray(context)) {
if(context.length > 0) {
return instance.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
return fn(context);
}
});
instance.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
if(context && typeof context === 'object') {
if (isArray(context)) {
for(var j = context.length; i<j; i++) {
if (data) {
data.index = i;
data.first = (i === 0);
data.last = (i === (context.length-1));
}
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) {
data.key = key;
data.index = i;
data.first = (i === 0);
}
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
instance.registerHelper('if', function(conditional, options) {
if (isFunction(conditional)) { conditional = conditional.call(this); }
// Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function(conditional, options) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
});
instance.registerHelper('with', function(context, options) {
if (isFunction(context)) { context = context.call(this); }
if (!Utils.isEmpty(context)) return options.fn(context);
});
instance.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
instance.log(level, context);
});
}
var logger = {
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
// State enum
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
level: 3,
// can be overridden in the host environment
log: function(level, obj) {
if (logger.level <= level) {
var method = logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, obj);
}
}
}
};
exports.logger = logger;
function log(level, obj) { logger.log(level, obj); }
exports.log = log;var createFrame = function(object) {
var obj = {};
Utils.extend(obj, object);
return obj;
};
exports.createFrame = createFrame;
},{"./exception":7,"./utils":10}],7:[function(require,module,exports){
"use strict";
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
function Exception(message, node) {
var line;
if (node && node.firstLine) {
line = node.firstLine;
message += ' - ' + line + ':' + node.firstColumn;
}
var tmp = Error.prototype.constructor.call(this, message);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
if (line) {
this.lineNumber = line;
this.column = node.firstColumn;
}
}
Exception.prototype = new Error();
exports["default"] = Exception;
},{}],8:[function(require,module,exports){
"use strict";
var Utils = require("./utils");
var Exception = require("./exception")["default"];
var COMPILER_REVISION = require("./base").COMPILER_REVISION;
var REVISION_CHANGES = require("./base").REVISION_CHANGES;
function checkRevision(compilerInfo) {
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
}
}
}
exports.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
function template(templateSpec, env) {
if (!env) {
throw new Exception("No environment passed to template");
}
// Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as psuedo-supported APIs.
var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
var result = env.VM.invokePartial.apply(this, arguments);
if (result != null) { return result; }
if (env.compile) {
var options = { helpers: helpers, partials: partials, data: data };
partials[name] = env.compile(partial, { data: data !== undefined }, env);
return partials[name](context, options);
} else {
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
}
};
// Just add water
var container = {
escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper,
programs: [],
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
programWrapper = program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = program(i, fn);
}
return programWrapper;
},
merge: function(param, common) {
var ret = param || common;
if (param && common && (param !== common)) {
ret = {};
Utils.extend(ret, common);
Utils.extend(ret, param);
}
return ret;
},
programWithDepth: env.VM.programWithDepth,
noop: env.VM.noop,
compilerInfo: null
};
return function(context, options) {
options = options || {};
var namespace = options.partial ? options : env,
helpers,
partials;
if (!options.partial) {
helpers = options.helpers;
partials = options.partials;
}
var result = templateSpec.call(
container,
namespace, context,
helpers,
partials,
options.data);
if (!options.partial) {
env.VM.checkRevision(container.compilerInfo);
}
return result;
};
}
exports.template = template;function programWithDepth(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
var prog = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
prog.program = i;
prog.depth = args.length;
return prog;
}
exports.programWithDepth = programWithDepth;function program(i, fn, data) {
var prog = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
prog.program = i;
prog.depth = 0;
return prog;
}
exports.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
var options = { partial: true, helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
}
}
exports.invokePartial = invokePartial;function noop() { return ""; }
exports.noop = noop;
},{"./base":6,"./exception":7,"./utils":10}],9:[function(require,module,exports){
"use strict";
// Build out our basic SafeString type
function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = function() {
return "" + this.string;
};
exports["default"] = SafeString;
},{}],10:[function(require,module,exports){
"use strict";
/*jshint -W004 */
var SafeString = require("./safe-string")["default"];
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
function escapeChar(chr) {
return escape[chr] || "&amp;";
}
function extend(obj, value) {
for(var key in value) {
if(Object.prototype.hasOwnProperty.call(value, key)) {
obj[key] = value[key];
}
}
}
exports.extend = extend;var toString = Object.prototype.toString;
exports.toString = toString;
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
var isFunction = function(value) {
return typeof value === 'function';
};
// fallback for older versions of Chrome and Safari
if (isFunction(/x/)) {
isFunction = function(value) {
return typeof value === 'function' && toString.call(value) === '[object Function]';
};
}
var isFunction;
exports.isFunction = isFunction;
var isArray = Array.isArray || function(value) {
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
};
exports.isArray = isArray;
function escapeExpression(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof SafeString) {
return string.toString();
} else if (!string && string !== 0) {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = "" + string;
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
}
exports.escapeExpression = escapeExpression;function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
}
}
exports.isEmpty = isEmpty;
},{"./safe-string":9}],11:[function(require,module,exports){
// Create a simple path alias to allow browserify to resolve
// the runtime on a supported path.
module.exports = require('./dist/cjs/handlebars.runtime');
},{"./dist/cjs/handlebars.runtime":5}],12:[function(require,module,exports){
var $ = require('unopinionate').selector;
var $document = $(document),
bindings = {};
var click = function(events) {
click.bind.apply(click, arguments);
return click;
};
/*** Configuration Options ***/
click.distanceLimit = 10;
click.timeLimit = 140;
/*** Useful Properties ***/
click.isTouch = ('ontouchstart' in window) ||
window.DocumentTouch &&
document instanceof DocumentTouch;
/*** Cached Functions ***/
var onTouchstart = function(e) {
e.stopPropagation(); //Prevents multiple click events from happening
click._doAnywheres(e);
var $this = $(this),
startTime = new Date().getTime(),
startPos = click._getPos(e);
$this.one('touchend', function(e) {
e.preventDefault(); //Prevents click event from firing
var time = new Date().getTime() - startTime,
endPos = click._getPos(e),
distance = Math.sqrt(
Math.pow(endPos.x - startPos.x, 2) +
Math.pow(endPos.y - startPos.y, 2)
);
if(time < click.timeLimit && distance < click.distanceLimit) {
//Find the correct callback
$.each(bindings, function(selector, callback) {
if($this.is(selector)) {
callback.apply(e.target, [e]);
return false;
}
});
}
});
};
/*** API ***/
click.bind = function(events) {
//Argument Surgery
if(!$.isPlainObject(events)) {
newEvents = {};
newEvents[arguments[0]] = arguments[1];
events = newEvents;
}
$.each(events, function(selector, callback) {
/*** Register Binding ***/
if(typeof bindings[selector] != 'undefined') {
click.unbind(selector); //Ensure no duplicates
}
bindings[selector] = callback;
/*** Touch Support ***/
if(click.isTouch) {
$document.delegate(selector, 'touchstart', onTouchstart);
}
/*** Mouse Support ***/
$document.delegate(selector, 'click', function(e) {
e.stopPropagation(); //Prevents multiple click events from happening
//click._doAnywheres(e); //Do anywheres first to be consistent with touch order
callback.apply(this, [e]);
});
});
return this;
};
click.unbind = function(selector) {
$document
.undelegate(selector, 'touchstart')
.undelegate(selector, 'click');
delete bindings[selector];
return this;
};
click.unbindAll = function() {
$.each(bindings, function(selector, callback) {
$document
.undelegate(selector, 'touchstart')
.undelegate(selector, 'click');
});
bindings = {};
return this;
};
click.trigger = function(selector, e) {
e = e || $.Event('click');
if(typeof bindings[selector] != 'undefined') {
bindings[selector](e);
}
else {
console.error("No click events bound for selector '"+selector+"'.");
}
return this;
};
click.anywhere = function(callback) {
click._anywheres.push(callback);
return this;
};
/*** Internal (but useful) Methods ***/
click._getPos = function(e) {
e = e.originalEvent;
if(e.pageX || e.pageY) {
return {
x: e.pageX,
y: e.pageY
};
}
else if(e.changedTouches) {
return {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY
};
}
else {
return {
x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop
};
}
};
click._anywheres = [];
click._doAnywheres = function(e) {
var i = click._anywheres.length;
while(i--) {
click._anywheres[i](e);
}
};
$(document).bind('mousedown', click._doAnywheres);
module.exports = click;
},{"unopinionate":15}],13:[function(require,module,exports){
var $ = require('unopinionate').selector;
var bodyScrollers = [];
$(function() {
var $html = $('html'),
$body = $('body');
$(window, document, 'body').bind('scroll touchmove', function() {
var top = $html[0].scrollTop || $body[0].scrollTop;
for(var i=0; i<bodyScrollers.length; i++) {
bodyScrollers[i](top);
}
});
});
var onScroll = function(callback) {
bodyScrollers.push(callback);
};
module.exports = onScroll;
},{"unopinionate":15}],14:[function(require,module,exports){
(function(root) {
var callbacks = [];
var transitionComplete = function(callback) {
if(callbacks.length === 0) {
setEvent();
}
callbacks.push(callback);
};
function setEvent() {
document.addEventListener(eventName(), function() {
var i = callbacks.length;
while(i--) {
callbacks[i]();
}
callbacks = [];
});
}
var _eventName;
function eventName() {
if(!_eventName) {
// Sourced from: http://stackoverflow.com/questions/5023514/how-do-i-normalize-css3-transition-functions-across-browsers
var el = document.createElement('fakeelement');
transitions = {
transition: 'transitionend',
OTransition: 'oTransitionEnd',
MozTransition: 'transitionend',
WebkitTransition: 'webkitTransitionEnd'
};
for(var t in transitions) {
if(el.style[t] !== undefined) {
_eventName = transitions[t];
}
}
}
return _eventName;
}
/*** Export ***/
// AMD
if(typeof define === 'function' && define.amd) {
define([], function() {
return transitionComplete;
});
}
// CommonJS
else if(typeof exports !== 'undefined') {
module.exports = transitionComplete;
}
// Browser Global
else {
root.transitionComplete = transitionComplete;
}
})(this);
},{}],15:[function(require,module,exports){
(function (global){
(function(root) {
var unopinionate = {
selector: root.jQuery || root.Zepto || root.ender || root.$,
template: root.Handlebars || root.Mustache
};
/*** Export ***/
//AMD
if(typeof define === 'function' && define.amd) {
define([], function() {
return unopinionate;
});
}
//CommonJS
else if(typeof module.exports !== 'undefined') {
module.exports = unopinionate;
}
//Global
else {
root.unopinionate = unopinionate;
}
})(typeof window != 'undefined' ? window : global);
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}]},{},[3]);

View File

@@ -64,7 +64,7 @@ module.exports.middleware = function(req, res, next) {
var _writeHead = res.writeHead
res.writeHead = function(status) {
if (status in images) {
res.setHeader("X-Status-Cat", module.exports.get_image(status))
res.setHeader('X-Status-Cat', module.exports.get_image(status))
}
_writeHead.apply(res, arguments)
}

View File

@@ -1,11 +1,11 @@
var async = require('async')
, semver = require('semver')
, UError = require('./error').UserError
, assert = require('assert')
, Error = require('http-errors')
, Local = require('./local-storage')
, Proxy = require('./up-storage')
, mystreams = require('./streams')
, utils = require('./utils')
, transaction = require('./transaction')
, Logger = require('./logger')
//
// Implements Storage interface
@@ -24,6 +24,7 @@ function Storage(config) {
this.uplinks[p].upname = p
}
this.local = new Local(config)
this.logger = Logger.logger.child()
return this
}
@@ -32,16 +33,9 @@ function Storage(config) {
// 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 metadata locally and send requests to do
// the same to all uplinks with write access. If all actions succeeded, we
// report success, if just one uplink fails, we abort.
// If it isn't, we create package locally
//
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
// we report failure, but package is not removed from uplink1. This might
// require manual intervention.
//
// Used storages: local (write) && uplinks (proxy_access, r/o) &&
// uplinks (proxy_publish, write)
// Used storages: local (write) && uplinks
//
Storage.prototype.add_package = function(name, metadata, callback) {
var self = this
@@ -51,118 +45,76 @@ Storage.prototype.add_package = function(name, metadata, callback) {
// - when we publishing package, we only publish it to some of them
// so all requests are necessary
check_package(function(err) {
check_package_local(function(err) {
if (err) return callback(err)
publish_package(function(err) {
check_package_remote(function(err) {
if (err) return callback(err)
callback()
publish_package(function(err) {
if (err) return callback(err)
callback()
})
})
})
function check_package(cb) {
self.get_package(name, function(err, results, err_results) {
function check_package_local(cb) {
self.local.get_package(name, {}, function(err, results) {
if (err && err.status !== 404) return cb(err)
if (results) {
return cb(Error[409]('this package is already present'))
}
cb()
})
}
function check_package_remote(cb) {
self._sync_package_with_uplinks(name, null, {}, function(err, results, err_results) {
// something weird
if (err && err.status !== 404) return cb(err)
// checking package
if (results) {
return cb(Error[409]('this package is already present'))
}
for (var i=0; i<err_results.length; i++) {
// checking error
// if uplink fails with a status other than 404, we report failure
if (err_results[i][0] != null) {
if (err_results[i][0].status !== 404) {
return cb(new UError({
status: 503,
msg: 'one of the uplinks is down, refuse to publish'
}))
return cb(Error[503]('one of the uplinks is down, refuse to publish'))
}
}
}
// checking package
if (results) {
return cb(new UError({
status: 409,
msg: 'this package is already present'
}))
}
return cb()
})
}
function publish_package(cb) {
var fw_uplinks = []
for (var i in self.uplinks) {
if (self.config.proxy_publish(name, i)) {
fw_uplinks.push(self.uplinks[i])
}
}
transaction(
fw_uplinks,
function localAction(cb) {
self.local.add_package(name, metadata, cb)
},
function localRollback(cb) {
self.local.remove_package(name, cb)
},
function remoteAction(remote, cb) {
remote.add_package(name, metadata, cb)
},
function remoteRollback(remote, cb) {
remote.remove_package(name, cb)
},
function(err) {
if (!err) {
callback()
} else if (err.uplink === 'local') {
return callback(err)
} else {
// hide uplink error with general message
return callback(new UError({
status: 503,
msg: 'can\'t upload to one of the uplinks, refuse to publish'
}))
}
}
)
self.local.add_package(name, metadata, callback)
}
}
//
// Add a new version of package {name} to a system
//
// Function uploads a new package version to all uplinks with write access
// and if everything succeeded it adds it locally.
//
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
// we report failure, but package is not removed from uplink1. This might
// require manual intervention.
//
// Used storages: local (write) && uplinks (proxy_publish, write)
// Used storages: local (write)
//
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
var self = this
return this.local.add_version(name, version, metadata, tag, callback)
}
var uplinks = []
for (var i in self.uplinks) {
if (self.config.proxy_publish(name, i)) {
uplinks.push(self.uplinks[i])
}
}
async.map(uplinks, function(up, cb) {
up.add_version(name, version, metadata, tag, cb)
}, function(err, results) {
if (err) {
return callback(new UError({
status: 503,
msg: 'can\'t upload to one of the uplinks, refuse to publish'
}))
}
self.local.add_version(name, version, metadata, tag, callback)
})
//
// Tags a package version with a provided tag
//
// Used storages: local (write)
//
Storage.prototype.add_tags = function(name, tag_hash, callback) {
return this.local.add_tags(name, tag_hash, callback)
}
//
@@ -171,13 +123,7 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback)
// Function changes a package info from local storage and all uplinks with
// write access.
//
// TODO: currently it works only locally
//
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
// we report failure, but package is not removed from uplink1. This might
// require manual intervention.
//
// Used storages: local (write) && uplinks (proxy_publish, write)
// Used storages: local (write)
//
Storage.prototype.change_package = function(name, metadata, revision, callback) {
return this.local.change_package(name, metadata, revision, callback)
@@ -186,16 +132,9 @@ Storage.prototype.change_package = function(name, metadata, revision, callback)
//
// Remove a package from a system
//
// Function removes a package from local storage and all uplinks with
// write access.
// Function removes a package from local storage
//
// TODO: currently it works only locally
//
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
// we report failure, but package is not removed from uplink1. This might
// require manual intervention.
//
// Used storages: local (write) && uplinks (proxy_publish, write)
// Used storages: local (write)
//
Storage.prototype.remove_package = function(name, callback) {
return this.local.remove_package(name, callback)
@@ -204,17 +143,11 @@ Storage.prototype.remove_package = function(name, callback) {
//
// Remove a tarball from a system
//
// Function removes a tarball from local storage and all uplinks with
// write access. Tarball in question should not be linked to in any existing
// 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.
//
// TODO: currently it works only locally
//
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
// we report failure, but package is not removed from uplink1. This might
// require manual intervention.
//
// Used storages: local (write) && uplinks (proxy_publish, write)
// Used storages: local (write)
//
Storage.prototype.remove_tarball = function(name, filename, revision, callback) {
return this.local.remove_tarball(name, filename, revision, callback)
@@ -225,71 +158,10 @@ Storage.prototype.remove_tarball = function(name, filename, revision, callback)
//
// Function is syncronous and returns a WritableStream
//
// Function uploads a tarball to all uplinks with write access and to
// local storage in parallel with a speed of a slowest pipe. It reports
// success if all uploads succeed.
//
// Used storages: local (write) && uplinks (proxy_publish, write)
// Used storages: local (write)
//
Storage.prototype.add_tarball = function(name, filename) {
var stream = new mystreams.UploadTarballStream()
var self = this
var upstreams = []
var localstream = self.local.add_tarball(name, filename)
upstreams.push(localstream)
for (var i in self.uplinks) {
if (self.config.proxy_publish(name, i)) {
upstreams.push(self.uplinks[i].add_tarball(name, filename))
}
}
function bail(err) {
upstreams.forEach(function(upstream) {
upstream.abort()
})
}
upstreams.forEach(function(upstream) {
stream.pipe(upstream)
upstream.on('error', function(err) {
if (err.code === 'EEXISTS') {
stream.emit('error', new UError({
status: 409,
msg: 'this tarball is already present'
}))
} else if (!stream.status && upstream !== localstream) {
stream.emit('error', new UError({
status: 503,
msg: 'one or more uplinks are unreachable'
}))
} else {
stream.emit('error', err)
}
bail(err)
})
upstream.on('success', function() {
upstream._sinopia_success = true
if (upstreams.filter(function(upstream) {
return !upstream._sinopia_success
}).length === 0) {
stream.emit('success')
}
})
})
stream.abort = function() {
bail()
}
stream.done = function() {
upstreams.forEach(function(upstream) {
upstream.done()
})
}
return stream
return this.local.add_tarball(name, filename)
}
//
@@ -326,52 +198,77 @@ Storage.prototype.get_tarball = function(name, filename) {
rstream = null // gc
self.local.get_package(name, function(err, info) {
if (err) return stream.emit('error', err)
if (!err && info._distfiles && info._distfiles[filename] != null) {
// information about this file exists locally
serve_file(info._distfiles[filename])
if (info._distfiles[filename] == null) {
return stream.emit('error', err404)
}
} else {
// we know nothing about this file, trying to get information elsewhere
var file = info._distfiles[filename]
var uplink = null
for (var p in self.uplinks) {
if (self.uplinks[p].can_fetch_url(file.url)) {
uplink = self.uplinks[p]
}
}
if (uplink == null) {
uplink = new Proxy({
url: file.url,
_autogenerated: true,
}, self.config)
}
self._sync_package_with_uplinks(name, info, {}, function(err, info) {
if (err) return stream.emit('error', err)
var savestream = self.local.add_tarball(name, filename)
savestream.on('error', function(err) {
savestream.abort()
stream.emit('error', err)
})
savestream.on('open', function() {
var rstream2 = uplink.get_url(file.url)
rstream2.on('error', function(err) {
savestream.abort()
stream.emit('error', err)
if (!info._distfiles || info._distfiles[filename] == null) {
return stream.emit('error', err404)
}
serve_file(info._distfiles[filename])
})
rstream2.on('end', function() {
savestream.done()
})
// XXX: check, what would happen if client disconnects?
rstream2.pipe(stream)
rstream2.pipe(savestream)
})
}
})
})
rstream.on('content-length', function(v) {
stream.emit('content-length', v)
})
rstream.on('open', function() {
is_open = true
rstream.pipe(stream)
})
return stream
function serve_file(file) {
var uplink = null
for (var p in self.uplinks) {
if (self.uplinks[p].can_fetch_url(file.url)) {
uplink = self.uplinks[p]
}
}
if (uplink == null) {
uplink = new Proxy({
url: file.url,
_autogenerated: true,
}, self.config)
}
var savestream = self.local.add_tarball(name, filename)
function on_open() {
var rstream2 = uplink.get_url(file.url)
rstream2.on('error', function(err) {
if (savestream) savestream.abort()
savestream = null
stream.emit('error', err)
})
rstream2.on('end', function() {
if (savestream) savestream.done()
})
rstream2.on('content-length', function(v) {
stream.emit('content-length', v)
if (savestream) savestream.emit('content-length', v)
})
rstream2.pipe(stream)
if (savestream) rstream2.pipe(savestream)
}
savestream.on('open', function() {
on_open()
})
savestream.on('error', function() {
if (savestream) savestream.abort()
savestream = null
on_open()
})
}
}
//
@@ -383,93 +280,271 @@ Storage.prototype.get_tarball = function(name, filename) {
//
// Used storages: local && uplink (proxy_access)
//
Storage.prototype.get_package = function(name, callback) {
// NOTE: callback(err, result, _uplink_errors)
// _uplink_errors is an array of errors used internally
// XXX: move it to another function maybe?
Storage.prototype.get_package = function(name, options, callback) {
if (typeof(options) === 'function') callback = options, options = {}
var self = this
self.local.get_package(name, function(err, data) {
self.local.get_package(name, options, function(err, data) {
if (err && (!err.status || err.status >= 500)) {
// report internal errors right away
return cb(err)
return callback(err)
}
var uplinks = []
for (var i in self.uplinks) {
if (self.config.proxy_access(name, i)) {
uplinks.push(self.uplinks[i])
self._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) {
if (err) return callback(err)
var whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme']
for (var i in result) {
if (whitelist.indexOf(i) === -1) delete result[i]
}
}
var result = data || {
if (self.config.ignore_latest_tag || !result['dist-tags'].latest) {
result['dist-tags'].latest = utils.semver_sort(Object.keys(result.versions))
}
for (var i in result['dist-tags']) {
if (Array.isArray(result['dist-tags'][i])) {
result['dist-tags'][i] = result['dist-tags'][i][result['dist-tags'][i].length-1]
if (result['dist-tags'][i] == null) delete result['dist-tags'][i]
}
}
// 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 invokes uplink.request for npm and local.get_recent_packages for
// local ones then sum up the result in a json object
//
// Used storages: local && uplink (proxy_access)
//
Storage.prototype.search = function(startkey, options, callback) {
var self = this
var uplinks = []
var i = 0
var uplinks
for (var p in self.uplinks) {
uplinks.push(p)
}
function merge_with_local_packages(err, res, body) {
if (err) return callback(err)
var j = 0
self.local.get_recent_packages(startkey, function(err, list) {
if (err) return callback(err)
var listL = list.length
if (!listL) return callback(null, body)
list.forEach(function(item) {
self.local.get_package(item.name, options, function(err, data) {
if (err) return callback(err)
var versions = utils.semver_sort(Object.keys(data.versions))
var latest = versions[versions.length - 1]
if (data.versions[latest]) {
body[item.name] = {
name : data.versions[latest].name,
description : data.versions[latest].description,
'dist-tags' : {
latest: latest
},
maintainers : data.versions[latest].maintainers || [data.versions[latest]._npmUser].filter(Boolean),
readmeFilename: data.versions[latest].readmeFilename || '',
time : {
modified: new Date(item.time).toISOString()
},
versions : {},
repository : data.versions[latest].repository,
keywords : data.versions[latest].keywords
}
body[item.name].versions[latest] = 'latest'
}
if (++j !== listL) {
return false
}
return callback(null, body)
})
})
})
}
function remote_search() {
var uplink = self.uplinks[uplinks[i]]
if (!uplink) {
return merge_with_local_packages(null, null, {})
}
self.uplinks[uplinks[i]].request({
uri: options.req.url,
timeout: self.uplinks[p].timeout,
json: true
}, function(err, res, body) {
if (err || Math.floor(res.statusCode / 100) > 3) {
i++
return remote_search()
}
return merge_with_local_packages(err, res, body)
})
}
remote_search()
}
Storage.prototype.get_local = function(callback) {
var self = this
, locals = this.config.localList.get()
, packages = [];
var getPackage = function(i) {
self.local.get_package(locals[i], function(err, info) {
if (!err) {
var latest = Array.isArray(info['dist-tags'].latest)
? utils.semver_sort(info['dist-tags'].latest).pop()
: info['dist-tags'].latest
if (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 information 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)
Storage.prototype._sync_package_with_uplinks = function(name, pkginfo, options, callback) {
var self = this
if (!pkginfo) {
var exists = false
pkginfo = {
name: name,
versions: {},
'dist-tags': {},
_uplinks: {},
}
var exists = !err
var latest = result['dist-tags'].latest
} else {
var exists = true
}
async.map(uplinks, function(up, cb) {
var oldetag = null
if (utils.is_object(result._uplinks[up.upname]))
oldetag = result._uplinks[up.upname].etag
var uplinks = []
for (var i in self.uplinks) {
if (self.config.proxy_access(name, i)) {
uplinks.push(self.uplinks[i])
}
}
up.get_package(name, oldetag, function(err, up_res, etag) {
if (err || !up_res) return cb(null, [err || new Error('no data')])
try {
utils.validate_metadata(up_res, name)
} catch(err) {
return cb(null, [err])
}
result._uplinks[up.upname] = {
etag: etag
}
var this_version = up_res['dist-tags'].latest
if (latest == null
|| (!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, uplink_errors) {
if (err) return callback(err)
if (!exists) {
return callback(new UError({
status: 404,
msg: 'no such package available'
}), null, uplink_errors)
async.map(uplinks, function(up, cb) {
var _options = Object.create(options)
if (utils.is_object(pkginfo._uplinks[up.upname])) {
var fetched = pkginfo._uplinks[up.upname].fetched
if (fetched && fetched > (Date.now() - up.maxage)) {
return cb()
}
self.local.update_versions(name, result, function(err) {
if (err) return callback(err)
_options.etag = pkginfo._uplinks[up.upname].etag
}
var whitelist = ['_rev', 'name', 'versions', 'dist-tags']
for (var i in result) {
if (!~whitelist.indexOf(i)) delete result[i]
}
callback(null, result, uplink_errors)
})
up.get_package(name, _options, function(err, up_res, etag) {
if (err && err.status === 304)
pkginfo._uplinks[up.upname].fetched = Date.now()
if (err || !up_res) return cb(null, [err || Error('no data')])
try {
utils.validate_metadata(up_res, name)
} catch(err) {
self.logger.error({
sub: 'out',
err: err,
}, 'package.json validating error @{!err.message}\n@{err.stack}')
return cb(null, [err])
}
pkginfo._uplinks[up.upname] = {
etag: etag,
fetched: Date.now()
}
try {
Storage._merge_versions(pkginfo, up_res, 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()
})
}, function(err, uplink_errors) {
assert(!err && Array.isArray(uplink_errors))
if (!exists) {
return callback( Error[404]('no such package available')
, null
, uplink_errors )
}
self.local.update_versions(name, pkginfo, function(err, pkginfo) {
if (err) return callback(err)
return callback(null, pkginfo, uplink_errors)
})
})
}
module.exports = Storage
// function gets a local info and an info from uplinks and tries to merge it
// exported for unit tests only
Storage._merge_versions = function(local, up, config) {
// copy new versions to a cache
// NOTE: if a certain version was updated, we can't refresh it reliably
for (var i in up.versions) {
if (local.versions[i] == null) {
local.versions[i] = up.versions[i]
}
}
// refresh dist-tags
for (var i in up['dist-tags']) {
var added = utils.tag_version(local, up['dist-tags'][i], i, config || {})
if (i === 'latest' && added) {
// if remote has more fresh package, we should borrow its readme
local.readme = up.readme
}
}
}
module.exports = Storage

View File

@@ -51,16 +51,3 @@ function add_abstract_method(self, name) {
})
}
function __test() {
var test = new ReadTarball()
test.abort()
setTimeout(function() {
test.abort = function() {
console.log('ok')
}
test.abort = function() {
throw 'fail'
}
}, 100)
}

View File

@@ -1,46 +0,0 @@
var async = require('async')
//
// Function performs a certain task on a multiple uplinks
// and reverts changes if something fails
//
// uplinks - list of uplinks not counting local
// localAction, localRollback - function(cb)
// remoteAction, remoteRollback - function(uplink, cb)
//
module.exports = function(uplinks, localAction, localRollback, remoteAction, remoteRollback, callback) {
var uplink_ids = uplinks.map(function(_, i) {
return i
})
localAction(function(err) {
if (err) return callback(err)
async.map(uplink_ids, function(i, cb) {
remoteAction(uplinks[i], function(err) {
cb(null, err)
})
}, function(err, res) {
var return_err = err
// let err be first non-null element in the array
for (var i=0; i<res.length; i++) {
if (return_err) break
return_err = res[i]
}
if (!return_err) return callback()
async.map(uplink_ids, function(i, cb) {
if (res[i]) return cb()
remoteRollback(uplinks[i], function() {
cb()
})
}, function(err) {
localRollback(function() {
callback(return_err)
})
})
})
})
}

View File

@@ -1,9 +1,13 @@
var URL = require('url')
, request = require('request')
, UError = require('./error').UserError
, Stream = require('stream')
, zlib = require('zlib')
, Error = require('http-errors')
, mystreams = require('./streams')
, Logger = require('./logger')
, utils = require('./utils')
, parse_interval = require('./config').parse_interval
, encode = encodeURIComponent
//
// Implements Storage interface
@@ -12,15 +16,14 @@ var URL = require('url')
function Storage(config, mainconfig) {
if (!(this instanceof Storage)) return new Storage(config)
this.config = config
this.is_alive = false
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)
if (this.url.hostname === 'registry.npmjs.org') {
this.ca = this.ca || require('./npmsslkeys')
// npm registry is too slow working with ssl :(
/*if (this.config._autogenerated) {
// encrypt all the things!
@@ -32,11 +35,24 @@ function Storage(config, mainconfig) {
_setupProxy.call(this, 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 + '\nWe changed time format to nginx-like one\n(see http://wiki.nginx.org/ConfigNotation)\nso please update your config accordingly')
}
// a bunch of different configurable timers
this.maxage = parse_interval(config_get('maxage' , '2m' ))
this.timeout = parse_interval(config_get('timeout' , '30s'))
this.max_fails = Number(config_get('max_fails' , 2 ))
this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' ))
return this
// just a helper (`config[key] || default` doesn't work because of zeroes)
function config_get(key, def) {
return config[key] != null ? config[key] : def
}
}
function _setupProxy(hostname, config, mainconfig, isHTTPS) {
debugger;
var no_proxy
var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy'
@@ -63,7 +79,8 @@ debugger;
if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item
if (hostname.lastIndexOf(no_proxy_item) === hostname.length - no_proxy_item.length) {
if (this.proxy) {
this.logger.debug({url: this.url.href, rule: no_proxy_item}, 'not using proxy for @{url}, excluded by @{rule} rule')
this.logger.debug({url: this.url.href, rule: no_proxy_item},
'not using proxy for @{url}, excluded by @{rule} rule')
this.proxy = false
}
break
@@ -75,15 +92,28 @@ debugger;
if (typeof(this.proxy) !== 'string') {
delete this.proxy
} else {
this.logger.debug({url: this.url.href, proxy: this.proxy}, 'using proxy @{proxy} for @{url}')
this.logger.debug({url: this.url.href, proxy: this.proxy},
'using proxy @{proxy} for @{url}')
}
}
Storage.prototype.request = function(options, cb) {
if (!this.status_check()) {
var req = new Stream.Readable()
process.nextTick(function() {
if (typeof(cb) === 'function') cb(Error('uplink is offline'))
req.emit('error', Error('uplink is offline'))
})
// preventing 'Uncaught, unspecified "error" event'
req.on('error', function(){})
return req
}
var self = this
, headers = options.headers || {}
headers.accept = headers.accept || 'application/json'
headers['user-agent'] = headers['user-agent'] || this.userAgent
headers['Accept'] = headers['Accept'] || 'application/json'
headers['Accept-Encoding'] = headers['Accept-Encoding'] || 'gzip'
headers['User-Agent'] = headers['User-Agent'] || this.userAgent
var method = options.method || 'GET'
, uri = options.uri_full || (this.config.url + options.uri)
@@ -95,7 +125,7 @@ Storage.prototype.request = function(options, cb) {
if (utils.is_object(options.json)) {
var json = JSON.stringify(options.json)
headers['content-type'] = headers['content-type'] || 'application/json'
headers['Content-Type'] = headers['Content-Type'] || 'application/json'
}
var req = request({
@@ -105,14 +135,37 @@ Storage.prototype.request = function(options, cb) {
body: json,
ca: this.ca,
proxy: this.proxy,
encoding: null,
timeout: this.timeout,
}, function(err, res, body) {
var error
if (!err) {
var res_length = body.length
var res_length = err ? 0 : body.length
do_gunzip(function() {
do_decode()
do_log()
if (cb) cb(err, res, body)
})
function do_gunzip(cb) {
if (err) return cb()
if (res.headers['content-encoding'] !== 'gzip') return cb()
zlib.gunzip(body, function(er, buf) {
if (er) err = er
body = buf
return cb()
})
}
function do_decode() {
if (err) {
error = err.message
return
}
if (options.json && res.statusCode < 300) {
try {
body = JSON.parse(body)
body = JSON.parse(body.toString('utf8'))
} catch(_err) {
body = {}
err = _err
@@ -121,52 +174,69 @@ Storage.prototype.request = function(options, cb) {
}
if (!err && utils.is_object(body)) {
if (body.error) {
if (typeof(body.error) === 'string') {
error = body.error
}
}
} else {
error = err.message
}
var msg = '@{!status}, req: \'@{request.method} @{request.url}\''
if (error) {
msg += ', error: @{!error}'
} else {
msg += ', 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: res_length || 0,
function do_log() {
var message = '@{!status}, req: \'@{request.method} @{request.url}\''
if (error) {
message += ', error: @{!error}'
} else {
message += ', bytes: @{bytes.in}/@{bytes.out}'
}
}, msg)
if (cb) cb.apply(self, arguments)
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: res_length || 0,
}
}, message)
}
})
var status_called = false
req.on('response', function(res) {
self.status_check(true)
if (!req._sinopia_aborted && !status_called) {
status_called = true
self.status_check(true)
}
})
req.on('error', function() {
self.status_check(false)
req.on('error', function(_err) {
if (!req._sinopia_aborted && !status_called) {
status_called = true
self.status_check(false)
}
})
return req
}
Storage.prototype.status_check = function(alive) {
if (arguments.length === 0) {
if (!this.is_alive && Math.abs(Date.now() - this.is_alive_time()) > 60*1000) {
if (this.failed_requests >= this.max_fails && Math.abs(Date.now() - this.last_request_time) < this.fail_timeout) {
return false
} else {
return true
}
} else {
this.is_alive = alive
this.is_alive_time = Date.now()
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()
}
}
@@ -178,123 +248,62 @@ Storage.prototype.can_fetch_url = function(url) {
&& url.path.indexOf(this.url.path) === 0
}
Storage.prototype.add_package = function(name, metadata, callback) {
this.request({
uri: '/' + escape(name),
method: 'PUT',
json: metadata,
}, function(err, res, body) {
if (err) return callback(err)
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return callback(new Error('bad status code: ' + res.statusCode))
}
callback(null, body)
})
}
Storage.prototype.get_package = function(name, options, callback) {
if (typeof(options) === 'function') callback = options, options = {}
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
this.request({
uri: '/' + escape(name) + '/' + escape(version) + '/-tag/' + escape(tag),
method: 'PUT',
json: metadata,
}, function(err, res, body) {
if (err) return callback(err)
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return callback(new Error('bad status code: ' + res.statusCode))
}
callback(null, body)
})
}
Storage.prototype.add_tarball = function(name, filename) {
var stream = new mystreams.UploadTarballStream()
, self = this
var wstream = this.request({
uri: '/' + escape(name) + '/-/' + escape(filename) + '/whatever',
method: 'PUT',
headers: {
'content-type': 'application/octet-stream'
},
})
wstream.on('response', function(res) {
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return stream.emit('error', new UError({
msg: 'bad uplink status code: ' + res.statusCode,
status: 500,
}))
}
stream.emit('success')
})
wstream.on('error', function(err) {
stream.emit('error', err)
})
stream.abort = function() {
process.nextTick(function() {
if (wstream.req) {
wstream.req.abort()
}
})
var headers = {}
if (options.etag) {
headers['If-None-Match'] = options.etag
headers['Accept'] = 'application/octet-stream'
}
stream.done = function() {}
stream.pipe(wstream)
this._add_proxy_headers(options.req, headers)
return stream
}
Storage.prototype.get_package = function(name, etag, callback) {
if (etag) {
var headers = {
'if-none-match': etag
}
}
this.request({
uri: '/' + escape(name),
uri: '/' + encode(name),
json: true,
headers: headers,
}, 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,
}))
return callback(Error[404]("package doesn't exist on uplink"))
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return callback(new Error('bad status code: ' + res.statusCode))
var error = Error('bad status code: ' + res.statusCode)
error.remoteStatus = res.statusCode
return callback(error)
}
callback(null, body, res.headers.etag)
})
}
Storage.prototype.get_tarball = function(name, filename) {
Storage.prototype.get_tarball = function(name, options, filename) {
if (!options) options = {}
return this.get_url(this.config.url + '/' + name + '/-/' + filename)
}
Storage.prototype.get_url = function(url) {
var stream = new mystreams.ReadTarballStream()
stream.abort = function() {}
var current_length = 0, expected_length
var rstream = this.request({
uri_full: url,
encoding: null,
headers: {
Accept: 'application/octet-stream',
},
})
rstream.on('response', function(res) {
if (res.statusCode === 404) {
return stream.emit('error', new UError({
msg: 'file doesn\'t exist on uplink',
status: 404,
}))
return stream.emit('error', Error[404]("file doesn't exist on uplink"))
}
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
return stream.emit('error', new UError({
msg: 'bad uplink status code: ' + res.statusCode,
status: 500,
}))
return stream.emit('error', Error('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'])
}
rstream.pipe(stream)
@@ -303,8 +312,34 @@ Storage.prototype.get_url = function(url) {
rstream.on('error', function(err) {
stream.emit('error', err)
})
rstream.on('data', function(d) {
current_length += d.length
})
rstream.on('end', function(d) {
if (d) current_length += d.length
if (expected_length && current_length != expected_length)
stream.emit('error', Error('content length mismatch'))
})
return stream
}
Storage.prototype._add_proxy_headers = function(req, headers) {
if (req) {
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 + ' (Sinopia)'
}
module.exports = Storage

View File

@@ -1,17 +1,21 @@
var assert = require('assert')
, semver = require('semver')
, Logger = require('./logger')
, URL = require('url')
// from normalize-package-data/lib/fixer.js
module.exports.validate_name = function(name) {
if (typeof(name) !== 'string') return false
name = name.toLowerCase()
if (
name.charAt(0) === "." || // ".bin", etc.
name.match(/[\/@\s\+%:]/) ||
name !== encodeURIComponent(name) ||
name.toLowerCase() === "node_modules" ||
name.toLowerCase() === "__proto__" ||
name.toLowerCase() === "package.json" ||
name.toLowerCase() === "favicon.ico"
// all URL-safe characters and "@" for issue #75
!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'
) {
return false
} else {
@@ -24,7 +28,7 @@ module.exports.is_object = function(obj) {
}
module.exports.validate_metadata = function(object, name) {
assert(module.exports.is_object(object))
assert(module.exports.is_object(object), 'not a json object')
assert.equal(object.name, name)
if (!module.exports.is_object(object['dist-tags'])) {
@@ -78,12 +82,68 @@ module.exports.filter_tarball_urls = function(pkg, req, config) {
}
for (var ver in pkg.versions) {
if (pkg.versions[ver].dist != null
&& pkg.versions[ver].dist.tarball != null) {
pkg.versions[ver].dist.__sinopia_orig_tarball = pkg.versions[ver].dist.tarball
pkg.versions[ver].dist.tarball = filter(pkg.versions[ver].dist.tarball)
var dist = pkg.versions[ver].dist
if (dist != null && dist.tarball != null) {
//dist.__sinopia_orig_tarball = dist.tarball
dist.tarball = filter(dist.tarball)
}
}
return pkg
}
function can_add_tag(tag, config) {
if (!tag) return false
if (tag === 'latest' && config.ignore_latest_tag) return false
return true
}
module.exports.tag_version = function(data, version, tag, config) {
if (!can_add_tag(tag, config)) return false
switch(typeof(data['dist-tags'][tag])) {
case 'string':
data['dist-tags'][tag] = [data['dist-tags'][tag]]
break
case 'object': // array
break
default:
data['dist-tags'][tag] = []
}
if (data['dist-tags'][tag].indexOf(version) === -1) {
data['dist-tags'][tag].push(version)
data['dist-tags'][tag] = module.exports.semver_sort(data['dist-tags'][tag])
return data['dist-tags'][tag][data['dist-tags'][tag].length - 1] === version
}
return false
}
// gets version from a package object taking into account semver weirdness
module.exports.get_version = function(object, version) {
if (object.versions[version] != null) return object.versions[version]
try {
version = semver.parse(version, true)
for (var k in object.versions) {
if (version.compare(semver.parse(k, true)) === 0) {
return object.versions[k]
}
}
} catch(err) {
return undefined
}
}
// function filters out bad semver versions and sorts the array
module.exports.semver_sort = function semver_sort(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)
}

16
node_modules/crypt3/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,16 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules
build/

20
node_modules/crypt3/LICENSE generated vendored Normal file
View File

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

43
node_modules/crypt3/README.md generated vendored Normal file
View File

@@ -0,0 +1,43 @@
node-crypt3
===========
[crypt3link]: https://en.wikipedia.org/wiki/Crypt_(C) "crypt() in C"
[crypt(3)][crypt3link] for Node.js
Installation
------------
Install using `npm install crypt3` and use:
```javascript
var crypt = require('crypt3');
```
Example password check
----------------------
```javascript
if( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/') !== '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/' ) {
console.error('Access denied!');
return;
}
```
Example password encoding
-------------------------
Use `crypt(key[, salt])`:
```javascript
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh') ); // Salt generated automatically using default SHA512
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('md5') ) ); // MD5 salt
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('blowfish') ) ); // Blowfish salt (only some Linux distros)
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha256') ) ); // SHA-256
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha512') ) ); // SHA-512
```
Create hashes
-------------
Use `crypt.createSalt([type=sha512])` where type is one of `md5`, `blowfish`, `sha256` or `sha512` (default).

14
node_modules/crypt3/binding.gyp generated vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"targets": [
{
"target_name": "crypt3",
"sources": [ "crypt3.cc" ],
"include_dirs" : [ "<!(node -e \"require('nan')\")" ],
"conditions": [
['OS!="mac"', {
'link_settings': { "libraries": [ "-lcrypt" ] }
}]
]
}
]
}

39
node_modules/crypt3/crypt3.cc generated vendored Normal file
View File

@@ -0,0 +1,39 @@
/* Node.js Crypt(3) implementation */
#include <node.h>
#include <v8.h>
#include <errno.h>
#include <unistd.h> // for crypt if _XOPEN_SOURCE exists
#include <nan.h>
using namespace v8;
NAN_METHOD(Method) {
NanScope();
if (args.Length() < 2) {
return NanThrowTypeError("Wrong number of arguments");
}
if (!args[0]->IsString() || !args[1]->IsString()) {
return NanThrowTypeError("Wrong arguments");
}
v8::String::Utf8Value key(args[0]->ToString());
v8::String::Utf8Value salt(args[1]->ToString());
char* res = crypt(*key, *salt);
if (res != NULL) {
NanReturnValue(NanNew<String>(res));
} else {
return NanThrowError(node::ErrnoException(errno, "crypt"));
}
}
void init(Handle<Object> exports) {
exports->Set(NanNew<String>("crypt"), NanNew<FunctionTemplate>(Method)->GetFunction());
}
NODE_MODULE(crypt3, init)
/* EOF */

33
node_modules/crypt3/index.js generated vendored Normal file
View File

@@ -0,0 +1,33 @@
/** Node.js Crypt(3) Library */
var salters = {
'md5': function() { return '$1$'+require('crypto').randomBytes(10).toString('base64'); },
'blowfish': function() { return '$2a$'+require('crypto').randomBytes(10).toString('base64'); },
'sha256': function() { return '$5$'+require('crypto').randomBytes(10).toString('base64'); },
'sha512': function() { return '$6$'+require('crypto').randomBytes(10).toString('base64'); }
};
function createSalt(type) {
type = type || 'sha512';
if(!salters[type]) throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type);
return salters[type]();
};
/** 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)
*/
var crypt3 = module.exports = function(key, salt) {
salt = salt || createSalt();
return require('./build/Release/crypt3').crypt(key, salt);
};
/** 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
*/
crypt3.createSalt = createSalt;
/* EOF */

30
node_modules/crypt3/node_modules/nan/.dntrc generated vendored Normal file
View File

@@ -0,0 +1,30 @@
## DNT config file
## see https://github.com/rvagg/dnt
NODE_VERSIONS="\
master \
v0.11.13 \
v0.10.30 \
v0.10.29 \
v0.10.28 \
v0.10.26 \
v0.10.25 \
v0.10.24 \
v0.10.23 \
v0.10.22 \
v0.10.21 \
v0.10.20 \
v0.10.19 \
v0.8.28 \
v0.8.27 \
v0.8.26 \
v0.8.24 \
"
OUTPUT_PREFIX="nan-"
TEST_CMD=" \
cd /dnt/ && \
npm install && \
node_modules/.bin/node-gyp --nodedir /usr/src/node/ rebuild --directory test && \
node_modules/.bin/tap --gc test/js/*-test.js \
"

200
node_modules/crypt3/node_modules/nan/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,200 @@
# NAN ChangeLog
### Version 1.3.0: current Node unstable: 0.11.13, Node stable: 0.10.30
**1.3.0 Aug 2 2014**
- Added NanNew<v8::String, std::string>(std::string)
- Added NanNew<v8::String, std::string&>(std::string&)
- Added NanAsciiString helper class
- Added NanUtf8String helper class
- Added NanUcs2String helper class
- Deprecated NanRawString()
- Deprecated NanCString()
- Added NanGetIsolateData(v8::Isolate *isolate)
- Added NanMakeCallback(v8::Handle<v8::Object> target, v8::Handle<v8::Function> func, int argc, v8::Handle<v8::Value>* argv)
- Added NanMakeCallback(v8::Handle<v8::Object> target, v8::Handle<v8::String> symbol, int argc, v8::Handle<v8::Value>* argv)
- Added NanMakeCallback(v8::Handle<v8::Object> target, const char* method, int argc, v8::Handle<v8::Value>* argv)
- Added NanSetTemplate(v8::Handle<v8::Template> templ, v8::Handle<v8::String> name , v8::Handle<v8::Data> value, v8::PropertyAttribute attributes)
- Added NanSetPrototypeTemplate(v8::Local<v8::FunctionTemplate> templ, v8::Handle<v8::String> name, v8::Handle<v8::Data> value, v8::PropertyAttribute attributes)
- Added NanSetInstanceTemplate(v8::Local<v8::FunctionTemplate> templ, const char *name, v8::Handle<v8::Data> value)
- Added NanSetInstanceTemplate(v8::Local<v8::FunctionTemplate> templ, v8::Handle<v8::String> name, v8::Handle<v8::Data> value, v8::PropertyAttribute attributes)
**1.2.0 Jun 5 2014**
- Add NanSetPrototypeTemplate
- Changed NAN_WEAK_CALLBACK internals, switched _NanWeakCallbackData to class,
introduced _NanWeakCallbackDispatcher
- Removed -Wno-unused-local-typedefs from test builds
- Made test builds Windows compatible ('Sleep()')
**1.1.2 May 28 2014**
- Release to fix more stuff-ups in 1.1.1
**1.1.1 May 28 2014**
- Release to fix version mismatch in nan.h and lack of changelog entry for 1.1.0
**1.1.0 May 25 2014**
- Remove nan_isolate, use v8::Isolate::GetCurrent() internally instead
- Additional explicit overloads for NanNew(): (char*,int), (uint8_t*[,int]),
(uint16_t*[,int), double, int, unsigned int, bool, v8::String::ExternalStringResource*,
v8::String::ExternalAsciiStringResource*
- Deprecate NanSymbol()
- Added SetErrorMessage() and ErrorMessage() to NanAsyncWorker
**1.0.0 May 4 2014**
- Heavy API changes for V8 3.25 / Node 0.11.13
- Use cpplint.py
- Removed NanInitPersistent
- Removed NanPersistentToLocal
- Removed NanFromV8String
- Removed NanMakeWeak
- Removed NanNewLocal
- Removed NAN_WEAK_CALLBACK_OBJECT
- Removed NAN_WEAK_CALLBACK_DATA
- Introduce NanNew, replaces NanNewLocal, NanPersistentToLocal, adds many overloaded typed versions
- Introduce NanUndefined, NanNull, NanTrue and NanFalse
- Introduce NanEscapableScope and NanEscapeScope
- Introduce NanMakeWeakPersistent (requires a special callback to work on both old and new node)
- Introduce NanMakeCallback for node::MakeCallback
- Introduce NanSetTemplate
- Introduce NanGetCurrentContext
- Introduce NanCompileScript and NanRunScript
- Introduce NanAdjustExternalMemory
- Introduce NanAddGCEpilogueCallback, NanAddGCPrologueCallback, NanRemoveGCEpilogueCallback, NanRemoveGCPrologueCallback
- Introduce NanGetHeapStatistics
- Rename NanAsyncWorker#SavePersistent() to SaveToPersistent()
**0.8.0 Jan 9 2014**
- NanDispose -> NanDisposePersistent, deprecate NanDispose
- Extract _NAN_*_RETURN_TYPE, pull up NAN_*()
**0.7.1 Jan 9 2014**
- Fixes to work against debug builds of Node
- Safer NanPersistentToLocal (avoid reinterpret_cast)
- Speed up common NanRawString case by only extracting flattened string when necessary
**0.7.0 Dec 17 2013**
- New no-arg form of NanCallback() constructor.
- NanCallback#Call takes Handle rather than Local
- Removed deprecated NanCallback#Run method, use NanCallback#Call instead
- Split off _NAN_*_ARGS_TYPE from _NAN_*_ARGS
- Restore (unofficial) Node 0.6 compatibility at NanCallback#Call()
- Introduce NanRawString() for char* (or appropriate void*) from v8::String
(replacement for NanFromV8String)
- Introduce NanCString() for null-terminated char* from v8::String
**0.6.0 Nov 21 2013**
- Introduce NanNewLocal<T>(v8::Handle<T> value) for use in place of
v8::Local<T>::New(...) since v8 started requiring isolate in Node 0.11.9
**0.5.2 Nov 16 2013**
- Convert SavePersistent and GetFromPersistent in NanAsyncWorker from protected and public
**0.5.1 Nov 12 2013**
- Use node::MakeCallback() instead of direct v8::Function::Call()
**0.5.0 Nov 11 2013**
- Added @TooTallNate as collaborator
- New, much simpler, "include_dirs" for binding.gyp
- Added full range of NAN_INDEX_* macros to match NAN_PROPERTY_* macros
**0.4.4 Nov 2 2013**
- Isolate argument from v8::Persistent::MakeWeak removed for 0.11.8+
**0.4.3 Nov 2 2013**
- Include node_object_wrap.h, removed from node.h for Node 0.11.8.
**0.4.2 Nov 2 2013**
- Handle deprecation of v8::Persistent::Dispose(v8::Isolate* isolate)) for
Node 0.11.8 release.
**0.4.1 Sep 16 2013**
- Added explicit `#include <uv.h>` as it was removed from node.h for v0.11.8
**0.4.0 Sep 2 2013**
- Added NAN_INLINE and NAN_DEPRECATED and made use of them
- Added NanError, NanTypeError and NanRangeError
- Cleaned up code
**0.3.2 Aug 30 2013**
- Fix missing scope declaration in GetFromPersistent() and SaveToPersistent
in NanAsyncWorker
**0.3.1 Aug 20 2013**
- fix "not all control paths return a value" compile warning on some platforms
**0.3.0 Aug 19 2013**
- Made NAN work with NPM
- Lots of fixes to NanFromV8String, pulling in features from new Node core
- Changed node::encoding to Nan::Encoding in NanFromV8String to unify the API
- Added optional error number argument for NanThrowError()
- Added NanInitPersistent()
- Added NanReturnNull() and NanReturnEmptyString()
- Added NanLocker and NanUnlocker
- Added missing scopes
- Made sure to clear disposed Persistent handles
- Changed NanAsyncWorker to allocate error messages on the heap
- Changed NanThrowError(Local<Value>) to NanThrowError(Handle<Value>)
- Fixed leak in NanAsyncWorker when errmsg is used
**0.2.2 Aug 5 2013**
- Fixed usage of undefined variable with node::BASE64 in NanFromV8String()
**0.2.1 Aug 5 2013**
- Fixed 0.8 breakage, node::BUFFER encoding type not available in 0.8 for
NanFromV8String()
**0.2.0 Aug 5 2013**
- Added NAN_PROPERTY_GETTER, NAN_PROPERTY_SETTER, NAN_PROPERTY_ENUMERATOR,
NAN_PROPERTY_DELETER, NAN_PROPERTY_QUERY
- Extracted _NAN_METHOD_ARGS, _NAN_GETTER_ARGS, _NAN_SETTER_ARGS,
_NAN_PROPERTY_GETTER_ARGS, _NAN_PROPERTY_SETTER_ARGS,
_NAN_PROPERTY_ENUMERATOR_ARGS, _NAN_PROPERTY_DELETER_ARGS,
_NAN_PROPERTY_QUERY_ARGS
- Added NanGetInternalFieldPointer, NanSetInternalFieldPointer
- Added NAN_WEAK_CALLBACK, NAN_WEAK_CALLBACK_OBJECT,
NAN_WEAK_CALLBACK_DATA, NanMakeWeak
- Renamed THROW_ERROR to _NAN_THROW_ERROR
- Added NanNewBufferHandle(char*, size_t, node::smalloc::FreeCallback, void*)
- Added NanBufferUse(char*, uint32_t)
- Added NanNewContextHandle(v8::ExtensionConfiguration*,
v8::Handle<v8::ObjectTemplate>, v8::Handle<v8::Value>)
- Fixed broken NanCallback#GetFunction()
- Added optional encoding and size arguments to NanFromV8String()
- Added NanGetPointerSafe() and NanSetPointerSafe()
- Added initial test suite (to be expanded)
- Allow NanUInt32OptionValue to convert any Number object
**0.1.0 Jul 21 2013**
- Added `NAN_GETTER`, `NAN_SETTER`
- Added `NanThrowError` with single Local<Value> argument
- Added `NanNewBufferHandle` with single uint32_t argument
- Added `NanHasInstance(Persistent<FunctionTemplate>&, Handle<Value>)`
- Added `Local<Function> NanCallback#GetFunction()`
- Added `NanCallback#Call(int, Local<Value>[])`
- Deprecated `NanCallback#Run(int, Local<Value>[])` in favour of Call

46
node_modules/crypt3/node_modules/nan/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,46 @@
Copyright 2013, NAN contributors:
- Rod Vagg <https://github.com/rvagg>
- Benjamin Byholm <https://github.com/kkoopa>
- Trevor Norris <https://github.com/trevnorris>
- Nathan Rajlich <https://github.com/TooTallNate>
- Brett Lawson <https://github.com/brett19>
- Ben Noordhuis <https://github.com/bnoordhuis>
(the "Original Author")
All rights reserved.
MIT +no-false-attribs License
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
Distributions of all or part of the Software intended to be used
by the recipients as they would use the unmodified Software,
containing modifications that substantially alter, remove, or
disable functionality of the Software, outside of the documented
configuration mechanisms provided by the Software, shall be
modified such that the Original Author's bug reporting email
addresses and urls are either replaced with the contact information
of the parties responsible for the changes, or removed entirely.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
Except where noted, this license applies to any and all software
programs and associated documentation files created by the
Original Author, when distributed with the Software.

1054
node_modules/crypt3/node_modules/nan/README.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

32
node_modules/crypt3/node_modules/nan/appveyor.yml generated vendored Normal file
View File

@@ -0,0 +1,32 @@
# http://www.appveyor.com/docs/appveyor-yml
# Test against these versions of Node.js.
environment:
matrix:
- nodejs_version: "0.8"
- nodejs_version: "0.10"
- nodejs_version: "0.11"
# Install scripts. (runs after repo cloning)
install:
# Get the latest stable version of Node 0.STABLE.latest
- npm install npm
- move node_modules npm
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
# Typical npm stuff.
- npm/.bin/npm install
- npm/.bin/npm run rebuild-tests
# Post-install test scripts.
test_script:
# Output useful info for debugging.
- node --version
- npm --version
- cmd: npm test
# Don't actually build.
build: off
# Set build version format here instead of in the admin panel.
version: "{build}"

1
node_modules/crypt3/node_modules/nan/include_dirs.js generated vendored Normal file
View File

@@ -0,0 +1 @@
console.log(require('path').relative('.', __dirname));

2331
node_modules/crypt3/node_modules/nan/nan.h generated vendored Normal file

File diff suppressed because it is too large Load Diff

63
node_modules/crypt3/node_modules/nan/package.json generated vendored Normal file

File diff suppressed because one or more lines are too long

40
node_modules/crypt3/package.json generated vendored Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "crypt3",
"version": "0.1.5",
"description": "Node.js crypt(3) bindings",
"main": "index.js",
"scripts": {
"test": "node test/test.js",
"install": "node-gyp rebuild"
},
"repository": {
"type": "git",
"url": "https://github.com/sendanor/node-crypt3.git"
},
"keywords": [
"crypt",
"password",
"md5",
"sha256",
"sha512",
"blowfish",
"hash"
],
"dependencies": { "nan": "~1.3.0" },
"author": {
"name": "Jaakko-Heikki Heusala",
"email": "jheusala@iki.fi"
},
"license": "MIT",
"gypfile": true,
"readme": "node-crypt3\n===========\n\n[crypt3link]: https://en.wikipedia.org/wiki/Crypt_(C) \"crypt() in C\"\n\n[crypt(3)][crypt3link] for Node.js\n\nInstallation\n------------\n\nInstall using `npm install crypt3` and use:\n\n```javascript\nvar crypt = require('crypt3');\n```\n\nExample password check\n----------------------\n\n```javascript\nif( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/') !== '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/' ) {\n\tconsole.error('Access denied!');\n\treturn;\n}\n```\n\nExample password encoding\n-------------------------\n\nUse `crypt(key[, salt])`:\n\n```javascript\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh') ); // Salt generated automatically using default SHA512\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('md5') ) ); // MD5 salt\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('blowfish') ) ); // Blowfish salt (only some Linux distros)\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha256') ) ); // SHA-256\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha512') ) ); // SHA-512\n```\n\nCreate hashes\n-------------\n\nUse `crypt.createSalt([type=sha512])` where type is one of `md5`, `blowfish`, `sha256` or `sha512` (default). \n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/sendanor/node-crypt3/issues"
},
"homepage": "https://github.com/sendanor/node-crypt3",
"_id": "crypt3@0.1.5",
"_shasum": "f21e9ba7a57736f47e0654ad27f8668966de02db",
"_resolved": "git://github.com/sendanor/node-crypt3.git#9b893c95ed956adc3da681b125c371112b1ad31d",
"_from": "crypt3@git://github.com/sendanor/node-crypt3.git"
}

14
node_modules/crypt3/test/test.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
var assert = require('assert')
var crypt3 = require('../')
assert.throws(function() {
crypt3()
}, /Wrong arguments/)
assert.equal(crypt3('pass', 'salt'), 'sa5JEXtYx/rm6')
assert.equal(crypt3('pass', 'sa5JEXtYx/rm6'), 'sa5JEXtYx/rm6')
var hash = crypt3('password')
assert.equal(crypt3('password', hash), hash)
assert.notEqual(crypt3('bad-pass', hash), hash)

43
node_modules/helpers.less/.gitignore generated vendored Executable file
View File

@@ -0,0 +1,43 @@
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sql
*.sqlite
# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Node Stuff #
##############
node_modules/

17
node_modules/helpers.less/Gruntfile.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
browserify: {
'dist/subview.js': ['src/main.js'],
'examples/build.js': ['examples/example.js']
},
watch: {
files: [ "src/*.js", "examples/example.js"],
tasks: [ 'browserify' ]
}
});
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-watch');
};

11
node_modules/helpers.less/README.md generated vendored Normal file
View File

@@ -0,0 +1,11 @@
To Build:
```bash
grunt browserify
```
To Develop:
```bash
grunt watch
```

123
node_modules/helpers.less/helpers.less generated vendored Normal file
View File

@@ -0,0 +1,123 @@
.backface-visibility(@style) {
-webkit-backface-visibility: @style;
-moz-backface-visibility: @style;
-ms-backface-visibility: @style;
-o-backface-visibility: @style;
backface-visibility: @style;
}
.perspective(@style) {
-webkit-perspective: @style;
-moz-perspective: @style;
-ms-perspective: @style;
-o-perspective: @style;
perspective: @style;
}
.border-radius(@radius) {
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
border-radius: @radius;
}
.border-radius-topleft(@radius) {
-moz-border-radius-topleft: @radius;
border-top-left-radius: @radius;
}
.border-radius-topright(@radius) {
-moz-border-radius-topright: @radius;
border-top-right-radius: @radius;
}
.border-radius-bottomleft(@radius) {
-moz-border-radius-bottomleft: @radius;
border-bottom-left-radius: @radius;
}
.border-radius-bottomright(@radius) {
-moz-border-radius-bottomright: @radius;
border-bottom-right-radius: @radius;
}
.no-select() {
-moz-user-select: none;
-ms-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-o-user-select: none;
user-select: none;
}
.do-select() {
-moz-user-select: text;
-ms-user-select: text;
-khtml-user-select: text;
-webkit-user-select: text;
-o-user-select: text;
user-select: text;
}
.border-box() {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.box-shadow(@value1, @value2:X, ...) {
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
-moz-box-shadow: @value;
-webkit-box-shadow: @value;
box-shadow: @value;
}
.transition(@value1, @value2:X, ...) {
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
-webkit-transition: @value;
-moz-transition: @value;
-ms-transition: @value;
-o-transition: @value;
transition: @value;
}
.transformTransition(@value1, @value2:X, ...) {
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
-webkit-transition: -webkit-transform @value;
-moz-transition: -moz-transform @value;
-ms-transition: -ms-transform @value;
-o-transition: -o-transform @value;
transition: transform @value;
}
.animation(@value1, @value2:X, ...) {
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
-webkit-animation: @value;
-moz-animation: @value;
-o-animation: @value;
animation: @value;
}
.transform(@value1, @value2:X, ...) {
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
-webkit-transform: @value;
-moz-transform: @value;
-o-transform: @value;
-ms-transform: @value;
transform: @value;
}
.rotate(@deg) {
.transform(rotate(@deg));
}
.scale(@ratio) {
.transform(scale(@ratio, @ratio));
}
.translate(@x, @y) {
.transform(translate(@x, @y));
}

27
node_modules/helpers.less/package.json generated vendored Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "helpers.less",
"description": "A set of very convenient less helpers.",
"version": "0.1.0",
"author": {
"name": "Brian Peacock"
},
"main": "helpers.less",
"repository": {
"type": "git",
"url": "https://github.com/bpeacock/helpers.less"
},
"dependencies": {},
"devDependencies": {},
"license": "MIT",
"keywords": [],
"readme": "To Build:\n\n```bash\ngrunt browserify\n```\n\nTo Develop:\n\n```bash\ngrunt watch\n```",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/bpeacock/helpers.less/issues"
},
"homepage": "https://github.com/bpeacock/helpers.less",
"_id": "helpers.less@0.1.0",
"_shasum": "d77d92fb3f710e0807f00bd2de920a9e4374799a",
"_resolved": "git://github.com/bpeacock/helpers.less.git#4184c4abac632b4b14129ec9ef6e1173370adc22",
"_from": "helpers.less@git://github.com/bpeacock/helpers.less.git"
}

22
node_modules/http-errors/LICENSE generated vendored Normal file
View File

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

63
node_modules/http-errors/README.md generated vendored Normal file
View File

@@ -0,0 +1,63 @@
# http-errors
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url]
Create HTTP errors for Express, Koa, Connect, etc. with ease.
## Example
```js
var createError = require('http-errors');
app.use(function (req, res, next) {
if (!req.user) return next(createError(401, 'Please login to view this page.'));
next();
})
```
## API
This is the current API, currently extracted from Koa and subject to change.
### Error Properties
- `message`
- `status` and `statusCode` - the status code of the error, defaulting to `500`
### createError([status], [message], [properties])
```js
var err = createError(404, 'This video does not exist!');
```
- `status: 500` - the status code as a number
- `message` - the message of the error, defaulting to node's text for that status code.
- `properties` - custom properties to attach to the object
### new createError\[code || name\](\[msg]\))
```js
var err = new createError.NotFound();
```
- `code` - the status code as a number
- `name` - the name of the error as a "bumpy case", i.e. `NotFound` or `InternalServerError`.
## License
[MIT](LICENSE)
[npm-image]: https://img.shields.io/npm/v/http-errors.svg?style=flat
[npm-url]: https://npmjs.org/package/http-errors
[node-version-image]: https://img.shields.io/node/v/http-errors.svg?style=flat
[node-version-url]: http://nodejs.org/download/
[travis-image]: https://img.shields.io/travis/jshttp/http-errors.svg?style=flat
[travis-url]: https://travis-ci.org/jshttp/http-errors
[coveralls-image]: https://img.shields.io/coveralls/jshttp/http-errors.svg?style=flat
[coveralls-url]: https://coveralls.io/r/jshttp/http-errors
[downloads-image]: https://img.shields.io/npm/dm/http-errors.svg?style=flat
[downloads-url]: https://npmjs.org/package/http-errors

75
node_modules/http-errors/index.js generated vendored Normal file
View File

@@ -0,0 +1,75 @@
var statuses = require('statuses');
var inherits = require('inherits');
exports = module.exports = function () {
// so much arity going on ~_~
var err;
var msg;
var status = 500;
var props = {};
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (arg instanceof Error) {
err = arg;
status = err.status || err.statusCode || status;
continue;
}
switch (typeof arg) {
case 'string':
msg = arg;
break;
case 'number':
status = arg;
break;
case 'object':
props = arg;
break;
}
}
if (typeof status !== 'number' || !statuses[status]) status = 500;
err = err || new Error(msg || statuses[status]);
err = err || new Error(msg || statuses[status]);
err.expose = status < 500;
for (var key in props) err[key] = props[key];
err.status = err.statusCode = status;
return err;
};
// create generic error objects
var codes = statuses.codes.filter(function (num) {
return num >= 400;
});
codes.forEach(function (code) {
if (code >= 500) {
var ServerError = function ServerError(msg) {
var self = new Error(msg != null ? msg : statuses[code])
Error.captureStackTrace(self, arguments.callee)
self.__proto__ = ServerError.prototype
return self
}
inherits(ServerError, Error);
ServerError.prototype.status =
ServerError.prototype.statusCode = code;
ServerError.prototype.expose = false;
exports[code] =
exports[statuses[code].replace(/\s+/g, '')] = ServerError;
return;
}
var ClientError = function ClientError(msg) {
var self = new Error(msg != null ? msg : statuses[code])
Error.captureStackTrace(self, arguments.callee)
self.__proto__ = ClientError.prototype
return self
}
inherits(ClientError, Error);
ClientError.prototype.status =
ClientError.prototype.statusCode = code;
ClientError.prototype.expose = false;
exports[code] =
exports[statuses[code].replace(/\s+/g, '')] = ClientError;
return;
});

16
node_modules/http-errors/node_modules/inherits/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,16 @@
The ISC License
Copyright (c) Isaac Z. Schlueter
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@@ -0,0 +1,42 @@
Browser-friendly inheritance fully compatible with standard node.js
[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).
This package exports standard `inherits` from node.js `util` module in
node environment, but also provides alternative browser-friendly
implementation through [browser
field](https://gist.github.com/shtylman/4339901). Alternative
implementation is a literal copy of standard one located in standalone
module to avoid requiring of `util`. It also has a shim for old
browsers with no `Object.create` support.
While keeping you sure you are using standard `inherits`
implementation in node.js environment, it allows bundlers such as
[browserify](https://github.com/substack/node-browserify) to not
include full `util` package to your client code if all you need is
just `inherits` function. It worth, because browser shim for `util`
package is large and `inherits` is often the single function you need
from it.
It's recommended to use this package instead of
`require('util').inherits` for any code that has chances to be used
not only in node.js but in browser too.
## usage
```js
var inherits = require('inherits');
// then use exactly as the standard one
```
## note on version ~1.0
Version ~1.0 had completely different motivation and is not compatible
neither with 2.0 nor with standard node.js `inherits`.
If you are using version ~1.0 and planning to switch to ~2.0, be
careful:
* new version uses `super_` instead of `super` for referencing
superclass
* new version overwrites current prototype while old one preserves any
existing fields on it

View File

@@ -0,0 +1 @@
module.exports = require('util').inherits

View File

@@ -0,0 +1,23 @@
if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
};
} else {
// old school shim for old browsers
module.exports = function inherits(ctor, superCtor) {
ctor.super_ = superCtor
var TempCtor = function () {}
TempCtor.prototype = superCtor.prototype
ctor.prototype = new TempCtor()
ctor.prototype.constructor = ctor
}
}

View File

@@ -0,0 +1,35 @@
{
"name": "inherits",
"description": "Browser-friendly inheritance fully compatible with standard node.js inherits()",
"version": "2.0.1",
"keywords": [
"inheritance",
"class",
"klass",
"oop",
"object-oriented",
"inherits",
"browser",
"browserify"
],
"main": "./inherits.js",
"browser": "./inherits_browser.js",
"repository": {
"type": "git",
"url": "git://github.com/isaacs/inherits"
},
"license": "ISC",
"scripts": {
"test": "node test"
},
"readme": "Browser-friendly inheritance fully compatible with standard node.js\n[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).\n\nThis package exports standard `inherits` from node.js `util` module in\nnode environment, but also provides alternative browser-friendly\nimplementation through [browser\nfield](https://gist.github.com/shtylman/4339901). Alternative\nimplementation is a literal copy of standard one located in standalone\nmodule to avoid requiring of `util`. It also has a shim for old\nbrowsers with no `Object.create` support.\n\nWhile keeping you sure you are using standard `inherits`\nimplementation in node.js environment, it allows bundlers such as\n[browserify](https://github.com/substack/node-browserify) to not\ninclude full `util` package to your client code if all you need is\njust `inherits` function. It worth, because browser shim for `util`\npackage is large and `inherits` is often the single function you need\nfrom it.\n\nIt's recommended to use this package instead of\n`require('util').inherits` for any code that has chances to be used\nnot only in node.js but in browser too.\n\n## usage\n\n```js\nvar inherits = require('inherits');\n// then use exactly as the standard one\n```\n\n## note on version ~1.0\n\nVersion ~1.0 had completely different motivation and is not compatible\nneither with 2.0 nor with standard node.js `inherits`.\n\nIf you are using version ~1.0 and planning to switch to ~2.0, be\ncareful:\n\n* new version uses `super_` instead of `super` for referencing\n superclass\n* new version overwrites current prototype while old one preserves any\n existing fields on it\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/isaacs/inherits/issues"
},
"homepage": "https://github.com/isaacs/inherits",
"_id": "inherits@2.0.1",
"_shasum": "b17d08d326b4423e568eff719f91b0b1cbdf69f1",
"_from": "inherits@~2.0.1",
"_resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}

25
node_modules/http-errors/node_modules/inherits/test.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
var inherits = require('./inherits.js')
var assert = require('assert')
function test(c) {
assert(c.constructor === Child)
assert(c.constructor.super_ === Parent)
assert(Object.getPrototypeOf(c) === Child.prototype)
assert(Object.getPrototypeOf(Object.getPrototypeOf(c)) === Parent.prototype)
assert(c instanceof Child)
assert(c instanceof Parent)
}
function Child() {
Parent.call(this)
test(this)
}
function Parent() {}
inherits(Child, Parent)
var c = new Child
test(c)
console.log('ok')

22
node_modules/http-errors/node_modules/statuses/LICENSE generated vendored Normal file
View File

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

View File

@@ -0,0 +1,114 @@
# Statuses
[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url]
HTTP status utility for node.
## API
```js
var status = require('statuses');
```
### var code = status(Integer || String)
If `Integer` or `String` is a valid HTTP code or status message, then the appropriate `code` will be returned. Otherwise, an error will be thrown.
```js
status(403) // => 403
status('403') // => 403
status('forbidden') // => 403
status('Forbidden') // => 403
status(306) // throws, as it's not supported by node.js
```
### status.codes
Returns an array of all the status codes as `Integer`s.
### var msg = status[code]
Map of `code` to `status message`. `undefined` for invalid `code`s.
```js
status[404] // => 'Not Found'
```
### var code = status[msg]
Map of `status message` to `code`. `msg` can either be title-cased or lower-cased. `undefined` for invalid `status message`s.
```js
status['not found'] // => 404
status['Not Found'] // => 404
```
### status.redirect[code]
Returns `true` if a status code is a valid redirect status.
```js
status.redirect[200] // => undefined
status.redirect[301] // => true
```
### status.empty[code]
Returns `true` if a status code expects an empty body.
```js
status.empty[200] // => undefined
status.empty[204] // => true
status.empty[304] // => true
```
### status.retry[code]
Returns `true` if you should retry the rest.
```js
status.retry[501] // => undefined
status.retry[503] // => true
```
### statuses/codes.json
```js
var codes = require('statuses/codes.json');
```
This is a JSON file of the status codes
taken from `require('http').STATUS_CODES`.
This is saved so that codes are consistent even in older node.js versions.
For example, `308` will be added in v0.12.
## Adding Status Codes
The status codes are primarily sourced from http://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv.
Additionally, custom codes are added from http://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
These are added manually in the `lib/*.json` files.
If you would like to add a status code, add it to the appropriate JSON file.
To rebuild `codes.json`, run the following:
```bash
# update src/iana.json
npm run update
# build codes.json
npm run build
```
[npm-image]: https://img.shields.io/npm/v/statuses.svg?style=flat
[npm-url]: https://npmjs.org/package/statuses
[node-version-image]: http://img.shields.io/badge/node.js-%3E%3D_0.6-brightgreen.svg?style=flat
[node-version-url]: http://nodejs.org/download/
[travis-image]: https://img.shields.io/travis/jshttp/statuses.svg?style=flat
[travis-url]: https://travis-ci.org/jshttp/statuses
[coveralls-image]: https://img.shields.io/coveralls/jshttp/statuses.svg?style=flat
[coveralls-url]: https://coveralls.io/r/jshttp/statuses?branch=master
[downloads-image]: http://img.shields.io/npm/dm/statuses.svg?style=flat
[downloads-url]: https://npmjs.org/package/statuses

View File

@@ -0,0 +1,64 @@
{
"100": "Continue",
"101": "Switching Protocols",
"102": "Processing",
"200": "OK",
"201": "Created",
"202": "Accepted",
"203": "Non-Authoritative Information",
"204": "No Content",
"205": "Reset Content",
"206": "Partial Content",
"207": "Multi-Status",
"208": "Already Reported",
"226": "IM Used",
"300": "Multiple Choices",
"301": "Moved Permanently",
"302": "Found",
"303": "See Other",
"304": "Not Modified",
"305": "Use Proxy",
"306": "(Unused)",
"307": "Temporary Redirect",
"308": "Permanent Redirect",
"400": "Bad Request",
"401": "Unauthorized",
"402": "Payment Required",
"403": "Forbidden",
"404": "Not Found",
"405": "Method Not Allowed",
"406": "Not Acceptable",
"407": "Proxy Authentication Required",
"408": "Request Timeout",
"409": "Conflict",
"410": "Gone",
"411": "Length Required",
"412": "Precondition Failed",
"413": "Payload Too Large",
"414": "URI Too Long",
"415": "Unsupported Media Type",
"416": "Range Not Satisfiable",
"417": "Expectation Failed",
"418": "I'm a teapot",
"422": "Unprocessable Entity",
"423": "Locked",
"424": "Failed Dependency",
"425": "Unordered Collection",
"426": "Upgrade Required",
"428": "Precondition Required",
"429": "Too Many Requests",
"431": "Request Header Fields Too Large",
"451": "Unable For Legal Reasons",
"500": "Internal Server Error",
"501": "Not Implemented",
"502": "Bad Gateway",
"503": "Service Unavailable",
"504": "Gateway Timeout",
"505": "HTTP Version Not Supported",
"506": "Variant Also Negotiates",
"507": "Insufficient Storage",
"508": "Loop Detected",
"509": "Bandwidth Limit Exceeded",
"510": "Not Extended",
"511": "Network Authentication Required"
}

View File

@@ -0,0 +1,60 @@
var codes = require('./codes.json');
module.exports = status;
// [Integer...]
status.codes = Object.keys(codes).map(function (code) {
code = ~~code;
var msg = codes[code];
status[code] = msg;
status[msg] = status[msg.toLowerCase()] = code;
return code;
});
// status codes for redirects
status.redirect = {
300: true,
301: true,
302: true,
303: true,
305: true,
307: true,
308: true,
};
// status codes for empty bodies
status.empty = {
204: true,
205: true,
304: true,
};
// status codes for when you should retry the request
status.retry = {
502: true,
503: true,
504: true,
};
function status(code) {
if (typeof code === 'number') {
if (!status[code]) throw new Error('invalid status code: ' + code);
return code;
}
if (typeof code !== 'string') {
throw new TypeError('code must be a number or string');
}
// '403'
var n = parseInt(code, 10)
if (!isNaN(n)) {
if (!status[n]) throw new Error('invalid status code: ' + n);
return n;
}
n = status[code.toLowerCase()];
if (!n) throw new Error('invalid status message: "' + code + '"');
return n;
}

View File

@@ -0,0 +1,49 @@
{
"name": "statuses",
"description": "HTTP status utility",
"version": "1.2.0",
"author": {
"name": "Jonathan Ong",
"email": "me@jongleberry.com",
"url": "http://jongleberry.com"
},
"repository": {
"type": "git",
"url": "git://github.com/jshttp/statuses"
},
"license": "MIT",
"keywords": [
"http",
"status",
"code"
],
"files": [
"index.js",
"codes.json",
"LICENSE"
],
"devDependencies": {
"csv-parse": "0.0.6",
"istanbul": "0",
"mocha": "1",
"request": "^2.44.0",
"stream-to-array": "^2.0.2"
},
"scripts": {
"build": "node scripts/build.js",
"update": "node scripts/update.js",
"test": "mocha --reporter spec --bail --check-leaks",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks"
},
"readme": "# Statuses\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nHTTP status utility for node.\n\n## API\n\n```js\nvar status = require('statuses');\n```\n\n### var code = status(Integer || String)\n\nIf `Integer` or `String` is a valid HTTP code or status message, then the appropriate `code` will be returned. Otherwise, an error will be thrown.\n\n```js\nstatus(403) // => 403\nstatus('403') // => 403\nstatus('forbidden') // => 403\nstatus('Forbidden') // => 403\nstatus(306) // throws, as it's not supported by node.js\n```\n\n### status.codes\n\nReturns an array of all the status codes as `Integer`s.\n\n### var msg = status[code]\n\nMap of `code` to `status message`. `undefined` for invalid `code`s.\n\n```js\nstatus[404] // => 'Not Found'\n```\n\n### var code = status[msg]\n\nMap of `status message` to `code`. `msg` can either be title-cased or lower-cased. `undefined` for invalid `status message`s.\n\n```js\nstatus['not found'] // => 404\nstatus['Not Found'] // => 404\n```\n\n### status.redirect[code]\n\nReturns `true` if a status code is a valid redirect status.\n\n```js\nstatus.redirect[200] // => undefined\nstatus.redirect[301] // => true\n```\n\n### status.empty[code]\n\nReturns `true` if a status code expects an empty body.\n\n```js\nstatus.empty[200] // => undefined\nstatus.empty[204] // => true\nstatus.empty[304] // => true\n```\n\n### status.retry[code]\n\nReturns `true` if you should retry the rest.\n\n```js\nstatus.retry[501] // => undefined\nstatus.retry[503] // => true\n```\n\n### statuses/codes.json\n\n```js\nvar codes = require('statuses/codes.json');\n```\n\nThis is a JSON file of the status codes\ntaken from `require('http').STATUS_CODES`.\nThis is saved so that codes are consistent even in older node.js versions.\nFor example, `308` will be added in v0.12.\n\n## Adding Status Codes\n\nThe status codes are primarily sourced from http://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv.\nAdditionally, custom codes are added from http://en.wikipedia.org/wiki/List_of_HTTP_status_codes.\nThese are added manually in the `lib/*.json` files.\nIf you would like to add a status code, add it to the appropriate JSON file.\n\nTo rebuild `codes.json`, run the following:\n\n```bash\n# update src/iana.json\nnpm run update\n# build codes.json\nnpm run build\n```\n\n[npm-image]: https://img.shields.io/npm/v/statuses.svg?style=flat\n[npm-url]: https://npmjs.org/package/statuses\n[node-version-image]: http://img.shields.io/badge/node.js-%3E%3D_0.6-brightgreen.svg?style=flat\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/statuses.svg?style=flat\n[travis-url]: https://travis-ci.org/jshttp/statuses\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/statuses.svg?style=flat\n[coveralls-url]: https://coveralls.io/r/jshttp/statuses?branch=master\n[downloads-image]: http://img.shields.io/npm/dm/statuses.svg?style=flat\n[downloads-url]: https://npmjs.org/package/statuses\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/jshttp/statuses/issues"
},
"homepage": "https://github.com/jshttp/statuses",
"_id": "statuses@1.2.0",
"_shasum": "4445790d65bec29184f50d54810f67e290c1679e",
"_from": "statuses@1",
"_resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.0.tgz"
}

49
node_modules/http-errors/package.json generated vendored Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "http-errors",
"description": "Create HTTP error objects",
"version": "1.2.5",
"author": {
"name": "Jonathan Ong",
"email": "me@jongleberry.com",
"url": "http://jongleberry.com"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/jshttp/http-errors"
},
"dependencies": {
"inherits": "~2.0.1",
"statuses": "1"
},
"devDependencies": {
"istanbul": "0",
"mocha": "1"
},
"engines": {
"node": ">= 0.6"
},
"scripts": {
"test": "mocha --reporter spec --bail",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter dot"
},
"keywords": [
"http",
"error"
],
"files": [
"index.js",
"LICENSE"
],
"readme": "# http-errors\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nCreate HTTP errors for Express, Koa, Connect, etc. with ease.\n\n## Example\n\n```js\nvar createError = require('http-errors');\n\napp.use(function (req, res, next) {\n if (!req.user) return next(createError(401, 'Please login to view this page.'));\n next();\n})\n```\n\n## API\n\nThis is the current API, currently extracted from Koa and subject to change.\n\n### Error Properties\n\n- `message`\n- `status` and `statusCode` - the status code of the error, defaulting to `500`\n\n### createError([status], [message], [properties])\n\n```js\nvar err = createError(404, 'This video does not exist!');\n```\n\n- `status: 500` - the status code as a number\n- `message` - the message of the error, defaulting to node's text for that status code.\n- `properties` - custom properties to attach to the object\n\n### new createError\\[code || name\\](\\[msg]\\))\n\n```js\nvar err = new createError.NotFound();\n```\n\n- `code` - the status code as a number\n- `name` - the name of the error as a \"bumpy case\", i.e. `NotFound` or `InternalServerError`.\n\n## License\n\n[MIT](LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/http-errors.svg?style=flat\n[npm-url]: https://npmjs.org/package/http-errors\n[node-version-image]: https://img.shields.io/node/v/http-errors.svg?style=flat\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/http-errors.svg?style=flat\n[travis-url]: https://travis-ci.org/jshttp/http-errors\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/http-errors.svg?style=flat\n[coveralls-url]: https://coveralls.io/r/jshttp/http-errors\n[downloads-image]: https://img.shields.io/npm/dm/http-errors.svg?style=flat\n[downloads-url]: https://npmjs.org/package/http-errors\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/jshttp/http-errors/issues"
},
"homepage": "https://github.com/jshttp/http-errors",
"_id": "http-errors@1.2.5",
"_shasum": "61da92170b47c12bd11083653e9ed44a9b7abe92",
"_from": "http-errors@~1.2.0",
"_resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.2.5.tgz"
}

2
node_modules/mkdirp/.npmignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
npm-debug.log

5
node_modules/mkdirp/.travis.yml generated vendored Normal file
View File

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

21
node_modules/mkdirp/LICENSE generated vendored Normal file
View File

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

33
node_modules/mkdirp/bin/cmd.js generated vendored Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env node
var mkdirp = require('../');
var minimist = require('minimist');
var fs = require('fs');
var argv = minimist(process.argv.slice(2), {
alias: { m: 'mode', h: 'help' },
string: [ 'mode' ]
});
if (argv.help) {
fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout);
return;
}
var paths = argv._.slice();
var mode = argv.mode ? parseInt(argv.mode, 8) : undefined;
(function next () {
if (paths.length === 0) return;
var p = paths.shift();
if (mode === undefined) mkdirp(p, cb)
else mkdirp(p, mode, cb)
function cb (err) {
if (err) {
console.error(err.message);
process.exit(1);
}
else next();
}
})();

12
node_modules/mkdirp/bin/usage.txt generated vendored Normal file
View File

@@ -0,0 +1,12 @@
usage: mkdirp [DIR1,DIR2..] {OPTIONS}
Create each supplied directory including any necessary parent directories that
don't yet exist.
If the directory already exists, do nothing.
OPTIONS are:
-m, --mode If a directory needs to be created, set the mode as an octal
permission string.

6
node_modules/mkdirp/examples/pow.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
var mkdirp = require('mkdirp');
mkdirp('/tmp/foo/bar/baz', function (err) {
if (err) console.error(err)
else console.log('pow!')
});

97
node_modules/mkdirp/index.js generated vendored Normal file
View File

@@ -0,0 +1,97 @@
var path = require('path');
var fs = require('fs');
module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
function mkdirP (p, opts, f, made) {
if (typeof opts === 'function') {
f = opts;
opts = {};
}
else if (!opts || typeof opts !== 'object') {
opts = { mode: opts };
}
var mode = opts.mode;
var xfs = opts.fs || fs;
if (mode === undefined) {
mode = 0777 & (~process.umask());
}
if (!made) made = null;
var cb = f || function () {};
p = path.resolve(p);
xfs.mkdir(p, mode, function (er) {
if (!er) {
made = made || p;
return cb(null, made);
}
switch (er.code) {
case 'ENOENT':
mkdirP(path.dirname(p), opts, function (er, made) {
if (er) cb(er, made);
else mkdirP(p, opts, cb, made);
});
break;
// In the case of any other error, just see if there's a dir
// there already. If so, then hooray! If not, then something
// is borked.
default:
xfs.stat(p, function (er2, stat) {
// if the stat fails, then that's super weird.
// let the original error be the failure reason.
if (er2 || !stat.isDirectory()) cb(er, made)
else cb(null, made);
});
break;
}
});
}
mkdirP.sync = function sync (p, opts, made) {
if (!opts || typeof opts !== 'object') {
opts = { mode: opts };
}
var mode = opts.mode;
var xfs = opts.fs || fs;
if (mode === undefined) {
mode = 0777 & (~process.umask());
}
if (!made) made = null;
p = path.resolve(p);
try {
xfs.mkdirSync(p, mode);
made = made || p;
}
catch (err0) {
switch (err0.code) {
case 'ENOENT' :
made = sync(path.dirname(p), opts, made);
sync(p, opts, made);
break;
// In the case of any other error, just see if there's a dir
// there already. If so, then hooray! If not, then something
// is borked.
default:
var stat;
try {
stat = xfs.statSync(p);
}
catch (err1) {
throw err0;
}
if (!stat.isDirectory()) throw err0;
break;
}
}
return made;
};

View File

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

18
node_modules/mkdirp/node_modules/minimist/LICENSE generated vendored Normal file
View File

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

View File

@@ -0,0 +1,2 @@
var argv = require('../')(process.argv.slice(2));
console.dir(argv);

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