Compare commits

...

292 Commits

Author SHA1 Message Date
fengmk2
6d76a590e5 Release 0.3.13 2014-03-27 16:28:01 +08:00
fengmk2
3a98b63736 Merge pull request #299 from cnpm/fix-user-update
fix npm adduser update 409 bug
2014-03-27 16:24:34 +08:00
fengmk2
f5a2090fda Merge pull request #298 from cnpm/fix-ml-cov
fix multiline coverage
2014-03-27 16:18:23 +08:00
fengmk2
989a69143f fix npm adduser update 409 bug 2014-03-27 16:18:01 +08:00
dead_horse
77e6db4f0e fix multiline coverage 2014-03-27 16:03:00 +08:00
fengmk2
a3f7affe7b Release 0.3.13 2014-03-27 15:47:19 +08:00
dead_horse
2cd0453c6b Merge pull request #297 from cnpm/pkg-engine
show package engines. fixed #280
2014-03-27 15:23:32 +08:00
fengmk2
84bc126d57 show package engines. fixed #280 2014-03-27 15:13:18 +08:00
dead_horse
760421661c Merge pull request #296 from cnpm/sync-with-local
dont sync local package field. fix #295
2014-03-27 15:04:25 +08:00
fengmk2
425e430a3a dont sync local package field. fix #295 2014-03-27 14:55:52 +08:00
fengmk2
ad3602bf15 Release 0.3.12 2014-03-26 15:28:05 +08:00
fengmk2
2c5852b344 Merge pull request #294 from cnpm/hotfix-sync
fix result.successes not exist error
2014-03-26 15:22:59 +08:00
dead_horse
115a349fc5 fix result.successes not exist error 2014-03-26 15:21:37 +08:00
fengmk2
3ceea3ccf4 Merge pull request #293 from cnpm/search-list
fix search list
2014-03-24 14:20:51 +08:00
dead_horse
ab6a9b2bea fix search list 2014-03-24 14:17:22 +08:00
fengmk2
9cc84abbb5 Merge pull request #292 from cnpm/issue291-all
only return package name in /-/all and /-/all/since, fixed #291
2014-03-24 11:27:31 +08:00
dead_horse
c1c07cf2e4 add simple request for listall 2014-03-24 11:11:54 +08:00
dead_horse
ce5c97956b only return package name in /-/all and /-/all/since, fixed #291 2014-03-24 10:31:28 +08:00
dead_horse
7cb7a517da refine docs foloder 2014-03-23 02:28:03 +08:00
dead_horse
c7df6f0f1a fix typo 2014-03-23 02:26:38 +08:00
fengmk2
ee59176b67 Merge pull request #290 from cnpm/modified-as-etag
use module gmt_modified as etag. fix #288
2014-03-22 02:09:42 +08:00
fengmk2
f160d741cb use module gmt_modified as etag. fix #288 2014-03-22 01:55:13 +08:00
dead_horse
94974f81ae fix typo, remove unused config in package.json 2014-03-21 17:26:54 +08:00
fengmk2
41c0fea3dd Merge pull request #289 from cnpm/readme
Readme
2014-03-21 16:58:27 +08:00
dead_horse
dfae519e96 fix typo 2014-03-21 16:56:15 +08:00
dead_horse
4d7d4f06b1 web page only list cnpm registry related info 2014-03-21 16:25:47 +08:00
dead_horse
5a890ff27e update readme 2014-03-21 16:15:38 +08:00
fengmk2
ac124d0e2d Merge pull request #286 from cnpm/qnfs-generator
use generator in qnfs
2014-03-20 21:17:03 +08:00
dead_horse
7e267156eb use generator in qnfs 2014-03-20 20:35:58 +08:00
fengmk2
258c34fea8 Release 0.3.11 2014-03-20 10:41:37 +08:00
fengmk2
cd9d403237 Merge pull request #284 from cnpm/issue283-maintainer
use common.isMaintainer, fixed #283
2014-03-20 10:35:55 +08:00
dead_horse
6d43612044 use common.isMaintainer, fixed #283 2014-03-20 09:49:47 +08:00
fengmk2
2e99a779c7 Merge pull request #282 from cnpm/update-thunkify
update thunkify-wrap, breaking change in thunkify-wrap
2014-03-18 10:49:39 +08:00
dead_horse
43259ed126 update dependencies 2014-03-18 10:44:21 +08:00
fengmk2
51e32e3ece Merge pull request #281 from cnpm/issue279-co-mocha
use co-mocha for test, fixed #279
2014-03-18 09:35:39 +08:00
dead_horse
37d9d62d3f use co-mocha for test, fixed #279 2014-03-17 22:27:43 +08:00
dead_horse
2e0a1ca3e8 update thunkify-wrap, breaking change in thunkify-wrap 2014-03-17 17:36:37 +08:00
fengmk2
5abaee4f2a Merge pull request #278 from cnpm/use-multiline
Use multiline
2014-03-17 14:06:59 +08:00
dead_horse
5e2ca1d5e8 refactor SQLs by using multiline 2014-03-17 13:27:09 +08:00
dead_horse
c0a6bb4869 fix typo 2014-03-16 22:28:56 +08:00
dead_horse
2aba0836cf use multiline to refactor sqls 2014-03-16 22:20:11 +08:00
fengmk2
e1636e2c7d ignore contributors 2014-03-16 16:43:58 +08:00
fengmk2
15245dc68b Release 0.3.10 2014-03-16 16:33:02 +08:00
dead_horse
8dedabaa2d Merge pull request #277 from cnpm/only-authsession-send-cookie
Only /_session request send the authSession. fixed #223
2014-03-16 16:24:35 +08:00
fengmk2
f4b82bbf0b Only /_session request send the authSession. fixed #223
* no auth requests url dont send the authSession
* baseauth dont create session cookie too
2014-03-16 16:14:12 +08:00
dead_horse
78d81c02cd Merge pull request #276 from cnpm/sync-npm-user
Sync npm user
2014-03-16 15:28:31 +08:00
fengmk2
1c743f3759 sync npm user info when maintainers and contributors not exists. fixed #82 2014-03-16 14:58:44 +08:00
fengmk2
041f80ab14 save npm user to mysql 2014-03-16 12:44:38 +08:00
fengmk2
e5646c6553 password salt always be randoms 2014-03-16 11:51:06 +08:00
fengmk2
6134983bcc Merge pull request #275 from cnpm/issue274-sync-by-install
remove session access in /name and /name/version, fixed #274
2014-03-16 11:36:27 +08:00
dead_horse
7dab569d43 remove session access in /name and /name/version, fixed #274 2014-03-15 23:20:34 +08:00
fengmk2
5aa84b03c6 Merge pull request #270 from cnpm/update-session
Update session
2014-03-15 22:59:23 +08:00
dead_horse
77fc133ecb fix update maintainer session error 2014-03-15 22:56:03 +08:00
dead_horse
f98416e217 update koa-middlewares 2014-03-15 22:52:50 +08:00
dead_horse
5f49b97aa4 fix test, fix sync_by_install 2014-03-15 22:52:50 +08:00
dead_horse
8bdfd593c9 use defer session 2014-03-15 22:52:48 +08:00
dead_horse
c214da39dc Merge pull request #272 from cnpm/author-add
Support npm owner|author add [name] [pkg]. fixed #271
2014-03-15 22:41:34 +08:00
fengmk2
f2be453ef6 Support npm owner|author add [name] [pkg]. fixed #271
* npm 1.4.4+ adduser new flow support
* npm author add, rm, ls support
2014-03-15 17:15:42 +08:00
fengmk2
e2b7003749 Release 0.3.9 2014-03-14 15:05:45 +08:00
fengmk2
944ac6a712 custom user-agent 2014-03-14 15:04:29 +08:00
dead_horse
c502c00063 Merge pull request #269 from cnpm/co-urllib
use co-urllib instead of thunkify urllib; fix mock http.request test cases
2014-03-14 14:42:12 +08:00
fengmk2
d536ad954b use co-urllib instead of thunkify urllib; fix mock http.request test cases 2014-03-14 14:33:20 +08:00
fengmk2
100706f311 request limit custom message 2014-03-13 10:47:06 +08:00
fengmk2
8d8fd31a0e Merge pull request #268 from cnpm/issue267-limit
add koa-limit, fixed #267
2014-03-13 09:55:30 +08:00
dead_horse
7aa2ea3086 add config.redis check 2014-03-13 09:31:30 +08:00
dead_horse
5bf40164a1 add koa-limit, fixed #267 2014-03-13 01:34:36 +08:00
fengmk2
e497fb354d Release 0.3.8 2014-03-11 15:13:11 +08:00
fengmk2
e647d4a1fe Merge pull request #265 from cnpm/update-middlewares
update middlewares, fixed 264
2014-03-11 15:08:33 +08:00
dead_horse
33465ecf86 update middlewares, fixed 264 2014-03-11 15:06:56 +08:00
fengmk2
3eb3eb6be9 Release 0.3.7 2014-03-11 12:02:39 +08:00
fengmk2
6ab1556ec0 show worker die date time 2014-03-10 13:50:38 +08:00
fengmk2
9fd21a5c25 Merge pull request #261 from cnpm/update-koa
update to koa@0.5.1
2014-03-08 10:35:20 +08:00
dead_horse
53b8398e93 update to koa@0.5.1 2014-03-08 10:30:27 +08:00
fengmk2
c43b117e67 hotfix for star user 2014-03-08 00:23:11 +08:00
fengmk2
7db269e5f4 Merge pull request #253 from cnpm/issue237-sync-worker
Issue237 sync worker
2014-03-07 23:48:09 +08:00
dead_horse
92ed8984d3 fix yield gather, sync missing deps even no missing versions 2014-03-07 23:45:13 +08:00
dead_horse
71d351da29 fix return versions 2014-03-07 22:36:13 +08:00
dead_horse
c8e1d8cc4a fix makefile, remove eventproxy 2014-03-07 22:36:13 +08:00
dead_horse
fd79879d0c refactor sync_module_worker 2014-03-07 22:36:13 +08:00
dead_horse
354b82f6ea refactor sync_module_worker 2014-03-07 22:36:13 +08:00
dead_horse
de0487cccc add make test-dev, fixed #259 2014-03-07 22:35:15 +08:00
dead_horse
081ccd66df change npm.js to generator 2014-03-07 22:35:15 +08:00
dead_horse
cebf501182 update urllib, proxy/npm.js use generator
but SyncModuleWorker still use callback type npm.js,
so just use co wrap to thunk.
after refactor SyncModuleWorker, remove this co wrap.
2014-03-07 22:35:14 +08:00
dead_horse
8fe593a182 sync_all and sync_exist to generator 2014-03-07 22:34:43 +08:00
dead_horse
8770458a46 change function to generator 2014-03-07 22:34:43 +08:00
dead_horse
559dc03cd4 need node >= v0.11.9 2014-03-06 16:16:36 +08:00
fengmk2
70d12f235a Release 0.3.6 2014-03-06 15:05:07 +08:00
fengmk2
aa7077e4b0 install missing package should sync it from source npm. fixed #252 2014-03-06 15:04:07 +08:00
dead_horse
f5c19668f5 Merge pull request #254 from cnpm/jshint
Add jshint check: $ make jshint
2014-03-06 14:09:07 +08:00
fengmk2
2b2a1904c6 npm publish dont contains .jshint* 2014-03-06 14:02:33 +08:00
fengmk2
a234c7e289 npm test run jshint 2014-03-06 14:00:43 +08:00
fengmk2
63562c30ec Add jshint check: $ make jshint
* .jshint make esnext true
* .jshintignore ignore no need check dirs
2014-03-06 13:55:13 +08:00
dead_horse
6968331b38 Merge pull request #251 from cnpm/yield-next
use `yield* next` instead of `yield next`
2014-03-05 16:55:32 +08:00
fengmk2
fc1a17e4d1 use yield* next instead of yield next
* According to koajs/compose#2, show performances improve a lot.
2014-03-05 16:49:58 +08:00
fengmk2
3872b7dfdf replace dist.u.qiniudn.com with cnpmjs.org/dist 2014-03-05 13:20:45 +08:00
fengmk2
e340ce081a Release 0.3.5 2014-03-05 13:18:39 +08:00
dead_horse
170a689168 Merge pull request #250 from cnpm/dist
redirect /dist/xxx.tgz => http://dist.u.qiniudn.com/xxx.tgz fixed #249
2014-03-05 11:08:05 +08:00
fengmk2
7794d1099d redirect /dist/xxx.tgz => http://dist.u.qiniudn.com/xxx.tgz fixed #249 2014-03-05 11:00:41 +08:00
dead_horse
bd8686c448 Merge pull request #247 from cnpm/redirect
redirect /name to /package/name when /name is 404. fixed #245
2014-03-04 23:54:48 +08:00
fengmk2
d8a76d03e0 redirect /name to /package/name when /name is 404. fixed #245 2014-03-04 23:22:32 +08:00
dead_horse
4460810799 Merge pull request #244 from cnpm/missing-propertys
Add missing properies and sync missing star users. fixed #235
2014-03-04 21:35:57 +08:00
fengmk2
4124525eb8 Add missing properies and sync missing star users. fixed #235
* add readmeFilename and missing created, modified time.
* Sync star users on pkg sync flow
* Show star users on registry.show
* Show star users on web package show
* Need to create new table module_star
2014-03-04 21:20:43 +08:00
fengmk2
ff50946cd3 Release 0.3.4 2014-03-04 16:32:25 +08:00
fengmk2
3637eeefdd add cov 2014-03-04 16:31:22 +08:00
dead_horse
cecb41e6ea Merge pull request #243 from cnpm/coverage
use istanbul run test coverage
2014-03-03 21:52:52 +08:00
fengmk2
72b3240ef5 use istanbul run test coverage 2014-03-03 21:02:11 +08:00
dead_horse
869d5681ce Merge pull request #242 from cnpm/gzip
gzip support. fix #241
2014-03-03 16:45:37 +08:00
fengmk2
ff1bfd5acc gzip support. fix #241 2014-03-03 16:44:29 +08:00
dead_horse
7e3129c8b8 Merge pull request #240 from stanzheng/patch-1
readme spelling patch
2014-03-03 00:42:27 +08:00
Stanley Zheng
0618c732d7 thanks dead_horse 2014-03-02 11:39:30 -05:00
Stanley Zheng
9b34ab408c readme spelling patch 2014-03-02 11:10:40 -05:00
fengmk2
d990d3aa91 Merge pull request #234 from cnpm/issue233-remove-readme
Issue233 remove readme
2014-02-28 23:25:08 +08:00
dead_horse
03ef728049 default readme to null, fixed #233 2014-02-28 22:37:45 +08:00
dead_horse
f00ed85d41 remove readme in versions 2014-02-28 22:32:44 +08:00
fengmk2
685af2a367 Release 0.3.3 2014-02-28 17:41:09 +08:00
dead_horse
818f216fb4 Merge pull request #232 from cnpm/host-hotfix
get request host from request.headers
2014-02-28 16:36:44 +08:00
fengmk2
1b47495565 get request host from request.headers 2014-02-28 16:35:51 +08:00
dead_horse
0ab314f27c Merge pull request #231 from cnpm/bug-fix
fix deps display bug#230 and nsf.url TypeError#229
2014-02-28 15:40:04 +08:00
fengmk2
95076c8787 fix deps display bug#230 and nsf.url TypeError#229 2014-02-28 15:35:37 +08:00
fengmk2
4e6eb0a9cc Release 0.3.2 2014-02-28 14:46:17 +08:00
fengmk2
29f17dd5d1 Merge pull request #228 from cnpm/update-sess
update koa-sess and koa-redis
2014-02-28 14:42:03 +08:00
dead_horse
3a48637ef1 update koa-sess and koa-redis 2014-02-28 14:40:47 +08:00
fengmk2
e1029b005f Merge pull request #227 from cnpm/fix-nfs
Fix nfs
2014-02-28 12:20:43 +08:00
dead_horse
66771dfc3b fix sync all test 2014-02-28 12:15:30 +08:00
dead_horse
66f05a2f07 remove nfs.downloadStream first, fix tmppath error 2014-02-28 12:04:28 +08:00
fengmk2
6660cabbb6 Merge pull request #226 from cnpm/giturl-bug-fix
fix fengmk2/giturl#1 bug
2014-02-27 20:53:33 +08:00
fengmk2
fbe1971957 fix fengmk2/giturl#1 bug 2014-02-27 20:52:19 +08:00
fengmk2
d75c3877bb Release 0.3.1 2014-02-27 15:06:17 +08:00
dead_horse
564ec488ea Merge pull request #225 from cnpm/etag
add etag fixed #224
2014-02-27 15:00:26 +08:00
fengmk2
1559c16c3d add etag fixed #224 2014-02-27 14:58:08 +08:00
fengmk2
2345536cbd travis ci install on source npm 2014-02-27 10:16:03 +08:00
fengmk2
89808e398b Release 0.3.0 2014-02-27 10:13:05 +08:00
fengmk2
5ddf238c08 fix typo and dont sync not exists packages 2014-02-27 10:12:53 +08:00
dead_horse
1ae193e306 Merge pull request #221 from cnpm/koa-merge
Use koa instead of connect
2014-02-27 09:57:54 +08:00
dead_horse
9d511c326c use koa-middlewares
contain some frequently-used middlewares in this module
only need to maintain this module
2014-02-27 09:51:44 +08:00
fengmk2
78d7a77b0d fix signed cookie not work on npm@1.3.25; node --harmony-generators 2014-02-27 09:51:44 +08:00
fengmk2
0f494822bc fix opensearch test case 2014-02-27 09:51:44 +08:00
fengmk2
211df84514 update koa bodyparser 2014-02-27 09:51:44 +08:00
fengmk2
fee243726e logger.error(err) should send err stack email notice 2014-02-27 09:51:44 +08:00
fengmk2
52e7e6d069 json body parse limit and bug fix.
* override json limit to default 10mb. fixed #209
 * fix #210 addPackageAndDist package version detect bug
2014-02-27 09:51:42 +08:00
dead_horse
551ae832e3 fix sync 404 reason not clear 2014-02-27 09:51:07 +08:00
dead_horse
9af99f4af2 all controllers to koa 2014-02-27 09:51:07 +08:00
dead_horse
3e8ecda9e4 controller/web/user.js to koa 2014-02-27 09:51:07 +08:00
dead_horse
fb744176f8 change web connect to koa 2014-02-27 09:51:06 +08:00
dead_horse
5e1ab4356d use outputError 2014-02-27 09:51:06 +08:00
dead_horse
5fb9a007f9 use yield exports.addPackageAndDist.call(this, next); 2014-02-27 09:51:06 +08:00
dead_horse
780a5aa158 add end() when ws write end 2014-02-27 09:51:06 +08:00
dead_horse
ab2ff4ed9e fix yield coWrite 2014-02-27 09:51:06 +08:00
dead_horse
2dad7553e6 fix all the test of registry module.test.js 2014-02-27 09:51:05 +08:00
dead_horse
acfa2e418b convert registry/module.js to koa type 2014-02-27 09:51:03 +08:00
fengmk2
74101fda7a fix auth middleware 2014-02-27 09:48:21 +08:00
fengmk2
2ec1eec91c finish registry user controller koa and update mm to support thunkify. fixed #196 2014-02-27 09:48:20 +08:00
fengmk2
b3e966184a change controllers/user.js to koa 2014-02-27 09:48:20 +08:00
dead_horse
b09960858c thunkify all proxy 2014-02-27 09:48:20 +08:00
dead_horse
84634af0c2 convert all middlewares to koa type 2014-02-27 09:48:20 +08:00
dead_horse
d60d7eaf2e change regsitry sync to koa 2014-02-27 09:48:18 +08:00
dead_horse
6d6a994997 addd koa-jsonp, koa-bodyparser, fix / controller 2014-02-27 09:46:42 +08:00
fengmk2
ab564c3b32 first koa run registry home page / 2014-02-27 09:46:39 +08:00
fengmk2
55b836388d Merge pull request #212 from cnpm/fix-sync-404
return friendly 404 reason
2014-02-26 08:56:32 +08:00
dead_horse
3f45384b74 return friendly 404 reason 2014-02-26 00:21:48 +08:00
dead_horse
6fe2997fb5 Merge pull request #211 from cnpm/bug-fix
Bug fix
2014-02-25 22:29:26 +08:00
fengmk2
cdd857ca2d override json limit to default 10mb. fixed #209 2014-02-25 20:57:54 +08:00
fengmk2
c9e513350a fix #210 addPackageAndDist package version detect bug 2014-02-25 20:56:53 +08:00
fengmk2
7b2cbd6d1d Release 0.2.27 2014-02-19 18:10:50 +08:00
fengmk2
90959ba34f Merge pull request #193 from cnpm/issue189-search-api
support json result in search, fixed #189
2014-02-19 17:42:14 +08:00
dead_horse
0f6b6a2f2b support json result in search, fixed #189 2014-02-19 17:40:44 +08:00
fengmk2
666d98d86e Release 0.2.26 2014-02-19 16:28:57 +08:00
dead_horse
e244efb153 Merge pull request #192 from cnpm/publish-add-deps
npm publish also need to add deps
2014-02-19 15:29:04 +08:00
fengmk2
67d824e5dc npm publish also need to add deps 2014-02-19 15:23:02 +08:00
fengmk2
daf29f760d Release 0.2.25 2014-02-19 14:23:11 +08:00
dead_horse
e5939d170f Merge pull request #191 from cnpm/module_deps
Dependents support. fixed #190
2014-02-19 14:15:01 +08:00
fengmk2
3526c9eff7 max handle number of package.json dependencies property 2014-02-19 14:07:06 +08:00
fengmk2
973889c73a Dependents support. fixed #190 2014-02-19 13:29:33 +08:00
fengmk2
70cd5f5cb6 Release 0.2.24 2014-02-13 18:43:13 +08:00
fengmk2
ba6139f265 Merge pull request #187 from cnpm/issue186-remvoe
refactor remove module, fixed #186
2014-02-13 18:41:49 +08:00
dead_horse
645e4913ba fix if delete all the versions 2014-02-13 17:34:56 +08:00
dead_horse
46eba8d03a refactor remove module, fixed #186
1. list all the modules, find which versions to be removed and which to
be remained.
2. remove all the modules need to be removed
3. list all the tags, find which tags need to be removed and remove
them.
4. if the latest tag removed, need to generate a new latest tag.
2014-02-13 14:51:29 +08:00
fengmk2
9d2a649aac Release 0.2.23 2014-01-26 16:58:30 +08:00
dead_horse
bf53537f00 Merge pull request #184 from cnpm/admin-role-fix
system admin can add, publish, remove the packages. fixed #176
2014-01-26 00:52:15 -08:00
fengmk2
1047e18732 system admin can add, publish, remove the packages. fixed #176 2014-01-26 16:39:01 +08:00
fengmk2
2981a17b10 Release 0.2.22 2014-01-26 16:22:30 +08:00
dead_horse
041416abf1 Merge pull request #183 from cnpm/keyword-search
add keyword and search support keyword. #181
2014-01-26 00:19:06 -08:00
fengmk2
c117b67f3b add keyword and search support keyword. #181 2014-01-26 15:25:05 +08:00
fengmk2
bc2ac45588 Release 0.2.21 2014-01-24 16:37:40 +08:00
fengmk2
95eba3fcfc refactor code styles on package.html 2014-01-24 16:31:40 +08:00
fengmk2
e98b409f61 Merge pull request #180 from 4simple/master
nav-tabs support in package page
2014-01-24 00:19:04 -08:00
dead_horse
fedec47349 Merge pull request #179 from cnpm/show-server-error
Show registry server error response. fixed #178
2014-01-24 00:11:36 -08:00
4simple
b63a565a37 nav-tabs e.preventDefault 2014-01-24 15:58:49 +08:00
fengmk2
67eb7fa380 Show registry server error response. fixed #178 2014-01-24 15:22:07 +08:00
4simple
a117886b71 nav-tabs for package.html 2014-01-24 00:22:24 +08:00
fengmk2
152f6800da Release 0.2.20 2014-01-23 14:31:34 +08:00
dead_horse
2f7e52f86f hotfix check missing readme error 2014-01-23 14:29:20 +08:00
dead_horse
bb4b51c54f hotfix sync missing dependencies and readmes 2014-01-23 14:26:30 +08:00
fengmk2
4c2303e4fa Merge pull request #175 from cnpm/issue174-readme
Issue174 readme
2014-01-22 22:17:00 -08:00
dead_horse
b70201db79 fix sync readme error, fixed #174 2014-01-23 14:16:26 +08:00
dead_horse
dd095147b0 add updateReadme in module 2014-01-23 14:07:39 +08:00
fengmk2
08c4445e78 Release 0.2.19 2014-01-22 16:45:28 +08:00
dead_horse
b892ad8185 Merge pull request #173 from cnpm/no-auth-on-install
npm install no need to check authorization header. fixed #171
2014-01-22 00:32:21 -08:00
fengmk2
a8ee1e43bf npm install no need to check authorization header. fixed #171 2014-01-22 16:31:17 +08:00
fengmk2
dd84674b98 Release 0.2.18 2014-01-20 21:43:07 +08:00
dead_horse
ccec2cb45b Merge pull request #170 from cnpm/show-git-web-url
Support gitlab git url to display and click. fixed #160
2014-01-20 05:34:09 -08:00
fengmk2
f3a4500191 Support gitlab git url to display and click. fixed #160 2014-01-20 21:15:48 +08:00
fengmk2
161a860f74 fix redis crash 2014-01-19 09:55:15 +08:00
fengmk2
f58e8a24b0 Release 0.2.17 2014-01-17 11:42:42 +08:00
fengmk2
9bc6a6dd74 custom logo url 2014-01-17 11:42:14 +08:00
fengmk2
c50364807d Merge pull request #167 from cnpm/hotfix-layout
hotfix layout bug
2014-01-16 19:05:44 -08:00
dead_horse
b3e3ecfc3a hotfix layout bug 2014-01-17 11:03:18 +08:00
fengmk2
637b5fe785 Release 0.2.16 2014-01-16 18:18:37 +08:00
dead_horse
6eed7b9002 Merge pull request #166 from cnpm/update-publish-time
fix publish-time bug
2014-01-16 02:10:11 -08:00
fengmk2
899becb448 fix publish-time bug 2014-01-16 18:09:03 +08:00
fengmk2
7e982ca877 Release 0.2.15 2014-01-16 17:12:54 +08:00
fengmk2
26156253c7 add publish_time to debug 2014-01-16 17:12:32 +08:00
fengmk2
9445d46bbf Release 0.2.14 2014-01-16 16:05:52 +08:00
fengmk2
55bfcc853e Merge pull request #164 from cnpm/issue163-time-error
update publish_time, fixed #163
2014-01-15 23:58:42 -08:00
fengmk2
4eeb1b25de Merge pull request #165 from cnpm/add-autod
add make autod
2014-01-15 23:58:02 -08:00
dead_horse
42959196c6 add make autod 2014-01-16 15:17:37 +08:00
dead_horse
d264b47b09 update publish_time, fixed #163 2014-01-16 14:58:55 +08:00
fengmk2
4e412d1808 Release 0.2.13 2014-01-15 18:48:37 +08:00
dead_horse
73527da1f1 Merge pull request #162 from cnpm/fix-custom-footer
markdown tmpl not support footer, need to wrap on app start
2014-01-15 02:47:26 -08:00
fengmk2
7986f198af markdown tmpl not support footer, need to wrap on app start 2014-01-15 18:27:07 +08:00
fengmk2
adb1411313 Release 0.2.12 2014-01-15 18:05:36 +08:00
dead_horse
2f24e9828e Merge pull request #161 from cnpm/more-custom
add footer and npm client name customable
2014-01-15 01:52:21 -08:00
fengmk2
d7e5921e24 add footer and npm client name customable 2014-01-15 17:44:41 +08:00
fengmk2
e3e6a1aaa5 Release 0.2.11 2014-01-15 14:52:28 +08:00
dead_horse
0d1969fadd Merge pull request #158 from cnpm/packagePageContributorSearch
package page contributor link to search, default is true
2014-01-14 22:50:28 -08:00
fengmk2
028e599d51 package page contributor link to search, default is true 2014-01-15 14:46:06 +08:00
fengmk2
fa0dd5c23f Release 0.2.10 2014-01-14 20:57:14 +08:00
dead_horse
7be5df8e70 Merge pull request #156 from cnpm/fix-Disposition
fix #155 Content-Disposition wrong.
2014-01-14 04:29:34 -08:00
fengmk2
adddf0e4c5 fix #155 Content-Disposition wrong. 2014-01-14 20:24:22 +08:00
fengmk2
28cc13d583 Release 0.2.9 2014-01-14 15:41:55 +08:00
dead_horse
c85b27b9b2 Merge pull request #154 from cnpm/couch-search-api
support couch db search api. fixed #153
2014-01-13 23:22:22 -08:00
fengmk2
46795adf54 support startkey=c and startkey="c" 2014-01-14 15:17:04 +08:00
fengmk2
c8ab1735a5 support couch db search api. fixed #153 2014-01-14 15:14:30 +08:00
dead_horse
4c759b40c8 Merge pull request #152 from cnpm/sync-by-query-name
support sync by query.name
2014-01-13 22:20:05 -08:00
fengmk2
1bab099f38 fix fork me image link 2014-01-14 13:55:47 +08:00
fengmk2
f95f814a8c support sync by query.name 2014-01-14 13:54:48 +08:00
fengmk2
ffcb0d669a Release 0.2.8 2014-01-14 10:14:56 +08:00
dead_horse
81ca81d578 Merge pull request #150 from cnpm/download-link
add download link for package page
2014-01-13 18:11:22 -08:00
fengmk2
803f6d42f8 dont show err stack on test env 2014-01-14 09:59:39 +08:00
fengmk2
5366f16bcb add download link for package page 2014-01-14 09:49:15 +08:00
fengmk2
ab18e49131 Release 0.2.7 2014-01-13 21:53:55 +08:00
fengmk2
3c874244a7 Merge pull request #149 from cnpm/issue148-shasum
add shasum when nfs.upload and hfs.uploadBuffer, fixed #148
2014-01-13 05:52:18 -08:00
dead_horse
d463b09c81 add shasum when nfs.upload and hfs.uploadBuffer, fixed #148 2014-01-13 21:50:22 +08:00
fengmk2
40301f260b Release 0.2.6 2014-01-13 17:15:32 +08:00
fengmk2
72dbde1906 Merge pull request #147 from cnpm/issue146-session-config
support custom session store
2014-01-13 01:13:56 -08:00
dead_horse
d68157faa8 support custom session store, fixed #146 2014-01-13 17:09:04 +08:00
fengmk2
c88991c028 Release 0.2.5 2014-01-13 12:00:47 +08:00
dead_horse
262abe8520 Merge pull request #144 from cnpm/download-stream
nfs download to a writeable stream.
2014-01-12 18:50:50 -08:00
fengmk2
9dd942df1a add download timeout and unit test 2014-01-13 10:44:55 +08:00
fengmk2
9ec552e08d use downloadStream() first 2014-01-13 09:48:02 +08:00
fengmk2
2420164a9d nfs download to a writeable stream.
nfs.download(key, writeStream, cb), should not create a tmp file,
and also add content-length, content-type headers.
2014-01-11 00:14:17 +08:00
fengmk2
822f2f6a4e Release 0.2.4 2014-01-10 15:59:56 +08:00
fengmk2
d423a987ae Merge pull request #143 from cnpm/issue142-module
set main script to  index.js, fixed #142
2014-01-09 23:59:07 -08:00
dead_horse
70cefd817e set main script to index.js, fixed #142 2014-01-10 15:52:32 +08:00
fengmk2
2606af24a1 Release 0.2.3 2014-01-10 15:15:59 +08:00
dead_horse
5b781cbda1 Merge pull request #140 from cnpm/package-display
dont show sync button on private package
2014-01-09 23:05:13 -08:00
fengmk2
1519b52a74 dont show sync button on private package 2014-01-10 14:38:23 +08:00
dead_horse
4b73f6e0a7 Merge pull request #139 from cnpm/sync-as-publish-with-no-deps
Sync package as publish with no deps. fixed #138
2014-01-09 21:09:33 -08:00
fengmk2
88bcb14a4f Sync package as publish with no deps. fixed #138
Let user sync their private packages from private registry to other.
Just use `$ cnpm sync --publish --no-deps private_pkg1 private_pkg2`.
2014-01-10 12:46:38 +08:00
fengmk2
5bd2cf5d50 Release 0.2.2 2014-01-10 10:58:35 +08:00
fengmk2
e8fcfc67a8 Merge pull request #137 from fengmk2/issue136-nfs-support
Issue136 nfs support
2014-01-09 18:48:53 -08:00
dead_horse
580fc8c777 keep compatibility
temp save path add random string
add download statistics
add if else after ufs.upload
2014-01-10 10:39:47 +08:00
dead_horse
60fdcdd0e4 qnfs upload only callback a url 2014-01-09 23:11:40 +08:00
dead_horse
09fdff49a1 compat remove package
use dist.tarball to store the key, avoid to change mysql
2014-01-09 18:01:43 +08:00
dead_horse
2f1b5034c4 set tarball url 2014-01-09 17:27:14 +08:00
dead_horse
6571ec53bb fix test 2014-01-09 16:11:31 +08:00
dead_horse
4a106f1bdf new npm publish in one request, add _publish_in_cnpm 2014-01-09 16:08:56 +08:00
dead_horse
92c20c90ac support unsure name ufs
upload return {key: 'xxx'}, then save it
2014-01-09 16:08:56 +08:00
fengmk2
31a920c5b3 contributors maybe a object 2014-01-08 11:23:54 +08:00
dead_horse
a3b51c6803 Merge pull request #135 from fengmk2/hotfix
Object #<Object> has no method 'forEach' fixed #134
2014-01-07 19:18:53 -08:00
fengmk2
2482c06e04 Object #<Object> has no method 'forEach' fixed #134 2014-01-08 11:17:51 +08:00
fengmk2
bbafa027e8 Merge pull request #133 from fengmk2/issue132-config
support custom config as a module, fixed issue #132
2014-01-07 19:10:52 -08:00
dead_horse
3ca62486d2 support custom config as a module, fixed issue #132 2014-01-07 21:27:33 +08:00
dead_horse
6371d60b70 Merge pull request #130 from fengmk2/fix-publish
Fix new npm publish flow
2013-12-27 05:59:15 -08:00
fengmk2
8014e0281f support npm new publish flow. fixed #129 2013-12-27 21:43:41 +08:00
fengmk2
253c11c855 add toString and constructor to test admin 2013-12-27 19:05:57 +08:00
fengmk2
49c5c73281 fix #119 hasOwnProperty check admin bug. 2013-12-27 19:04:02 +08:00
fengmk2
684edc1a28 Release 0.2.0 2013-12-27 18:37:01 +08:00
dead_horse
cc60007679 Merge pull request #128 from fengmk2/module-name-case-care
fix #127 execSync and execsync.
2013-12-27 02:18:43 -08:00
fengmk2
6788180ca7 remove to lower case 2013-12-27 18:07:58 +08:00
fengmk2
57c9b2544b fix #127 execSync and execsync. 2013-12-27 18:04:49 +08:00
dead_horse
c91995644c Merge pull request #126 from fengmk2/contributors
add contributors list on package page
2013-12-26 23:08:50 -08:00
fengmk2
41fa1ad524 add contributors list on package page 2013-12-27 15:00:13 +08:00
dead_horse
af14f03932 Merge pull request #125 from fengmk2/sync-typeerror
sync typeerror fix #statusCode
2013-12-24 22:42:58 -08:00
fengmk2
5a0b4fc351 mv blanket to config 2013-12-25 14:38:15 +08:00
fengmk2
f42a2bbeef sync typeerror fix #statusCode 2013-12-25 14:31:45 +08:00
fengmk2
8980e98a7d add disturl 2013-12-23 17:19:58 +08:00
fengmk2
b401ae2772 fix #122 admin security bug 2013-12-22 11:34:12 +08:00
fengmk2
e56fcdbdc4 fixed #121, let pkg 404 as success 2013-12-21 09:10:05 +08:00
fengmk2
4d7f6906dd Merge pull request #120 from fengmk2/fix-sql
fix sql insert error
2013-12-20 16:53:02 -08:00
dead_horse
f4b5977ad9 fix sql insert error 2013-12-21 01:42:33 +08:00
fengmk2
98c84e1556 fix typos 2013-12-20 17:52:01 +08:00
92 changed files with 5042 additions and 1916 deletions

5
.gitignore vendored
View File

@@ -7,6 +7,7 @@ coverage.html
*.pid
*.gz
dump.rdb
.DS_Store
pids
logs
@@ -19,3 +20,7 @@ config/config.js
backup/*.json
backup/*.gz
docs/web/history.md
view/web/_layout.html
bin/mysql.js
bin/test.sql
coverage/

4
.jshintignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
coverage/
.tmp/
.git/

95
.jshintrc Normal file
View File

@@ -0,0 +1,95 @@
{
// JSHint Default Configuration File (as on JSHint website)
// See http://jshint.com/docs/ for more details
"maxerr" : 50, // {int} Maximum error before stopping
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : false, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : false, // {int} Number of spaces to use for indentation
"latedef" : false, // true: Require variables/functions to be defined before being used
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : false, // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : false, // true: Require all defined variables be used
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
"trailing" : false, // true: Prohibit trailing whitespaces
"maxparams" : false, // {int} Max number of formal params allowed per function
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
"maxstatements" : false, // {int} Max number statements per function
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
"maxlen" : false, // {int} Max number of characters per line
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : true, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : true, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements"
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : true, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : true, // true: Tolerate multi-line strings
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
"shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
// Environments
"browser" : true, // Web Browser (window, document, etc)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : false, // jQuery
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
"noyield" : true, // allow generators without a yield
// Legacy
"nomen" : false, // true: Prohibit dangling `_` in variables
"onevar" : false, // true: Allow only one `var` statement per function
"passfail" : false, // true: Stop on first error
"white" : false, // true: Check against strict whitespace and indentation rules
// Custom Globals
"globals" : { // additional predefined global variables
// mocha
"describe": true,
"it": true,
"before": true,
"afterEach": true,
"beforeEach": true,
"after": true
}
}

View File

@@ -8,3 +8,10 @@ logo.png
public/dist/
backup/*.json
backup/*.gz
view/web/_layout.html
bin/mysql.js
bin/test.sql
coverage/
.jshintrc
.jshintignore
.DS_Store

View File

@@ -1,5 +1,3 @@
language: node_js
node_js:
- '0.10'
install: npm install --registry=http://registry.cnpmjs.org
script: make test-coveralls
- '0.11'

View File

@@ -1,7 +1,8 @@
# Ordered by date of first contribution.
# Auto-generated by 'contributors' on Fri, 20 Dec 2013 09:33:09 GMT.
# Auto-generated by 'contributors' on Mon, 03 Mar 2014 13:01:28 GMT.
# https://github.com/xingrz/node-contributors
fengmk2 <fengmk2@gmail.com> (https://github.com/fengmk2)
dead_horse <dead_horse@qq.com> (https://github.com/dead-horse)
AlsoTang <alsotang@gmail.com> (https://github.com/alsotang)
dead-horse <dead_horse@qq.com> (https://github.com/dead-horse)
alsotang <alsotang@gmail.com> (https://github.com/alsotang)
4simple <wondger@qq.com> (https://github.com/4simple)

View File

@@ -1,4 +1,336 @@
0.3.13 / 2014-03-27
==================
* fix npm adduser update 409 bug
* fix multiline coverage
* show package engines. fixed #280
* dont sync local package field. fix #295
0.3.12 / 2014-03-26
==================
* fix result.successes not exist error
* fix search list
* add simple request for listall
* only return package name in /-/all and /-/all/since, fixed #291
* refine docs foloder
* use module gmt_modified as etag. fix #288
* fix typo, remove unused config in package.json
* web page only list cnpm registry related info
* use generator in qnfs
0.3.11 / 2014-03-20
==================
* use common.isMaintainer, fixed #283
* update dependencies
* use co-mocha for test, fixed #279
* update thunkify-wrap, breaking change in thunkify-wrap
* refactor SQLs by using multiline
* use multiline to refactor sqls
* ignore contributors
0.3.10 / 2014-03-16
==================
* Only /_session request send the authSession. fixed #223
* sync npm user info when maintainers and contributors not exists. fixed #82
* save npm user to mysql
* password salt always be randoms
* remove session access in /name and /name/version, fixed #274
* fix update maintainer session error
* update koa-middlewares
* fix test, fix sync_by_install
* use defer session
* Support npm owner|author add [name] [pkg]. fixed #271
0.3.9 / 2014-03-14
==================
* custom user-agent
* use co-urllib instead of thunkify urllib; fix mock http.request test cases
* request limit custom message
* add config.redis check
* add koa-limit, fixed #267
0.3.8 / 2014-03-11
==================
* update middlewares, fixed missing charset bug #264
0.3.7 / 2014-03-11
==================
* show worker die date time
* update to koa@0.5.1
* hotfix for star user
* fix yield gather, sync missing deps even no missing versions
* fix return versions
* fix makefile, remove eventproxy
* refactor sync_module_worker
* add make test-dev, fixed #259
* change npm.js to generator
* update urllib, proxy/npm.js use generator
* sync_all and sync_exist to generator
* change function to generator
* need node >= v0.11.9
0.3.6 / 2014-03-06
==================
* install missing package should sync it from source npm. fixed #252
* npm publish dont contains .jshint*
* npm test run jshint
* Add jshint check: $ make jshint
* use `yield* next` instead of `yield next`
* replace dist.u.qiniudn.com with cnpmjs.org/dist
0.3.5 / 2014-03-05
==================
* redirect /dist/xxx.tgz => http://dist.u.qiniudn.com/xxx.tgz fixed #249
* redirect /name to /package/name when /name is 404. fixed #245
* Add missing properies and sync missing star users. fixed #235
0.3.4 / 2014-03-04
==================
* add cov
* use istanbul run test coverage
* gzip support. fix #241
* readme spelling patch (@stanzheng)
* default readme to null, fixed #233
* remove readme in versions
0.3.3 / 2014-02-28
==================
* Merge pull request #232 from cnpm/host-hotfix
* get request host from request.headers
* Merge pull request #231 from cnpm/bug-fix
* fix deps display bug#230 and nsf.url TypeError#229
0.3.2 / 2014-02-28
==================
* update koa-sess and koa-redis
* fix sync all test
* remove nfs.downloadStream first, fix tmppath error
* fix fengmk2/giturl#1 bug
0.3.1 / 2014-02-27
==================
* add etag fixed #224
* travis ci install on source npm
0.3.0 / 2014-02-27
==================
* fix typo and dont sync not exists pkgs
* use koa-middlewares
* fix signed cookie not work on npm@1.3.25; node --harmony-generators
* fix opensearch test case
* update koa bodyparser
* logger.error(err) should send err stack email notice
* json body parse limit and bug fix.
* fix sync 404 reason not clear
* all controllers to koa
* controller/web/user.js to koa
* change web connect to koa
* use outputError
* use yield exports.addPackageAndDist.call(this, next);
* add end() when ws write end
* fix yield coWrite
* fix all the test of registry module.test.js
* convert registry/module.js to koa type
* fix auth middleware
* finish registry user controller koa and update mm to support thunkify. fixed #196
* change controllers/user.js to koa
* thunkify all proxy
* convert all middlewares to koa type
* change regsitry sync to koa
* addd koa-jsonp, koa-bodyparser, fix / controller
* first koa run registry home page /
* Merge pull request #212 from cnpm/fix-sync-404
* return friendly 404 reason
* Merge pull request #211 from cnpm/bug-fix
* override json limit to default 10mb. fixed #209
* fix #210 addPackageAndDist package version detect bug
0.2.27 / 2014-02-19
==================
* support json result in search, fixed #189
0.2.26 / 2014-02-19
==================
* npm publish also need to add deps
0.2.25 / 2014-02-19
==================
* max handle number of package.json `dependencies` property
* Dependents support. fixed #190
0.2.24 / 2014-02-13
==================
* fix if delete all the versions
* refactor remove module, fixed #186
0.2.23 / 2014-01-26
==================
* system admin can add, publish, remove the packages. fixed #176
0.2.22 / 2014-01-26
==================
* add keyword and search support keyword. #181
0.2.21 / 2014-01-24
==================
* refactor code styles on package.html
* nav-tabs e.preventDefault
* Show registry server error response. fixed #178
* nav-tabs for package.html (@4simple)
0.2.20 / 2014-01-23
==================
* hotfix sync missing dependencies and readmes
* fix sync readme error, fixed #174
* add updateReadme in module
0.2.19 / 2014-01-22
==================
* npm install no need to check authorization header. fixed #171
0.2.18 / 2014-01-20
==================
* Support gitlab git url to display and click. fixed #160
* fix redis crash
0.2.17 / 2014-01-17
==================
* custom logo url
* hotfix layout bug
0.2.16 / 2014-01-16
==================
* fix publish-time bug
0.2.15 / 2014-01-16
==================
* add publish_time to debug
0.2.14 / 2014-01-16
==================
* add make autod
* update publish_time, fixed #163
0.2.13 / 2014-01-15
==================
* markdown tmpl not support footer, need to wrap on app start
0.2.12 / 2014-01-15
==================
* add footer and npm client name customable
0.2.11 / 2014-01-15
==================
* package page contributor link to search, default is true
0.2.10 / 2014-01-14
==================
* fix #155 Content-Disposition wrong.
0.2.9 / 2014-01-14
==================
* support startkey=c and startkey="c"
* support couch db search api. fixed #153
* fix fork me image link
* support sync by query.name
0.2.8 / 2014-01-14
==================
* dont show err stack on test env
* add download link for package page
0.2.7 / 2014-01-13
==================
* add shasum when nfs.upload and hfs.uploadBuffer, fixed #148
0.2.6 / 2014-01-13
==================
* support custom session store, fixed #146
0.2.5 / 2014-01-13
==================
* add download timeout and unit test
* use downloadStream() first
* nfs download to a writeable stream.
0.2.4 / 2014-01-10
==================
* set main script to index.js, fixed #142
0.2.3 / 2014-01-10
==================
* Dont show sync button on private package
* Sync package as publish with no deps. fixed #138
0.2.2 / 2014-01-10
==================
* keep compatibility
* qnfs upload only callback a url
* compat remove package
* set tarball url
* new npm publish in one request, add _publish_in_cnpm
* support unsure name ufs
* contributors maybe a object
* Object #<Object> has no method 'forEach' fixed #134
* support custom config as a module, fixed issue #132
* support npm new publish flow. fixed #129
* add toString and constructor to test admin
* fix #119 hasOwnProperty check admin bug.
0.2.0 / 2013-12-27
==================
* remove to lower case
* fix #127 execSync and execsync.
* add contributors list on package page
* mv blanket to config
* sync typeerror fix #statusCode
* add disturl
* fix #122 admin security bug
* fixed #121, let pkg 404 as success
* fix sql insert error
* fix typos
0.1.3 / 2013-12-20
==================

View File

@@ -4,27 +4,40 @@ TIMEOUT = 30000
MOCHA_OPTS =
install:
@npm install --registry=http://registry.cnpmjs.org --cache=${HOME}/.npm/.cache/cnpm
@npm install --registry=http://r.cnpmjs.org \
--disturl=http://dist.cnpmjs.org
jshint: install
@-./node_modules/.bin/jshint ./
test: install
@NODE_ENV=test ./node_modules/mocha/bin/mocha \
@NODE_ENV=test ./node_modules/.bin/mocha \
--harmony-generators \
--reporter $(REPORTER) \
--timeout $(TIMEOUT) \
--require should \
--require co-mocha\
$(MOCHA_OPTS) \
$(TESTS)
test-cov:
@$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=travis-cov
test-cov cov: install
@NODE_ENV=test node --harmony \
node_modules/.bin/istanbul cover --preserve-comments \
./node_modules/.bin/_mocha \
-- -u exports \
--reporter $(REPORTER) \
--timeout $(TIMEOUT) \
--require should \
--require co-mocha\
$(MOCHA_OPTS) \
$(TESTS)
@./node_modules/.bin/cov coverage
test-cov-html:
@rm -f coverage.html
@$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=html-cov > coverage.html
@ls -lh coverage.html
contributors: install
@./node_modules/.bin/contributors -f plain -o AUTHORS
test-coveralls: test
@echo TRAVIS_JOB_ID $(TRAVIS_JOB_ID)
@-$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js
test-all: test test-cov
autod: install
@./node_modules/.bin/autod -w -e public,view,docs,backup,coverage
@$(MAKE) install
.PHONY: test

View File

@@ -1,31 +1,83 @@
cnpmjs.org
=======
[![Build Status](https://secure.travis-ci.org/fengmk2/cnpmjs.org.png)](http://travis-ci.org/fengmk2/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/fengmk2/cnpmjs.org/badge.png)](https://coveralls.io/r/fengmk2/cnpmjs.org)[![Dependency Status](https://gemnasium.com/fengmk2/cnpmjs.org.png)](https://gemnasium.com/fengmk2/cnpmjs.org)
[![Build Status](https://secure.travis-ci.org/cnpm/cnpmjs.org.png)](http://travis-ci.org/cnpm/cnpmjs.org) [![Dependency Status](https://gemnasium.com/cnpm/cnpmjs.org.png)](https://gemnasium.com/cnpm/cnpmjs.org)
[![NPM](https://nodei.co/npm/cnpmjs.org.png?downloads=true&stars=true)](https://nodei.co/npm/cnpmjs.org/)
![logo](https://raw.github.com/fengmk2/cnpmjs.org/master/logo.png)
![logo](https://raw.github.com/cnpm/cnpmjs.org/master/logo.png)
## What is this?
Private npm registry and web for Enterprise, base on MySQL and Simple File Store.
Private npm registry and web for Enterprise, base on [koa](http://koajs.com/),
MySQL and [Simple Store Service](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide).
@[JacksonTian](https://github.com/JacksonTian/) had a talk about [private npm](https://speakerdeck.com/jacksontian/qi-ye-ji-node-dot-jskai-fa).
Our goal is to provide a low cost maintenance and easy to use solution for private npm.
## What can you do with `cnpmjs.org`
* Build a private npm for your own enterprise. ([alibaba](http://www.alibaba.com/) is using `cnpmjs.org` now)
* Build a mirror NPM. (we use it to build a mirror in China: [cnpmjs.org](http://cnpmjs.org/))
* Build a completely independent NPM registry to store whatever you like.
### Features
* **Simple to deploy**: only need `mysql` and a [simple store system](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide).
You can get the source code through `npm` or `git`.
* **Low cost and easy maintenance**: `package.json` info store in MySQL, tarball(tgz file) store in CDN or other store systems.
* **Automatic synchronization**: automatic synchronization from any registry specified, support two sync modes:
- Sync all modules from a specified registry, like [npm registry](http://registry.npmjs.org).
- Only sync the modules that exists in your own registry.
* **Manual synchronization**: automatic synchronization may has little delay, but you can syn immediately by manually.
* **Customized client**: we provide a client [cnpm](https://github.com/cnpm/cnpm)
to extend `npm` with more features(`sync` command, [gzip](https://github.com/npm/npm-registry-client/pull/40) support).
And it easy to wrap for your own registry which build with `cnpmjs.org`.
* **Compatible with NPM client**: you can use the origin NPM client with `cnpmjs.org`,
only need to change the registry in config. Even include manual synchronization (through `install` command).
## Getting Start
* @[dead-horse](https://github.com/dead-horse): [What is cnpm?](http://deadhorse.me/slides/cnpmjs.html)
* install and deploy cnpmjs.org through npm: [examples](https://github.com/cnpm/custom-cnpm-example)
* Mirror NPM in China: [cnpmjs.org](http://cnpmjs.org)
* cnpm client: [cnpm](https://github.com/cnpm/cnpm), `npm install -g cnpm`
* [How to deploy cnpmjs.org](https://github.com/cnpm/cnpmjs.org/wiki/Deploy)
* [NFS guide](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide)
![cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=480&h=360)
## Install
## Develop
**Notice**: need node version >=0.11.9
```bash
$ npm install
# clone from git
$ git clone git@github.com:cnpm/cnpmjs.org.git
# install dependencise
$ make install
# test
$ make test
# coverage
$ make test-cov
# udpate dependencies
$ make autod
# start server
$ node --harmony-generators dispatch.js
```
## Usage
## Contribute
```js
$ node dispatch.js
```
* clone the project
* checkout a new branch
* add new features or fix bugs in the new branch
* make a pull request and we will review it ASAP
Tips: make sure your code is follow the [node-style-guide](https://github.com/felixge/node-style-guide).
## Authors
@@ -33,14 +85,16 @@ $ node dispatch.js
$ git summary
project : cnpmjs.org
repo age : 2 weeks
active : 81 days
commits : 205
files : 83
repo age : 4 months ago
commits : 472
active : 167 days
files : 104
authors :
120 fengmk2 58.5%
84 dead_horse 41.0%
1 Alsotang 0.5%
272 fengmk2 57.6%
195 dead_horse 41.3%
2 4simple 0.4%
2 Stanley Zheng 0.4%
1 Alsotang 0.2%
```
## License

View File

@@ -7,7 +7,7 @@ export NODE_ENV='production'
ulimit -c unlimited
cd `dirname $0`/..
NODEJS=node
NODEJS='node --harmony-generators'
BASE_HOME=`pwd`
PROJECT_NAME=`basename ${BASE_HOME}`
STDOUT_LOG=`$NODEJS -e "console.log(require('path').join(require('$BASE_HOME/config').logdir, 'nodejs_stdout.log'));\

View File

@@ -0,0 +1,74 @@
/**!
* cnpmjs.org - bin/restore_module_deps.js
*
* Copyright(c) 2014
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
"use strict";
/**
* Module dependencies.
*/
var mysql = require('../common/mysql');
var Module = require('../proxy/module');
var ModuleDeps = require('../proxy/module_deps');
var addCount = 0;
function restore(id, callback) {
var sql = 'SELECT id, name, package FROM module WHERE id > ? ORDER BY id ASC LIMIT 1000';
mysql.query(sql, [id], function (err, rows) {
if (err) {
return callback(err);
}
if (rows.length === 0) {
return callback(null, []);
}
console.log('[%s] got %d rows', id, rows.length);
rows.forEach(function (r) {
Module.parseRow(r);
if (!r.package) {
return;
}
var deps = Object.keys(r.package.dependencies || {});
if (!Array.isArray(deps) || !deps.length) {
return;
}
deps.forEach(function (dep) {
ModuleDeps.add(dep, r.name, function (err) {
// console.log('[%s] add %s <= %s, error: %s', id, dep, r.name, err);
});
});
addCount += deps.length;
});
setTimeout(function () {
console.log('[%s] add %d relations', id, addCount);
callback(null, rows);
}, 1000);
});
}
var id = 0;
function run() {
restore(id, function (err, rows) {
if (err) {
throw err;
}
if (rows.length === 0) {
console.log('finished, last id: %s, exit.', id);
process.exit(0);
}
id = rows[rows.length - 1].id;
run();
});
}
run();

View File

@@ -1,7 +1,12 @@
/*!
/**!
* cnpmjs.org - common/logger.js
* Copyright(c) 2013
* Author: dead_horse <undefined>
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
@@ -10,11 +15,12 @@
* Module dependencies.
*/
var config = require('../config');
var util = require('util');
var moment = require('moment');
var logstream = require('logfilestream');
var ms = require('ms');
var config = require('../config');
var mail = require('./mail');
var isTEST = process.env.NODE_ENV === 'test';
var ONE_DAY = ms('1d');
@@ -29,7 +35,9 @@ levels.forEach(function (catetory) {
var stream = logstream(options);
function write(msg) {
var time = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
var subject = null;
if (msg instanceof Error) {
subject = msg.name;
var err = {
name: msg.name,
code: msg.code,
@@ -57,12 +65,21 @@ levels.forEach(function (catetory) {
} else {
msg = time + ' ' + util.format.apply(util, arguments) + '\n';
}
if (!isTEST) {
stream.write(msg);
if (config.debug) {
var level = catetory;
console.log('[' + level + '] ' + msg);
} else {
stream.write(msg);
if (catetory === 'error' && subject) {
// send error email
var to = [];
for (var name in config.admins) {
to.push(config.admins[name]);
}
mail.error(to, subject, msg);
}
}
}
}

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - common/mail.js
*
* Copyright(c) cnpmjs.org and other contributors.

View File

@@ -15,6 +15,7 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var ready = require('ready');
var mysql = require('mysql');
var config = require('../config');
@@ -35,7 +36,13 @@ var pool = mysql.createPool({
exports.pool = pool;
exports.query = function (sql, values, cb) {
pool.query(sql, values, cb);
if (typeof values === 'function') {
cb = values;
values = null;
}
pool.query(sql, values, function (err, rows) {
cb(err, rows);
});
};
exports.queryOne = function (sql, values, cb) {
@@ -57,6 +64,8 @@ exports.escape = function (val) {
ready(exports);
thunkify(exports);
function init() {
exports.query('show tables', function (err, rows) {
if (err) {

View File

@@ -14,11 +14,15 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var qn = require('qn');
var config = require('../config');
var client = qn.create(config.qn);
thunkify(client, ['delete', 'uploadFile', 'upload']);
exports._client = client;
/**
* Upload file
*
@@ -26,20 +30,45 @@ var client = qn.create(config.qn);
* @param {Object} options
* - {String} key
* - {Number} size
* @param {Function(err, result)} callback
* - {Object} result
* - {String} url
*/
exports.upload = function (filepath, options, callback) {
client.delete(options.key, function (err, data) {
client.uploadFile(filepath, {key: options.key, size: options.size}, callback);
exports.upload = function *(filepath, options) {
try {
yield client.delete(options.key);
} catch (err) {
// ignore error here
}
var res = yield client.uploadFile(filepath, {
key: options.key,
size: options.size
});
var url = res && res[0] ? res[0].url : '';
return { url: url };
};
exports.uploadBuffer = function *(buf, options) {
try {
yield client.delete(options.key);
} catch (err) {
// ignore error here
}
var res = yield client.upload(buf, {key: options.key});
var url = res && res[0] ? res[0].url : '';
return { url: url };
};
exports.url = function (key) {
return client.resourceURL(key);
};
exports.remove = function (key, callback) {
client.delete(key, callback);
exports.remove = function *(key) {
try {
return yield client.delete(key);
} catch (err) {
if (err.name === 'QiniuFileNotExistsError') {
return;
}
throw err;
}
};

38
common/redis.js Normal file
View File

@@ -0,0 +1,38 @@
/**!
* cnpmjs.org - common/redis.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
var config = require('../config');
// close redis by set config.redis to `null` or `{}`
if (!config.redis || !config.redis.host || !config.redis.port) {
var redis = require('redis');
var wrapper = require('co-redis');
var logger = require('./logger');
var _client = redis.createClient(config.redis);
_client.on('error', function (err) {
logger.error(err);
});
module.exports = wrapper(_client);
} else {
console.warn('[%s] [worker:%s:common/redis.js] Redis config can not found',
Date(), process.pid);
module.exports = null;
}

View File

@@ -15,27 +15,20 @@
* Module dependencies.
*/
var connect = require('connect');
var RedisStore = require('connect-redis')(connect);
var middlewares = require('koa-middlewares');
var config = require('../config');
var session;
var key = 'AuthSession';
var cookie = { path: '/', httpOnly: true, maxAge: 3600000 * 24 * 30 };
var cookie = { path: '/', httpOnly: true, maxAge: 3600000 * 24 * 365, signed: false };
var options = {
key: key,
cookie: cookie,
defer: true,
rolling: false
};
if (config.debug) {
session = connect.cookieSession({
secret: config.sessionSecret,
key: key,
cookie: cookie
});
} else {
session = connect.session({
key: key,
secret: config.sessionSecret,
store: new RedisStore(config.redis),
cookie: cookie,
});
if (!config.debug) {
options.store = config.sessionStore || middlewares.RedisStore(config.redis);
}
module.exports = session;
module.exports = middlewares.session(options);

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - config/index.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -17,18 +17,20 @@
var path = require('path');
var fs = require('fs');
var os = require('os');
var mkdirp = require('mkdirp');
fs.existsSync = fs.existsSync || path.existsSync;
var pkg = require('../package.json');
var version = require('../package.json').version;
var root = path.dirname(__dirname);
var config = {
version: pkg.version,
version: version,
registryPort: 7001,
webPort: 7002,
enableCluster: false,
numCPUs: os.cpus().length,
debug: true, // if debug
logdir: path.join(root, '.tmp', 'logs'),
viewCache: false,
@@ -51,6 +53,7 @@ var config = {
port: 19533,
pass: 'cnpmjs_dev'
},
jsonLimit: '10mb', // max request json body size
uploadDir: path.join(root, 'public', 'dist'),
// qiniu cdn: http://www.qiniu.com/, it free for dev.
qn: {
@@ -68,14 +71,20 @@ var config = {
user: 'address@gmail.com',
pass: 'your password',
ssl: true,
debug: false
debug: false
},
disturl: 'http://dist.u.qiniudn.com',
logoURL: 'http://ww4.sinaimg.cn/large/69c1d4acgw1ebfly5kjlij208202oglr.jpg',
registryHost: 'r.cnpmjs.org',
customFooter: '', // you can add copyright and site total script html here
npmClientName: 'cnpm', // use `${name} install package`
packagePageContributorSearch: true, // package page contributor link to search, default is true
sourceNpmRegistry: 'http://registry.npmjs.org',
enablePrivate: true, // enable private mode, only admin can publish, other use just can sync package from source npm
admins: {
admin: 'admin@cnpmjs.org',
fengmk2: 'fengmk2@gmail.com',
admin: 'admin@cnpmjs.org',
dead_horse: 'dead_horse@qq.com',
cnpmjstest10: 'cnpmjstest10@cnpmjs.org',
},
@@ -83,6 +92,17 @@ var config = {
backupFilePrefix: '/cnpm/backup/', // backup filepath prefix
syncModel: 'none', // 'none', 'all', 'exist'
syncConcurrency: 1,
maxDependencies: 200, // max handle number of package.json `dependencies` property
limit: {
enable: false,
token: 'koa-limit:download',
limit: 1000,
interval: 1000 * 60 * 60 * 24,
whiteList: [],
blackList: [],
message: 'request frequency limited, any question, please contact fengmk2@gmail.com',
}
};
// load config/config.js, everything in config.js will cover the same key in index.js
@@ -98,3 +118,12 @@ mkdirp.sync(config.logdir);
mkdirp.sync(config.uploadDir);
module.exports = config;
config.loadConfig = function (customConfig) {
if (!customConfig) {
return;
}
for (var key in customConfig) {
config[key] = customConfig[key];
}
};

View File

@@ -14,10 +14,15 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var moment = require('moment');
var DownloadTotal = require('../proxy/download');
exports.total = function (name, callback) {
if (typeof name === 'function') {
callback = name;
name = null;
}
var end = moment();
var start = end.clone().subtract('months', 1).startOf('month');
var lastday = end.clone().subtract('days', 1).format('YYYY-MM-DD');
@@ -74,3 +79,5 @@ exports.total = function (name, callback) {
DownloadTotal[method].apply(DownloadTotal, args);
};
thunkify(exports);

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - controllers/registry/user.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -15,148 +15,199 @@
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:controllers:registry');
var logger = require('../../common/logger');
var debug = require('debug')('cnpmjs.org:controllers:registry:user');
var utility = require('utility');
var crypto = require('crypto');
var User = require('../../proxy/user');
var eventproxy = require('eventproxy');
var config = require('../../config');
exports.show = function (req, res, next) {
var name = req.params.name;
User.get(name, function (err, row) {
if (err) {
return next(err);
}
if (!row) {
return next();
}
exports.show = function *(next) {
var name = this.params.name;
var user = yield User.get(name);
if (!user) {
return yield *next;
}
res.setHeader('etag', '"' + row.rev + '"');
var data = {
_id: 'org.couchdb.user:' + row.name,
_rev: row.rev,
name: row.name,
email: row.email,
var data = user.json;
if (!data) {
data = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: row.gmt_modified,
date: user.gmt_modified,
};
res.json(data);
});
}
data._cnpm_meta = {
id: user.id,
npm_user: user.npm_user,
gmt_create: user.gmt_create,
gmt_modified: user.gmt_modified,
admin: !!config.admins[user.name],
};
this.body = data;
};
function ensurePasswordSalt(user, body) {
if (!user.password_sha && body.password) {
// create password_sha on server
user.salt = crypto.randomBytes(30).toString('hex');
user.password_sha = utility.sha1(body.password + user.salt);
}
}
// npm 1.4.4
// add new user first
// @see https://github.com/npm/npm-registry-client/commit/effb4bc88d443f764f2c2e8b4dd583cc72cf6084
// PUT /-/user/org.couchdb.user:mk2 { accept: 'application/json',
// 'accept-encoding': 'gzip',
// 'user-agent': 'node/v0.11.12 darwin x64',
// host: '127.0.0.1:7001',
// 'content-type': 'application/json',
// 'content-length': '150',
// connection: 'close' } { name: 'mk2',
// password: '123456',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:mk2',
// type: 'user',
// roles: [],
// date: '2014-03-15T02:33:19.465Z' }
// old npm flow
// json:
// { name: 'fengmk2',
// salt: 'xxxx',
// password_sha: 'xxxxxx',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:fengmk2',
// type: 'user',
// roles: [],
// date: '2013-12-04T12:56:13.714Z' } }
exports.add = function (req, res, next) {
var name = req.params.name;
var body = req.body || {};
// PUT /-/user/org.couchdb.user:mk2 { accept: 'application/json',
// 'user-agent': 'node/v0.8.26 darwin x64',
// host: '127.0.0.1:7001',
// 'content-type': 'application/json',
// 'content-length': '258',
// connection: 'keep-alive' }
// { name: 'mk2',
// salt: '18d8d51936478446a5466d4fb1633b80f3838b4caaa03649a885ac722cd6',
// password_sha: '8f4408912a6db1d96b132a90856d99db029cef3d',
// email: 'fengmk2@gmail.com',
// _id: 'org.couchdb.user:mk2',
// type: 'user',
// roles: [],
// date: '2014-03-15T02:39:25.696Z' }
exports.add = function *() {
var name = this.params.name;
var body = this.request.body || {};
var user = {
name: body.name,
salt: body.salt,
password_sha: body.password_sha,
email: body.email,
ip: req.socket && req.socket.remoteAddress || '0.0.0.0',
ip: this.ip || '0.0.0.0',
// roles: body.roles || [],
};
ensurePasswordSalt(user, body);
if (!user.name || !user.salt || !user.password_sha || !user.email) {
return res.json(422, {
this.status = 422;
this.body = {
error: 'paramError',
reason: 'params missing'
});
reason: 'params missing, name, email or password missing.'
};
return;
}
debug('add user: %j', user);
var ep = eventproxy.create();
ep.fail(next);
User.get(name, ep.doneLater(function (row) {
if (row) {
return res.json(409, {
error: 'conflict',
reason: 'Document update conflict.'
});
}
User.add(user, ep.done('add'));
}));
var existUser = yield User.get(name);
if (existUser) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'User ' + name + ' already exists.'
};
return;
}
ep.once('add', function (result) {
res.setHeader('etag', '"' + result.rev + '"');
// location: 'http://registry.npmjs.org/_users/org.couchdb.user:cnpmjstest1',
res.json(201, {
ok: true,
id: 'org.couchdb.user:' + name,
rev: result.rev
});
});
var result = yield User.add(user);
this.etag = '"' + result.rev + '"';
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + name,
rev: result.rev
};
};
exports.authSession = function (req, res, next) {
exports.authSession = function *() {
// body: {"name":"foo","password":"****"}
var body = req.body || {};
var body = this.request.body || {};
var name = body.name;
var password = body.password;
User.auth(name, password, function (err, user) {
debug('authSession %s: %j', name, user);
if (err) {
return next(err);
}
if (!user) {
return res.json(401, {ok: false, name: null, roles: []});
}
var user = yield User.auth(name, password);
debug('authSession %s: %j', name, user);
req.session.name = user.name;
res.json(200, {ok: true, name: user.name, roles: []});
});
if (!user) {
this.status = 401;
this.body = {ok: false, name: null, roles: []};
return;
}
var session = yield *this.session;
session.name = user.name;
this.body = {ok: true, name: user.name, roles: []};
};
exports.update = function (req, res, next) {
var name = req.params.name;
var rev = req.params.rev;
exports.update = function *(next) {
var name = this.params.name;
var rev = this.params.rev;
if (!name || !rev) {
return next();
return yield* next;
}
debug('update: %s, rev: %s, user.name: %s', name, rev, this.user.name);
debug('update: %s, rev: %s, session.name: %s', name, rev, req.session.name);
if (name !== req.session.name) {
// must authSession first
res.statusCode = 401;
return res.json({
if (name !== this.user.name) {
// must auth user first
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Name is incorrect.'
});
};
return;
}
var body = req.body || {};
var body = this.request.body || {};
var user = {
name: body.name,
salt: body.salt,
password_sha: body.password_sha,
email: body.email,
ip: req.socket && req.socket.remoteAddress || '0.0.0.0',
ip: this.ip || '0.0.0.0',
rev: body.rev || body._rev,
// roles: body.roles || [],
};
User.update(user, function (err, result) {
if (err) {
return next(err);
}
//check rev error
if (!result) {
return res.json(409, {
error: 'conflict',
reason: 'Document update conflict.'
});
}
res.json(201, {
ok: true,
id: 'org.couchdb.user:' + user.name,
rev: result.rev
});
});
ensurePasswordSalt(user, body);
if (!user.name || !user.salt || !user.password_sha || !user.email) {
this.status = 422;
this.body = {
error: 'paramError',
reason: 'params missing, name, email or password missing.'
};
return;
}
var result = yield User.update(user);
if (!result) {
this.status = 409;
this.body = {
error: 'conflict',
reason: 'Document update conflict.'
};
return;
}
this.status = 201;
this.body = {
ok: true,
id: 'org.couchdb.user:' + user.name,
rev: result.rev
};
};

View File

@@ -13,38 +13,63 @@
/**
* Module dependencies.
*/
var Log = require('../proxy/module_log');
var SyncModuleWorker = require('../proxy/sync_module_worker');
exports.sync = function (req, res, next) {
var username = req.session.name || 'anonymous';
var name = req.params.name;
SyncModuleWorker.sync(name, username, function (err, result) {
if (err) {
return next(err);
}
if (!result.ok) {
return res.json(result.statusCode, result.pkg);
}
res.json(201, {
ok: true,
logId: result.logId
});
});
exports.sync = function *() {
var username = this.user.name || 'anonymous';
var name = this.params.name;
var publish = this.query.publish === 'true';
var noDep = this.query.nodeps === 'true';
if (publish && !this.user.isAdmin) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Only admin can publish'
};
return;
}
var options = {
publish: publish,
noDep: noDep,
};
var result = yield SyncModuleWorker.sync(name, username, options);
// friendly 404 reason info
if (result.staticCache === 404) {
this.status = 404;
this.body = {
ok: false,
reason: 'can not found ' + name + ' in the source registry'
};
return;
}
if (!result.ok) {
this.status = result.statusCode;
this.body = result.pkg;
return;
}
this.status = 201;
this.body = {
ok: true,
logId: result.logId
};
};
exports.getSyncLog = function (req, res, next) {
var logId = req.params.id;
var name = req.params.name;
var offset = Number(req.query.offset) || 0;
Log.get(logId, function (err, row) {
if (err || !row) {
return next(err);
}
var log = row.log.trim();
if (offset > 0) {
log = log.split('\n').slice(offset).join('\n');
}
res.json(200, {ok: true, log: log});
});
exports.getSyncLog = function *(next) {
var logId = this.params.id;
var offset = Number(this.query.offset) || 0;
var row = yield Log.get(logId);
if (!row) {
return yield* next;
}
var log = row.log.trim();
if (offset > 0) {
log = log.split('\n').slice(offset).join('\n');
}
this.body = {ok: true, log: log};
};

View File

@@ -16,31 +16,25 @@
*/
var microtime = require('microtime');
var eventproxy = require('eventproxy');
var Total = require('../proxy/total');
var down = require('./download');
var Download = require('./download');
var version = require('../package.json').version;
var config = require('../config');
var startTime = '' + microtime.now();
exports.show = function (req, res, next) {
var ep = eventproxy.create();
ep.fail(next);
exports.show = function *() {
var r = yield [Total.get(), Download.total()];
var total = r[0];
var download = r[1];
Total.get(ep.done('total'));
down.total(null, ep.done('download'));
ep.all('total', 'download', function (total, download) {
total.download = download;
total.db_name = 'registry';
total.instance_start_time = startTime;
total.node_version = process.version;
total.app_version = version;
total.donate = 'https://me.alipay.com/imk2';
total.sync_model = config.syncModel;
if (req.query.callback) {
return res.jsonp(total, req.query.callback);
}
res.json(total);
});
total.download = download;
total.db_name = 'registry';
total.instance_start_time = startTime;
total.node_version = process.version;
total.app_version = version;
total.donate = 'https://me.alipay.com/imk2';
total.sync_model = config.syncModel;
this.body = total;
};

23
controllers/web/dist.js Normal file
View File

@@ -0,0 +1,23 @@
/**!
* cnpmjs.org - controllers/web/dist.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
"use strict";
/**
* Module dependencies.
*/
var config = require('../../config');
exports.redirect = function *(next) {
var params = this.params;
var url = config.disturl + (params[0] || '/');
this.redirect(url);
};

View File

@@ -14,87 +14,175 @@
* Module dependencies.
*/
var giturl = require('giturl');
var moment = require('moment');
var eventproxy = require('eventproxy');
var semver = require('semver');
var marked = require('marked');
var gravatar = require('gravatar');
var humanize = require('humanize-number');
var config = require('../../config');
var Module = require('../../proxy/module');
var down = require('../download');
var sync = require('../sync');
var Log = require('../../proxy/module_log');
var ModuleDeps = require('../../proxy/module_deps');
var setDownloadURL = require('../../lib/common').setDownloadURL;
var ModuleStar = require('../../proxy/module_star');
exports.display = function (req, res, next) {
var params = req.params;
exports.display = function *(next) {
var params = this.params;
var name = params.name;
var tag = params.version;
var ep = eventproxy.create();
ep.fail(next);
if (tag) {
var version = semver.valid(tag);
if (version) {
Module.get(name, version, ep.done('pkg'));
} else {
Module.getByTag(name, tag, ep.done('pkg'));
}
var getPackageMethod;
var getPackageArgs;
var version = semver.valid(tag || '');
if (version) {
getPackageMethod = 'get';
getPackageArgs = [name, version];
} else {
Module.getByTag(name, 'latest', ep.done('pkg'));
getPackageMethod = 'getByTag';
getPackageArgs = [name, tag || 'latest'];
}
var r = yield [
Module[getPackageMethod].apply(Module, getPackageArgs),
down.total(name),
ModuleDeps.list(name),
ModuleStar.listUsers(name),
];
var pkg = r[0];
var download = r[1];
var dependents = (r[2] || []).map(function (item) {
return item.deps;
});
var users = r[3];
if (!pkg || !pkg.package) {
return yield* next;
}
down.total(name, ep.done('download'));
pkg.package.fromNow = moment(pkg.publish_time).fromNow();
pkg = pkg.package;
pkg.users = users;
pkg.readme = marked(pkg.readme || '');
if (!pkg.readme) {
pkg.readme = pkg.description || '';
}
ep.all('pkg', 'download', function (pkg, download) {
if (!pkg || !pkg.package) {
return next();
}
pkg.package.fromNow = moment(pkg.publish_time).fromNow();
pkg = pkg.package;
pkg.readme = marked(pkg.readme || '');
if (pkg.maintainers) {
for (var i = 0; i < pkg.maintainers.length; i++) {
var maintainer = pkg.maintainers[i];
if (maintainer.email) {
maintainer.gravatar = gravatar.url(maintainer.email, {s: '50', d: 'retro'}, false);
}
if (pkg.maintainers) {
for (var i = 0; i < pkg.maintainers.length; i++) {
var maintainer = pkg.maintainers[i];
if (maintainer.email) {
maintainer.gravatar = gravatar.url(maintainer.email, {s: '50', d: 'retro'}, false);
}
}
}
setLicense(pkg);
for (var k in download) {
download[k] = humanize(download[k]);
if (pkg.contributors) {
// registry.cnpmjs.org/compressible
if (!Array.isArray(pkg.contributors)) {
pkg.contributors = [pkg.contributors];
}
for (var i = 0; i < pkg.contributors.length; i++) {
var contributor = pkg.contributors[i];
if (contributor.email) {
contributor.gravatar = gravatar.url(contributor.email, {s: '50', d: 'retro'}, false);
}
if (config.packagePageContributorSearch || !contributor.url) {
contributor.url = '/~' + encodeURIComponent(contributor.name);
}
}
}
res.render('package', {
title: 'Package - ' + pkg.name,
package: pkg,
download: download
});
if (pkg.repository && pkg.repository.url) {
pkg.repository.weburl = giturl.parse(pkg.repository.url) || pkg.repository.url;
}
setLicense(pkg);
for (var k in download) {
download[k] = humanize(download[k]);
}
setDownloadURL(pkg, this, config.registryHost);
pkg.dependents = dependents;
yield this.render('package', {
title: 'Package - ' + pkg.name,
package: pkg,
download: download
});
};
exports.search = function (req, res, next) {
var params = req.params;
var word = req.params.word;
Module.search(word, function (err, packages) {
if (err) {
return next(err);
}
exports.search = function *(next) {
var params = this.params;
var word = params.word;
var result = yield Module.search(word);
res.render('search', {
title: 'Keyword - ' + word,
var match = null;
for (var i = 0; i < result.searchMatchs.length; i++) {
var p = result.searchMatchs[i];
if (p.name === word) {
match = p;
break;
}
}
// return a json result
if (this.query && this.query.type === 'json') {
this.body = {
keyword: word,
packages: packages || []
});
match: match,
packages: result.searchMatchs,
keywords: result.keywordMatchs,
};
this.type = 'application/json; charset=utf-8';
return;
}
yield this.render('search', {
title: 'Keyword - ' + word,
keyword: word,
match: match,
packages: result.searchMatchs,
keywords: result.keywordMatchs,
});
};
exports.displaySync = function (req, res, next) {
var name = req.params.name;
res.render('sync', {
exports.rangeSearch = function *(next) {
var startKey = this.query.startkey || '';
if (startKey[0] === '"') {
startKey = startKey.substring(1);
}
if (startKey[startKey.length - 1] === '"') {
startKey = startKey.substring(0, startKey.length - 1);
}
var limit = Number(this.query.limit) || 20;
var result = yield Module.search(startKey, {limit: limit});
var packages = result.searchMatchs.concat(result.keywordMatchs);
var rows = [];
for (var i = 0; i < packages.length; i++) {
var p = packages[i];
var row = {
key: p.name,
count: 1,
value: {
name: p.name,
description: p.description,
}
};
rows.push(row);
}
this.body = {
rows: rows
};
};
exports.displaySync = function *(next) {
var name = this.params.name || this.query.name;
yield this.render('sync', {
name: name,
title: 'Sync - ' + name
});

View File

@@ -15,31 +15,24 @@
*/
var Module = require('../../proxy/module');
var User = require('../../proxy/user');
var eventproxy = require('eventproxy');
exports.display = function (req, res, next) {
var name = req.params.name;
exports.display = function *(next) {
var name = this.params.name;
var ep = eventproxy.create();
ep.fail(next);
Module.listByAuthor(name, ep.done('packages'));
User.get(name, ep.done('user'));
var r = yield [Module.listByAuthor(name), User.get(name)];
var packages = r[0];
var user = r[1];
if (!user && !packages.length) {
return yield* next;
}
user = {
name: name,
email: user && user.email
};
ep.all('packages', 'user', function (packages, user) {
//because of sync, maybe no this user in database,
//but his packages in this registry
if (!user && !packages.length) {
return next();
}
user = {
name: name,
email: user && user.email
};
return res.render('profile', {
title: 'User - ' + name,
packages: packages || [],
user: user
});
yield this.render('profile', {
title: 'User - ' + name,
packages: packages || [],
user: user
});
};

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - dispatch.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -17,7 +17,6 @@
var path = require('path');
var util = require('util');
var fs = require('fs');
var cluster = require('cluster');
var config = require('./config');
var workerPath = path.join(__dirname, 'worker.js');
@@ -30,25 +29,25 @@ if (config.enableCluster) {
});
cluster.on('fork', function (worker) {
console.log('[%s] [worker:%d] new worker start', new Date(), worker.process.pid);
console.log('[%s] [worker:%d] new worker start', Date(), worker.process.pid);
});
cluster.on('disconnect', function (worker) {
var w = cluster.fork();
console.error('[%s] [master:%s] wroker:%s disconnect! new worker:%s fork',
new Date(), process.pid, worker.process.pid, w.process.pid);
console.error('[%s] [master:%s] wroker:%s disconnect, suicide: %s, state: %s. New worker:%s fork',
Date(), process.pid, worker.process.pid, worker.suicide, worker.state, w.process.pid);
});
cluster.on('exit', function (worker, code, signal) {
var exitCode = worker.process.exitCode;
var err = new Error(util.format('worker %s died (code: %s, signal: %s)', worker.process.pid, exitCode, signal));
var err = new Error(util.format('worker %s died (code: %s, signal: %s, suicide: %s, state: %s)',
worker.process.pid, exitCode, signal, worker.suicide, worker.state));
err.name = 'WorkerDiedError';
console.error(err);
console.error('[%s] [master:%s] wroker exit: %s', Date(), process.pid, err.stack);
});
var numCPUs = require('os').cpus().length;
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
for (var i = 0; i < config.numCPUs; i++) {
cluster.fork();
}

View File

@@ -9,17 +9,43 @@ CREATE TABLE `user` (
`roles` varchar(200) NOT NULL DEFAULT '[]',
`rev` varchar(40) NOT NULL,
`email` varchar(400) NOT NULL,
`json` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'json details',
`npm_user` tinyint(1) DEFAULT '0' COMMENT 'user sync from npm or not, 1: true, other: false',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='user base info';
-- ALTER TABLE `user`
-- ADD `json` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'json details',
-- ADD `npm_user` tinyint(1) DEFAULT '0' COMMENT 'user sync from npm or not, 1: true, other: false';
CREATE TABLE `module_keyword` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`keyword` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'keyword',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`description` longtext,
PRIMARY KEY (`id`),
UNIQUE KEY `keyword_module_name` (`keyword`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module keyword';
CREATE TABLE `module_star` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`user` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'user name',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
PRIMARY KEY (`id`),
UNIQUE KEY `user_module_name` (`user`,`name`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module star';
CREATE TABLE `module` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`author` varchar(100) NOT NULL,
`name` varchar(100) NOT NULL COMMENT 'module name',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`version` varchar(30) NOT NULL COMMENT 'module version',
`description` longtext,
`package` longtext CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT 'package.json',
@@ -38,23 +64,25 @@ CREATE TABLE `module` (
-- ALTER TABLE `module` CHANGE `package` `package` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci;
-- ALTER TABLE `module` CHANGE `description` `description` LONGTEXT CHARACTER SET utf8 COLLATE utf8_general_ci;
-- show create table module\G
-- ALTER TABLE `module` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
CREATE TABLE `module_log` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`username` varchar(100) NOT NULL,
`name` varchar(100) NOT NULL COMMENT 'module name',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`log` longtext,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module sync log';
-- ALTER TABLE `module_log` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
CREATE TABLE `tag` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`name` varchar(100) NOT NULL COMMENT 'module name',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`tag` varchar(30) NOT NULL COMMENT 'tag name',
`version` varchar(30) NOT NULL COMMENT 'module version',
`module_id` bigint(20) unsigned NOT NULL COMMENT 'module id',
@@ -62,6 +90,7 @@ CREATE TABLE `tag` (
UNIQUE KEY `name` (`name`, `tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module tag';
-- ALTER TABLE `tag` ADD `module_id` BIGINT( 20 ) UNSIGNED NOT NULL;
-- ALTER TABLE `tag` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
CREATE TABLE `total` (
`name` varchar(100) NOT NULL COMMENT 'total name',
@@ -77,7 +106,7 @@ CREATE TABLE `total` (
`last_sync_module` varchar(100) COMMENT 'last sync success module name',
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='total info';
INSERT INTO total(name, gmt_modified, module_delete) VALUES('total', now(), 0, 0);
INSERT INTO total(name, gmt_modified) VALUES('total', now());
-- ALTER TABLE `total` ADD `last_sync_time` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'last timestamp sync from official registry'
-- ALTER TABLE `total` ADD `last_exist_sync_time` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'last timestamp sync exist packages from official registry'
-- ALTER TABLE `total` ADD `sync_status` tinyint unsigned NOT NULL DEFAULT '0' COMMENT 'system sync from official registry status'
@@ -92,8 +121,19 @@ CREATE TABLE `download_total` (
`gmt_create` datetime NOT NULL COMMENT 'create time',
`gmt_modified` datetime NOT NULL COMMENT 'modified time',
`date` varchar(10) NOT NULL COMMENT 'YYYY-MM-DD format',
`name` varchar(100) NOT NULL COMMENT 'module name',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`count` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'download count',
PRIMARY KEY (`id`),
UNIQUE KEY `date_name` (`date`, `name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module download total info';
-- ALTER TABLE `download_total` CHANGE `name` `name` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name';
CREATE TABLE `module_deps` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'module name',
`deps` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'which module depend on this module',
PRIMARY KEY (`id`),
UNIQUE KEY `name_deps` (`name`,`deps`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='module deps';

View File

@@ -1,5 +1,24 @@
# npm publish flow
## Flows
1. try to put package.json and tgz, maybe base64 tgz body
2. if new version not exists, publish success
3. if new version exists, 409, try to get full package info with ?write=true
4. if new version had publish, show: "Update the 'version' field in package.json and try again."
```bash
$ cnpm publish
npm http PUT http://r.cnpmjs.org/cnpmjs.org
npm http 409 http://r.cnpmjs.org/cnpmjs.org
npm http GET http://r.cnpmjs.org/cnpmjs.org?write=true
npm http 200 http://r.cnpmjs.org/cnpmjs.org?write=true
npm ERR! publish fail Cannot publish over existing version.
npm ERR! publish fail Update the 'version' field in package.json and try again.
```
## Details
code: https://github.com/isaacs/npm-registry-client/blob/master/lib/publish.js
* couch login if token not exists: [couch-login](https://github.com/isaacs/couch-login)

View File

@@ -91,23 +91,25 @@ $ open http://localhost:7001
$ open http://localhost:7002
```
## use cnpm cli with your own registry
You do not need to write another command line tool with your own registry,
## use cnpm cli with your own registry
You do not need to write another command line tool with your own registry,
just alias [cnpm](http://github.com/fengmk2/cnpm), then you can get a npm client for you own registry.
```
# install cnpm first
npm install -g cnpm
npm install -g cnpm
# then alias lnpm to cnpm, but change config to your own registry
alias lnpm='cnpm --registry=http://localhost:7001\
--registryweb=http://localhost:7002\
--cache=$HOME/.npm/.cache/lnpm\
--disturl=http://cnpmjs.org/dist\
--userconfig=$HOME/.lnpmrc'
#or put this in .zshrc or .bashrc
echo "#lnpm alias\nalias lnpm='cnpm --registry=http://localhost:7001\
--registryweb=http://localhost:7002\
--cache=$HOME/.npm/.cache/lnpm\
--disturl=http://cnpmjs.org/dist\
--userconfig=$HOME/.lnpmrc'" >> $HOME/.zshrc && source $HOME/.zshrc
```

View File

@@ -1,22 +1,8 @@
# cnpmjs.org: Private npm registry and web for Enterprise
[![Build Status](https://secure.travis-ci.org/fengmk2/cnpmjs.org.png)](http://travis-ci.org/fengmk2/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/fengmk2/cnpmjs.org/badge.png)](https://coveralls.io/r/fengmk2/cnpmjs.org) [![Dependency Status](https://gemnasium.com/fengmk2/cnpmjs.org.png)](https://gemnasium.com/fengmk2/cnpmjs.org)
[![NPM](https://nodei.co/npm/cnpmjs.org.png?downloads=true&stars=true)](https://nodei.co/npm/cnpmjs.org/)
## What is this?
> Private npm registry and web for Enterprise, base on MySQL and Simple File Store.
@[JacksonTian](https://github.com/JacksonTian/) had a talk about [private npm](https://speakerdeck.com/jacksontian/qi-ye-ji-node-dot-jskai-fa).
## Install your private npm registry
@see [Install and Get Started](/install).
## Registry
* Our public registry: [registry.cnpmjs.org](http://registry.cnpmjs.org)
* Our public registry: [r.cnpmjs.org](http://r.cnpmjs.org), syncing from [registry.npmjs.org](http://registry.npmjs.org)
* Current [cnpmjs.org](/) version: <span id="app-version"></span>
<table class="downloads">
@@ -114,30 +100,32 @@ $(function () {
});
</script>
## cnpm cli
## Usage
alias it:
use our npm client [cnpm](https://github.com/cnpm/cnpm)(More suitable with cnpmjs.org and gzip support), you can get our client through npm:
```
npm install -g cnpm --registry=http://r.cnpmjs.org
```
Or you can alias NPM to use it:
```bash
alias cnpm="npm --registry=http://registry.cnpmjs.org \
alias cnpm="npm --registry=http://r.cnpmjs.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=http://cnpmjs.org/dist \
--userconfig=$HOME/.cnpmrc"
#Or alias it in .bashrc or .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=http://registry.cnpmjs.org \
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=http://r.cnpmjs.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=http://cnpmjs.org/dist \
--userconfig=$HOME/.cnpmrc"' >> ~/.zshrc && source ~/.zshrc
```
Or you can just use our `cnpm` cli:
```bash
$ npm install cnpm -g
```
### install
Install package from [registry.cnpmjs.org](http://registry.cnpmjs.org). When isntall a package or version not exist, it will try to install from official registry([registry.npmjs.org](http://registry.npmjs.org)), and sync this package to cnpm in the backend.
Install package from [r.cnpmjs.org](http://r.cnpmjs.org). When installing a package or version does not exist, it will try to install from the official registry([registry.npmjs.org](http://registry.npmjs.org)), and sync this package to cnpm in the backend.
```
$ cnpm install [name]
@@ -166,12 +154,17 @@ $ cnpm publish [name]
$ cnpm unpublish [name]
```
### Orther command
Support all the other npm command.
### Other commands
Support all the other npm commands. e.g.:
```bash
$ cnpm info cnpm
```
## TODO list
@see Github [Issues](https://github.com/fengmk2/cnpmjs.org/issues)
@see Github [Issues](https://github.com/cnpm/cnpmjs.org/issues)
## Authors
@@ -181,14 +174,16 @@ Release [History](/history).
$ git summary
project : cnpmjs.org
repo age : 2 weeks
active : 81 days
commits : 205
files : 83
repo age : 4 months ago
commits : 472
active : 167 days
files : 104
authors :
120 fengmk2 58.5%
84 dead_horse 41.0%
1 Alsotang 0.5%
272 fengmk2 57.6%
195 dead_horse 41.3%
2 4simple 0.4%
2 Stanley Zheng 0.4%
1 Alsotang 0.2%
```
## npm and cnpm relation

30
index.js Normal file
View File

@@ -0,0 +1,30 @@
/**!
* cnpmjs.org - index.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
var config = require('./config');
exports.loadConfig = config.loadConfig;
exports.config = config;
exports.startWorker = function (customConfig) {
config.loadConfig(customConfig);
require('./worker');
};
exports.startSync = function (customConfig) {
config.loadConfig(customConfig);
require('./sync');
};

View File

@@ -17,6 +17,7 @@
var crypto = require('crypto');
var path = require('path');
var config = require('../config');
var util = require('util');
exports.getTarballFilepath = function (filename) {
// ensure download file path unique
@@ -28,8 +29,29 @@ exports.getCDNKey = function (name, filename) {
return '/' + name + '/-/' + filename;
};
exports.downloadURL = function (pkg, req) {
if (pkg.dist && pkg.dist.tarball) {
pkg.dist.tarball = 'http://' + req.headers.host + '/' + pkg.name + '/download/' + path.basename(pkg.dist.tarball);
exports.setDownloadURL = function (pkg, ctx, host) {
if (pkg.dist) {
host = host || ctx.host;
pkg.dist.tarball = util.format('%s://%s/%s/download/%s-%s.tgz',
ctx.protocol,
host, pkg.name, pkg.name, pkg.version);
}
};
exports.isAdmin = function (username) {
return typeof config.admins[username] === 'string';
};
exports.isMaintainer = function (user, maintainers) {
if (user.isAdmin) {
return true;
}
var username = user.name;
maintainers = maintainers || [];
var match = maintainers.filter(function (item) {
return item.name === username;
});
return match.length > 0;
};

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - middleware/auth.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -17,47 +17,44 @@
var debug = require('debug')('cnpmjs.org:middleware:auth');
var User = require('../proxy/user');
var config = require('../config');
var common = require('../lib/common');
module.exports = function (options) {
return function auth(req, res, next) {
req.session.onlySync = config.enablePrivate ? true : false;
if (req.session.name) {
if (config.admins[req.session.name]) {
req.session.isAdmin = true;
}
debug('auth exists user: %s, onlySync: %s, isAdmin: %s, headers: %j',
req.session.name, req.session.onlySync, req.session.isAdmin, req.headers);
return next();
return function *auth(next) {
var session = yield *this.session;
debug('%s, %s, %j', this.url, this.sessionId, session);
this.user = {};
if (session.name) {
this.user.name = session.name;
this.user.isAdmin = common.isAdmin(session.name);
debug('auth exists user: %j, headers: %j', this.user, this.header);
return yield *next;
}
var authorization = (req.headers.authorization || '').split(' ')[1] || '';
var authorization = (this.get('authorization') || '').split(' ')[1] || '';
authorization = authorization.trim();
if (!authorization) {
return next();
return yield *next;
}
authorization = new Buffer(authorization, 'base64').toString().split(':');
if (authorization.length !== 2) {
return yield *next;
}
var username = authorization[0];
var password = authorization[1];
User.auth(username, password, function (err, row) {
if (err) {
return next(err);
}
var row = yield User.auth(username, password);
if (!row) {
debug('auth fail user: %j, headers: %j', row, this.header);
return yield *next;
}
if (!row) {
debug('auth fail user: %j, headers: %j', row, req.headers);
return res.json(401, {
error: 'unauthorized',
reason: 'Name or password is incorrect.'
});
}
req.session.name = row.name;
if (config.admins.hasOwnProperty(req.session.name)) {
req.session.isAdmin = true;
}
debug('auth pass user: %j, onlySync: %s, isAdmin: %s, headers: %j',
row, req.session.onlySync, req.session.isAdmin, req.headers);
next();
});
this.user.name = row.name;
this.user.isAdmin = common.isAdmin(row.name);
debug('auth pass user: %j, headers: %j', this.user, this.header);
yield *next;
};
};

34
middleware/limit.js Normal file
View File

@@ -0,0 +1,34 @@
/**!
* cnpmjs.org - middleware/limit.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
var config = require('../config');
var limit = require('koa-limit');
var store = require('../common/redis');
var limitConfig = config.limit;
if (!limitConfig.enable) {
module.exports = function *ignoreLimit(next) {
yield *next;
};
} else {
if (!config.debug) {
limitConfig.store = store;
}
module.exports = limit(limitConfig);
}

View File

@@ -14,12 +14,14 @@
* Module dependencies.
*/
module.exports = function login(req, res, next) {
if (!req.session.name) {
return res.json(401, {
module.exports = function *login(next) {
if (!this.user.name) {
this.status = 401;
this.body = {
error: 'unauthorized',
reason: 'Login first.'
});
};
return;
}
next();
yield *next;
};

View File

@@ -24,8 +24,11 @@ var template = '<?xml version="1.0" encoding="UTF-8"?>\
var lastModifyDate = new Date();
module.exports = function publishable(req, res, next) {
res.charset = res.charset || 'utf-8';
res.setHeader('Content-Type', 'text/xml');
res.send(template.replace('${host}', req.headers.host));
module.exports = function *publishable(next) {
if (this.path === '/opensearch.xml') {
this.type = 'text/xml';
this.charset = 'utf-8';
this.body = template.replace('${host}', this.host);
}
yield *next;
};

View File

@@ -14,13 +14,17 @@
* Module dependencies.
*/
module.exports = function publishable(req, res, next) {
if (req.session.onlySync && !req.session.isAdmin) {
var config = require('../config');
module.exports = function *publishable(next) {
if (config.enablePrivate && !this.user.isAdmin) {
// private mode, only admin user can publish
return res.json(403, {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Private mode enable, only admin can publish this module'
});
};
return;
}
next();
yield *next;
};

View File

@@ -0,0 +1,28 @@
/**!
* cnpmjs.org - middleware/registry_not_found.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
module.exports = function *notFound(next) {
yield *next;
if (this.status) {
return;
}
this.status = 404;
this.body = {
error: 'not_found',
reason: 'document not found'
};
};

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - middleware/sync_by_install.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -17,24 +17,24 @@
var config = require('../config');
/**
* req.session.allowSync - allow sync triggle by cnpm install
* this.allowSync - allow sync triggle by cnpm install
*/
module.exports = function (req, res, next) {
module.exports = function *syncByInstall(next) {
if (!config.syncByInstall || !config.enablePrivate) {
// only config.enablePrivate should enable sync on install
return next();
return yield *next;
}
// request not by node, consider it request from web
if (req.headers['user-agent'] && req.headers['user-agent'].indexOf('node') !== 0) {
return next();
var ua = this.get('user-agent');
if (!ua || ua.indexOf('node') < 0) {
return yield *next;
}
req.session.allowSync = true;
if (req.session.isAdmin) {
// if current user is admin, should not enable auto sync on install, because it would be unpublish
req.session.allowSync = false;
if (this.query.write) {
return yield *next;
}
next();
this.allowSync = true;
yield *next;
};

View File

@@ -0,0 +1,33 @@
/**!
* cnpmjs.org - middleware/web_not_found.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
*/
'use strict';
/**
* Module dependencies.
*/
module.exports = function *notFound(next) {
yield *next;
if (this.status) {
return;
}
var m = /^\/([\w\-\_\.]+)\/?$/.exec(this.url);
if (m) {
return this.redirect('/package/' + m[1]);
}
this.status = 404;
this.type = 'text/html';
this.charset = 'utf-8';
this.body = 'Cannot GET ' + this.path;
};

View File

@@ -1,79 +1,81 @@
{
"name": "cnpmjs.org",
"version": "0.1.4",
"description": "Private npm registry and web",
"main": "dispatch.js",
"version": "0.3.13",
"description": "Private npm registry and web for Enterprise, base on MySQL and Simple Store Service",
"main": "index.js",
"scripts": {
"test": "make test-all",
"test": "make install && make jshint && make test",
"start": "./bin/nodejsctl start && cp History.md docs/web/history.md",
"status": "./bin/nodejsctl status",
"stop": "./bin/nodejsctl stop",
"blanket": {
"pattern": "//^((?!(node_modules|test|common)).)*$/",
"data-cover-flags": {
"debug": false
}
},
"travis-cov": {
"threshold": 90
}
"stop": "./bin/nodejsctl stop"
},
"dependencies": {
"forward": ">=0.0.4",
"humanize-number": ">=0.0.2",
"co": "3.0.5",
"co-gather": "0.0.1",
"co-read": "0.0.1",
"co-redis": "1.1.0",
"co-urllib": "0.1.3",
"co-write": "0.3.0",
"debug": "0.7.4",
"eventproxy": "0.3.0",
"giturl": "0.0.2",
"graceful": "0.0.6",
"gravatar": "1.0.6",
"urllib": ">=0.5.5",
"connect-markdown": "0.0.3",
"qn": ">=0.2.0",
"humanize-number": "0.0.2",
"koa": "0.5.1",
"koa-limit": "1.0.0",
"koa-markdown": "0.0.3",
"koa-middlewares": "0.0.12",
"logfilestream": "0.1.0",
"marked": "0.3.2",
"microtime": "0.5.1",
"debug": ">=0.7.4",
"utility": ">=0.1.9",
"ready": "0.1.1",
"connect": "2.12.0",
"connect-rt": "0.0.2",
"connect-redis": "1.4.6",
"connect-render": "0.3.2",
"urlrouter": ">=0.5.4",
"graceful": ">=0.0.5",
"moment": "2.4.0",
"logfilestream": ">=0.1.0",
"ms": "0.6.2",
"mime": "1.2.11",
"mkdirp": "0.3.5",
"mysql": "2.0.0-rc2",
"response-patch": "0.1.1",
"response-cookie": "0.0.2",
"bagpipe": "0.3.5",
"moment": "2.5.1",
"ms": "0.6.2",
"multiline": "0.2.0",
"mysql": "2.1.1",
"nodemailer": "0.6.1",
"qn": "0.2.1",
"ready": "0.1.1",
"redis": "0.10.1",
"semver": "2.2.1",
"marked": "0.2.10",
"nodemailer": "0.5.15",
"eventproxy": ">=0.2.6"
"thunkify-wrap": "0.1.1",
"utility": "0.1.11"
},
"devDependencies": {
"supertest": "*",
"should": "*",
"blanket": "*",
"travis-cov": "*",
"coveralls": "*",
"mocha-lcov-reporter": "*",
"autod": ">=0.0.13",
"chunkstream": "0.0.1",
"co-mocha": "0.0.2",
"contributors": "*",
"cov": "*",
"istanbul-harmony": "*",
"jshint": "*",
"mm": "0.2.1",
"mocha": "*",
"mm": "*",
"pedding": "*"
"pedding": "0.0.3",
"should": "3.1.3",
"supertest": "0.9.2"
},
"homepage": "https://github.com/fengmk2/cnpmjs.org",
"homepage": "https://github.com/cnpm/cnpmjs.org",
"repository": {
"type": "git",
"url": "git://github.com/fengmk2/cnpmjs.org.git",
"web": "https://github.com/fengmk2/cnpmjs.org"
"url": "git://github.com/cnpm/cnpmjs.org.git",
"web": "https://github.com/cnpm/cnpmjs.org"
},
"bugs": {
"url": "https://github.com/fengmk2/cnpmjs.org/issues",
"url": "https://github.com/cnpm/cnpmjs.org/issues",
"email": "fengmk2@gmail.com"
},
"keywords": [
"cnpmjs.org", "npm", "npmjs", "npmjs.org", "registry"
"cnpmjs.org",
"npm",
"npmjs",
"npmjs.org",
"registry"
],
"engines": {
"node": ">= 0.8.0"
"node": ">= 0.11.9"
},
"author": [
"fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)",

View File

@@ -14,30 +14,49 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var config = require('../config');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var PLUS_SQL = 'INSERT INTO download_total(gmt_create, gmt_modified, \
date, name, count) \
VALUES(now(), now(), ?, ?, ?) \
ON DUPLICATE KEY UPDATE gmt_modified=now(), \
count=count + VALUES(count), name=VALUES(name), date=VALUES(date);';
var PLUS_SQL = multiline(function () {;/*
INSERT INTO
download_total(gmt_create, gmt_modified, date, name, count)
VALUES
(now(), now(), ?, ?, ?)
ON DUPLICATE KEY UPDATE
count=count + VALUES(count),
name=VALUES(name),
date=VALUES(date);
*/});
exports.plusTotal = function (data, callback) {
mysql.query(PLUS_SQL, [data.date, data.name, data.count], callback);
};
var SELECT_ONE_TOTAL_SQL = 'SELECT date, count FROM download_total WHERE date>=? AND date<=? AND name=?;';
var SELECT_ONE_TOTAL_SQL = multiline(function () {;/*
SELECT
date, count
FROM
download_total
WHERE
date>=? AND date<=? AND name=?;
*/});
exports.getModuleTotal = function (name, start, end, callback) {
mysql.query(SELECT_ONE_TOTAL_SQL, [start, end, name], callback);
};
var SELECT_ALL_TOTAL_SQL = 'SELECT date, sum(count) AS count \
FROM download_total \
WHERE date>=? AND date<=? \
GROUP BY date;';
var SELECT_ALL_TOTAL_SQL = multiline(function () {;/*
SELECT
date, sum(count) AS count
FROM
download_total
WHERE
date>=? AND date<=?
GROUP BY
date;
*/});
exports.getTotal = function (start, end, callback) {
mysql.query(SELECT_ALL_TOTAL_SQL, [start, end], callback);
};
thunkify(exports);

View File

@@ -14,21 +14,37 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var utility = require('utility');
var eventproxy = require('eventproxy');
var config = require('../config');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var MODULE_COLUMNS = 'id, publish_time, gmt_create, gmt_modified, author, name, version, description, package, dist_tarball, dist_shasum, dist_size';
var MODULE_COLUMNS = 'id, publish_time, gmt_create, gmt_modified, author, name, \
version, description, package, dist_tarball, dist_shasum, dist_size';
var INSERT_MODULE_SQL = 'INSERT INTO module(gmt_create, gmt_modified, \
publish_time, author, name, version, package, dist_tarball, dist_shasum, dist_size, description) \
VALUES(now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?) \
ON DUPLICATE KEY UPDATE gmt_modified=now(), publish_time=VALUES(publish_time), description=VALUES(description), \
author=VALUES(author), name=VALUES(name), version=VALUES(version), package=VALUES(package), \
dist_tarball=VALUES(dist_tarball), dist_shasum=VALUES(dist_shasum), dist_size=VALUES(dist_size);';
var INSERT_MODULE_SQL = multiline(function () {;/*
INSERT INTO
module(gmt_create, gmt_modified, publish_time, author, name, version,
package, dist_tarball, dist_shasum, dist_size, description)
VALUES
(now(), now(), ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
gmt_modified=now(),
publish_time=VALUES(publish_time),
description=VALUES(description),
author=VALUES(author),
name=VALUES(name),
version=VALUES(version),
package=VALUES(package),
dist_tarball=VALUES(dist_tarball),
dist_shasum=VALUES(dist_shasum),
dist_size=VALUES(dist_size);
*/});
exports.add = function (mod, callback) {
var keywords = mod.package.keywords;
var pkg;
try {
pkg = stringifyPackage(mod.package);
@@ -51,16 +67,136 @@ exports.add = function (mod, callback) {
return callback(err);
}
callback(null, {id: result.insertId, gmt_modified: new Date()});
if (typeof keywords === 'string') {
keywords = [keywords];
}
if (!Array.isArray(keywords)) {
return;
}
var words = [];
for (var i = 0; i < keywords.length; i++) {
var w = keywords[i];
if (typeof w === 'string') {
w = w.trim();
if (w) {
words.push(w);
}
}
}
if (words.length === 0) {
return;
}
// add keywords
exports.addKeywords(mod, description, words, utility.noop);
});
};
var UPDATE_DESC_SQL = 'UPDATE module SET description=? WHERE id=?;';
var GET_KEYWORD_SQL = multiline(function () {;/*
SELECT
keyword
FROM
module_keyword
WHERE
name = ?
ORDER BY
keyword;
*/});
exports.getKeywords = function (name, callback) {
mysql.query(GET_KEYWORD_SQL, [name], function (err, rows) {
var keywords = [];
if (rows && rows.length) {
keywords = rows.map(function (r) {
return r.keyword;
});
}
callback(err, keywords);
});
};
var ADD_KEYWORD_SQL = multiline(function () {;/*
INSERT INTO
module_keyword(gmt_create, keyword, name, description)
VALUES
(now(), ?, ?, ?)
ON DUPLICATE KEY UPDATE
description=VALUES(description);
*/});
exports.addKeywords = function (name, description, keywords, callback) {
var sql = '';
var values = [];
for (var i = 0; i < keywords.length; i++) {
sql += ADD_KEYWORD_SQL;
values.push(keywords[i]);
values.push(name);
values.push(description);
}
mysql.query(sql, values, function (err, results) {
if (err) {
return callback(err);
}
var ids = [];
for (var i = 0; i < results.length; i++) {
var r = results[i];
if (r.insertId) {
ids.push(r.insertId);
}
}
callback(null, ids);
});
};
var UPDATE_DESC_SQL = multiline(function () {;/*
UPDATE
module
SET
description=?
WHERE
id=?;
*/});
exports.updateDescription = function (id, description, callback) {
mysql.query(UPDATE_DESC_SQL, [description, id], callback);
};
var UPDATE_DIST_SQL = 'UPDATE module SET version=?, package=?, dist_tarball=?, dist_shasum=?, dist_size=? WHERE id=?;';
var UPDATE_PACKAGE_SQL = multiline(function () {;/*
UPDATE
module
SET
package=?
WHERE
id=?;
*/});
exports.updateReadme = function (id, readme, callback) {
exports.getById(id, function (err, data) {
if (err) {
return callback(err);
}
data.package = data.package || {};
data.package.readme = readme;
var pkg = stringifyPackage(data.package);
mysql.query(UPDATE_PACKAGE_SQL, [pkg, id], callback);
});
};
var UPDATE_DIST_SQL = multiline(function () {;/*
UPDATE
module
SET
publish_time=?,
version=?,
package=?,
dist_tarball=?,
dist_shasum=?,
dist_size=?
WHERE
id=?;
*/});
exports.update = function (mod, callback) {
var pkg;
try {
@@ -69,7 +205,8 @@ exports.update = function (mod, callback) {
return callback(e);
}
var dist = mod.package.dist;
mysql.query(UPDATE_DIST_SQL, [mod.version, pkg, dist.tarball, dist.shasum, dist.size, mod.id],
mysql.query(UPDATE_DIST_SQL,
[mod.publish_time, mod.version, pkg, dist.tarball, dist.shasum, dist.size, mod.id],
function (err, result) {
if (err) {
return callback(err);
@@ -91,13 +228,22 @@ function parseRow(row) {
}
}
}
exports.parseRow = parseRow;
function stringifyPackage(pkg) {
return encodeURIComponent(JSON.stringify(pkg));
}
var SELECT_MODULE_BY_ID_SQL = 'SELECT ' + MODULE_COLUMNS + ' FROM module WHERE id=?;';
var SELECT_MODULE_BY_ID_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
id=?;
*/});
exports.getById = function (id, callback) {
id = Number(id);
mysql.queryOne(SELECT_MODULE_BY_ID_SQL, [id], function (err, row) {
@@ -114,8 +260,15 @@ exports.getById = function (id, callback) {
});
};
var SELECT_MODULE_SQL = 'SELECT ' + MODULE_COLUMNS + ' FROM module WHERE name=? AND version=?;';
var SELECT_MODULE_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
name=? AND version=?;
*/});
exports.get = function (name, version, callback) {
mysql.queryOne(SELECT_MODULE_SQL, [name, version], function (err, row) {
if (err || !row) {
@@ -131,14 +284,26 @@ exports.get = function (name, version, callback) {
});
};
var INSERT_TAG_SQL = 'INSERT INTO tag(gmt_create, gmt_modified, \
name, tag, version, module_id) \
VALUES(now(), now(), ?, ?, ?, ?) \
ON DUPLICATE KEY UPDATE gmt_modified=now(), module_id=VALUES(module_id), \
name=VALUES(name), tag=VALUES(tag), version=VALUES(version);';
var SELECT_MODULE_ID_SQL = 'SELECT id FROM module WHERE name=? AND version=?;';
var SELECT_MODULE_ID_SQL = multiline(function () {;/*
SELECT
id
FROM
module
WHERE
name=? AND version=?;
*/});
var INSERT_TAG_SQL = multiline(function () {;/*
INSERT INTO
tag(gmt_create, gmt_modified, name, tag, version, module_id)
VALUES
(now(), now(), ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
gmt_modified=now(),
module_id=VALUES(module_id),
name=VALUES(name),
tag=VALUES(tag),
version=VALUES(version);
*/});
exports.addTag = function (name, tag, version, callback) {
mysql.queryOne(SELECT_MODULE_ID_SQL, [name, version], function (err, row) {
if (err) {
@@ -154,8 +319,14 @@ exports.addTag = function (name, tag, version, callback) {
});
};
var SELECT_TAG_SQL = 'SELECT tag, version, gmt_modified, module_id FROM tag WHERE name=? AND tag=?;';
var SELECT_TAG_SQL = multiline(function () {;/*
SELECT
tag, version, gmt_modified, module_id
FROM
tag
WHERE
name=? AND tag=?;
*/});
exports.getByTag = function (name, tag, callback) {
mysql.queryOne(SELECT_TAG_SQL, [name, tag], function (err, row) {
if (err || !row) {
@@ -165,21 +336,51 @@ exports.getByTag = function (name, tag, callback) {
});
};
var DELETE_TAGS_SQL = 'DELETE FROM tag WHERE name=?;';
var DELETE_TAGS_SQL = multiline(function () {;/*
DELETE FROM
tag
WHERE
name=?;
*/});
exports.removeTags = function (name, callback) {
mysql.query(DELETE_TAGS_SQL, [name], callback);
};
var SELECT_ALL_TAGS_SQL = 'SELECT tag, version, gmt_modified, module_id FROM tag WHERE name=?;';
var DELETE_TAGS_BY_IDS_SQL = multiline(function () {;/*
DELETE FROM
tag
WHERE
id in (?);
*/});
exports.removeTagsByIds = function (ids, callback) {
mysql.query(DELETE_TAGS_BY_IDS_SQL, [ids], callback);
};
var SELECT_ALL_TAGS_SQL = multiline(function () {;/*
SELECT
id, tag, version, gmt_modified, module_id
FROM
tag
WHERE
name=?;
*/});
exports.listTags = function (name, callback) {
mysql.query(SELECT_ALL_TAGS_SQL, [name], callback);
};
var SELECT_LATEST_MODULE_SQL = 'SELECT ' + MODULE_COLUMNS +
' FROM module WHERE name=? AND version <> "next" ORDER BY publish_time DESC LIMIT 1;';
var SELECT_LATEST_MODULE_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
name=? AND version <> "next"
ORDER BY
publish_time DESC
LIMIT
1;
*/});
exports.getLatest = function (name, callback) {
exports.getByTag(name, 'latest', function (err, row) {
if (err || row) {
@@ -202,8 +403,17 @@ exports.getLatest = function (name, callback) {
});
};
var LIST_MODULE_SQL = 'SELECT ' + MODULE_COLUMNS + ' FROM module WHERE name=? ORDER BY id DESC;';
var LIST_MODULE_SQL = multiline(function () {;/*
SELECT
id, publish_time, gmt_create, gmt_modified, author, name,
version, description, package, dist_tarball, dist_shasum, dist_size
FROM
module
WHERE
name=?
ORDER BY
id DESC;
*/});
exports.listByName = function (name, callback) {
mysql.query(LIST_MODULE_SQL, [name], function (err, rows) {
if (err) {
@@ -223,10 +433,23 @@ exports.listByName = function (name, callback) {
});
};
var LIST_SINCE_SQLS = [
'SELECT module_id FROM tag WHERE tag="latest" AND gmt_modified>?',
'SELECT name, package FROM module WHERE id IN (?);'
];
var LIST_SINCE_SQLS = [];
LIST_SINCE_SQLS.push(multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
tag="latest" AND gmt_modified>?;
*/}));
LIST_SINCE_SQLS.push(multiline(function () {;/*
SELECT
distinct(name)
FROM
module
WHERE
id IN (?);
*/}));
exports.listSince = function (start, callback) {
var ep = eventproxy.create();
ep.fail(callback);
@@ -244,31 +467,91 @@ exports.listSince = function (start, callback) {
});
};
var LIST_SHORT_SQL = 'SELECT distinct(name) FROM tag ORDER BY name';
var LIST_ALL_NAME_SQL = multiline(function () {;/*
SELECT
distinct(name)
FROM
module;
*/});
exports.listAllNames = function (callback) {
mysql.query(LIST_ALL_NAME_SQL, [], callback);
};
var LIST_SHORT_SQL = multiline(function () {;/*
SELECT
distinct(name)
FROM
tag
ORDER BY
name;
*/});
exports.listShort = function (callback) {
mysql.query(LIST_SHORT_SQL, callback);
};
var LIST_ALL_MODULE_NAMES_SQL = 'SELECT distinct(name) FROM module ORDER BY name';
var LIST_ALL_MODULE_NAMES_SQL = multiline(function () {;/*
SELECT
distinct(name)
FROM
module
ORDER BY
name;
*/});
exports.listAllModuleNames = function (callback) {
mysql.query(LIST_ALL_MODULE_NAMES_SQL, callback);
};
var DELETE_MODULE_BY_NAME_SQL = 'DELETE FROM module WHERE name=?;';
var DELETE_MODULE_BY_NAME_SQL = multiline(function () {;/*
DELETE FROM
module
WHERE
name=?;
*/});
exports.removeByName = function (name, callback) {
mysql.query(DELETE_MODULE_BY_NAME_SQL, [name], callback);
};
var DELETE_MODULE_BY_NAME_AND_VERSIONS_SQL = 'DELETE FROM module WHERE name=? AND version IN(?);';
var DELETE_MODULE_BY_NAME_AND_VERSIONS_SQL = multiline(function () {;/*
DELETE FROM
module
WHERE
name=? AND version in(?);
*/});
exports.removeByNameAndVersions = function (name, versions, callback) {
mysql.query(DELETE_MODULE_BY_NAME_AND_VERSIONS_SQL, [name, versions], callback);
};
var LIST_BY_AUTH_SQLS = [
'SELECT distinct(name) AS name FROM module WHERE author = ? ORDER BY publish_time DESC LIMIT 100;',
'SELECT module_id FROM tag WHERE tag="latest" AND name IN (?)',
'SELECT name, description FROM module WHERE id IN (?) ORDER BY publish_time DESC'
];
var LIST_BY_AUTH_SQLS = [];
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
distinct(name) AS name
FROM
module
WHERE
author=?
ORDER BY
publish_time DESC
LIMIT
100;
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
tag="latest" AND name IN (?);
*/}));
LIST_BY_AUTH_SQLS.push(multiline(function () {;/*
SELECT
name, description
FROM
module
WHERE
id IN (?)
ORDER BY
publish_time DESC;
*/}));
exports.listByAuthor = function (author, callback) {
var ep = eventproxy.create();
ep.fail(callback);
@@ -295,26 +578,121 @@ exports.listByAuthor = function (author, callback) {
});
};
var SEARCH_SQLS = [
'SELECT module_id FROM tag WHERE name LIKE ? AND tag="latest" ORDER BY name LIMIT 100;',
'SELECT name, description FROM module WHERE id IN (?) ORDER BY name;'
];
exports.search = function (word, callback) {
word = word.replace(/^%/, '') + '%'; //ignore prefix %
var SEARCH_MODULES_SQL = multiline(function () {;/*
SELECT
module_id
FROM
tag
WHERE
name LIKE ? AND tag="latest"
ORDER BY
name
LIMIT
?;
*/});
var SEARCH_MODULES_BY_KEYWORD_SQL = multiline(function () {;/*
SELECT
name, description
FROM
module_keyword
WHERE
keyword=?
ORDER BY
id DESC
LIMIT
?;
*/});
var QUERY_MODULES_BY_ID_SQL = multiline(function () {;/*
SELECT
name, description
FROM
module
WHERE
id IN (?)
ORDER BY
name;
*/});
exports.search = function (word, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
var limit = options.limit || 100;
word = word.replace(/^%/, ''); //ignore prefix %
var ep = eventproxy.create();
ep.fail(callback);
mysql.query(SEARCH_SQLS[0], [word], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
// search flows:
// 1. prefix search by name
// 2. like search by name
// 3. keyword equal search
var ids = {};
mysql.query(SEARCH_MODULES_SQL, [word + '%', limit ], ep.done(function (rows) {
rows = rows || [];
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
}
ep.emit('ids', rows.map(function (r) {
return r.module_id;
}));
if (rows.length >= 20) {
return ep.emit('ids', Object.keys(ids));
}
mysql.query(SEARCH_MODULES_SQL, [ '%' + word + '%', limit ], ep.done('likeSearch'));
}));
ep.on('ids', function (ids) {
mysql.query(SEARCH_SQLS[1], [ids], ep.done(function (modules) {
callback(null, modules);
mysql.query(SEARCH_MODULES_BY_KEYWORD_SQL, [ word, limit ], ep.done('keywordRows'));
ep.on('likeSearch', function (rows) {
rows = rows || [];
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
}
ep.emit('ids', Object.keys(ids));
});
ep.all('ids', 'keywordRows', function (ids, keywordRows) {
keywordRows = keywordRows || [];
var data = {
keywordMatchs: keywordRows,
searchMatchs: []
};
if (ids.length === 0) {
return callback(null, data);
}
mysql.query(QUERY_MODULES_BY_ID_SQL, [ids], ep.done(function (modules) {
data.searchMatchs = modules;
callback(null, data);
}));
});
};
thunkify(exports);
exports.updateMaintainers = function *(id, maintainers) {
var mod = yield exports.getById(id);
mod.package.maintainers = maintainers;
var pkg = stringifyPackage(mod.package);
return yield mysql.query(UPDATE_PACKAGE_SQL, [pkg, id]);
};
var GET_LAST_MODIFIED_MODULE_SQL = multiline(function () {;/*
SELECT
id, gmt_modified
FROM
module
WHERE
name=?
ORDER BY
gmt_modified DESC;
*/});
exports.getLastModified = function *(name) {
var row = yield mysql.queryOne(GET_LAST_MODIFIED_MODULE_SQL, [name]);
return row && row.gmt_modified;
};

58
proxy/module_deps.js Normal file
View File

@@ -0,0 +1,58 @@
/**!
* cnpmjs.org - proxy/module_deps.js
*
* Copyright(c) 2014
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
"use strict";
/**
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var LIST_DEPS_SQL = multiline(function () {;/*
SELECT
deps
FROM
module_deps
WHERE
name=?;
*/});
exports.list = function (name, callback) {
mysql.query(LIST_DEPS_SQL, [name], callback);
};
var INSERT_DEPS_SQL = multiline(function () {;/*
INSERT INTO
module_deps(gmt_create, name, deps)
VALUES
(now(), ?, ?);
*/});
exports.add = function (name, deps, callback) {
mysql.query(INSERT_DEPS_SQL, [name, deps], function (err, result) {
if (err && err.code === 'ER_DUP_ENTRY') {
err = null;
}
callback(err);
});
};
var DELETE_DEPS_SQL = multiline(function () {;/*
DELETE FROM
module_deps
WHERE
name=? AND deps=?;
*/});
exports.remove = function (name, deps, callback) {
mysql.query(DELETE_DEPS_SQL, [name, deps], callback);
};
thunkify(exports);

View File

@@ -14,11 +14,16 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var mysql = require('../common/mysql');
var multiline = require('multiline');
var INSERT_LOG_SQL = 'INSERT INTO module_log(gmt_create, gmt_modified, name, username, log) \
VALUES(now(), now(), ?, ?, "");';
var INSERT_LOG_SQL = multiline(function () {;/*
INSERT INTO
module_log(gmt_create, gmt_modified, name, username, log)
VALUES
(now(), now(), ?, ?, "");
*/});
exports.create = function (data, callback) {
mysql.query(INSERT_LOG_SQL, [data.name, data.username], function (err, result) {
if (err) {
@@ -28,7 +33,15 @@ exports.create = function (data, callback) {
});
};
var APPEND_SQL = 'UPDATE module_log SET log=CONCAT(log, ?), gmt_modified=now() WHERE id=?;';
var APPEND_SQL = multiline(function () {;/*
UPDATE
module_log
SET
log=CONCAT(log, ?),
gmt_modified=now()
WHERE
id=?;
*/});
exports.append = function (id, log, callback) {
log = '\n' + log;
mysql.query(APPEND_SQL, [log, id], function (err) {
@@ -36,7 +49,16 @@ exports.append = function (id, log, callback) {
});
};
var SELECT_SQL = 'SELECT * FROM module_log WHERE id=?;';
var SELECT_SQL = multiline(function () {;/*
SELECT
*
FROM
module_log
WHERE
id=?;
*/});
exports.get = function (id, callback) {
mysql.queryOne(SELECT_SQL, [id], callback);
};
thunkify(exports);

73
proxy/module_star.js Normal file
View File

@@ -0,0 +1,73 @@
/**!
* cnpmjs.org - proxy/module_star.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var mysql = require('../common/mysql');
var multiline = require('multiline');
var ADD_SQL = multiline(function () {;/*
INSERT iNTO
module_star(name, user)
VALUES
(?, ?);
*/});
exports.add = function *add(name, user) {
try {
yield mysql.query(ADD_SQL, [name, user]);
} catch (err) {
if (err.code !== 'ER_DUP_ENTRY') {
throw err;
}
}
};
var REMOVE_SQL = multiline(function () {;/*
DELETE FROM
module_star
WHERE
name = ? AND user = ?;
*/});
exports.remove = function *(name, user) {
return yield mysql.query(REMOVE_SQL, [name, user]);
};
var LIST_USERS_SQL = multiline(function () {;/*
SELECT
user
FROM
module_star
WHERE
name = ?;
*/});
exports.listUsers = function *(name) {
var rows = yield mysql.query(LIST_USERS_SQL, [name]);
return rows.map(function (r) {
return r.user;
});
};
var LIST_USER_MODULES_SQL = multiline(function () {;/*
SELECT
name
FROM
module_star
WHERE
user = ?;
*/});
exports.listUserModules = function *(user) {
return (yield mysql.query(LIST_USER_MODULES_SQL, [user])).map(function (r) {
return r.name;
});
};

View File

@@ -14,46 +14,63 @@
* Module dependencies.
*/
var urllib = require('urllib');
var urllib = require('co-urllib');
var config = require('../config');
function request(url, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {
dataType: 'json',
timeout: 10000
};
}
var USER_AGENT = 'cnpmjs.org/' + config.version + ' ' + urllib.USER_AGENT;
function *request(url, options) {
options = options || {};
options.dataType = options.dataType || 'json';
options.timeout = options.timeout || 120000;
options.headers = {
'user-agent': USER_AGENT
};
url = config.sourceNpmRegistry + url;
urllib.request(url, options, callback);
var r;
try {
r = yield *urllib.request(url, options);
} catch (err) {
var statusCode = err.status || -1;
var data = err.data || '[empty]';
if (err.name === 'JSONResponseFormatError' && statusCode >= 500) {
err.name = 'NPMServerError';
err.message = 'Status ' + statusCode + ', ' + data.toString();
}
throw err;
}
return r;
}
exports.get = function (name, callback) {
request('/' + name, function (err, data, res) {
if (err) {
return callback(err);
}
res = res || {};
if (res.statusCode === 404) {
data = null;
}
callback(null, data, res);
exports.getUser = function *(name) {
var url = '/-/user/org.couchdb.user:' + name;
var r = yield *request(url);
var data = r.data;
if (data && !data.name) {
data = null;
}
return data;
};
exports.get = function *(name) {
var r = yield *request('/' + name);
var data = r.data;
if (r.status === 404) {
data = null;
}
return data;
};
exports.getAllSince = function *(startkey) {
var r = yield *request('/-/all/since?stale=update_after&startkey=' + startkey, {
timeout: 300000
});
return r.data;
};
exports.getAllSince = function (startkey, callback) {
request('/-/all/since?stale=update_after&startkey=' + startkey, {
dataType: 'json',
exports.getShort = function *() {
var r = yield *request('/-/short', {
timeout: 300000
}, callback);
};
exports.getShort = function (callback) {
request('/-/short', {
dataType: 'json',
timeout: 300000
}, callback);
});
return r.data;
};

View File

@@ -15,21 +15,29 @@
* Module dependencies.
*/
var co = require('co');
var gather = require('co-gather');
var thunkify = require('thunkify-wrap');
var debug = require('debug')('cnpmjs.org:proxy:sync_module_worker');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var eventproxy = require('eventproxy');
var urllib = require('urllib');
var urllib = require('co-urllib');
var utility = require('utility');
var ms = require('ms');
var nfs = require('../common/nfs');
var npm = require('./npm');
var common = require('../lib/common');
var Module = require('./module');
var ModuleDeps = require('./module_deps');
var Log = require('./module_log');
var ms = require('ms');
var config = require('../config');
var ModuleStar = require('./module_star');
var User = require('./user');
var USER_AGENT = 'sync.cnpmjs.org/' + config.version + ' ' + urllib.USER_AGENT;
function SyncModuleWorker(options) {
EventEmitter.call(this);
@@ -40,13 +48,15 @@ function SyncModuleWorker(options) {
}
this.names = options.name;
for (var i = 0; i < this.names.length; i++) {
// ensure package name is lower case
this.names[i] = this.names[i].toLowerCase();
}
// for (var i = 0; i < this.names.length; i++) {
// // ensure package name is lower case
// this.names[i] = this.names[i].toLowerCase();
// }
this.username = options.username;
this.concurrency = options.concurrency || 1;
this._publish = options.publish; // _publish_on_cnpm
this.syncingNames = {};
this.nameMap = {};
this.names.forEach(function (name) {
@@ -82,10 +92,26 @@ SyncModuleWorker.prototype.log = function (format, arg1, arg2) {
};
SyncModuleWorker.prototype.start = function () {
this.log('user: %s, sync %s worker start, %d concurrency.', this.username, this.names[0], this.concurrency);
for (var i = 0; i < this.concurrency; i++) {
this.next(i);
}
this.log('user: %s, sync %s worker start, %d concurrency, nodeps: %s, publish: %s',
this.username, this.names[0], this.concurrency, this.noDep, this._publish);
var self = this;
co(function *() {
var arr = [];
for (var i = 0; i < self.concurrency; i++) {
arr.push(self.next(i));
}
yield arr;
})();
};
SyncModuleWorker.prototype.pushSuccess = function (name) {
this.successes.push(name);
this.emit('success', name);
};
SyncModuleWorker.prototype.pushFail = function (name) {
this.fails.push(name);
this.emit('fail', name);
};
SyncModuleWorker.prototype.add = function (name) {
@@ -94,10 +120,11 @@ SyncModuleWorker.prototype.add = function (name) {
}
this.nameMap[name] = true;
this.names.push(name);
this.emit('add', name);
this.log(' add dependencies: %s', name);
};
SyncModuleWorker.prototype.next = function (concurrencyId) {
SyncModuleWorker.prototype.next = function *(concurrencyId) {
var name = this.names.shift();
if (!name) {
return process.nextTick(this.finish.bind(this));
@@ -105,324 +132,510 @@ SyncModuleWorker.prototype.next = function (concurrencyId) {
var that = this;
that.syncingNames[name] = true;
npm.get(name, function (err, pkg, response) {
if (!err && !pkg) {
err = new Error('Module ' + name + ' not exists, http status ' + response.statusCode);
err.name = 'NpmModuleNotExsitsError';
}
if (err) {
that.fails.push(name);
that.log('[error] [%s] get package error: %s', name, err.stack);
delete that.syncingNames[name];
return that.next(concurrencyId);
var pkg;
// get from npm
try {
pkg = yield npm.get(name);
} catch (err) {
// if 404
if (err.res && err.res.statusCode === 404) {
that.pushSuccess(name);
} else {
that.pushFail(name);
}
that.log('[error] [%s] get package error: %s', name, err.stack);
delete that.syncingNames[name];
yield *that.next(concurrencyId);
return;
}
if (!pkg) {
that.log('[error] [%s] get package error: package not exist', name);
delete that.syncingNames[name];
yield that.next(concurrencyId);
return;
}
that.log('[c#%d] [%s] start...', concurrencyId, pkg.name);
that._sync(pkg, function (err, versions) {
delete that.syncingNames[name];
if (err) {
that.fails.push(pkg.name);
that.log('[error] [%s] sync error: %s', name, err.stack);
return that.next(concurrencyId);
}
that.log('[%s] synced success, %d versions: %s',
name, versions.length, versions.join(', '));
that.successes.push(name);
that.emit('success', name);
that.next(concurrencyId);
});
});
that.log('[c#%d] [%s] start...', concurrencyId, name);
var versions;
try {
versions = yield that._sync(name, pkg);
} catch (err) {
that.pushFail(name);
that.log('[error] [%s] sync error: %s', name, err.stack);
delete that.syncingNames[name];
yield *that.next(concurrencyId);
return;
}
that.log('[%s] synced success, %d versions: %s',
name, versions.length, versions.join(', '));
that.pushSuccess(name);
delete that.syncingNames[name];
yield that.next(concurrencyId);
};
SyncModuleWorker.prototype._sync = function (pkg, callback) {
var username = this.name;
var name = pkg.name;
function *_listStarUsers(modName) {
var users = yield ModuleStar.listUsers(modName);
var userMap = {};
users.forEach(function (user) {
userMap[user] = true;
});
return userMap;
}
function *_addStar(modName, username) {
yield ModuleStar.add(modName, username);
}
function *_saveNpmUser(username) {
var user = yield *npm.getUser(username);
if (!user) {
return;
}
yield User.saveNpmUser(user);
}
SyncModuleWorker.prototype._sync = function *(name, pkg) {
var username = this.username;
var that = this;
var ep = eventproxy.create();
ep.fail(callback);
var hasModules = false;
Module.listByName(name, ep.done(function (rows) {
hasModules = rows.length > 0;
var map = {};
for (var i = 0; i < rows.length; i++) {
var r = rows[i];
if (!r.package || !r.package.dist) {
// package json parse error
continue;
}
var result = yield [
Module.listByName(name),
Module.listTags(name),
_listStarUsers(name)
];
var moduleRows = result[0];
var tagRows = result[1];
var existsStarUsers = result[2];
if (r.package && r.package._publish_on_cnpm) {
// publish on cnpm, dont sync this version package
that.log(' [%s] publish on local cnpm, don\'t sync.', pkg.name);
ep.unbind();
callback(null, []);
return;
}
if (r.version === 'next') {
continue;
}
if (!map.latest) {
map.latest = r;
}
map[r.version] = r;
hasModules = moduleRows.length > 0;
var map = {};
for (var i = 0; i < moduleRows.length; i++) {
var r = moduleRows[i];
if (!r.package || !r.package.dist) {
// package json parse error
continue;
}
ep.emit('existsMap', map);
}));
Module.listTags(name, ep.done(function (rows) {
var tags = {};
for (var i = 0; i < rows.length; i++) {
var r = rows[i];
if (!r.module_id) {
// no module_id, need to sync tags
continue;
}
tags[r.tag] = r.version;
if (r.package && r.package._publish_on_cnpm) {
// publish on cnpm, dont sync this version package
that.log(' [%s] publish on local cnpm, don\'t sync', name);
return [];
}
ep.emit('existsTags', tags);
}));
if (r.version === 'next') {
continue;
}
if (!map.latest) {
map.latest = r;
}
map[r.version] = r;
}
var tags = {};
for (var i = 0; i < tagRows.length; i++) {
var r = tagRows[i];
if (!r.module_id) {
// no module_id, need to sync tags
continue;
}
tags[r.tag] = r.version;
}
var missingVersions = [];
var missingTags = [];
var missingDescriptions = [];
ep.all('existsMap', 'existsTags', function (map, tags) {
var times = pkg.time || {};
pkg.versions = pkg.versions || {};
var versionNames = Object.keys(times);
if (versionNames.length === 0) {
versionNames = Object.keys(pkg.versions);
var missingReadmes = [];
var missingStarUsers = [];
var npmUsernames = {};
// find out all user names
for (var v in pkg.versions) {
var p = pkg.versions[v];
var maintainers = p.maintainers || [];
if (maintainers && !Array.isArray(maintainers)) {
maintainers = [maintainers];
}
if (versionNames.length === 0) {
that.log(' [%s] no times and no versions, hasModules: %s', pkg.name, hasModules);
if (!hasModules) {
// save a next module
var maintainer = pkg.maintainers && pkg.maintainers[0];
if (maintainer && maintainer.name) {
maintainer = maintainer.name;
}
if (!maintainer) {
maintainer = '-';
}
var nextMod = {
name: pkg.name,
maintainers.forEach(function (m) {
npmUsernames[m.name.toLowerCase()] = 1;
});
}
// get the missing star users
var starUsers = pkg.users || {};
for (var k in starUsers) {
if (!existsStarUsers[k]) {
missingStarUsers.push(k);
}
npmUsernames[k.toLowerCase()] = 1;
}
that.log(' [%s] found %d missing star users', name, missingStarUsers.length);
var times = pkg.time || {};
pkg.versions = pkg.versions || {};
var versionNames = Object.keys(times);
if (versionNames.length === 0) {
versionNames = Object.keys(pkg.versions);
}
if (versionNames.length === 0) {
that.log(' [%s] no times and no versions, hasModules: %s', name, hasModules);
if (!hasModules) {
// save a next module
var maintainer = pkg.maintainers && pkg.maintainers[0];
if (maintainer && maintainer.name) {
maintainer = maintainer.name;
}
if (!maintainer) {
maintainer = '-';
}
var nextMod = {
name: name,
version: 'next',
author: maintainer,
package: {
name: name,
version: 'next',
author: maintainer,
package: {
name: pkg.name,
version: 'next',
description: pkg.description || '',
readme: pkg.readme || '',
maintainers: pkg.maintainers || {
name: maintainer
},
description: pkg.description || '',
readme: pkg.readme || '',
maintainers: pkg.maintainers || {
name: maintainer
},
};
Module.add(nextMod, function (err, result) {
that.log(' [%s] save next module, %j, error: %s', pkg.name, result, err);
});
},
};
try {
var result = yield Module.add(nextMod);
that.log(' [%s] save next module, %j', name, result);
} catch (err) {
that.log(' [%s] save next module error %s', err.message);
}
}
var versions = [];
for (var i = 0; i < versionNames.length; i++) {
var v = versionNames[i];
var exists = map[v] || {};
var version = pkg.versions[v];
if (!version || !version.dist) {
}
var versions = [];
for (var i = 0; i < versionNames.length; i++) {
var v = versionNames[i];
var exists = map[v] || {};
var version = pkg.versions[v];
if (!version || !version.dist || !version.dist.tarball) {
continue;
}
//patch for readme
if (!version.readme) {
version.readme = pkg.readme;
}
var publish_time = times[v];
version.publish_time = publish_time ? Date.parse(publish_time) : null;
if (!version.maintainers || !version.maintainers[0]) {
version.maintainers = pkg.maintainers;
}
var sourceAuthor = version.maintainers && version.maintainers[0] &&
version.maintainers[0].name || exists.author;
if (exists.package && exists.package.dist.shasum === version.dist.shasum &&
exists.author === sourceAuthor) {
// * author make sure equal
// * shasum make sure equal
if ((version.publish_time === exists.publish_time) ||
(!version.publish_time && exists.publish_time)) {
// debug(' [%s] %s publish_time equal: %s, %s',
// name, version.version, version.publish_time, exists.publish_time);
// * publish_time make sure equal
if (exists.description === null && version.description) {
// * make sure description exists
missingDescriptions.push({
id: exists.id,
description: version.description
});
}
if (!exists.package.readme && version.readme) {
// * make sure readme exists
missingReadmes.push({
id: exists.id,
readme: version.readme
});
}
continue;
}
var publish_time = times[v];
version.publish_time = publish_time ? Date.parse(publish_time) : null;
if (!version.maintainers || !version.maintainers[0]) {
version.maintainers = pkg.maintainers;
}
var sourceAuthor = version.maintainers && version.maintainers[0] && version.maintainers[0].name || exists.author;
if (exists.package && exists.package.dist.shasum === version.dist.shasum && exists.author === sourceAuthor) {
// * author make sure equal
// * shasum make sure equal
if ((version.publish_time === exists.publish_time) || (!version.publish_time && exists.publish_time)) {
debug(' [%s] %s publish_time equal: %s, %s',
pkg.name, version.version, version.publish_time, exists.publish_time);
// * publish_time make sure equal
if (exists.description === null && version.description) {
// * make sure description exists
missingDescriptions.push({
id: exists.id,
description: version.description
});
}
continue;
}
}
versions.push(version);
}
versions.push(version);
}
var sourceTags = pkg['dist-tags'] || {};
for (var t in sourceTags) {
var sourceTagVersion = sourceTags[t];
if (sourceTagVersion && tags[t] !== sourceTagVersion) {
missingTags.push([t, sourceTagVersion]);
}
}
if (versions.length === 0) {
that.log(' [%s] all versions are exists', pkg.name);
return ep.emit('syncDone');
var sourceTags = pkg['dist-tags'] || {};
for (var t in sourceTags) {
var sourceTagVersion = sourceTags[t];
if (sourceTagVersion && tags[t] !== sourceTagVersion) {
missingTags.push([t, sourceTagVersion]);
}
}
if (versions.length === 0) {
that.log(' [%s] all versions are exists', name);
} else {
versions.sort(function (a, b) {
return a.publish_time - b.publish_time;
});
missingVersions = versions;
that.log(' [%s] %d versions', pkg.name, versions.length);
ep.emit('syncModule', missingVersions.shift());
});
that.log(' [%s] %d versions need to sync', name, versions.length);
}
missingVersions = versions;
var versionNames = [];
var syncIndex = 0;
ep.on('syncModule', function (syncModule) {
// sync missing versions
while (missingVersions.length) {
var index = syncIndex++;
that._syncOneVersion(index, syncModule, function (err, result) {
if (err) {
that.log(' [%s:%d] error, version: %s, %s: %s',
syncModule.name, index, syncModule.version, err.name, err.message);
} else {
versionNames.push(syncModule.version);
}
var syncModule = missingVersions.shift();
if (!syncModule.dist.tarball) {
continue;
}
try {
var result = yield that._syncOneVersion(index, syncModule);
versionNames.push(syncModule.version);
} catch (err) {
that.log(' [%s:%d] error, version: %s, %s: %s',
syncModule.name, index, syncModule.version, err.name, err.message);
}
}
var nextVersion = missingVersions.shift();
if (!nextVersion) {
return ep.emit('syncDone', result);
}
ep.emit('syncModule', nextVersion);
});
});
ep.on('syncDone', function () {
// sync missing descriptions
function *syncDes() {
if (missingDescriptions.length === 0) {
return ep.emit('descriptionDone');
return;
}
that.log(' [%s] saving %d descriptions', name, missingDescriptions.length);
var res = yield gather(missingDescriptions.map(function (item) {
return Module.updateDescription(item.id, item.description);
}));
that.log(' [%s] saving %d descriptions', pkg.name, missingDescriptions.length);
missingDescriptions.forEach(function (item) {
Module.updateDescription(item.id, item.description, function (err, result) {
if (err) {
return that.log(' save error, %s: description %j, error: %s', item.id, item.description, err);
}
that.log(' saved, id: %s, description length: %d', item.id, item.description.length);
ep.emit('saveDescription');
});
});
for (var i = 0; i < res.length; i++) {
var item = missingDescriptions[i];
var r = res[i];
if (r.error) {
that.log(' save error, id: %s, description: %s, error: %s',
item.id, item.description, r.error.message);
} else {
that.log(' saved, id: %s, description length: %d',
item.id, item.description.length);
}
}
}
ep.after('saveDescription', missingDescriptions.length, function () {
ep.emit('descriptionDone');
});
});
ep.on('syncDone', function () {
// sync missing tags
function *syncTag() {
if (missingTags.length === 0) {
return ep.emit('tagDone');
return;
}
that.log(' [%s] adding %d tags', name, missingTags.length);
// sync tags
var res = yield gather(missingTags.map(function (item) {
return Module.addTag(name, item[0], item[1]);
}));
for (var i = 0; i < res.length; i++) {
var item = missingTags[i];
var r = res[i];
if (r.error) {
that.log(' add tag %s:%s error, error: %s',
item.id, item.description, r.error.message);
} else {
that.log(' added tag %s:%s, module_id: %s',
item[0], item[1], r.value && r.value.module_id);
}
}
}
// sycn missing readme
function *syncReadme() {
if (missingReadmes.length === 0) {
return;
}
that.log(' [%s] saving %d readmes', name, missingReadmes.length);
var res = yield gather(missingReadmes.map(function (item) {
return Module.updateReadme(item.id, item.readme);
}));
for (var i = 0; i < res.length; i++) {
var item = missingReadmes[i];
var r = res[i];
if (r.error) {
that.log(' save error, id: %s, error: %s', item.id, r.error.message);
} else {
that.log(' saved, id: %s', item.id);
}
}
}
function *syncMissingUsers() {
var missingUsers = [];
var names = Object.keys(npmUsernames);
if (names.length === 0) {
return;
}
var rows = yield *User.listByNames(names);
var map = {};
rows.forEach(function (r) {
map[r.name] = r;
});
names.forEach(function (username) {
var r = map[username];
if (!r || !r.json) {
missingUsers.push(username);
}
});
if (missingUsers.length === 0) {
that.log(' [%s] all %d npm users exists', name, names.length);
return;
}
that.log(' [%s] adding %d tags', pkg.name, missingTags.length);
// sync tags
missingTags.forEach(function (item) {
Module.addTag(pkg.name, item[0], item[1], ep.done(function (result) {
that.log(' added tag %s:%s, module_id: %s', item[0], item[1], result && result.module_id);
ep.emit('addTag');
}));
});
that.log(' [%s] saving %d/%d missing npm users: %j',
name, missingUsers.length, names.length, missingUsers);
var res = yield gather(missingUsers.map(function (username) {
return _saveNpmUser(username);
}));
ep.after('addTag', missingTags.length, function () {
ep.emit('tagDone');
});
});
for (var i = 0; i < res.length; i++) {
var r = res[i];
if (r.error) {
that.log(' save npm user error, %s', r.error.message);
}
}
}
ep.all('tagDone', 'descriptionDone', function () {
// TODO: set latest version
callback(null, versionNames);
});
// sync missing star users
function *syncMissingStarUsers() {
if (missingStarUsers.length === 0) {
return;
}
that.log(' [%s] saving %d star users', name, missingStarUsers.length);
var res = yield gather(missingStarUsers.map(function (username) {
return _addStar(name, username);
}));
for (var i = 0; i < res.length; i++) {
var r = res[i];
if (r.error) {
that.log(' add star user error, %s', r.error.message);
}
}
}
yield [syncDes(), syncTag(), syncReadme(), syncMissingStarUsers(), syncMissingUsers()];
return versionNames;
};
SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePackage, callback) {
SyncModuleWorker.prototype._syncOneVersion = function *(versionIndex, sourcePackage) {
var that = this;
var username = this.username;
var downurl = sourcePackage.dist.tarball;
var filename = path.basename(downurl);
var filepath = common.getTarballFilepath(filename);
var ws = fs.createWriteStream(filepath);
var options = {
writeStream: ws,
followRedirect: true,
timeout: 600000, // 10 minutes download
headers: {
'user-agent': USER_AGENT
}
};
var ep = eventproxy.create();
ep.fail(function (err) {
// remove tmp file whatever
fs.unlink(filepath, utility.noop);
callback(err);
});
that.log(' [%s:%d] syncing, version: %s, dist: %j',
sourcePackage.name, versionIndex, sourcePackage.version, sourcePackage.dist);
var dependencies = Object.keys(sourcePackage.dependencies || {});
var devDependencies = Object.keys(sourcePackage.devDependencies || {});
that.log(' [%s:%d] syncing, version: %s, dist: %j, no deps: %s, ' +
'publish on cnpm: %s, dependencies: %d, devDependencies: %d',
sourcePackage.name, versionIndex, sourcePackage.version,
sourcePackage.dist, that.noDep, that._publish,
dependencies.length, devDependencies.length);
if (dependencies.length > config.maxDependencies) {
dependencies = dependencies.slice(0, config.maxDependencies);
}
if (devDependencies.length > config.maxDependencies) {
devDependencies = devDependencies.slice(0, config.maxDependencies);
}
if (!that.noDep) {
for (var k in sourcePackage.dependencies) {
that.add(k);
for (var i = 0; i < dependencies.length; i++) {
that.add(dependencies[i]);
}
for (var k in sourcePackage.devDependencies) {
that.add(k);
for (var i = 0; i < devDependencies.length; i++) {
that.add(devDependencies[i]);
}
}
// add module dependence
try {
yield dependencies.map(function (depName) {
return ModuleDeps.add(depName, sourcePackage.name);
});
} catch (err) {
// ignore
}
var shasum = crypto.createHash('sha1');
var dataSize = 0;
urllib.request(downurl, options, ep.done(function (_, response) {
var statusCode = response && response.statusCode || -1;
try {
// get tarball
var r = yield *urllib.request(downurl, options);
var statusCode = r.status || -1;
if (statusCode === 404) {
// just copy source dist
shasum = sourcePackage.dist.shasum;
return ep.emit('uploadResult', {
return yield afterUpload({
url: downurl
});
}
if (statusCode !== 200) {
var err = new Error('Download ' + downurl + ' fail, status: ' + statusCode);
err.name = 'DownloadTarballError';
err.data = sourcePackage;
return ep.emit('error', err);
throw err;
}
// read and check
var rs = fs.createReadStream(filepath);
rs.once('error', function (err) {
ep.emit('error', err);
});
rs.on('data', function (data) {
shasum.update(data);
dataSize += data.length;
});
rs.on('end', function () {
shasum = shasum.digest('hex');
if (shasum !== sourcePackage.dist.shasum) {
var err = new Error('Download ' + downurl + ' shasum:' + shasum + ' not match ' + sourcePackage.dist.shasum);
err.name = 'DownloadTarballShasumError';
err.data = sourcePackage;
return ep.emit('error', err);
}
var end = thunkify.event(rs);
yield end(); // after end event emit
var key = common.getCDNKey(sourcePackage.name, filename);
nfs.upload(filepath, {key: key, size: dataSize}, ep.done('uploadResult'));
});
}));
// check shasum
shasum = shasum.digest('hex');
if (shasum !== sourcePackage.dist.shasum) {
var err = new Error('Download ' + downurl + ' shasum:' + shasum +
' not match ' + sourcePackage.dist.shasum);
err.name = 'DownloadTarballShasumError';
err.data = sourcePackage;
throw err;
}
ep.on('uploadResult', function (result) {
options = {
key: common.getCDNKey(sourcePackage.name, filename),
size: dataSize,
shasum: shasum
};
// upload to NFS
var result = yield nfs.upload(filepath, options);
return yield afterUpload(result);
} finally {
// remove tmp file whatever
fs.unlink(filepath, utility.noop);
}
function *afterUpload(result) {
//make sure sync module have the correct author info
//only if can not get maintainers, use the username
var author = username;
@@ -437,50 +650,63 @@ SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePacka
author: author,
publish_time: sourcePackage.publish_time,
};
// delete _publish_on_cnpm, because other cnpm maybe sync from current cnpm
delete mod.package._publish_on_cnpm;
if (that._publish) {
// sync as publish
mod.package._publish_on_cnpm = true;
}
var dist = {
tarball: result.url,
shasum: shasum,
size: dataSize,
noattachment: dataSize === 0,
};
if (result.url) {
dist.tarball = result.url;
} else if (result.key) {
dist.key = result.key;
dist.tarball = result.key;
}
mod.package.dist = dist;
Module.add(mod, ep.done(function (result) {
that.log(' [%s:%s] done, insertId: %s, author: %s, version: %s, size: %d, publish_time: %j',
sourcePackage.name, versionIndex,
result.id,
author, mod.version, dataSize, new Date(mod.publish_time));
callback(null, result);
}));
});
var r = yield Module.add(mod);
that.log(' [%s:%s] done, insertId: %s, author: %s, version: %s, ' +
'size: %d, publish_time: %j, publish on cnpm: %s',
sourcePackage.name, versionIndex,
r.id,
author, mod.version, dataSize,
new Date(mod.publish_time),
that._publish);
return r;
}
};
SyncModuleWorker.sync = function (name, username, callback) {
npm.get(name, function (err, pkg, response) {
if (err) {
return callback(err);
}
if (!pkg || !pkg._rev) {
return callback(null, {
ok: false,
statusCode: response.statusCode,
pkg: pkg
});
}
Log.create({name: name, username: username}, function (err, result) {
if (err) {
return callback(err);
}
var worker = new SyncModuleWorker({
logId: result.id,
name: name,
username: username,
});
worker.start();
callback(null, {
ok: true,
logId: result.id,
pkg: pkg
});
});
SyncModuleWorker.sync = function *(name, username, options) {
options = options || {};
var pkg = yield npm.get(name);
if (!pkg || !pkg._rev) {
return {
ok: false,
pkg: pkg
};
}
var result = yield Log.create({name: name, username: username});
var worker = new SyncModuleWorker({
logId: result.id,
name: name,
username: username,
noDep: options.noDep,
publish: options.publish,
});
worker.start();
return {
ok: true,
logId: result.id,
pkg: pkg
};
};

View File

@@ -14,16 +14,26 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var eventproxy = require('eventproxy');
var config = require('../config');
var mysql = require('../common/mysql');
var eventproxy = require('eventproxy');
var multiline = require('multiline');
var DB_SIZE_SQL = 'SELECT TABLE_NAME AS name, data_length, index_length \
FROM information_schema.tables \
WHERE TABLE_SCHEMA = ? \
GROUP BY TABLE_NAME \
ORDER BY data_length DESC \
LIMIT 0 , 200';
var DB_SIZE_SQL = multiline(function () {;/*
SELECT
TABLE_NAME AS name, data_length, index_length
FROM
information_schema.tables
WHERE
TABLE_SCHEMA = ?
GROUP BY
TABLE_NAME
ORDER BY
data_length DESC
LIMIT
0, 200;
*/});
var TOTAL_MODULE_SQL = 'SELECT count(distinct(name)) AS count FROM module;';
var TOTAL_VERSION_SQL = 'SELECT count(name) AS count FROM module;';
@@ -83,7 +93,14 @@ exports.get = function (callback) {
});
};
var PLUS_DELETE_MODULE_SQL = 'UPDATE total SET module_delete=module_delete+1 WHERE name="total";';
var PLUS_DELETE_MODULE_SQL = multiline(function () {;/*
UPDATE
total
SET
module_delete=module_delete+1
WHERE
name="total";
*/});
exports.plusDeleteModule = function (callback) {
mysql.query(PLUS_DELETE_MODULE_SQL, callback);
};
@@ -92,24 +109,55 @@ exports.getTotalInfo = function (callback) {
mysql.queryOne(TOTAL_INFO_SQL, callback);
};
var SET_LAST_SYNC_TIME_SQL = 'UPDATE total SET last_sync_time=? WHERE name="total";';
var SET_LAST_SYNC_TIME_SQL = multiline(function () {;/*
UPDATE
total
SET
last_sync_time=?
WHERE
name="total";
*/});
exports.setLastSyncTime = function (time, callback) {
mysql.query(SET_LAST_SYNC_TIME_SQL, Number(time), callback);
};
var SET_LAST_EXIST_SYNC_TIME_SQL = 'UPDATE total SET last_exist_sync_time=? WHERE name="total";';
var SET_LAST_EXIST_SYNC_TIME_SQL = multiline(function () {;/*
UPDATE
total
SET
last_exist_sync_time=?
WHERE
name="total";
*/});
exports.setLastExistSyncTime = function (time, callback) {
mysql.query(SET_LAST_EXIST_SYNC_TIME_SQL, Number(time), callback);
};
var UPDATE_SYNC_STATUS_SQL = 'UPDATE total SET sync_status = ? WHERE name="total";';
var UPDATE_SYNC_STATUS_SQL = multiline(function () {;/*
UPDATE
total
SET
sync_status=?
WHERE
name="total";
*/});
exports.updateSyncStatus = function (status, callback) {
mysql.query(UPDATE_SYNC_STATUS_SQL, [status], callback);
};
var UPDATE_SYNC_NUM_SQL = 'UPDATE total SET sync_status = ?, need_sync_num = ?, \
success_sync_num = ?, fail_sync_num = ?, left_sync_num = ?, last_sync_module=? \
WHERE name="total";';
var UPDATE_SYNC_NUM_SQL = multiline(function () {;/*
UPDATE
total
SET
sync_status = ?,
need_sync_num = ?,
success_sync_num = ?,
fail_sync_num = ?,
left_sync_num = ?,
last_sync_module = ?
WHERE
name="total";
*/});
exports.updateSyncNum = function (params, callback) {
var query = [
params.syncStatus, params.need || 0,
@@ -118,3 +166,5 @@ exports.updateSyncNum = function (params, callback) {
];
mysql.query(UPDATE_SYNC_NUM_SQL, query, callback);
};
thunkify(exports);

View File

@@ -14,36 +14,43 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var utility = require('utility');
var config = require('../config');
var mysql = require('../common/mysql');
var crypto = require('crypto');
var COLUMNS = 'id, rev, name, email, salt, password_sha, ip, roles, gmt_create, gmt_modified';
var SELECT_USER_SQL = 'SELECT ' + COLUMNS + ' FROM user WHERE name=?;';
function sha1(s) {
return crypto.createHash("sha1").update(s).digest("hex");
}
function passwordSha(password, salt) {
return sha1(password + salt);
}
exports.passwordSha = passwordSha;
var multiline = require('multiline');
var SELECT_USER_SQL = multiline(function () {;/*
SELECT
id, rev, name, email, salt, password_sha, ip,
roles, json, npm_user, gmt_create, gmt_modified
FROM
user
WHERE
name=?;
*/});
exports.get = function (name, callback) {
mysql.queryOne(SELECT_USER_SQL, [name], function (err, row) {
if (row) {
try {
row.roles = JSON.parse(row.roles);
row.roles = row.roles ? JSON.parse(row.roles) : [];
} catch (e) {
row.roles = [];
}
try {
row.json = row.json ? JSON.parse(row.json) : null;
} catch (e) {
row.json = null;
}
}
callback(err, row);
});
};
function passwordSha(password, salt) {
return utility.sha1(password + salt);
}
exports.auth = function (name, password, callback) {
exports.get(name, function (err, row) {
if (err || !row) {
@@ -58,9 +65,14 @@ exports.auth = function (name, password, callback) {
});
};
var INSERT_USER_SQL = 'INSERT INTO user(rev, name, email, salt, password_sha, ip, roles, gmt_create, gmt_modified) \
VALUES(?, ?, ?, ?, ?, ?, ?, now(), now())';
var INSERT_USER_SQL = multiline(function () {;/*
INSERT INTO
user(rev, name, email, salt, password_sha,
ip, roles, gmt_create, gmt_modified)
VALUES
(?, ?, ?, ?, ?, ?, ?, now(), now());
*/});
exports.add = function (user, callback) {
var roles = user.roles || [];
try {
@@ -75,9 +87,20 @@ exports.add = function (user, callback) {
});
};
var UPDATE_USER_SQL = 'UPDATE user SET rev=?, email=?, salt=?, password_sha=?, ip=?, roles=?, gmt_modified=now() \
WHERE name=? AND rev=?;';
var UPDATE_USER_SQL = multiline(function () {;/*
UPDATE
user
SET
rev=?,
email=?,
salt=?,
password_sha=?,
ip=?,
roles=?,
gmt_modified=now()
WHERE
name=? AND rev=?;
*/});
exports.update = function (user, callback) {
var rev = user.rev || user._rev;
var revNo = Number(rev.split('-', 1));
@@ -98,10 +121,41 @@ exports.update = function (user, callback) {
var values = [newRev, user.email, user.salt, user.password_sha, user.ip, roles, user.name, rev];
mysql.query(UPDATE_USER_SQL, values, function (err, data) {
if (err || !data.affectedRows) {
if (err) {
return callback(err);
}
callback(null, {rev: newRev});
});
};
thunkify(exports);
exports.passwordSha = passwordSha;
exports.saveNpmUser = function *(user) {
var sql = 'SELECT id, json FROM user WHERE name=?;';
var row = yield mysql.queryOne(sql, [user.name]);
if (!row) {
sql = 'INSERT INTO user(npm_user, json, rev, name, email, salt, password_sha, ip, gmt_create, gmt_modified) \
VALUES(1, ?, ?, ?, ?, "0", "0", "0", now(), now());';
yield mysql.query(sql, [JSON.stringify(user), user._rev, user.name, user.email]);
} else {
sql = 'UPDATE user SET json=?, rev=? WHERE id=?;';
yield mysql.query(sql, [JSON.stringify(user), user._rev, row.id]);
}
};
var LIST_BY_NAMES_SQL = multiline(function () {;/*
SELECT
id, name, email, json
FROM
user
WHERE
name in (?);
*/});
exports.listByNames = function *(names) {
if (names.length === 0) {
return [];
}
return yield mysql.query(LIST_BY_NAMES_SQL, [names]);
};

View File

@@ -15,6 +15,8 @@
* Module dependencies.
*/
var middlewares = require('koa-middlewares');
var limit = require('../middleware/limit');
var login = require('../middleware/login');
var publishable = require('../middleware/publishable');
var syncByInstall = require('../middleware/sync_by_install');
@@ -24,7 +26,7 @@ var user = require('../controllers/registry/user');
var sync = require('../controllers/sync');
function routes(app) {
app.get('/', total.show);
app.get('/', middlewares.jsonp(), total.show);
//before /:name/:version
//get all modules, for npm search
@@ -34,36 +36,36 @@ function routes(app) {
app.get('/-/short', mod.listAllModuleNames);
// module
app.get('/:name', [syncByInstall], mod.show);
app.get('/:name/:version', [syncByInstall], mod.get);
app.get('/:name', syncByInstall, mod.show);
app.get('/:name/:version', syncByInstall, mod.get);
// try to add module
app.put('/:name', [login, publishable], mod.add);
app.put('/:name', login, publishable, mod.add);
// sync from source npm
app.put('/:name/sync', sync.sync);
app.get('/:name/sync/log/:id', sync.getSyncLog);
app.get('/:name/download/:filename', mod.download);
// need limit by ip
app.get('/:name/download/:filename', limit, mod.download);
// put tarball
// https://registry.npmjs.org/cnpmjs.org/-/cnpmjs.org-0.0.0.tgz/-rev/1-c85bc65e8d2470cc4d82b8f40da65b8e
app.put('/:name/-/:filename/-rev/:rev', [login, publishable], mod.upload);
app.put('/:name/-/:filename/-rev/:rev', login, publishable, mod.upload);
// delete tarball
app.delete('/:name/download/:filename/-rev/:rev', [login, publishable], mod.removeTar);
app.delete('/:name/download/:filename/-rev/:rev', login, publishable, mod.removeTar);
// put package.json to module
app.put('/:name/:version/-tag/latest', [login, publishable], mod.updateLatest);
app.put('/:name/:version/-tag/latest', login, publishable, mod.updateLatest);
// update module, unpublish will PUT this
app.put('/:name/-rev/:rev', [login, publishable], mod.removeWithVersions);
app.delete('/:name/-rev/:rev', [login, publishable], mod.removeAll);
app.put('/:name/-rev/:rev', login, publishable, mod.updateOrRemove);
app.delete('/:name/-rev/:rev', login, publishable, mod.removeAll);
// try to create a new user
// https://registry.npmjs.org/-/user/org.couchdb.user:fengmk2
app.put('/-/user/org.couchdb.user::name', user.add);
app.get('/-/user/org.couchdb.user::name', user.show);
app.put('/-/user/org.couchdb.user::name/-rev/:rev', login, user.update);
// _session
app.post('/_session', user.authSession);
}

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - routes/web.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -19,6 +19,7 @@ var pkg = require('../controllers/web/package');
var user = require('../controllers/web/user');
var sync = require('../controllers/sync');
var total = require('../controllers/total');
var dist = require('../controllers/web/dist');
function routes(app) {
app.get('/total', total.show);
@@ -31,6 +32,11 @@ function routes(app) {
app.get('/sync/:name', pkg.displaySync);
app.put('/sync/:name', sync.sync);
app.get('/sync/:name/log/:id', sync.getSyncLog);
app.get('/sync', pkg.displaySync);
app.get('/_list/search/search', pkg.rangeSearch);
app.get(/^\/dist(\/.+)?/, dist.redirect);
}
module.exports = routes;

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - servers/registry.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -15,69 +15,54 @@
* Module dependencies.
*/
require('response-patch');
var koa = require('koa');
var app = module.exports = koa();
var http = require('http');
var connect = require('connect');
var rt = require('connect-rt');
var responseCookie = require('response-cookie');
var urlrouter = require('urlrouter');
var forward = require('forward');
var path = require('path');
var microtime = require('microtime');
var middlewares = require('koa-middlewares');
var routes = require('../routes/registry');
var logger = require('../common/logger');
var config = require('../config');
var session = require('../common/session');
var auth = require('../middleware/auth');
var notFound = require('../middleware/registry_not_found');
var rootdir = path.dirname(__dirname);
var app = connect();
app.use(middlewares.rt({headerName: 'X-ReadTime', timer: microtime}));
app.use(rt({headerName: 'X-ReadTime'}));
app.use(function (req, res, next) {
res.req = req;
next();
});
app.use(middlewares.rewrite('/favicon.ico', '/public/favicon.ico'));
app.use('/favicon.ico', forward(path.join(rootdir, 'public', 'favicon.png')));
app.use('/dist', connect.static(config.uploadDir));
app.use(responseCookie());
app.use(connect.cookieParser());
app.use(connect.query());
app.use(connect.json());
app.keys = ['todokey', config.sessionSecret];
app.outputErrors = true;
app.proxy = true;
app.use(session);
app.use(middlewares.bodyParser({jsonLimit: config.jsonLimit}));
app.use(auth());
app.use(notFound);
app.use(middlewares.gzip());
app.use(middlewares.fresh());
app.use(middlewares.etag());
/**
* Routes
*/
app.use(urlrouter(routes));
app.use(function (req, res, next) {
res.json(404, {error: 'not_found', reason: 'document not found'});
});
app.use(middlewares.router(app));
routes(app);
/**
* Error handler
*/
app.use(function (err, req, res, next) {
err.url = err.url || req.url;
if (process.env.NODE_ENV !== 'test') {
console.error(err.stack);
logger.error(err);
}
if (config.debug) {
return next(err);
}
res.json(500, {
error: err.name,
reason: err.message
});
app.on('error', function (err, ctx) {
err.url = err.url || ctx.request.url;
logger.error(err);
});
app = http.createServer(app);
app = http.createServer(app.callback());
if (!module.parent) {
app.listen(config.registryPort);
}
module.exports = app;

View File

@@ -15,81 +15,95 @@
* Module dependencies.
*/
require('response-patch');
var path = require('path');
var http = require('http');
var connect = require('connect');
var rt = require('connect-rt');
var urlrouter = require('urlrouter');
var connectMarkdown = require('connect-markdown');
var fs = require('fs');
var microtime = require('microtime');
var koa = require('koa');
var middlewares = require('koa-middlewares');
var markdown = require('koa-markdown');
var session = require('../common/session');
var opensearch = require('../middleware/opensearch');
var notFound = require('../middleware/web_not_found');
var auth = require('../middleware/auth');
var routes = require('../routes/web');
var logger = require('../common/logger');
var config = require('../config');
var session = require('../common/session');
var render = require('connect-render');
var opensearch = require('../middleware/opensearch');
var app = connect();
var app = koa();
var rootdir = path.dirname(__dirname);
app.use(rt({headerName: 'X-ReadTime'}));
app.use(function (req, res, next) {
res.req = req;
next();
});
app.use('/public', connect.static(path.join(rootdir, 'public')));
app.use('/opensearch.xml', opensearch);
app.use(connect.cookieParser());
app.use(middlewares.rt({headerName: 'X-ReadTime', timer: microtime}));
app.use(middlewares.staticCache(path.join(__dirname, '..', 'public'), {
buffer: !config.debug,
maxAge: config.debug ? 0 : 60 * 60 * 24 * 7,
dir: path.join(rootdir, 'public')
}));
app.use(opensearch);
app.keys = ['todokey', config.sessionSecret];
app.outputErrors = true;
app.proxy = true;
app.use(session);
app.use(connect.query());
app.use(connect.json());
app.use(middlewares.bodyParser());
app.use(auth());
app.use(notFound);
app.use(middlewares.gzip());
app.use(middlewares.fresh());
app.use(middlewares.etag());
var viewDir = path.join(rootdir, 'view', 'web');
var docDir = path.join(rootdir, 'docs', 'web');
app.use('/', connectMarkdown({
var layoutFile = path.join(viewDir, '_layout.html');
var footer = config.customFooter || fs.readFileSync(path.join(viewDir, 'footer.html'), 'utf8');
var layout = fs.readFileSync(path.join(viewDir, 'layout.html'), 'utf8')
.replace('{{footer}}', footer)
.replace('{{logoURL}}', config.logoURL);
fs.writeFileSync(layoutFile, layout);
app.use(markdown({
baseUrl: '/',
root: docDir,
layout: path.join(viewDir, 'layout.html'),
layout: layoutFile,
titleHolder: '<%- locals.title %>',
bodyHolder: '<%- locals.body %>',
indexName: 'readme'
}));
var helpers = {
var locals = {
config: config
};
app.use(render({
middlewares.render(app, {
root: viewDir,
viewExt: '.html',
layout: 'layout',
viewExt: 'html',
layout: '_layout',
cache: config.viewCache,
helpers: helpers
}));
debug: config.debug,
locals: locals
});
/**
* Routes
*/
app.use(urlrouter(routes));
app.use(middlewares.router(app));
routes(app);
/**
* Error handler
*/
app.use(function (err, req, res, next) {
err.url = err.url || req.url;
console.error(err.stack);
app.on('error', function (err, ctx) {
err.url = err.url || ctx.request.url;
logger.error(err);
if (config.debug) {
return next(err);
}
res.statusCode = 500;
res.send('Server has some problems. :(');
});
app = http.createServer(app);
app = http.createServer(app.callback());
if (!module.parent) {
app.listen(config.webPort);
}
module.exports = app;

View File

@@ -22,8 +22,9 @@ var utility = require('utility');
var debug = require('debug')('cnpmjs.org:sync:index');
var Total = require('../proxy/total');
var logger = require('../common/logger');
var co = require('co');
var sync;
var sync = null;
switch (config.syncModel) {
case 'all':
@@ -34,28 +35,41 @@ case 'exist':
break;
}
if (!sync && config.enableCluster) {
console.log('[%s] [sync_worker:%s] no need to sync, exit now', Date(), process.pid);
process.exit(0);
}
console.log('[%s] [sync_worker:%s] syncing with %s mode',
Date(), process.pid, config.syncModel);
//set sync_status = 0 at first
Total.updateSyncStatus(0, utility.noop);
// the same time only sync once
var syncing = false;
function handleSync() {
var handleSync = co(function *() {
debug('mode: %s, syncing: %s', config.syncModel, syncing);
// check sync every one 30 minutes
if (!syncing) {
syncing = true;
debug('start syncing');
sync(function (err, data) {
if (config.debug) {
console.log(err, data);
} else {
sendMailToAdmin(err, data, new Date());
}
syncing = false;
});
var data;
var error;
try {
var data = yield *sync();
} catch (err) {
error = err;
}
if (config.debug) {
error && console.error(error.stack);
data && console.log(data);
} else {
sendMailToAdmin(error, data, new Date());
}
syncing = false;
}
}
});
if (sync) {
handleSync();
@@ -85,7 +99,7 @@ function sendMailToAdmin(err, result, syncTime) {
'Start sync time is %s.\n %d packges sync failed: %j ...\n %d packages sync successes :%j ...',
syncTime, result.fails.length, result.fails.slice(0, 10),
result.successes.length, result.successes.slice(0, 10));
} else {
} else if (result.successes && result.successes.length) {
subject = 'Sync Finished';
type = 'log';
html = util.format('Sync packages from official registry finished.\n' +
@@ -93,7 +107,7 @@ function sendMailToAdmin(err, result, syncTime) {
syncTime, result.successes.length, result.successes.slice(0, 10));
}
debug('send email with type: %s, subject: %s, html: %s', type, subject, html);
if (type !== 'log') {
if (type && type !== 'log') {
mail[type](to, subject, html, function (err) {
if (err) {
logger.info('send email with type: %s, subject: %s, html: %s', type, subject, html);

View File

@@ -19,18 +19,20 @@ var utility = require('utility');
var Total = require('../proxy/total');
function Status(options) {
this.worker = options.worker;
this.need = options.need;
this.lastSyncModule = '';
this.successes = 0;
this.fails = 0;
this.left = options.need;
}
Status.prototype.log = function (syncDone) {
var params = {
syncStatus: syncDone ? 0 : 1,
need: this.need,
success: this.worker.successes.length,
fail: this.worker.fails.length,
left: this.worker.names.length,
success: this.successes,
fail: this.fails,
left: this.left,
lastSyncModule: this.lastSyncModule,
};
Total.updateSyncNum(params, utility.noop);
@@ -43,19 +45,30 @@ Status.prototype.start = function () {
this.started = true;
//every 30s log it into mysql
this.timer = setInterval(this.log.bind(this), 30000);
this.worker.on('success', function (moduleName) {
debug('sync [%s] success', moduleName);
this.lastSyncModule = moduleName;
}.bind(this));
this.worker.on('end', function () {
this.started = false;
this.log(true);
clearInterval(this.timer);
}.bind(this));
};
Status.init = function (options) {
return new Status(options);
Status.init = function (options, worker) {
var status = new Status(options);
status.start();
worker.on('success', function (moduleName) {
debug('sync [%s] success', moduleName);
status.lastSyncModule = moduleName;
status.successes++;
status.left--;
});
worker.on('fail', function () {
status.fails++;
status.left--;
});
worker.on('add', function () {
status.left++;
});
worker.on('end', function () {
status.started = false;
status.log(true);
clearInterval(status.timer);
});
};
module.exports = Status;

View File

@@ -24,6 +24,8 @@ var Npm = require('../proxy/npm');
var Total = require('../proxy/total');
var SyncModuleWorker = require('../proxy/sync_module_worker');
var Module = require('../proxy/module');
var co = require('co');
var thunkify = require('thunkify-wrap');
function subtract(subtracter, minuend) {
subtracter = subtracter || [];
@@ -31,13 +33,11 @@ function subtract(subtracter, minuend) {
var map = {};
var results = [];
minuend.forEach(function (name) {
map[name.toLowerCase()] = true;
map[name] = true;
});
subtracter.forEach(function (name) {
var lowerName = name.toLowerCase();
if (!map[lowerName] && !/[A-Z]/.test(name)) {
// ensure package name is lower case
results.push(lowerName);
if (!map[name]) {
results.push(name);
}
});
return results;
@@ -57,109 +57,87 @@ function union(arrOne, arrTwo) {
* when sync from official at the first time
* get all packages by short and restart from last synced module
* @param {String} lastSyncModule
* @param {Function} callback
*/
function getFirstSyncPackages(lastSyncModule, callback) {
Npm.getShort(function (err, pkgs) {
if (err || !lastSyncModule) {
return callback(err, pkgs);
}
// start from last success
var lastIndex = pkgs.indexOf(lastSyncModule);
if (lastIndex > 0) {
pkgs = pkgs.slice(lastIndex);
}
return callback(null, pkgs);
});
function *getFirstSyncPackages(lastSyncModule) {
var pkgs = yield Npm.getShort();
if (!lastSyncModule) {
return pkgs;
}
// start from last success
var lastIndex = pkgs.indexOf(lastSyncModule);
if (lastIndex > 0) {
return pkgs.slice(lastIndex);
}
}
/**
* get all the packages that update time > lastSyncTime
* @param {Number} lastSyncTime
* @param {Function} callback
*/
function getCommonSyncPackages(lastSyncTime, callback) {
Npm.getAllSince(lastSyncTime, function (err, data) {
if (err || !data) {
return callback(err, []);
}
delete data._updated;
return callback(null, Object.keys(data));
});
function *getCommonSyncPackages(lastSyncTime) {
var data = yield Npm.getAllSince(lastSyncTime);
if (!data) {
return [];
}
delete data._updated;
return Object.keys(data);
}
/**
* get all the missing packages
* @param {Function} callback
*/
function getMissPackages(callback) {
var ep = eventproxy.create();
ep.fail(callback);
Npm.getShort(ep.doneLater('allPackages'));
Module.listAllModuleNames(ep.doneLater(function (rows) {
var existPackages = rows.map(function (row) {
return row.name;
});
ep.emit('existPackages', existPackages);
}));
ep.all('allPackages', 'existPackages', function (allPackages, existPackages) {
callback(null, subtract(allPackages, existPackages));
function *getMissPackages(callback) {
var r = yield [Npm.getShort(), Module.listAllModuleNames];
var allPackages = r[0];
var existPackages = r[1].map(function (row) {
return row.name;
});
return subtract(allPackages, existPackages);
}
//only sync not exist once
var syncNotExist = true;
module.exports = function sync(callback) {
var ep = eventproxy.create();
ep.fail(callback);
module.exports = function *sync() {
var syncTime = Date.now();
Total.getTotalInfo(ep.doneLater('totalInfo'));
var info = yield Total.getTotalInfo();
if (!info) {
throw new Error('can not found total info');
}
ep.once('totalInfo', function (info) {
if (!info) {
return callback(new Error('can not found total info'));
}
debug('Last sync time %s', new Date(info.last_sync_time));
// TODO: 记录上次同步的最后一个模块名称
if (!info.last_sync_time) {
debug('First time sync all packages from official registry');
return getFirstSyncPackages(info.last_sync_module, ep.done('syncPackages'));
}
if (syncNotExist) {
getMissPackages(ep.done('missPackages'));
syncNotExist = false;
} else {
ep.emitLater('missPackages', []);
}
getCommonSyncPackages(info.last_sync_time - ms('10m'), ep.doneLater('newestPackages'));
ep.all('missPackages', 'newestPackages', function (missPackages, newestPackages) {
ep.emit('syncPackages', union(missPackages, newestPackages));
});
});
var packages;
debug('Last sync time %s', new Date(info.last_sync_time));
if (!info.last_sync_time) {
debug('First time sync all packages from official registry');
packages = yield getFirstSyncPackages(info.last_sync_module);
} else {
packages = yield getCommonSyncPackages(info.last_sync_time - ms('10m'));
}
ep.once('syncPackages', function (packages) {
packages = packages || [];
debug('Total %d packages to sync', packages.length);
var worker = new SyncModuleWorker({
username: 'admin',
name: packages,
noDep: true,
concurrency: config.syncConcurrency,
});
Status.init({
worker: worker,
need: packages.length
}).start();
worker.start();
worker.once('end', function () {
debug('All packages sync done, successes %d, fails %d',
worker.successes.length, worker.fails.length);
//only when all succss, set last sync time
!worker.fails.length && Total.setLastSyncTime(syncTime, utility.noop);
callback(null, {
successes: worker.successes,
fails: worker.fails
});
});
packages = packages || [];
if (!packages.length) {
debug('no packages need be sync');
return;
}
debug('Total %d packages to sync', packages.length);
var worker = new SyncModuleWorker({
username: 'admin',
name: packages,
noDep: true,
concurrency: config.syncConcurrency,
});
Status.init({need: packages.length}, worker);
worker.start();
var end = thunkify.event(worker);
yield end();
debug('All packages sync done, successes %d, fails %d',
worker.successes.length, worker.fails.length);
//only when all succss, set last sync time
if (!worker.fails.length) {
Total.setLastSyncTime(syncTime, utility.noop);
}
return {
successes: worker.successes,
fails: worker.fails
};
};

View File

@@ -24,6 +24,7 @@ var debug = require('debug')('cnpmjs.org:sync:sync_hot');
var utility = require('utility');
var Status = require('./status');
var ms = require('ms');
var thunkify = require('thunkify-wrap');
function intersection(arrOne, arrTwo) {
arrOne = arrOne || [];
@@ -39,71 +40,67 @@ function intersection(arrOne, arrTwo) {
return results;
}
module.exports = function sync(callback) {
var ep = eventproxy.create();
ep.fail(callback);
module.exports = function *sync() {
var syncTime = Date.now();
Module.listShort(ep.doneLater(function (packages) {
packages = packages.map(function (p) {
return p.name;
});
ep.emit('existPackages', packages);
}));
Total.getTotalInfo(ep.doneLater('totalInfo'));
ep.once('totalInfo', function (info) {
if (!info) {
return callback(new Error('can not found total info'));
}
if (!info.last_exist_sync_time) {
debug('First time sync all packages from official registry');
return Npm.getShort(ep.done(function (pkgs) {
if (!info.last_sync_module) {
return ep.emit('allPackages', pkgs);
}
// start from last success
var lastIndex = pkgs.indexOf(info.last_sync_module);
if (lastIndex > 0) {
pkgs = pkgs.slice(lastIndex);
}
ep.emit('allPackages', pkgs);
}));
}
Npm.getAllSince(info.last_exist_sync_time - ms('10m'), ep.done(function (data) {
if (!data) {
return ep.emit('allPackages', []);
}
if (data._updated) {
syncTime = data._updated;
delete data._updated;
}
return ep.emit('allPackages', Object.keys(data));
}));
var r = yield [Module.listShort(), Total.getTotalInfo()];
var existPackages = r[0].map(function (p) {
return p.name;
});
var info = r[1];
if (!info) {
throw new Error('can not found total info');
}
ep.all('existPackages', 'allPackages', function (existPackages, allPackages) {
var packages = intersection(existPackages, allPackages);
debug('Total %d packages to sync', packages.length);
var worker = new SyncModuleWorker({
username: 'admin',
name: packages,
concurrency: config.syncConcurrency,
});
Status.init({
worker: worker,
need: packages.length
}).start();
worker.start();
worker.once('end', function () {
debug('All packages sync done, successes %d, fails %d',
worker.successes.length, worker.fails.length);
Total.setLastExistSyncTime(syncTime, utility.noop);
callback(null, {
successes: worker.successes,
fails: worker.fails
});
});
var allPackages;
if (!info.last_exist_sync_time) {
debug('First time sync all packages from official registry');
var pkgs = yield Npm.getShort();
if (info.last_sync_module) {
// start from last success
var lastIndex = pkgs.indexOf(info.last_sync_module);
if (lastIndex > 0) {
pkgs = pkgs.slice(lastIndex);
}
}
allPackages = pkgs;
} else {
debug('sync new module from last exist sync time: %s', info.last_sync_time);
var data = yield Npm.getAllSince(info.last_exist_sync_time - ms('10m'));
if (!data) {
allPackages = [];
}
if (data._updated) {
syncTime = data._updated;
delete data._updated;
}
allPackages = Object.keys(data);
}
var packages = intersection(existPackages, allPackages);
if (!packages.length) {
debug('no packages need be sync');
return;
}
debug('Total %d packages to sync', packages.length);
var worker = new SyncModuleWorker({
username: 'admin',
name: packages,
concurrency: config.syncConcurrency
});
Status.init({need: packages.length}, worker);
worker.start();
var end = thunkify.event(worker);
yield end();
debug('All packages sync done, successes %d, fails %d',
worker.successes.length, worker.fails.length);
Total.setLastExistSyncTime(syncTime, utility.noop);
return {
successes: worker.successes,
fails: worker.fails
};
};

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - test/controllers/registry/module.test.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -17,6 +17,7 @@
var fs = require('fs');
var path = require('path');
var thunkify = require('thunkify-wrap');
var should = require('should');
var request = require('supertest');
var mm = require('mm');
@@ -25,6 +26,7 @@ var app = require('../../../servers/registry');
var Module = require('../../../proxy/module');
var Npm = require('../../../proxy/npm');
var controller = require('../../../controllers/registry/module');
var ModuleDeps = require('../../../proxy/module_deps');
var fixtures = path.join(path.dirname(path.dirname(__dirname)), 'fixtures');
@@ -64,38 +66,80 @@ describe('controllers/registry/module.test.js', function () {
});
});
describe('GET /:name', function () {
it('should return module info', function (done) {
describe('GET /:name get module package info', function () {
var etag;
it('should return module info and etag', function (done) {
request(app)
.get('/cnpmjs.org')
.expect('content-type', 'application/json')
.expect(200, function (err, res) {
should.not.exist(err);
// should have etag
res.headers.should.have.property('etag');
etag = res.headers.etag;
etag.should.match(/^"\d{13}"$/);
res.body.should.have.keys('_id', '_rev', 'name', 'description',
'versions', 'dist-tags', 'readme', 'maintainers',
'time', 'author', 'repository', '_attachments');
// res.body.author.should.eql({
// "name": "fengmk2",
// "email": "fengmk2@gmail.com",
// "url": "http://fengmk2.github.com"
// });
'time', 'author', 'repository', '_attachments',
'users', 'readmeFilename', 'homepage', 'bugs', 'license');
res.body.name.should.equal('cnpmjs.org');
res.body.versions[Object.keys(res.body.versions)[0]].dist.tarball.should.include('/cnpmjs.org/download');
res.body.versions[Object.keys(res.body.versions)[0]]
.dist.tarball.should.include('/cnpmjs.org/download');
res.body.time.should.have.property('modified');
res.body.time.modified.should.be.a.String;
res.body.time.should.have.property('created');
res.body.time.created.should.be.a.String;
// should not contains authSession cookie
should.not.exist(res.headers['set-cookie']);
done();
});
});
it('should return module info and gzip when accept-encoding=gzip', function (done) {
request(app)
.get('/cnpmjs.org')
.set('accept-encoding', 'gzip')
.expect('content-encoding', 'gzip')
.expect(200, function (err, res) {
// console.log(res.headers)
should.not.exist(err);
// should have etag
res.headers.should.have.property('etag');
etag = res.headers.etag;
res.body.should.have.keys('_id', '_rev', 'name', 'description',
'versions', 'dist-tags', 'readme', 'maintainers',
'time', 'author', 'repository', '_attachments',
'users', 'readmeFilename', 'homepage', 'bugs', 'license');
res.body.name.should.equal('cnpmjs.org');
res.body.versions[Object.keys(res.body.versions)[0]].dist.tarball.should.include('/cnpmjs.org/download');
should.not.exist(res.headers['set-cookie']);
done();
});
});
it('should 304 when etag match', function (done) {
request(app)
.get('/cnpmjs.org')
.set('If-None-Match', etag)
.expect(304, done);
});
});
describe('GET /:name/:(version|tag)', function () {
it('should return module@version info', function (done) {
request(app)
.get('/cnpmjs.org/0.0.0')
.get('/cnpmjs.org/0.3.9')
.expect(200, function (err, res) {
should.not.exist(err);
var body = res.body;
body.name.should.equal('cnpmjs.org');
body.version.should.equal('0.0.0');
body._id.should.equal('cnpmjs.org@0.0.0');
body.dist.tarball.should.include('cnpmjs.org-0.0.0.tgz');
body.version.should.match(/\d+\.\d+\.\d+/);
body._id.should.match(/cnpmjs\.org@\d+\.\d+\.\d+/);
body.dist.tarball.should.match(/cnpmjs\.org-\d+\.\d+\.\d+\.tgz/);
body.should.have.property('_cnpm_publish_time');
body._cnpm_publish_time.should.be.a.Number;
body.should.have.property('_publish_on_cnpm', true);
done();
});
});
@@ -114,20 +158,97 @@ describe('controllers/registry/module.test.js', function () {
});
});
it('should get cnpmjs.org@0.1.0 with _publish_on_cnpm=true', function (done) {
it('should get cnpmjs.org@latest with _publish_on_cnpm=true', function (done) {
request(app)
.get('/cnpmjs.org/0.1.0')
.get('/cnpmjs.org/latest')
.expect(200, function (err, res) {
should.not.exist(err);
var body = res.body;
body.name.should.equal('cnpmjs.org');
body.version.should.equal('0.1.0');
// body.version.should.equal('latest');
body._publish_on_cnpm.should.equal(true);
done();
});
});
});
describe('PUT /:name/-rev/id update maintainers', function () {
before(function (done) {
request(app)
.put('/cnpmjs.org/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}]
})
.set('authorization', baseauth)
.expect('content-type', 'application/json', done);
});
it('should add new maintainers', function (done) {
request(app)
.put('/cnpmjs.org/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}, {
name: 'fengmk2',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', baseauth)
.expect(201)
.expect('content-type', 'application/json', done);
});
it('should add again new maintainers', function (done) {
request(app)
.put('/cnpmjs.org/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}, {
name: 'fengmk2',
email: 'fengmk2@cnpmjs.org'
}]
})
.set('authorization', baseauth)
.expect(201)
.expect('content-type', 'application/json', done);
});
it('should rm maintainers', function (done) {
request(app)
.put('/cnpmjs.org/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}]
})
.set('authorization', baseauth)
.expect(201)
.expect('content-type', 'application/json', done);
});
it('should rm again maintainers', function (done) {
request(app)
.put('/cnpmjs.org/-rev/1')
.send({
maintainers: [{
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}]
})
.set('authorization', baseauth)
.expect(201)
.expect('content-type', 'application/json', done);
});
});
describe('PUT /:name', function () {
var pkg = {
name: 'testputmodule',
@@ -137,6 +258,13 @@ describe('controllers/registry/module.test.js', function () {
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}],
keywords: [
'testputmodule', 'test', 'cnpmjstest'
],
dependencies: {
'foo-testputmodule': "*",
'bar-testputmodule': '*'
}
};
var baseauth = 'Basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64');
var baseauthOther = 'Basic ' + new Buffer('cnpmjstest101:cnpmjstest101').toString('base64');
@@ -172,14 +300,7 @@ describe('controllers/registry/module.test.js', function () {
.put('/' + pkg.name)
.set('authorization', baseauth)
.send(pkg)
.expect(409, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'conflict',
reason: 'Document update conflict.'
});
done();
});
.expect(201, done);
});
it('should try to add return 403 when not module user and only next module exists',
@@ -205,7 +326,7 @@ describe('controllers/registry/module.test.js', function () {
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('_id', '_rev', 'name', 'description', 'versions', 'dist-tags',
'readme', 'maintainers', 'time', '_attachments');
'readme', 'maintainers', 'time', '_attachments', 'users');
res.body.versions.should.eql({});
res.body.time.should.eql({});
res.body['dist-tags'].should.eql({});
@@ -291,6 +412,7 @@ describe('controllers/registry/module.test.js', function () {
var pkg = require(path.join(fixtures, 'testputmodule.json')).versions['0.1.8'];
pkg.name = 'testputmodule';
pkg.version = '0.1.9';
pkg.dependencies['foo-testputmodule'] = '*';
request(app)
.put('/' + pkg.name + '/' + pkg.version + '/-tag/latest')
.set('authorization', baseauth)
@@ -298,7 +420,16 @@ describe('controllers/registry/module.test.js', function () {
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'rev');
done();
// should get deps foo-testputmodule contains 'testputmodule'
ModuleDeps.list('foo-testputmodule', function (err, rows) {
should.not.exist(err);
var exists = rows.filter(function (r) {
return r.deps === 'testputmodule';
});
exists.should.length(1);
exists[0].deps.should.equal('testputmodule');
done();
});
});
});
@@ -346,6 +477,60 @@ describe('controllers/registry/module.test.js', function () {
done();
});
});
it('should publish with tgz base64, addPackageAndDist()', function (done) {
var pkg = require(path.join(fixtures, 'package_and_tgz.json'));
// delete first
request(app)
.del('/' + pkg.name + '/-rev/1')
.set('authorization', baseauth)
.expect({ok: true})
.expect(200, function (err, res) {
should.not.exist(err);
request(app)
.put('/' + pkg.name)
.set('authorization', baseauth)
.send(pkg)
.expect(201, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'rev');
res.body.ok.should.equal(true);
// upload again should 409
request(app)
.put('/' + pkg.name)
.set('authorization', baseauth)
.send(pkg)
.expect(409, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'conflict',
reason: 'Document update conflict.'
});
done();
});
});
});
});
it('should version_error when versions missing', function (done) {
var pkg = require(path.join(fixtures, 'package_and_tgz.json'));
delete pkg.versions;
request(app)
.put('/' + pkg.name)
.set('authorization', baseauth)
.send(pkg)
.expect(400, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'version_error',
reason: 'filename or version not found, filename: mk2testmodule-0.0.1.tgz, version: undefined'
});
done();
});
});
});
describe('GET /-/all', function () {
@@ -370,7 +555,6 @@ describe('controllers/registry/module.test.js', function () {
res.body._updated.should.be.a.Number;
var keys = Object.keys(res.body);
keys.length.should.be.above(1);
res.body[keys[1]].dist.tarball.should.include('/download/');
done();
});
});
@@ -442,6 +626,7 @@ describe('controllers/registry/module.test.js', function () {
it('should remove version ok', function (done) {
//do not really remove it here
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/testputmodule/-rev/' + lastRev)
.set('authorization', baseauth)
@@ -453,6 +638,15 @@ describe('controllers/registry/module.test.js', function () {
});
});
describe('GET /:name/download/:filename', function () {
it('should download a file with 302 redirect', function (done) {
request(app)
.get('/cutter/download/cutter-0.0.2.tgz')
.expect('Location', 'http://qtestbucket.qiniudn.com/cutter/-/cutter-0.0.2.tgz')
.expect(302, done);
});
});
describe('DELETE /:name/download/:filename/-rev/:rev', function () {
var lastRev;
before(function (done) {
@@ -517,7 +711,11 @@ describe('controllers/registry/module.test.js', function () {
request(app)
.del('/testputmodule/-rev/' + lastRev)
.set('authorization', baseauth)
.expect(200, done);
.expect(200, function (err, res) {
should.not.exist(err);
should.not.exist(res.headers['set-cookie']);
done();
});
});
});
});

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - test/controllers/registry/user.test.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -16,30 +16,49 @@
var should = require('should');
var request = require('supertest');
var mm = require('mm');
var app = require('../../../servers/registry');
var user = require('../../../proxy/user');
var mm = require('mm');
var mysql = require('../../../common/mysql');
describe('controllers/registry/user.test.js', function () {
before(function (done) {
app.listen(0, done);
});
after(function (done) {
app.close(done);
});
afterEach(mm.restore);
describe('GET /-/user/org.couchdb.user:name', function () {
it('should return user info', function (done) {
request(app)
.get('/-/user/org.couchdb.user:cnpmjstest1')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('_id', '_rev', 'name', 'email', 'type', 'roles', 'date');
res.body.should.have.keys('_id', '_rev', 'name', 'email', 'type',
'_cnpm_meta', 'roles', 'date');
res.body.name.should.equal('cnpmjstest1');
done();
});
});
it('should return npm user info', function (done) {
request(app)
.get('/-/user/org.couchdb.user:fengmk2')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.name.should.equal('fengmk2');
res.body.github.should.equal('fengmk2');
res.body._cnpm_meta.should.have.keys('id', 'npm_user', 'gmt_create',
'gmt_modified', 'admin');
res.body._cnpm_meta.admin.should.equal(true);
done();
});
});
it('should return 404 when not eixst', function (done) {
request(app)
.get('/-/user/org.couchdb.user:cnpmjstest_notexist')
@@ -47,7 +66,7 @@ describe('controllers/registry/user.test.js', function () {
});
it('should return 500 when mysql error', function (done) {
mm.error(user, 'get', 'mock error');
mm.error(mysql, 'query', 'mock mysql error');
request(app)
.get('/-/user/org.couchdb.user:cnpmjstest1')
.expect(500, done);
@@ -91,7 +110,7 @@ describe('controllers/registry/user.test.js', function () {
password_sha: 'password_sha',
email: 'email'
})
.expect(500, done);
.expect(500, done);
});
it('should 201 when user.add ok', function (done) {
@@ -145,7 +164,12 @@ describe('controllers/registry/user.test.js', function () {
ok: true,
name: 'name',
roles: []
}, done);
}, function (err, res) {
should.not.exist(err);
should.exist(res.headers['set-cookie']);
res.headers['set-cookie'].join(';').should.include('AuthSession=');
done();
});
});
});
@@ -167,6 +191,11 @@ describe('controllers/registry/user.test.js', function () {
mm.error(user, 'update', 'mock error');
request(app)
.put('/-/user/org.couchdb.user:cnpmjstest10/-rev/:1-123')
.send({
name: 'cnpmjstest10',
password: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
})
.set('authorization', 'basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64'))
.expect(500, done);
});
@@ -176,6 +205,8 @@ describe('controllers/registry/user.test.js', function () {
.put('/-/user/org.couchdb.user:cnpmjstest10/-rev/:1-123')
.set('authorization', 'basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64'))
.send({
name: 'cnpmjstest10',
password: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org',
rev: '1-123'
})
@@ -188,6 +219,8 @@ describe('controllers/registry/user.test.js', function () {
.put('/-/user/org.couchdb.user:cnpmjstest10/-rev/:1-123')
.set('authorization', 'basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64'))
.send({
name: 'cnpmjstest10',
password: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org',
rev: '1-123'
})

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - test/controllers/sync.test.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -17,12 +17,12 @@
var request = require('supertest');
var should = require('should');
var registryApp = require('../../servers/registry');
var webApp = require('../../servers/web');
var pedding = require('pedding');
var mm = require('mm');
var Npm = require('../../proxy/npm');
var path = require('path');
var Npm = require('../../proxy/npm');
var registryApp = require('../../servers/registry');
var webApp = require('../../servers/web');
describe('controllers/sync.test.js', function () {
before(function (done) {
@@ -40,9 +40,52 @@ describe('controllers/sync.test.js', function () {
describe('sync source npm package', function () {
var logIdRegistry;
var logIdWeb;
it('should sync success', function (done) {
it('should sync as publish success', function (done) {
request(registryApp)
.del('/utility/-rev/123')
.set('authorization', baseauth)
.end(function (err, res) {
should.not.exist(err);
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(registryApp)
.put('/utility/sync?publish=true&nodeps=true')
.set('authorization', baseauth)
.end(function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'logId');
logIdRegistry = res.body.logId;
done();
});
});
});
it('should sync as publish 403 when user not admin', function (done) {
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(registryApp)
.put('/utility_unit_test/sync?publish=true&nodeps=true')
.expect(403)
.expect({
error: 'no_perms',
reason: 'Only admin can publish'
}, done);
});
it('should sync through web success', function (done) {
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(webApp)
.put('/sync/utility')
.end(function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'logId');
logIdWeb = res.body.logId;
done();
});
});
it('should sync through registry success', function (done) {
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
done = pedding(2, done);
request(registryApp)
.put('/utility/sync')
.set('authorization', baseauth)
@@ -52,14 +95,6 @@ describe('controllers/sync.test.js', function () {
logIdRegistry = res.body.logId;
done();
});
request(webApp)
.put('/sync/utility')
.end(function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'logId');
logIdWeb = res.body.logId;
done();
});
});
it('should get sync log', function (done) {

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - test/controllers/total.test.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -17,9 +17,9 @@
var should = require('should');
var request = require('supertest');
var pedding = require('pedding');
var registryApp = require('../../servers/registry');
var webApp = require('../../servers/web');
var pedding = require('pedding');
describe('controllers/total.test.js', function () {
before(function (done) {
@@ -44,6 +44,7 @@ describe('controllers/total.test.js', function () {
done();
});
});
it('should return total info by jsonp', function (done) {
request(registryApp)
.get('?callback=totalCallback')
@@ -51,7 +52,8 @@ describe('controllers/total.test.js', function () {
.expect(/totalCallback\({.*}\)/, done);
});
});
describe('GET /total in web', function () {
describe.skip('GET /total in web', function () {
it('should return total info', function (done) {
request(webApp)
.get('/total')
@@ -62,5 +64,5 @@ describe('controllers/total.test.js', function () {
done();
});
});
});
});
});

View File

@@ -0,0 +1,37 @@
/**!
* cnpmjs.org - test/controllers/web/dist.test.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.cnpmjs.org)
*/
'use strict';
/**
* Module dependencies.
*/
var should = require('should');
var request = require('supertest');
var app = require('../../../servers/web');
describe('controllers/web/dist.test.js', function () {
before(function (done) {
app.listen(0, done);
});
after(function (done) {
app.close(done);
});
describe('GET /dist', function (done) {
it('should 302 to config.disturl', function (done) {
request(app)
.get('/dist')
.expect('Location', 'http://dist.u.qiniudn.com/')
.expect(302, done);
});
});
});

View File

@@ -31,14 +31,64 @@ describe('controllers/web/package.test.js', function () {
afterEach(mm.restore);
describe('GET /_list/search/search', function () {
it('should search with "c"', function (done) {
request(app)
.get('/_list/search/search?startkey="c"&limit=2')
.expect('content-type', 'application/json')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('rows');
res.body.rows.length.should.above(0);
res.body.rows.forEach(function (row) {
row.should.have.keys('key', 'count', 'value');
row.value.should.have.keys('name', 'description');
});
done();
});
});
it('should search with c', function (done) {
request(app)
.get('/_list/search/search?startkey=c&limit=2')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('rows');
res.body.rows.length.should.above(0);
res.body.rows.forEach(function (row) {
row.should.have.keys('key', 'count', 'value');
row.value.should.have.keys('name', 'description');
});
done();
});
});
it('should search return empty', function (done) {
request(app)
.get('/_list/search/search?startkey="cddddsdasdaasds"&limit=2')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.eql({rows: []});
done();
});
});
});
describe('GET /package/:name', function (done) {
it('should get 200', function (done) {
request(app)
.get('/package/cutter')
.expect(200)
.expect('content-encoding', 'gzip')
.expect('content-type', 'text/html; charset=utf-8')
.expect(/<div id="package">/)
.expect(/<th>Maintainers<\/th>/)
.expect(/<th>Version<\/th>/, done);
.expect(/<th>Version<\/th>/, function (err, res) {
should.not.exist(err);
res.should.have.header('etag');
res.text.should.include('<meta charset="utf-8">');
done();
});
});
it('should get 404', function (done) {
@@ -87,6 +137,13 @@ describe('controllers/web/package.test.js', function () {
.expect(/Packages match/, done);
});
it('should list by keyword with json ok', function (done) {
request(app)
.get('/browse/keyword/cnpm?type=json')
.expect(200)
.expect('content-type', 'application/json; charset=utf-8', done);
});
it('should list no match ok', function (done) {
request(app)
.get('/browse/keyword/notexistpackage')
@@ -99,7 +156,7 @@ describe('controllers/web/package.test.js', function () {
request(app)
.get('/browse/keyword/notexistpackage')
.expect(500)
.expect(/MockError: mm mock error/, done);
.expect(/Internal Server Error/, done);
});
});
@@ -113,7 +170,7 @@ describe('controllers/web/package.test.js', function () {
url: 'http://opensource.org/licenses/MIT'
});
var p = {license: ['http://foo/MIT']};
p = {license: ['http://foo/MIT']};
pkg.setLicense(p);
p.license.should.have.keys('name', 'url');
p.license.should.eql({
@@ -121,7 +178,7 @@ describe('controllers/web/package.test.js', function () {
url: 'http://foo/MIT'
});
var p = {license: {name: 'mit', url: 'http://foo/mit'}};
p = {license: {name: 'mit', url: 'http://foo/mit'}};
pkg.setLicense(p);
p.license.should.have.keys('name', 'url');
p.license.should.eql({

View File

@@ -31,6 +31,7 @@ describe('controllers/web/user.test.js', function () {
request(app)
.get('/~dead_horse')
.expect(200)
.expect('content-type', 'text/html; charset=utf-8')
.expect(/<div id="profile">/)
.expect(/Packages by /, done);
});

7
test/fixtures/500.txt vendored Normal file
View File

@@ -0,0 +1,7 @@
Internal routing error
Sorry, we cannot connect to the intended server.
We have just been notified of this problem. We will correct it as soon as possible.
Feel free to contact us if you have any questions: support@iriscouch.com

1
test/fixtures/fengmk2.json vendored Normal file
View File

@@ -0,0 +1 @@
{"_id":"org.couchdb.user:fengmk2","_rev":"25-ba29aa975022944b4ba547ab11ce3af5","name":"fengmk2","email":"fengmk2@gmail.com","type":"user","roles":[],"date":"2014-03-15T08:06:18.870Z","fullname":"fengmk2","avatar":"https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=50&d=retro","freenode":"","github":"fengmk2","homepage":"http://fengmk2.github.com","twitter":"fengmk2","avatarMedium":"https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=100&d=retro","avatarLarge":"https://secure.gravatar.com/avatar/95b9d41231617a05ced5604d242c9670?s=496&d=retro","fields":[{"name":"fullname","value":"fengmk2","title":"Full Name","show":"fengmk2"},{"name":"email","value":"fengmk2@gmail.com","title":"Email","show":"<a href=\"mailto:fengmk2@gmail.com\">fengmk2@gmail.com</a>"},{"name":"github","value":"fengmk2","title":"Github","show":"<a rel=\"me\" href=\"https://github.com/fengmk2\">fengmk2</a>"},{"name":"twitter","value":"fengmk2","title":"Twitter","show":"<a rel=\"me\" href=\"https://twitter.com/fengmk2\">@fengmk2</a>"},{"name":"appdotnet","value":"","title":"App.net","show":""},{"name":"homepage","value":"http://fengmk2.github.com","title":"Homepage","show":"<a rel=\"me\" href=\"http://fengmk2.github.com/\">http://fengmk2.github.com</a>"},{"name":"freenode","value":"","title":"IRC Handle","show":""}],"appdotnet":"fengmk2"}

1
test/fixtures/package_and_tgz.json vendored Normal file
View File

@@ -0,0 +1 @@
{"_id":"mk2testmodule","name":"mk2testmodule","description":"","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"mk2testmodule","version":"0.0.1","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1"},"author":"","license":"ISC","readme":"ERROR: No README data found!","_id":"mk2testmodule@0.0.1","dist":{"shasum":"fa475605f88bab9b1127833633ca3ae0a477224c","tarball":"http://127.0.0.1:7001/mk2testmodule/-/mk2testmodule-0.0.1.tgz"},"_from":".","_npmVersion":"1.4.3","_npmUser":{"name":"fengmk2","email":"fengmk2@gmail.com"},"maintainers":[{"name":"fengmk2","email":"fengmk2@gmail.com"}]}},"readme":"ERROR: No README data found!","maintainers":[{"name":"fengmk2","email":"fengmk2@gmail.com"}],"_attachments":{"mk2testmodule-0.0.1.tgz":{"content_type":"application/octet-stream","data":"H4sIAAAAAAAAA+2SsWrDMBCGPfspDg2ZinOyEgeylg6Zu2YR8rVRHEtGkkOg5N0jWaFdujVQAv6W4/7/dHcSGqTq5Ccthxyro7emeDCI2KxWkOKmaaaIdc4TouZQ8FqgwI3AdVMgF8ijho9e5DdGH6SLq/y1T74LfMcn4asEYEb2xLbA+q4O5ENv2/FE7CVZZ3JeW5NcrLDiWW3JK6eHcHey2Es9Zdq0dIkfKau50EcjjYpCmpDKSB0s7Nmbc9ZtwVhIBviBlP7Q1O4ZLBZAFx2As3jyOnWTYzhY9zPzpBUZPy2/e39l5bX87wedmZmZeRJuheTX2wAIAAA=","length":251}}}

29
test/lib/common.test.js Normal file
View File

@@ -0,0 +1,29 @@
/*!
* cnpmjs.org - test/lib/common.test.js.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* dead_horse <dead_horse@qq.com>
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var common = require('../../lib/common');
describe('lib/common.test.js', function () {
describe('isAdmin()', function () {
it('should admin is admin', function () {
common.isAdmin('admin').should.equal(true);
common.isAdmin('fengmk2').should.equal(true);
common.isAdmin('constructor').should.equal(false);
common.isAdmin('toString').should.equal(false);
});
});
});

View File

@@ -29,8 +29,9 @@ describe('middleware/auth.test.js', function () {
app.close(done);
});
afterEach(mm.restore);
describe('auth()', function () {
afterEach(mm.restore);
it('should pass if no authorization', function (done) {
request(app)
.get('/-/user/org.couchdb.user:cnpmjstest1')
@@ -44,11 +45,12 @@ describe('middleware/auth.test.js', function () {
.expect(200, done);
});
it('should 401 with authorization and check fail', function (done) {
it('should pass with authorization and check fail', function (done) {
// npm install no need to check auth
request(app)
.get('/-/user/org.couchdb.user:cnpmjstest10')
.set('authorization', 'basic ' + new Buffer('cnpmjstest10:cnpmjstest').toString('base64'))
.expect(401, done);
.expect(200, done);
});
it('should 500 with authorization and mysql error', function (done) {

View File

@@ -30,8 +30,8 @@ describe('middleware/opensearch.test.js', function () {
it('should get 200', function (done) {
request(app)
.get('/opensearch.xml')
.set('host', 'localhost:7002')
.expect(/http:\/\/localhost:7002/, done);
.set('host', 'localhost')
.expect(/http:\/\/localhost/, done);
});
});
});

View File

@@ -0,0 +1,63 @@
/**!
* cnpmjs.org - test/middleware/web_not_found.test.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var should = require('should');
var request = require('supertest');
var app = require('../../servers/web');
describe('middleware/web_not_found.test.js', function () {
before(function (done) {
app.listen(0, done);
});
after(function (done) {
app.close(done);
});
describe('web_not_found()', function () {
it('should redirect /byte to /package/byte', function (done) {
request(app)
.get('/byte')
.expect('Location', '/package/byte')
.expect(302, done);
});
it('should redirect /byte/ to /package/byte', function (done) {
request(app)
.get('/byte/')
.expect('Location', '/package/byte')
.expect(302, done);
});
it('should 404 /~byte', function (done) {
request(app)
.get('/~byte')
.expect(404, done);
});
it('should 200 /package/byte', function (done) {
request(app)
.get('/package/byte')
.expect(200, done);
});
it('should 404 /package/byte404', function (done) {
request(app)
.get('/package/byte404')
.expect(404, done);
});
});
});

View File

@@ -23,6 +23,7 @@ var Module = require('../../proxy/module');
var fixtures = path.join(path.dirname(__dirname), 'fixtures');
var id;
describe('proxy/module.test.js', function () {
afterEach(mm.restore);
@@ -36,6 +37,79 @@ describe('proxy/module.test.js', function () {
});
});
describe('search()', function () {
it('should search modules', function (done) {
Module.search('as', function (err, data) {
should.not.exist(err);
data.should.have.keys('keywordMatchs', 'searchMatchs');
data.searchMatchs.length.should.above(0);
data.searchMatchs.forEach(function (row) {
row.should.have.keys('name', 'description');
});
done();
});
});
it('should search match keywords modules', function (done) {
Module.search('aa', function (err, data) {
should.not.exist(err);
data.should.have.keys('keywordMatchs', 'searchMatchs');
data.keywordMatchs.length.should.above(0);
data.keywordMatchs.forEach(function (row) {
row.should.have.keys('name', 'description');
});
done();
});
});
it('should search return empty', function (done) {
Module.search('emptyemptyemptyempty', function (err, data) {
should.not.exist(err);
data.should.eql({
keywordMatchs: [],
searchMatchs: []
});
done();
});
});
});
describe('addKeywords()', function () {
var mockName = 'aa' + Date.now();
after(function (done) {
mysql.query('DELETE FROM module_keyword WHERE name=?', [mockName], done);
});
it('should add diff keywords to module', function (done) {
Module.addKeywords(mockName, mockName, ['aa', 'bb', 'cc'], function (err, results) {
should.not.exist(err);
results.should.be.an.Array;
results.should.length(3);
done();
});
});
it('should add same keywords to module', function (done) {
Module.addKeywords('aa', 'desc aa', ['aa', 'bb', 'cc'], function (err, results) {
should.not.exist(err);
results.should.be.an.Array;
results.should.length(0);
done();
});
});
});
describe('getKeywords()', function () {
it('should get aa module keywords', function (done) {
Module.getKeywords('aa', function (err, keywords) {
should.not.exist(err);
keywords.should.eql(['aa', 'bb', 'cc']);
done();
});
});
});
describe('add()', function () {
it('should success ad he@0.3.6', function (done) {
var sourcePackage = require('../fixtures/0.3.6.json');
@@ -53,6 +127,7 @@ describe('proxy/module.test.js', function () {
};
mod.package.dist = dist;
Module.add(mod, function (err, result) {
id = result.id;
should.not.exist(err);
Module.getById(result.id, function (err, row) {
should.not.exist(err);
@@ -74,4 +149,17 @@ describe('proxy/module.test.js', function () {
});
});
});
describe('updateReadme()', function () {
it('should update ok', function (done) {
Module.updateReadme(id, 'test', function (err, data) {
should.not.exist(err);
Module.getById(id, function (err, data) {
should.not.exist(err);
data.package.readme.should.equal('test');
done();
});
});
});
});
});

View File

@@ -0,0 +1,58 @@
/**!
* cnpmjs.org - test/proxy/module_deps.test.js
*
* Copyright(c) 2014
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
"use strict";
/**
* Module dependencies.
*/
var should = require('should');
var pedding = require('pedding');
var ModuleDeps = require('../../proxy/module_deps');
describe('proxy/module_deps.test.js', function () {
before(function (done) {
done = pedding(2, done);
ModuleDeps.remove('testmodule', 'foo', done);
ModuleDeps.remove('testmodule', 'bar', done);
});
describe('add()', function () {
it('should add foo, bar success', function (done) {
done = pedding(2, done);
ModuleDeps.add('testmodule', 'foo', function (err) {
should.not.exist(err);
// add again should work
ModuleDeps.add('testmodule', 'foo', function (err) {
should.not.exist(err);
done();
});
});
ModuleDeps.add('testmodule', 'bar', done);
});
});
describe('list()', function () {
it('should list testmodule deps', function (done) {
ModuleDeps.list('testmodule', function (err, rows) {
should.not.exist(err);
should.exist(rows);
rows.should.be.an.Array;
rows.should.length(2);
rows.forEach(function (row) {
row.should.have.property('deps');
});
done();
});
});
});
});

View File

@@ -0,0 +1,51 @@
/**!
* cnpmjs.org - test/proxy/module_star.test.js
*
* Copyright(c) cnpmjs.org and other contributors.
* MIT Licensed
*
* Authors:
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
*/
'use strict';
/**
* Module dependencies.
*/
var should = require('should');
var Star = require('../../proxy/module_star');
describe('proxy/module_star.test.js', function () {
before(function *() {
yield Star.remove('testmodule', 'fengmk2');
yield Star.remove('testmodule', 'mk1');
yield Star.remove('testmodule', 'mk2');
});
it('should add a star', function *() {
yield Star.add('testmodule', 'fengmk2');
// again should be ok
yield Star.add('testmodule', 'fengmk2');
yield Star.add('testmodule', 'fengmk2');
});
it('should get all star users', function *() {
yield Star.add('testmodule', 'fengmk2');
yield Star.add('testmodule', 'mk1');
yield Star.add('testmodule', 'mk2');
var rows = yield Star.listUsers('testmodule');
rows.should.containDeep(['fengmk2', 'mk1', 'mk2']);
});
it('should get user all star modules', function *() {
yield Star.add('testmodule', 'fengmk2');
yield Star.add('testmodule1', 'fengmk2');
yield Star.add('testmodule2', 'fengmk2');
var rows = yield Star.listUserModules('fengmk2');
rows.should.containDeep(['testmodule', 'testmodule1', 'testmodule2']);
});
});

View File

@@ -16,35 +16,46 @@
var should = require('should');
var mm = require('mm');
var fs = require('fs');
var path = require('path');
var ChunkStream = require('chunkstream');
var npm = require('../../proxy/npm');
var fixtures = path.join(path.dirname(__dirname), 'fixtures');
describe('proxy/npm.test.js', function () {
afterEach(mm.restore);
it('should return a module info from source npm', function (done) {
npm.get('pedding', function (err, data) {
should.not.exist(err);
should.exist(data);
data.name.should.equal('pedding');
done();
});
it('should return a module info from source npm', function *() {
var data = yield npm.get('pedding');
data.name.should.equal('pedding');
});
it('should return null when module not exist', function (done) {
npm.get('pedding-not-exists', function (err, data) {
should.not.exist(err);
should.not.exist(data);
done();
});
it('should return null when module not exist', function *() {
var data = yield npm.get('pedding-not-exists');
should.not.exist(data);
});
it('should return error when http error', function (done) {
mm.http.request(/\//, '{');
npm.get('pedding-not-exists', function (err, data) {
should.exist(err);
it('should return error when http error', function *() {
mm.http.request(/\//, new ChunkStream(['{']));
try {
var data = yield npm.get('pedding-not-exists');
throw new Error('should not run this');
} catch (err) {
err.name.should.equal('JSONResponseFormatError');
should.not.exist(data);
done();
});
}
});
it('should return ServerError when http 500 response', function *() {
var content = fs.createReadStream(path.join(fixtures, '500.txt'));
mm.http.request(/\//, content, { statusCode: 500 });
// http://registry.npmjs.org/octopie
try {
var data = yield npm.get('octopie');
throw new Error('should not run this');
} catch (err) {
err.name.should.equal('NPMServerError');
err.message.should.equal('Status 500, ' + fs.readFileSync(path.join(fixtures, '500.txt'), 'utf8'));
}
});
});

View File

@@ -47,7 +47,9 @@ describe('proxy/sync_module_worker.test.js', function () {
worker.start();
worker.on('end', function () {
worker.successes.concat(worker.fails).should.eql(['cnpmjs.org', 'cutter']);
var names = worker.successes.concat(worker.fails);
names.sort();
names.should.eql(['cnpmjs.org', 'cutter']);
done();
});
});

View File

@@ -6,6 +6,7 @@
*
* Authors:
* dead_horse <dead_horse@qq.com> (http://deadhorse.me)
* fengmk2 <fengmk2@gmail.com> (http://fengmk2.cnpmjs.org)
*/
'use strict';
@@ -14,10 +15,14 @@
* Module dependencies.
*/
var mysql = require('../../common/mysql');
var should = require('should');
var user = require('../../proxy/user');
var mm = require('mm');
var path = require('path');
var fs = require('fs');
var mysql = require('../../common/mysql');
var user = require('../../proxy/user');
var fixtures = path.join(path.dirname(__dirname), 'fixtures');
var mockUser = {
name: 'mockuser',
@@ -49,7 +54,9 @@ describe('proxy/user.test.js', function () {
it('should get user ok', function (done) {
user.get('mockuser', function (err, data) {
should.not.exist(err);
data.should.have.keys('id', 'rev', 'name', 'email', 'salt', 'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
data.should.have.keys('id', 'rev', 'name', 'email', 'salt',
'json', 'npm_user',
'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
done();
});
});
@@ -68,7 +75,9 @@ describe('proxy/user.test.js', function () {
it('should auth user ok', function (done) {
user.auth(mockUser.name, mockUser.password, function (err, data) {
should.not.exist(err);
data.should.have.keys('id', 'rev', 'name', 'email', 'salt', 'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
data.should.have.keys('id', 'rev', 'name', 'email', 'salt',
'json', 'npm_user',
'password_sha', 'ip', 'roles', 'gmt_create', 'gmt_modified');
done();
});
});
@@ -153,4 +162,32 @@ describe('proxy/user.test.js', function () {
});
});
});
describe('saveNpmUser()', function () {
var existUser = JSON.parse(fs.readFileSync(path.join(fixtures, 'fengmk2.json')));
var notExistUser = JSON.parse(fs.readFileSync(path.join(fixtures, 'fengmk2.json')));
notExistUser.name = 'fengmk2-not-exists';
before(function *() {
yield mysql.query('delete from user where name=?', [notExistUser.name]);
});
it('should save npm user to exists user', function *() {
yield user.saveNpmUser(existUser);
var r = yield mysql.queryOne('select rev, json, npm_user from user where name=?', existUser.name);
should.exist(r);
r.npm_user.should.equal(0);
r.rev.should.equal(existUser._rev);
JSON.parse(r.json).should.eql(existUser);
});
it('should save npm user to not exists user and create it', function *() {
yield user.saveNpmUser(notExistUser);
var r = yield mysql.queryOne('select name, json, npm_user from user where name=?', notExistUser.name);
r.name.should.equal(notExistUser.name);
should.exist(r);
r.npm_user.should.equal(1);
JSON.parse(r.json).should.eql(notExistUser);
});
});
});

View File

@@ -17,19 +17,27 @@
var SyncModuleWorker = require('../proxy/sync_module_worker');
var mysql = require('../common/mysql');
var Log = require('../proxy/module_log');
var config = require('../config');
var name = process.argv[2] || 'address,pedding';
var names = name.split(',');
config.sourceNpmRegistry = 'http://r.cnpmjs.org';
var names = process.argv[2] || 'byte';
names = names.split(',');
Log.create({
name: names[0],
username: 'fengmk2',
}, function (err, result) {
if (err) {
throw err;
}
var worker = new SyncModuleWorker({
logId: result.id,
name: names,
username: 'fengmk2',
concurrency: names.length,
// noDep: true,
// publish: true,
});
worker.start();

View File

@@ -24,22 +24,17 @@ describe('sync/sync_all.js', function () {
describe('sync()', function () {
afterEach(mm.restore);
it('should sync first time ok', function (done) {
it('should sync first time ok', function *() {
mm.data(Npm, 'getShort', ['cnpmjs.org', 'cutter']);
mm.data(Total, 'getTotalInfo', {last_sync_time: 0});
sync(function (err, data) {
should.not.exist(err);
data.successes.should.eql(['cnpmjs.org', 'cutter']);
mm.restore();
Total.getTotalInfo(function (err, result) {
should.not.exist(err);
result.last_sync_module.should.equal('cutter');
done();
});
});
var data = yield sync;
data.successes.should.eql(['cnpmjs.org', 'cutter']);
mm.restore();
var result = yield Total.getTotalInfo();
result.last_sync_module.should.equal('cutter');
});
it('should sync common ok', function (done) {
it('should sync common ok', function *() {
mm.data(Npm, 'getAllSince', {
_updated: Date.now(),
'cnpmjs.org': {},
@@ -48,11 +43,9 @@ describe('sync/sync_all.js', function () {
mm.data(Npm, 'getShort', ['cnpmjs.org', 'cutter', 'cnpm']);
mm.data(Total, 'getTotalInfo', {last_sync_time: Date.now()});
mm.data(Module, 'listAllModuleNames', [{name: 'cnpmjs.org'}, {name: 'cutter'}]);
sync(function (err, data) {
should.not.exist(err);
data.successes.should.eql(['cnpm', 'cnpmjs.org', 'cutter']);
done();
});
var data = yield sync;
data.successes.should.eql(['cnpmjs.org', 'cutter']);
mm.restore();
});
});
});

View File

@@ -23,28 +23,22 @@ describe('sync/sync_exist.js', function () {
describe('sync()', function () {
afterEach(mm.restore);
it('should sync first time ok', function (done) {
it('should sync first time ok', function *() {
mm.data(Npm, 'getShort', ['cnpmjs.org', 'cutter']);
mm.data(Total, 'getTotalInfo', {last_exist_sync_time: 0});
sync(function (err, data) {
should.not.exist(err);
data.successes.should.eql(['cnpmjs.org', 'cutter']);
done();
});
var data = yield sync();
data.successes.should.eql(['cnpmjs.org', 'cutter']);
});
it('should sync common ok', function (done) {
it('should sync common ok', function *() {
mm.data(Npm, 'getAllSince', {
_updated: Date.now(),
'cnpmjs.org': {},
cutter: {}
});
mm.data(Total, 'getTotalInfo', {last_exist_sync_time: Date.now()});
sync(function (err, data) {
should.not.exist(err);
data.successes.should.eql(['cnpmjs.org', 'cutter']);
done();
});
var data = yield sync();
data.successes.should.eql(['cnpmjs.org', 'cutter']);
});
});
});

5
view/web/footer.html Normal file
View File

@@ -0,0 +1,5 @@
Copyright 2013 - 2014 &copy; cnpmjs.org
|
<a href="/">Home</a>
|
<script>var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_5757157'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s17.cnzz.com/stat.php%3Fid%3D5757157%26online%3D1%26show%3Dline' type='text/javascript'%3E%3C/script%3E"));</script>

View File

@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%- locals.title %></title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="/public/favicon.png">
@@ -29,6 +30,10 @@
table.downloads td.count {
text-align: right;
}
.nav-tabs{margin:20px 0;}
.nav-cont{display:none;}
.nav-cont.active{display:block;}
</style>
<script>
$(function () {
@@ -41,26 +46,32 @@
return location.href = '/browse/keyword/' + val;
}
});
$(".nav-tabs li").each(function (index) {
$(this).data("index", index);
})
.on("click", function (e) {
e.preventDefault();
$(".nav-tabs li.active,.nav-cont.active").removeClass("active");
$(this).addClass("active");
$(".nav-cont").eq($(this).data("index")).addClass("active");
});
});
</script>
</head>
<body>
<div class="container">
<div id="content-header">
<a href="/"><img src="http://ww4.sinaimg.cn/large/69c1d4acgw1ebfly5kjlij208202oglr.jpg"></a>
<a href="/"><img src="{{logoURL}}"></a>
<input type="text" id="search-input" class="form-control" placeholder="Search Packages">
</div>
<div id="content-body"><%- locals.body %></div>
<div class="bottom">
<hr/>
<p>
Copyright 2013 - 2014 &copy; cnpmjs.org
|
<a href="/">Home</a>
|
<script>var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_5757157'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s17.cnzz.com/stat.php%3Fid%3D5757157%26online%3D1%26show%3Dline' type='text/javascript'%3E%3C/script%3E"));</script>
{{footer}}
</p>
<a href="https://github.com/fengmk2/cnpmjs.org" id="fork" target="_blank">
<a href="https://github.com/cnpm/cnpmjs.org" id="fork" target="_blank">
<img alt="Fork me on GitHub" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
</a>
</div>

View File

@@ -1,7 +1,13 @@
<div id="package">
<h1>
<%= package.name %>
<small>(<a href="/sync/<%= package.name %>" target="_blank">SYNC</a> missed versions from official npm registry)</small>
<small>
<% if (package._publish_on_cnpm) { %>
(Private package)
<% } else { %>
(<a href="/sync/<%= package.name %>" target="_blank">SYNC</a> missed versions from official npm registry)
<% } %>
</small>
</h1>
<% if (package.deprecated) { %>
@@ -12,175 +18,243 @@
<p class="description"><%= package.description %></p>
<% } %>
<pre class="sh"><code>$ cnpm install <%= package.name %></code></pre>
<table class="downloads">
<tbody>
<tr>
<td class="count"><%- download.today %></td><td> downloads today</td>
<td class="count"><%- download.thisweek %></td><td> downloads in this week</td>
<td class="count"><%- download.thismonth %></td><td> downloads in this month</td>
</tr>
<tr>
<td class="count"><%- download.lastday %></td><td> downloads in the last day</td>
<td class="count"><%- download.lastweek %></td><td> downloads in the last week</td>
<td class="count"><%- download.lastmonth %></td><td> downloads in the last month</td>
</tr>
</tbody>
</table>
<table class="metadata">
<% if (package.maintainers) { %>
<tr>
<th>Maintainers</th>
<td>
<% package.maintainers.forEach(function (m) { %>
<div class="user">
<a class="username" href="/~<%= m.name %>">
<% if (m.gravatar) { %>
<img src="<%- m.gravatar %>" class="avatar">
<% } %>
<%= m.name %>
</a>
</div>
<% }) %>
</td>
</tr>
<% } %>
<tr>
<th>Version</th>
<td>
<b>
<%= package.version %>
</b>
<% if (package.fromNow) { %>
last updated
<%= package.fromNow %>
<% } %>
</td>
</tr>
<% if (package.license) { %>
<tr>
<th>License</th>
<td>
<a href="<%= package.license.url %>" target="_blank"><%= package.license.name %></a>
</td>
</tr>
<% } %>
<%
if (typeof package.keywords === 'string') {
package.keywords = package.keywords.split(/\s*,?\s*/)
}
if (Array.isArray(package.keywords)) {
%>
<tr>
<th>Keywords</th>
<td><%-
package.keywords.map(function (kw) {
kw = kw.replace(/</g, '&lt;').replace(/"/g, '&quot;')
return '<a href="/browse/keyword/' + kw + '">' + kw + '</a>'
}).join(', ')
%></td>
</tr>
<% } %>
<% if (package.repository && package.repository !== 'undefined') {
var gh = package.repository.url &&
package.repository.url.match(
/^(?:https?:\/\/|git(?::\/\/|@))(gist.github.com|github.com)[:\/](.*?)(?:.git)?$/)
if (gh) {
gh = 'https://' + gh[1] + '/' + gh[2]
}
%>
<tr>
<th>Repository</td>
<td>
<% if (gh) { %><a href="<%= gh %>" target="_blank"><% } %>
<%= package.repository.url %><% if (gh) { %></a><% } %>
(<%= package.repository.type %>)
</td>
</tr>
<% } %>
<% if (typeof package.homepage === 'string') { %>
<tr>
<th>Homepage</td>
<td>
<a href="<%= encodeURI(package.homepage) %>" target="_blank"><%= package.homepage.replace(/</g, '&lt;') %></a>
</td>
</tr>
<% } %>
<% if (package.bugs && package.bugs.url) { %>
<tr>
<th>Bugs</td>
<td>
<a href="<%= package.bugs.url %>" target="_blank"><%= package.bugs.url %></a>
</td>
</tr>
<% } %>
<tr>
<%
var deps = Object.keys(package.dependencies || {})
var l = deps.length
%>
<th>Dependencies<%= l > 5 ? ' (' + l + ')' : '' %></th>
<td>
<%
if (l === 0) {
%>None<%
} else {
var m = 200
if (l > m) deps = deps.slice(0, m)
deps.forEach(function(pkg, i) {
if (i > 0) { %>, <% }
%>
<a href="/package/<%= pkg %>"><%= pkg %></a><%
})
if (l > m) {
%>, and <%= l-m %> more<%
}
}
%>
</td>
</tr>
<% if (package.users) {
var starredBy = package.starredBy
var l = starredBy.length
%>
<tr>
<th>Starred by<%= l > 5 ? ' (' + l + ')' : '' %></th>
<td>
<%
var max = 20
if (l > max) {
starredBy = starredBy.sort(function (a, b) {
return Math.random() * 2 - 1
}).slice(0, max)
}
starredBy.forEach(function (user, i) {
if (i > 0) { %>, <% }
%><a href="/~<%= user %>"><%= user %></a><%
})
if (l > max) {
%><br><a href="/browse/star/<%= package.name %>">and
<%= (l-max) %> more</a><%
}
%>
</td>
</tr>
<% } %>
</table>
<div class="details">
<ul class="toc">
<% if (package.readme) { %>
<li><a href="#readme">Read Me</a></li>
<% } %>
</ul>
<pre class="sh"><code>$ <%- config.npmClientName %> install <%= package.name %> <% if (package.preferGlobal) { %>-g<% } %></code></pre>
<ul class="nav nav-tabs">
<% if (package.readme) { %>
<section id="readme">
<%- package.readme %>
</section>
<li class="active"><a href="#readme">Readme.md</a></li>
<li><a href="#meta">package.json</a></li>
<% } else { %>
<li class="active"><a href="#meta">package.json</a></li>
<% } %>
</ul>
<% if (package.readme) { %>
<div class="nav-cont active">
<div class="details">
<section id="readme">
<%- package.readme %>
</section>
</div>
</div>
<% } %>
<div class="nav-cont">
<table class="downloads">
<tbody>
<tr>
<td class="count"><%- download.today %></td><td> downloads today</td>
<td class="count"><%- download.thisweek %></td><td> downloads in this week</td>
<td class="count"><%- download.thismonth %></td><td> downloads in this month</td>
</tr>
<tr>
<td class="count"><%- download.lastday %></td><td> downloads in the last day</td>
<td class="count"><%- download.lastweek %></td><td> downloads in the last week</td>
<td class="count"><%- download.lastmonth %></td><td> downloads in the last month</td>
</tr>
</tbody>
</table>
<table class="metadata">
<% if (Array.isArray(package.maintainers) && package.maintainers.length > 0) { %>
<tr>
<th>Maintainers</th>
<td>
<% package.maintainers.forEach(function (m) { %>
<span class="user">
<a class="username" href="/~<%= m.name %>">
<% if (m.gravatar) { %>
<img src="<%- m.gravatar %>" class="avatar">
<% } %>
<%= m.name %>
</a>
</span>
<% }) %>
</td>
</tr>
<% } %>
<tr>
<th>Version</th>
<td>
<b>
<%= package.version %>
</b>
<% if (package.fromNow) { %>
last updated
<%= package.fromNow %>
<% } %>
</td>
</tr>
<% if (package.engines) {%>
<tr>
<th>Engines</th>
<td>
<ul>
<% for (var k in package.engines) { %>
<li><%= k %>: <%= package.engines[k] %></li>
<% } %>
</ul>
</td>
</tr>
<% } %>
<% if (package.license) { %>
<tr>
<th>License</th>
<td>
<a href="<%= package.license.url %>" target="_blank"><%= package.license.name %></a>
</td>
</tr>
<% } %>
<%
if (typeof package.keywords === 'string') {
package.keywords = package.keywords.split(/\s*,?\s*/)
}
if (Array.isArray(package.keywords)) {
%>
<tr>
<th>Keywords</th>
<td><%-
package.keywords.map(function (kw) {
kw = kw.replace(/</g, '&lt;').replace(/"/g, '&quot;')
return '<a href="/browse/keyword/' + kw + '">' + kw + '</a>'
}).join(', ')
%></td>
</tr>
<% } %>
<% if (package.repository && package.repository !== 'undefined') { %>
<tr>
<th>Repository</td>
<td>
<a href="<%= package.repository.weburl || package.repository.url %>" target="_blank">
<%= package.repository.url %>
</a>
(<%= package.repository.type %>)
</td>
</tr>
<% } %>
<% if (typeof package.homepage === 'string') { %>
<tr>
<th>Homepage</td>
<td>
<a href="<%= encodeURI(package.homepage) %>" target="_blank"><%= package.homepage.replace(/</g, '&lt;') %></a>
</td>
</tr>
<% } %>
<% if (package.bugs && package.bugs.url) { %>
<tr>
<th>Bugs</td>
<td>
<a href="<%= package.bugs.url %>" target="_blank"><%= package.bugs.url %></a>
</td>
</tr>
<% } %>
<tr>
<%
var deps = Object.keys(package.dependencies || {})
var l = deps.length
%>
<th>Dependencies<%= l > 0 ? ' (' + l + ')' : '' %></th>
<td>
<%
if (l === 0) {
%>None<%
} else {
var m = 200
if (l > m) deps = deps.slice(0, m)
deps.forEach(function(pkg, i) {
if (i > 0) { %>, <% }
%>
<a href="/package/<%= pkg %>"><%= pkg %></a><%
})
if (l > m) {
%>, and <%= l-m %> more<%
}
}
%>
</td>
</tr>
<tr>
<%
var deps = package.dependents || [];
var l = deps.length
%>
<th>Dependents<%= l > 0 ? ' (' + l + ')' : '' %></th>
<td>
<%
if (l === 0) {
%>None<%
} else {
var m = 200
if (l > m) deps = deps.slice(0, m)
deps.forEach(function(pkg, i) {
if (i > 0) { %>, <% }
%>
<a href="/package/<%= pkg %>"><%= pkg %></a><%
})
if (l > m) {
%>, and <%= l-m %> more<%
}
}
%>
</td>
</tr>
<% if (package.users.length > 0) {
var users = package.users
var l = users.length
%>
<tr>
<th>Starred by (<%= l %>)</th>
<td>
<%
var max = 20
if (l > max) {
users = users.sort(function (a, b) {
return Math.random() * 2 - 1
}).slice(0, max)
}
users.forEach(function (user, i) {
if (i > 0) { %>, <% }
%><a href="/~<%= user %>"><%= user %></a><%
})
if (l > max) {
%><br><a href="/browse/star/<%= package.name %>">and
<%= (l - max) %> more</a><%
}
%>
</td>
</tr>
<% } %>
<% if (Array.isArray(package.contributors) && package.contributors.length > 0) { %>
<tr>
<th>Contributors</th>
<td>
<% package.contributors.forEach(function (m) { %>
<span class="user">
<a class="username" target="_blank" href="<%= m.url %>">
<% if (m.gravatar) { %>
<img src="<%- m.gravatar %>" class="avatar">
<% } %>
<%= m.name %>
</a>
</span>
<% }) %>
</td>
</tr>
<% } %>
<% if (package.dist && package.dist.tarball) { %>
<tr>
<th>Download</th>
<td>
<a class="downloadlink" target="_blank" href="<%= package.dist.tarball %>">
<%= package.dist.tarball %>
</a>
</td>
</tr>
<% } %>
</table>
</div>
</div>

View File

@@ -16,26 +16,52 @@
<div id="search">
<% if (!packages.length) { %>
<div class="alert alert-warning">
Can not found package match <%= keyword %>. You can
Can not found package match <%= keyword %>. You can
<a href="/sync/<%= keyword %>" target="_blank">SYNC</a> from official npm registry or
<a href="https://npmjs.org/search?q=<%= keyword %>" target="_blank">SEARCH</a> in official npm website.
</div>
<% } else if (packages[0].name !== keyword) { %>
<% } else if (!match) { %>
<div class="alert alert-info">
Can not found package <%= keyword %>. You can
Can not found package <%= keyword %>. You can
<a href="/sync/<%= keyword %>" target="_blank">SYNC</a> from official npm registry or
<a href="https://npmjs.org/search?q=<%= keyword %>" target="_blank">SEARCH</a> in official npm website.
</div>
</div>
<% } %>
<% if (packages.length) { %>
<h1>
Packages match <span style="color: #09f;"><%= keyword %></span>
Packages match "<span style="color: #09f;"><%= keyword %></span>"
</h1>
<hr />
<% for (var i = 0; i < packages.length; i++) { %>
<div class="package <%= packages[i].name === keyword ? 'match' : '' %>">
<a href="/package/<%= packages[i].name %>" class="package-name"><%= packages[i].name %></a>
<span class="package-description"><%= packages[i].description %></span>
<% if (match) { %>
<div class="package match">
<a href="/package/<%= match.name %>" class="package-name"><%= match.name %></a>
<span class="package-description"><%= match.description %></span>
</div>
<% } %>
<% for (var i = 0; i < packages.length; i++) {
var item = packages[i];
if (item.name === keyword) {
continue;
}
%>
<div class="package">
<a href="/package/<%= item.name %>" class="package-name"><%= item.name %></a>
<span class="package-description"><%= item.description %></span>
</div>
<% } %>
<% } %>
<% if (keywords.length) { %>
<h1>
Keywords match "<span style="color: #09f;"><%= keyword %></span>"
</h1>
<hr />
<% for (var i = 0; i < keywords.length; i++) {
var item = keywords[i];
%>
<div class="package <%= item.name === keyword ? 'match' : '' %>">
<a href="/package/<%= item.name %>" class="package-name"><%= item.name %></a>
<span class="package-description"><%= item.description %></span>
</div>
<% } %>
<% } %>

View File

@@ -11,6 +11,7 @@
var $notify = $('#sync-notify');
var timer;
var name = '<%= name %>';
var resourceURL = '/sync/' + name;
$(function() {
var checkLogId = location.hash.match(/logid=(\d+)/);
var logid = checkLogId ? checkLogId[1] : '';
@@ -18,7 +19,7 @@
return getSyncLog(logid);
}
$.ajax({
url: location.pathname,
url: resourceURL,
type: 'PUT',
dataType: 'json',
success: handleSyncSucess,
@@ -53,7 +54,7 @@
var hasFail = false;
function getSyncLog(id) {
$.ajax({
url: location.pathname + '/log/' + id + '?offset=' + offset,
url: resourceURL + '/log/' + id + '?offset=' + offset,
type: 'GET',
dataType: 'json',
success: function (data) {

View File

@@ -1,4 +1,4 @@
/*!
/**!
* cnpmjs.org - worker.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -16,7 +16,7 @@
*/
var graceful = require('graceful');
var logger = require('./common/logger');
var config = require('./config');
var registry = require('./servers/registry');
var web = require('./servers/web');
@@ -34,6 +34,7 @@ graceful({
if (err.message) {
err.message += ' (uncaughtException throw ' + throwErrorCount + ' times on pid:' + process.pid + ')';
}
console.error(err);
console.error(err.stack);
logger.error(err);
}
});