Compare commits

...

110 Commits

Author SHA1 Message Date
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
62 changed files with 2348 additions and 1439 deletions

3
.gitignore vendored
View File

@@ -20,3 +20,6 @@ backup/*.json
backup/*.gz
docs/web/history.md
view/web/_layout.html
bin/mysql.js
bin/test.sql
coverage/

View File

@@ -9,3 +9,5 @@ public/dist/
backup/*.json
backup/*.gz
view/web/_layout.html
bin/mysql.js
coverage/

View File

@@ -1,5 +1,3 @@
language: node_js
node_js:
- '0.10'
install: make install
script: make test-coveralls
- '0.11'

View File

@@ -1,7 +1,8 @@
# Ordered by date of first contribution.
# Auto-generated by 'contributors' on Fri, 10 Jan 2014 02:56:32 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,144 @@
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
==================

View File

@@ -4,34 +4,33 @@ TIMEOUT = 30000
MOCHA_OPTS =
install:
@npm install --registry=http://registry.cnpmjs.org --cache=${HOME}/.npm/.cache/cnpm --disturl=http://dist.u.qiniudn.com
@npm install --registry=http://registry.cnpmjs.org \
--cache=${HOME}/.npm/.cache/cnpm --disturl=http://dist.u.qiniudn.com
test: install
@NODE_ENV=test ./node_modules/mocha/bin/mocha \
test:
@NODE_ENV=test node --harmony \
node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha \
-- -u exports \
--reporter $(REPORTER) \
--timeout $(TIMEOUT) \
--require should \
$(MOCHA_OPTS) \
$(TESTS)
@-$(MAKE) check-coverage
test-cov:
@$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=travis-cov
check-coverage:
@./node_modules/.bin/istanbul check-coverage \
--statements 100 \
--functions 100 \
--branches 100 \
--lines 100
test-cov-html:
@rm -f coverage.html
@$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=html-cov > coverage.html
@ls -lh coverage.html
cov:
@./node_modules/.bin/cov coverage
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
contributors:
@./node_modules/.bin/contributors -f plain -o AUTHORS
test-all: test test-cov
contributors: install
@./node_modules/contributors/bin/contributors -f plain -o AUTHORS
autod: install
autod:
@./node_modules/.bin/autod -w -e public,view,docs,backup
@$(MAKE) install

View File

@@ -1,7 +1,7 @@
cnpmjs.org
=======
[![Build Status](https://secure.travis-ci.org/cnpm/cnpmjs.org.png)](http://travis-ci.org/cnpm/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/cnpm/cnpmjs.org/badge.png)](https://coveralls.io/r/cnpm/cnpmjs.org)[![Dependency Status](https://gemnasium.com/cnpm/cnpmjs.org.png)](https://gemnasium.com/cnpm/cnpmjs.org)
[![Build Status](https://secure.travis-ci.org/cnpm/cnpmjs.org.png)](http://travis-ci.org/cnpm/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/cnpm/cnpmjs.org/badge.png)](https://coveralls.io/r/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/)
@@ -9,7 +9,7 @@ cnpmjs.org
## What is this?
Private npm registry and web for Enterprise, base on MySQL and [Simple Store Service](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide).
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).
@@ -19,29 +19,35 @@ Private npm registry and web for Enterprise, base on MySQL and [Simple Store Ser
## Install
```bash
$ npm install
$ npm install --registry=http://r.cnpmjs.org --disturl=http://dist.u.qiniudn.com
```
## Usage
```js
$ node dispatch.js
$ node --harmony-generators dispatch.js
```
## Guide
* [How to deploy cnpmjs.org](https://github.com/cnpm/cnpmjs.org/wiki/Deploy)
* [NFS guide](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide)
## Authors
```bash
$ git summary
project : cnpmjs.org
repo age : 5 weeks
active : 98 days
commits : 239
files : 85
repo age : 3 months
active : 145 days
commits : 366
files : 94
authors :
140 fengmk2 58.6%
98 dead_horse 41.0%
1 Alsotang 0.4%
217 fengmk2 59.3%
146 dead_horse 39.9%
2 4simple 0.5%
1 Alsotang 0.3%
```
## 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

@@ -35,7 +35,9 @@ var pool = mysql.createPool({
exports.pool = pool;
exports.query = function (sql, values, cb) {
pool.query(sql, values, cb);
pool.query(sql, values, function (err, rows) {
cb(err, rows);
});
};
exports.queryOne = function (sql, values, cb) {

View File

@@ -14,9 +14,9 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var qn = require('qn');
var config = require('../config');
var client = qn.create(config.qn);
exports._client = client;
@@ -61,3 +61,5 @@ exports.url = function (key) {
exports.remove = function (key, callback) {
client.delete(key, callback);
};
thunkify(exports);

View File

@@ -15,27 +15,18 @@
* 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,
};
if (config.debug) {
session = connect.cookieSession({
secret: config.sessionSecret,
key: key,
cookie: cookie
});
} else {
session = connect.session({
key: key,
secret: config.sessionSecret,
store: config.sessionStore || 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

@@ -51,6 +51,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: {
@@ -71,6 +72,7 @@ var config = {
debug: false
},
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`
@@ -78,8 +80,8 @@ var config = {
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',
},
@@ -87,6 +89,7 @@ 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
};
// load config/config.js, everything in config.js will cover the same key in index.js

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.
@@ -18,30 +18,24 @@
var debug = require('debug')('cnpmjs.org:controllers:registry');
var logger = require('../../common/logger');
var User = require('../../proxy/user');
var eventproxy = require('eventproxy');
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();
}
res.setHeader('etag', '"' + row.rev + '"');
var data = {
_id: 'org.couchdb.user:' + row.name,
_rev: row.rev,
name: row.name,
email: row.email,
type: 'user',
roles: [],
date: row.gmt_modified,
};
res.json(data);
});
exports.show = function *(next) {
var name = this.params.name;
var user = yield User.get(name);
if (!user) {
return yield next;
}
this.etag = '"' + user.rev + '"';
var data = {
_id: 'org.couchdb.user:' + user.name,
_rev: user.rev,
name: user.name,
email: user.email,
type: 'user',
roles: [],
date: user.gmt_modified,
};
this.body = data;
};
// json:
@@ -53,110 +47,109 @@ exports.show = function (req, res, next) {
// 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 || {};
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 || [],
};
if (!user.name || !user.salt || !user.password_sha || !user.email) {
return res.json(422, {
this.status = 422;
this.body = {
error: 'paramError',
reason: 'params 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: 'Document update conflict.'
};
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;
}
this.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, session.name: %s', name, rev, req.session.name);
debug('update: %s, rev: %s, session.name: %s', name, rev, this.session.name);
if (name !== req.session.name) {
if (name !== this.session.name) {
// must authSession first
res.statusCode = 401;
return res.json({
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
});
});
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

@@ -17,48 +17,60 @@
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;
var publish = req.query.publish === 'true';
var noDep = req.query.nodeps === 'true';
if (publish && !req.session.isAdmin) {
return res.json(403, {
exports.sync = function *() {
var username = this.session.name || 'anonymous';
var name = this.params.name;
var publish = this.query.publish === 'true';
var noDep = this.query.nodeps === 'true';
if (publish && !this.session.isAdmin) {
this.status = 403;
this.body = {
error: 'no_perms',
reason: 'Only admin can publish'
});
};
return;
}
var options = {
publish: publish,
noDep: noDep,
};
SyncModuleWorker.sync(name, username, options, 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
});
});
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 name = this.params.name;
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;
};

View File

@@ -14,6 +14,7 @@
* Module dependencies.
*/
var giturl = require('giturl');
var moment = require('moment');
var eventproxy = require('eventproxy');
var semver = require('semver');
@@ -25,129 +26,148 @@ 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;
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)
];
var pkg = r[0];
var download = r[1];
var dependents = (r[2] || []).map(function (item) {
return item.deps;
});
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.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);
}
}
}
if (pkg.contributors) {
// registry.cnpmjs.org/compressible
if (!Array.isArray(pkg.contributors)) {
pkg.contributors = [pkg.contributors];
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);
}
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);
}
if (config.packagePageContributorSearch || !contributor.url) {
contributor.url = '/~' + encodeURIComponent(contributor.name);
}
}
}
setLicense(pkg);
if (pkg.repository && pkg.repository.url) {
pkg.repository.weburl = giturl.parse(pkg.repository.url) || pkg.repository.url;
}
for (var k in download) {
download[k] = humanize(download[k]);
}
setLicense(pkg);
setDownloadURL(pkg, req, config.registryHost);
for (var k in download) {
download[k] = humanize(download[k]);
}
setDownloadURL(pkg, this, config.registryHost);
res.render('package', {
title: 'Package - ' + pkg.name,
package: pkg,
download: download
});
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);
}
res.render('search', {
title: 'Keyword - ' + word,
exports.search = function *(next) {
var params = this.params;
var word = params.word;
var result = yield Module.search(word);
// return a json result
if (this.query && this.query.type === 'json') {
this.body = {
keyword: word,
packages: packages || []
});
packages: result.searchMatchs,
keywords: result.keywordMatchs
};
this.charset = 'utf-8';
return;
}
yield this.render('search', {
title: 'Keyword - ' + word,
keyword: word,
packages: result.searchMatchs,
keywords: result.keywordMatchs,
});
};
exports.rangeSearch = function (req, res, next) {
var startKey = req.query.startkey || '';
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(req.query.limit) || 20;
Module.search(startKey, {limit: limit}, function (err, packages) {
if (err) {
return next(err);
}
var limit = Number(this.query.limit) || 20;
var result = yield Module.search(startKey, {limit: limit});
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);
}
res.json({
rows: rows
});
});
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 (req, res, next) {
var name = req.params.name || req.query.name;
res.render('sync', {
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

@@ -14,6 +14,17 @@ CREATE TABLE `user` (
KEY `gmt_modified` (`gmt_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='user base info';
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` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`gmt_create` datetime NOT NULL COMMENT 'create time',
@@ -101,3 +112,13 @@ CREATE TABLE `download_total` (
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

@@ -6,7 +6,7 @@
## What is this?
> Private npm registry and web for Enterprise, base on MySQL and [Simple Store Service](https://github.com/cnpm/cnpmjs.org/wiki/NFS-Guide).
> 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).
@@ -139,7 +139,7 @@ $ npm install cnpm -g
### install
Install package from [r.cnpmjs.org](http://r.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]
@@ -188,14 +188,15 @@ Release [History](/history).
$ git summary
project : cnpmjs.org
repo age : 5 weeks
active : 98 days
commits : 239
files : 85
repo age : 7 weeks
active : 132 days
commits : 315
files : 88
authors :
140 fengmk2 58.6%
98 dead_horse 41.0%
1 Alsotang 0.4%
190 fengmk2 60.3%
122 dead_horse 38.7%
2 4simple 0.6%
1 Alsotang 0.3%
```
## npm and cnpm relation

View File

@@ -29,11 +29,11 @@ exports.getCDNKey = function (name, filename) {
return '/' + name + '/-/' + filename;
};
exports.setDownloadURL = function (pkg, req, host) {
exports.setDownloadURL = function (pkg, ctx, host) {
if (pkg.dist) {
host = host || req.headers.host;
host = host || ctx.get('host') || ctx.host;
pkg.dist.tarball = util.format('%s://%s/%s/download/%s-%s.tgz',
req.connection.encrypted ? 'https' : 'http',
ctx.protocol,
host, pkg.name, pkg.name, pkg.version);
}
};
@@ -41,3 +41,17 @@ exports.setDownloadURL = function (pkg, req, host) {
exports.isAdmin = function (username) {
return typeof config.admins[username] === 'string';
};
exports.isMaintainer = function (ctx, maintainers) {
if (ctx.session.isAdmin) {
return true;
}
var username = ctx.session.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.
@@ -20,41 +20,46 @@ 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) {
req.session.isAdmin = common.isAdmin(req.session.name);
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) {
debug('%s, %s, %j', this.url, this.sessionId, this.session);
if (!this.session) {
// redis crash
this.session = {};
return yield next;
}
var authorization = (req.headers.authorization || '').split(' ')[1] || '';
this.session.onlySync = config.enablePrivate ? true : false;
if (this.session.name) {
this.session.isAdmin = common.isAdmin(this.session.name);
debug('auth exists user: %s, onlySync: %s, isAdmin: %s, headers: %j',
this.session.name, this.session.onlySync, this.session.isAdmin, this.header);
return yield next;
}
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);
this.session.name = null;
this.session.isAdmin = false;
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;
req.session.isAdmin = common.isAdmin(req.session.name);
debug('auth pass user: %j, onlySync: %s, isAdmin: %s, headers: %j',
row, req.session.onlySync, req.session.isAdmin, req.headers);
next();
});
this.session.name = row.name;
this.session.isAdmin = common.isAdmin(this.session.name);
debug('auth pass user: %j, onlySync: %s, isAdmin: %s, headers: %j',
row, this.session.onlySync, this.session.isAdmin, this.header);
yield next;
};
};

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.session.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,15 @@
* Module dependencies.
*/
module.exports = function publishable(req, res, next) {
if (req.session.onlySync && !req.session.isAdmin) {
module.exports = function *publishable(next) {
if (this.session.onlySync && !this.session.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

@@ -21,25 +21,25 @@ var config = require('../config');
* req.session.allowSync - allow sync triggle by cnpm install
*/
module.exports = function (req, res, next) {
module.exports = function *(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();
if (this.get('user-agent') && this.get('user-agent').indexOf('node') !== 0) {
return yield next;
}
req.session.allowSync = true;
if (req.session.isAdmin) {
this.session.allowSync = true;
if (this.session.isAdmin) {
// if current user is admin, should not enable auto sync on install, because it would be unpublish
req.session.allowSync = false;
this.session.allowSync = false;
}
// TODO: allow sync will let publish sync package...
req.session.allowSync = false;
this.session.allowSync = false;
debug('%s allowSync: %s', req.session.name, req.session.allowSync);
next();
debug('%s allowSync: %s', this.session.name, this.session.allowSync);
yield next;
};

View File

@@ -0,0 +1,27 @@
/**!
* 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;
}
this.status = 404;
this.type = 'text/html';
this.charset = 'utf-8';
this.body = 'Cannot GET ' + this.path;
};

View File

@@ -1,68 +1,58 @@
{
"name": "cnpmjs.org",
"version": "0.2.14",
"version": "0.3.4",
"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 test",
"start": "./bin/nodejsctl start && cp History.md docs/web/history.md",
"status": "./bin/nodejsctl status",
"stop": "./bin/nodejsctl stop"
},
"config": {
"blanket": {
"pattern": "//^((?!(node_modules|test|common)).)*$/",
"data-cover-flags": {
"debug": false
}
},
"travis-cov": {
"threshold": 90
"cov": {
"threshold": 83
}
},
"dependencies": {
"bagpipe": "0.3.5",
"connect": "2.12.0",
"connect-markdown": "0.0.3",
"connect-redis": "1.4.6",
"connect-render": "0.3.2",
"connect-rt": "0.0.2",
"co-read": "0.0.1",
"co-write": "0.3.0",
"debug": "0.7.4",
"eventproxy": "0.2.6",
"eventproxy": "0.3.0",
"forward": "0.0.4",
"graceful": "0.0.5",
"giturl": "0.0.2",
"graceful": "0.0.6",
"gravatar": "1.0.6",
"humanize-number": "0.0.2",
"koa": "0.5.0",
"koa-markdown": "0.0.2",
"koa-middlewares": "0.0.8",
"logfilestream": "0.1.0",
"marked": "0.3.0",
"marked": "0.3.1",
"microtime": "0.5.1",
"mime": "1.2.11",
"mkdirp": "0.3.5",
"moment": "2.5.0",
"moment": "2.5.1",
"ms": "0.6.2",
"mysql": "2.0.0",
"nodemailer": "0.6.0",
"mysql": "2.1.0",
"nodemailer": "0.6.1",
"qn": "0.2.0",
"ready": "0.1.1",
"response-cookie": "0.0.2",
"response-patch": "0.1.1",
"semver": "2.2.1",
"thunkify-wrap": "0.0.3",
"urllib": "0.5.5",
"urlrouter": "0.5.4",
"utility": "0.1.10"
},
"devDependencies": {
"autod": ">=0.0.10",
"blanket": "*",
"autod": ">=0.0.13",
"contributors": "*",
"coveralls": "*",
"mm": "0.1.8",
"cov": "*",
"istanbul": "git://github.com/gotwarlost/istanbul.git#harmony",
"mm": "0.2.0",
"mocha": "*",
"mocha-lcov-reporter": "*",
"pedding": "0.0.3",
"should": "2.1.1",
"supertest": "0.8.3",
"travis-cov": "*"
"should": "3.1.3",
"supertest": "0.9.0"
},
"homepage": "https://github.com/cnpm/cnpmjs.org",
"repository": {
@@ -78,7 +68,7 @@
"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,6 +14,7 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var config = require('../config');
var mysql = require('../common/mysql');
@@ -41,3 +42,5 @@ var SELECT_ALL_TOTAL_SQL = 'SELECT date, sum(count) AS count \
exports.getTotal = function (start, end, callback) {
mysql.query(SELECT_ALL_TOTAL_SQL, [start, end], callback);
};
thunkify(exports);

View File

@@ -14,6 +14,7 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var utility = require('utility');
var eventproxy = require('eventproxy');
var config = require('../config');
@@ -29,6 +30,7 @@ var INSERT_MODULE_SQL = 'INSERT INTO module(gmt_create, gmt_modified, \
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,6 +53,74 @@ 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 GET_KEYWORD_SQL = '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 = '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);
});
};
@@ -59,7 +129,21 @@ 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 = '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 = 'UPDATE module SET publish_time=?, version=?, package=?, \
dist_tarball=?, dist_shasum=?, dist_size=? WHERE id=?;';
exports.update = function (mod, callback) {
var pkg;
@@ -69,7 +153,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,6 +176,7 @@ function parseRow(row) {
}
}
}
exports.parseRow = parseRow;
function stringifyPackage(pkg) {
return encodeURIComponent(JSON.stringify(pkg));
@@ -171,7 +257,12 @@ 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 = '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 = '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);
@@ -295,10 +386,10 @@ exports.listByAuthor = function (author, callback) {
});
};
var SEARCH_SQLS = [
'SELECT module_id FROM tag WHERE name LIKE ? AND tag="latest" ORDER BY name LIMIT ?;',
'SELECT name, description FROM module WHERE id IN (?) ORDER BY name;'
];
var SEARCH_MODULES_SQL = 'SELECT module_id FROM tag WHERE name LIKE ? AND tag="latest" ORDER BY name LIMIT ?;';
var SEARCH_MODULES_BY_KEYWORD_SQL = 'SELECT name, description FROM module_keyword WHERE keyword = ? ORDER BY id DESC LIMIT ?;';
var QUERY_MODULES_BY_ID_SQL = 'SELECT name, description FROM module WHERE id IN (?) ORDER BY name;';
exports.search = function (word, options, callback) {
if (typeof options === 'function') {
callback = options;
@@ -306,21 +397,58 @@ exports.search = function (word, options, callback) {
}
options = options || {};
var limit = options.limit || 100;
word = word.replace(/^%/, '') + '%'; //ignore prefix %
word = word.replace(/^%/, ''); //ignore prefix %
var ep = eventproxy.create();
ep.fail(callback);
mysql.query(SEARCH_SQLS[0], [word, limit], 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);

44
proxy/module_deps.js Normal file
View File

@@ -0,0 +1,44 @@
/**!
* 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 LIST_DEPS_SQL = 'SELECT deps FROM module_deps WHERE name=?;';
exports.list = function (name, callback) {
mysql.query(LIST_DEPS_SQL, [name], callback);
};
var INSERT_DEPS_SQL = '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 = '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,6 +14,7 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var mysql = require('../common/mysql');
var INSERT_LOG_SQL = 'INSERT INTO module_log(gmt_create, gmt_modified, name, username, log) \
@@ -40,3 +41,5 @@ var SELECT_SQL = 'SELECT * FROM module_log WHERE id=?;';
exports.get = function (id, callback) {
mysql.queryOne(SELECT_SQL, [id], callback);
};
thunkify(exports);

View File

@@ -14,6 +14,7 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var urllib = require('urllib');
var config = require('../config');
@@ -26,7 +27,16 @@ function request(url, options, callback) {
options.dataType = options.dataType || 'json';
options.timeout = options.timeout || 120000;
url = config.sourceNpmRegistry + url;
urllib.request(url, options, callback);
urllib.request(url, options, function (err, data, res) {
if (err) {
var statusCode = res && res.statusCode || -1;
if (err.name === 'JSONResponseFormatError' && statusCode >= 500) {
err.name = 'NPMServerError';
err.message = 'Status ' + statusCode + ', ' + (data && data.toString() || 'empty body');
}
}
callback(err, data, res);
});
}
exports.get = function (name, callback) {
@@ -55,3 +65,5 @@ exports.getShort = function (callback) {
timeout: 300000
}, callback);
};
thunkify(exports);

View File

@@ -15,6 +15,7 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var debug = require('debug')('cnpmjs.org:proxy:sync_module_worker');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
@@ -24,12 +25,14 @@ var crypto = require('crypto');
var eventproxy = require('eventproxy');
var urllib = require('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');
function SyncModuleWorker(options) {
EventEmitter.call(this);
@@ -194,6 +197,8 @@ SyncModuleWorker.prototype._sync = function (name, pkg, callback) {
var missingVersions = [];
var missingTags = [];
var missingDescriptions = [];
var missingReadmes = [];
ep.all('existsMap', 'existsTags', function (map, tags) {
var times = pkg.time || {};
pkg.versions = pkg.versions || {};
@@ -239,7 +244,10 @@ SyncModuleWorker.prototype._sync = function (name, pkg, callback) {
if (!version || !version.dist) {
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]) {
@@ -261,6 +269,14 @@ SyncModuleWorker.prototype._sync = function (name, pkg, callback) {
description: version.description
});
}
if (!exists.package.readme && version.readme) {
// * make sure readme exists
missingReadmes.push({
id: exists.id,
readme: version.readme
});
}
continue;
}
}
@@ -317,10 +333,11 @@ SyncModuleWorker.prototype._sync = function (name, pkg, callback) {
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(' save error, id %s, description: %s, error: %s', item.id, item.description, err);
} else {
that.log(' saved, id: %s, description length: %d', item.id, item.description.length);
}
that.log(' saved, id: %s, description length: %d', item.id, item.description.length);
ep.emit('saveDescription');
ep.emitLater('saveDescription');
});
});
@@ -339,7 +356,7 @@ SyncModuleWorker.prototype._sync = function (name, pkg, callback) {
missingTags.forEach(function (item) {
Module.addTag(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');
ep.emitLater('addTag');
}));
});
@@ -348,7 +365,29 @@ SyncModuleWorker.prototype._sync = function (name, pkg, callback) {
});
});
ep.all('tagDone', 'descriptionDone', function () {
ep.once('syncDone', function () {
if (missingReadmes.length === 0) {
return ep.emit('readmeDone');
}
that.log(' [%s] saving %d readmes', name, missingReadmes.length);
missingReadmes.forEach(function (item) {
Module.updateReadme(item.id, item.readme, function (err, result) {
if (err) {
that.log(' save error, id: %s, error: %s', item.id, err);
} else {
that.log(' saved, id: %s', item.id);
}
ep.emitLater('saveReadme');
});
});
ep.after('saveReadme', missingReadmes.length, function () {
ep.emit('readmeDone');
});
});
ep.all('tagDone', 'descriptionDone', 'readmeDone', function () {
// TODO: set latest version
callback(null, versionNames);
});
@@ -373,19 +412,37 @@ SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePacka
callback(err);
});
that.log(' [%s:%d] syncing, version: %s, dist: %j, no deps: %s, publish on cnpm: %s',
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);
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 deps relations
dependencies.forEach(function (depName) {
ModuleDeps.add(depName, sourcePackage.name, utility.noop);
});
var shasum = crypto.createHash('sha1');
var dataSize = 0;
urllib.request(downurl, options, ep.done(function (_, response) {
@@ -518,3 +575,5 @@ SyncModuleWorker.sync = function (name, username, options, callback) {
});
});
};
SyncModuleWorker.sync = thunkify(SyncModuleWorker.sync);

View File

@@ -14,9 +14,10 @@
* 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 DB_SIZE_SQL = 'SELECT TABLE_NAME AS name, data_length, index_length \
FROM information_schema.tables \
@@ -118,3 +119,5 @@ exports.updateSyncNum = function (params, callback) {
];
mysql.query(UPDATE_SYNC_NUM_SQL, query, callback);
};
thunkify(exports);

View File

@@ -14,6 +14,7 @@
* Module dependencies.
*/
var thunkify = require('thunkify-wrap');
var utility = require('utility');
var config = require('../config');
var mysql = require('../common/mysql');
@@ -29,7 +30,6 @@ function sha1(s) {
function passwordSha(password, salt) {
return sha1(password + salt);
}
exports.passwordSha = passwordSha;
exports.get = function (name, callback) {
mysql.queryOne(SELECT_USER_SQL, [name], function (err, row) {
@@ -105,3 +105,5 @@ exports.update = function (user, callback) {
});
};
thunkify(exports);
exports.passwordSha = passwordSha;

View File

@@ -15,6 +15,7 @@
* Module dependencies.
*/
var middlewares = require('koa-middlewares');
var login = require('../middleware/login');
var publishable = require('../middleware/publishable');
var syncByInstall = require('../middleware/sync_by_install');
@@ -24,7 +25,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,10 +35,10 @@ 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);
@@ -47,23 +48,22 @@ function routes(app) {
// 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.removeWithVersions);
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 - servers/registry.js
*
* Copyright(c) cnpmjs.org and other contributors.
@@ -15,69 +15,55 @@
* 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 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(rt({headerName: 'X-ReadTime'}));
app.use(function (req, res, next) {
res.req = req;
next();
});
app.use(middlewares.rt({headerName: 'X-ReadTime'}));
app.use('/favicon.ico', forward(path.join(rootdir, 'public', 'favicon.png')));
app.use(middlewares.rewrite('/favicon.ico', '/public/favicon.ico'));
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.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;
app.on('error', function (err, ctx) {
err.url = err.url || ctx.request.url;
logger.error(err);
if (process.env.NODE_ENV !== 'test') {
console.error(err.stack);
}
if (config.debug) {
return next(err);
}
res.json(500, {
error: err.name,
reason: err.message
});
});
app = http.createServer(app);
app = http.createServer(app.callback());
if (!module.parent) {
app.listen(config.registryPort);
}
module.exports = app;

View File

@@ -15,47 +15,52 @@
* Module dependencies.
*/
require('response-patch');
var path = require('path');
var http = require('http');
var fs = require('fs');
var connect = require('connect');
var rt = require('connect-rt');
var urlrouter = require('urlrouter');
var connectMarkdown = require('connect-markdown');
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 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'}));
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.use(session);
app.use(connect.query());
app.use(connect.json());
app.use(middlewares.bodyParser());
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');
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);
var layout = fs.readFileSync(path.join(viewDir, 'layout.html'), 'utf8')
.replace('{{footer}}', footer)
.replace('{{logoURL}}', config.logoURL);
fs.writeFileSync(layoutFile, layout);
app.use('/', connectMarkdown({
app.use(markdown({
baseUrl: '/',
root: docDir,
layout: layoutFile,
titleHolder: '<%- locals.title %>',
@@ -63,41 +68,38 @@ app.use('/', connectMarkdown({
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;
app.on('error', function (err, ctx) {
err.url = err.url || ctx.request.url;
logger.error(err);
if (process.env.NODE_ENV !== 'test') {
console.error(err.stack);
}
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

@@ -106,7 +106,7 @@ function getMissPackages(callback) {
}
//only sync not exist once
var syncNotExist = true;
var syncNotExist = false;
module.exports = function sync(callback) {
var ep = eventproxy.create();
ep.fail(callback);

View File

@@ -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,25 +66,52 @@ 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(200, function (err, res) {
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');
// res.body.author.should.eql({
// "name": "fengmk2",
// "email": "fengmk2@gmail.com",
// "url": "http://fengmk2.github.com"
// });
res.body.name.should.equal('cnpmjs.org');
res.body.versions[Object.keys(res.body.versions)[0]].dist.tarball.should.include('/cnpmjs.org/download');
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');
res.body.name.should.equal('cnpmjs.org');
res.body.versions[Object.keys(res.body.versions)[0]].dist.tarball.should.include('/cnpmjs.org/download');
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 () {
@@ -96,6 +125,9 @@ describe('controllers/registry/module.test.js', function () {
body.version.should.equal('0.2.1');
body._id.should.equal('cnpmjs.org@0.2.1');
body.dist.tarball.should.include('cnpmjs.org-0.2.1.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();
});
});
@@ -137,6 +169,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');
@@ -284,6 +323,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)
@@ -291,7 +331,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();
});
});
});
@@ -339,6 +388,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 () {
@@ -435,6 +538,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,34 +557,6 @@ describe('controllers/registry/module.test.js', function () {
.expect('Location', 'http://qtestbucket.qiniudn.com/cutter/-/cutter-0.0.2.tgz')
.expect(302, done)
});
it('should download a file direct from nfs stream', function (done) {
var nfs = require('../../../common/nfs');
mm(nfs, 'downloadStream', function (key, writeStream, options, callback) {
options.timeout.should.equal(600000);
nfs._client.download(key, {writeStream: writeStream, timeout: options.timeout}, callback);
});
Module.__get__ = Module.get;
mm(Module, 'get', function (name, version, callback) {
Module.__get__(name, version, function (err, info) {
info.package.dist.key = 'cutter/-/cutter-0.0.2.tgz';
callback(err, info);
});
});
request(app)
.get('/cutter/download/cutter-0.0.2.tgz')
.expect('ETag', 'c61fde5e8c26d053574d0c722097029fd1bc963a')
.expect('Content-Type', 'application/octet-stream')
.expect('Content-Length', '3139')
.expect('Content-Disposition', 'attachment; filename="cutter-0.0.2.tgz"')
.expect(200)
.end(function (err, res) {
should.not.exist(err);
// TODO: why supertest change buffer to text?
// res.text.length.should.equal(3139);
done();
});
});
});
describe('DELETE /:name/download/:filename/-rev/:rev', function () {

View File

@@ -16,18 +16,22 @@
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)
@@ -47,7 +51,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 +95,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) {

View File

@@ -58,20 +58,21 @@ describe('controllers/sync.test.js', function () {
should.not.exist(err);
res.body.should.have.keys('ok', 'logId');
logIdRegistry = res.body.logId;
setTimeout(function () {
request(registryApp)
.get('/utility')
.expect(200)
.end(function (err, res) {
should.not.exist(err);
Object.keys(res.body.versions).length.should.above(0);
for (var v in res.body.versions) {
var pkg = res.body.versions[v];
pkg.should.have.property('_publish_on_cnpm', true);
}
done();
});
}, 3000);
done();
// setTimeout(function () {
// request(registryApp)
// .get('/utility')
// .expect(200)
// .end(function (err, res) {
// should.not.exist(err);
// Object.keys(res.body.versions).length.should.above(0);
// for (var v in res.body.versions) {
// var pkg = res.body.versions[v];
// pkg.should.have.property('_publish_on_cnpm', true);
// }
// done();
// });
// }, 5000);
});
});
});

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

@@ -38,11 +38,11 @@ describe('controllers/web/package.test.js', function () {
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('rows');
res.body.rows.should.eql([
{ key: 'c', count: 1, value: { name: 'c', description: 'Give folders or directories comments and view them easy.' } },
{ key: 'charset', count: 1,
value: { name: 'charset', description: 'Get the content charset from header and html content-type.' } }
]);
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();
});
});
@@ -53,11 +53,11 @@ describe('controllers/web/package.test.js', function () {
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('rows');
res.body.rows.should.eql([
{ key: 'c', count: 1, value: { name: 'c', description: 'Give folders or directories comments and view them easy.' } },
{ key: 'charset', count: 1,
value: { name: 'charset', description: 'Get the content charset from header and html content-type.' } }
]);
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();
});
});
@@ -78,9 +78,14 @@ describe('controllers/web/package.test.js', function () {
request(app)
.get('/package/cutter')
.expect(200)
.expect('content-encoding', 'gzip')
.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');
done();
});
});
it('should get 404', function (done) {
@@ -129,6 +134,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')
@@ -141,7 +153,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);
});
});

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/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}}}

View File

@@ -45,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

@@ -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

@@ -16,8 +16,12 @@
var should = require('should');
var mm = require('mm');
var fs = require('fs');
var path = require('path');
var npm = require('../../proxy/npm');
var fixtures = path.join(path.dirname(__dirname), 'fixtures');
describe('proxy/npm.test.js', function () {
afterEach(mm.restore);
@@ -47,4 +51,16 @@ describe('proxy/npm.test.js', function () {
done();
});
});
it('should return ServerError when http 500 response', function (done) {
var content = fs.readFileSync(path.join(fixtures, '500.txt'), 'utf8');
mm.http.request(/\//, content, { statusCode: 500 });
// http://registry.npmjs.org/octopie
npm.get('octopie', function (err, data) {
should.exist(err);
err.name.should.equal('NPMServerError');
err.message.should.equal('Status 500, ' + content);
done();
});
});
});

View File

@@ -50,7 +50,7 @@ describe('sync/sync_all.js', function () {
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']);
data.successes.should.eql(['cnpmjs.org', 'cutter']);
done();
});
});

View File

@@ -29,6 +29,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,13 +45,23 @@
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>

View File

@@ -18,204 +18,231 @@
<p class="description"><%= package.description %></p>
<% } %>
<pre class="sh"><code>$ <%- config.npmClientName %> 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 (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.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>
<% } %>
<% 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 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.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) {
var starredBy = package.starredBy
var l = starredBy.length
%>
<tr>
<th>Starred by<%= l > 0 ? ' (' + 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>
<% } %>
<% 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,43 @@
<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) { %>
<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>
<% for (var i = 0; i < packages.length; i++) {
var item = packages[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>
<% } %>
<% } %>
<% 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

@@ -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);
}
});