Compare commits

..

111 Commits

Author SHA1 Message Date
fengmk2
666d98d86e Release 0.2.26 2014-02-19 16:28:57 +08:00
dead_horse
e244efb153 Merge pull request #192 from cnpm/publish-add-deps
npm publish also need to add deps
2014-02-19 15:29:04 +08:00
fengmk2
67d824e5dc npm publish also need to add deps 2014-02-19 15:23:02 +08:00
fengmk2
daf29f760d Release 0.2.25 2014-02-19 14:23:11 +08:00
dead_horse
e5939d170f Merge pull request #191 from cnpm/module_deps
Dependents support. fixed #190
2014-02-19 14:15:01 +08:00
fengmk2
3526c9eff7 max handle number of package.json dependencies property 2014-02-19 14:07:06 +08:00
fengmk2
973889c73a Dependents support. fixed #190 2014-02-19 13:29:33 +08:00
fengmk2
70cd5f5cb6 Release 0.2.24 2014-02-13 18:43:13 +08:00
fengmk2
ba6139f265 Merge pull request #187 from cnpm/issue186-remvoe
refactor remove module, fixed #186
2014-02-13 18:41:49 +08:00
dead_horse
645e4913ba fix if delete all the versions 2014-02-13 17:34:56 +08:00
dead_horse
46eba8d03a refactor remove module, fixed #186
1. list all the modules, find which versions to be removed and which to
be remained.
2. remove all the modules need to be removed
3. list all the tags, find which tags need to be removed and remove
them.
4. if the latest tag removed, need to generate a new latest tag.
2014-02-13 14:51:29 +08:00
fengmk2
9d2a649aac Release 0.2.23 2014-01-26 16:58:30 +08:00
dead_horse
bf53537f00 Merge pull request #184 from cnpm/admin-role-fix
system admin can add, publish, remove the packages. fixed #176
2014-01-26 00:52:15 -08:00
fengmk2
1047e18732 system admin can add, publish, remove the packages. fixed #176 2014-01-26 16:39:01 +08:00
fengmk2
2981a17b10 Release 0.2.22 2014-01-26 16:22:30 +08:00
dead_horse
041416abf1 Merge pull request #183 from cnpm/keyword-search
add keyword and search support keyword. #181
2014-01-26 00:19:06 -08:00
fengmk2
c117b67f3b add keyword and search support keyword. #181 2014-01-26 15:25:05 +08:00
fengmk2
bc2ac45588 Release 0.2.21 2014-01-24 16:37:40 +08:00
fengmk2
95eba3fcfc refactor code styles on package.html 2014-01-24 16:31:40 +08:00
fengmk2
e98b409f61 Merge pull request #180 from 4simple/master
nav-tabs support in package page
2014-01-24 00:19:04 -08:00
dead_horse
fedec47349 Merge pull request #179 from cnpm/show-server-error
Show registry server error response. fixed #178
2014-01-24 00:11:36 -08:00
4simple
b63a565a37 nav-tabs e.preventDefault 2014-01-24 15:58:49 +08:00
fengmk2
67eb7fa380 Show registry server error response. fixed #178 2014-01-24 15:22:07 +08:00
4simple
a117886b71 nav-tabs for package.html 2014-01-24 00:22:24 +08:00
fengmk2
152f6800da Release 0.2.20 2014-01-23 14:31:34 +08:00
dead_horse
2f7e52f86f hotfix check missing readme error 2014-01-23 14:29:20 +08:00
dead_horse
bb4b51c54f hotfix sync missing dependencies and readmes 2014-01-23 14:26:30 +08:00
fengmk2
4c2303e4fa Merge pull request #175 from cnpm/issue174-readme
Issue174 readme
2014-01-22 22:17:00 -08:00
dead_horse
b70201db79 fix sync readme error, fixed #174 2014-01-23 14:16:26 +08:00
dead_horse
dd095147b0 add updateReadme in module 2014-01-23 14:07:39 +08:00
fengmk2
08c4445e78 Release 0.2.19 2014-01-22 16:45:28 +08:00
dead_horse
b892ad8185 Merge pull request #173 from cnpm/no-auth-on-install
npm install no need to check authorization header. fixed #171
2014-01-22 00:32:21 -08:00
fengmk2
a8ee1e43bf npm install no need to check authorization header. fixed #171 2014-01-22 16:31:17 +08:00
fengmk2
dd84674b98 Release 0.2.18 2014-01-20 21:43:07 +08:00
dead_horse
ccec2cb45b Merge pull request #170 from cnpm/show-git-web-url
Support gitlab git url to display and click. fixed #160
2014-01-20 05:34:09 -08:00
fengmk2
f3a4500191 Support gitlab git url to display and click. fixed #160 2014-01-20 21:15:48 +08:00
fengmk2
161a860f74 fix redis crash 2014-01-19 09:55:15 +08:00
fengmk2
f58e8a24b0 Release 0.2.17 2014-01-17 11:42:42 +08:00
fengmk2
9bc6a6dd74 custom logo url 2014-01-17 11:42:14 +08:00
fengmk2
c50364807d Merge pull request #167 from cnpm/hotfix-layout
hotfix layout bug
2014-01-16 19:05:44 -08:00
dead_horse
b3e3ecfc3a hotfix layout bug 2014-01-17 11:03:18 +08:00
fengmk2
637b5fe785 Release 0.2.16 2014-01-16 18:18:37 +08:00
dead_horse
6eed7b9002 Merge pull request #166 from cnpm/update-publish-time
fix publish-time bug
2014-01-16 02:10:11 -08:00
fengmk2
899becb448 fix publish-time bug 2014-01-16 18:09:03 +08:00
fengmk2
7e982ca877 Release 0.2.15 2014-01-16 17:12:54 +08:00
fengmk2
26156253c7 add publish_time to debug 2014-01-16 17:12:32 +08:00
fengmk2
9445d46bbf Release 0.2.14 2014-01-16 16:05:52 +08:00
fengmk2
55bfcc853e Merge pull request #164 from cnpm/issue163-time-error
update publish_time, fixed #163
2014-01-15 23:58:42 -08:00
fengmk2
4eeb1b25de Merge pull request #165 from cnpm/add-autod
add make autod
2014-01-15 23:58:02 -08:00
dead_horse
42959196c6 add make autod 2014-01-16 15:17:37 +08:00
dead_horse
d264b47b09 update publish_time, fixed #163 2014-01-16 14:58:55 +08:00
fengmk2
4e412d1808 Release 0.2.13 2014-01-15 18:48:37 +08:00
dead_horse
73527da1f1 Merge pull request #162 from cnpm/fix-custom-footer
markdown tmpl not support footer, need to wrap on app start
2014-01-15 02:47:26 -08:00
fengmk2
7986f198af markdown tmpl not support footer, need to wrap on app start 2014-01-15 18:27:07 +08:00
fengmk2
adb1411313 Release 0.2.12 2014-01-15 18:05:36 +08:00
dead_horse
2f24e9828e Merge pull request #161 from cnpm/more-custom
add footer and npm client name customable
2014-01-15 01:52:21 -08:00
fengmk2
d7e5921e24 add footer and npm client name customable 2014-01-15 17:44:41 +08:00
fengmk2
e3e6a1aaa5 Release 0.2.11 2014-01-15 14:52:28 +08:00
dead_horse
0d1969fadd Merge pull request #158 from cnpm/packagePageContributorSearch
package page contributor link to search, default is true
2014-01-14 22:50:28 -08:00
fengmk2
028e599d51 package page contributor link to search, default is true 2014-01-15 14:46:06 +08:00
fengmk2
fa0dd5c23f Release 0.2.10 2014-01-14 20:57:14 +08:00
dead_horse
7be5df8e70 Merge pull request #156 from cnpm/fix-Disposition
fix #155 Content-Disposition wrong.
2014-01-14 04:29:34 -08:00
fengmk2
adddf0e4c5 fix #155 Content-Disposition wrong. 2014-01-14 20:24:22 +08:00
fengmk2
28cc13d583 Release 0.2.9 2014-01-14 15:41:55 +08:00
dead_horse
c85b27b9b2 Merge pull request #154 from cnpm/couch-search-api
support couch db search api. fixed #153
2014-01-13 23:22:22 -08:00
fengmk2
46795adf54 support startkey=c and startkey="c" 2014-01-14 15:17:04 +08:00
fengmk2
c8ab1735a5 support couch db search api. fixed #153 2014-01-14 15:14:30 +08:00
dead_horse
4c759b40c8 Merge pull request #152 from cnpm/sync-by-query-name
support sync by query.name
2014-01-13 22:20:05 -08:00
fengmk2
1bab099f38 fix fork me image link 2014-01-14 13:55:47 +08:00
fengmk2
f95f814a8c support sync by query.name 2014-01-14 13:54:48 +08:00
fengmk2
ffcb0d669a Release 0.2.8 2014-01-14 10:14:56 +08:00
dead_horse
81ca81d578 Merge pull request #150 from cnpm/download-link
add download link for package page
2014-01-13 18:11:22 -08:00
fengmk2
803f6d42f8 dont show err stack on test env 2014-01-14 09:59:39 +08:00
fengmk2
5366f16bcb add download link for package page 2014-01-14 09:49:15 +08:00
fengmk2
ab18e49131 Release 0.2.7 2014-01-13 21:53:55 +08:00
fengmk2
3c874244a7 Merge pull request #149 from cnpm/issue148-shasum
add shasum when nfs.upload and hfs.uploadBuffer, fixed #148
2014-01-13 05:52:18 -08:00
dead_horse
d463b09c81 add shasum when nfs.upload and hfs.uploadBuffer, fixed #148 2014-01-13 21:50:22 +08:00
fengmk2
40301f260b Release 0.2.6 2014-01-13 17:15:32 +08:00
fengmk2
72dbde1906 Merge pull request #147 from cnpm/issue146-session-config
support custom session store
2014-01-13 01:13:56 -08:00
dead_horse
d68157faa8 support custom session store, fixed #146 2014-01-13 17:09:04 +08:00
fengmk2
c88991c028 Release 0.2.5 2014-01-13 12:00:47 +08:00
dead_horse
262abe8520 Merge pull request #144 from cnpm/download-stream
nfs download to a writeable stream.
2014-01-12 18:50:50 -08:00
fengmk2
9dd942df1a add download timeout and unit test 2014-01-13 10:44:55 +08:00
fengmk2
9ec552e08d use downloadStream() first 2014-01-13 09:48:02 +08:00
fengmk2
2420164a9d nfs download to a writeable stream.
nfs.download(key, writeStream, cb), should not create a tmp file,
and also add content-length, content-type headers.
2014-01-11 00:14:17 +08:00
fengmk2
822f2f6a4e Release 0.2.4 2014-01-10 15:59:56 +08:00
fengmk2
d423a987ae Merge pull request #143 from cnpm/issue142-module
set main script to  index.js, fixed #142
2014-01-09 23:59:07 -08:00
dead_horse
70cefd817e set main script to index.js, fixed #142 2014-01-10 15:52:32 +08:00
fengmk2
2606af24a1 Release 0.2.3 2014-01-10 15:15:59 +08:00
dead_horse
5b781cbda1 Merge pull request #140 from cnpm/package-display
dont show sync button on private package
2014-01-09 23:05:13 -08:00
fengmk2
1519b52a74 dont show sync button on private package 2014-01-10 14:38:23 +08:00
dead_horse
4b73f6e0a7 Merge pull request #139 from cnpm/sync-as-publish-with-no-deps
Sync package as publish with no deps. fixed #138
2014-01-09 21:09:33 -08:00
fengmk2
88bcb14a4f Sync package as publish with no deps. fixed #138
Let user sync their private packages from private registry to other.
Just use `$ cnpm sync --publish --no-deps private_pkg1 private_pkg2`.
2014-01-10 12:46:38 +08:00
fengmk2
5bd2cf5d50 Release 0.2.2 2014-01-10 10:58:35 +08:00
fengmk2
e8fcfc67a8 Merge pull request #137 from fengmk2/issue136-nfs-support
Issue136 nfs support
2014-01-09 18:48:53 -08:00
dead_horse
580fc8c777 keep compatibility
temp save path add random string
add download statistics
add if else after ufs.upload
2014-01-10 10:39:47 +08:00
dead_horse
60fdcdd0e4 qnfs upload only callback a url 2014-01-09 23:11:40 +08:00
dead_horse
09fdff49a1 compat remove package
use dist.tarball to store the key, avoid to change mysql
2014-01-09 18:01:43 +08:00
dead_horse
2f1b5034c4 set tarball url 2014-01-09 17:27:14 +08:00
dead_horse
6571ec53bb fix test 2014-01-09 16:11:31 +08:00
dead_horse
4a106f1bdf new npm publish in one request, add _publish_in_cnpm 2014-01-09 16:08:56 +08:00
dead_horse
92c20c90ac support unsure name ufs
upload return {key: 'xxx'}, then save it
2014-01-09 16:08:56 +08:00
fengmk2
31a920c5b3 contributors maybe a object 2014-01-08 11:23:54 +08:00
dead_horse
a3b51c6803 Merge pull request #135 from fengmk2/hotfix
Object #<Object> has no method 'forEach' fixed #134
2014-01-07 19:18:53 -08:00
fengmk2
2482c06e04 Object #<Object> has no method 'forEach' fixed #134 2014-01-08 11:17:51 +08:00
fengmk2
bbafa027e8 Merge pull request #133 from fengmk2/issue132-config
support custom config as a module, fixed issue #132
2014-01-07 19:10:52 -08:00
dead_horse
3ca62486d2 support custom config as a module, fixed issue #132 2014-01-07 21:27:33 +08:00
dead_horse
6371d60b70 Merge pull request #130 from fengmk2/fix-publish
Fix new npm publish flow
2013-12-27 05:59:15 -08:00
fengmk2
8014e0281f support npm new publish flow. fixed #129 2013-12-27 21:43:41 +08:00
fengmk2
253c11c855 add toString and constructor to test admin 2013-12-27 19:05:57 +08:00
fengmk2
49c5c73281 fix #119 hasOwnProperty check admin bug. 2013-12-27 19:04:02 +08:00
44 changed files with 1822 additions and 401 deletions

3
.gitignore vendored
View File

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

View File

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

View File

@@ -1,7 +1,8 @@
# Ordered by date of first contribution.
# Auto-generated by 'contributors' on Fri, 27 Dec 2013 06:55:24 GMT.
# Auto-generated by 'contributors' on Fri, 24 Jan 2014 08:35:59 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,157 @@
0.2.6 / 2014-02-19
==================
* npm publish also need to add deps
0.2.25 / 2014-02-19
==================
* max handle number of package.json `dependencies` property
* Dependents support. fixed #190
0.2.24 / 2014-02-13
==================
* fix if delete all the versions
* refactor remove module, fixed #186
0.2.23 / 2014-01-26
==================
* system admin can add, publish, remove the packages. fixed #176
0.2.22 / 2014-01-26
==================
* add keyword and search support keyword. #181
0.2.21 / 2014-01-24
==================
* refactor code styles on package.html
* nav-tabs e.preventDefault
* Show registry server error response. fixed #178
* nav-tabs for package.html (@4simple)
0.2.20 / 2014-01-23
==================
* hotfix sync missing dependencies and readmes
* fix sync readme error, fixed #174
* add updateReadme in module
0.2.19 / 2014-01-22
==================
* npm install no need to check authorization header. fixed #171
0.2.18 / 2014-01-20
==================
* Support gitlab git url to display and click. fixed #160
* fix redis crash
0.2.17 / 2014-01-17
==================
* custom logo url
* hotfix layout bug
0.2.16 / 2014-01-16
==================
* fix publish-time bug
0.2.15 / 2014-01-16
==================
* add publish_time to debug
0.2.14 / 2014-01-16
==================
* add make autod
* update publish_time, fixed #163
0.2.13 / 2014-01-15
==================
* markdown tmpl not support footer, need to wrap on app start
0.2.12 / 2014-01-15
==================
* add footer and npm client name customable
0.2.11 / 2014-01-15
==================
* package page contributor link to search, default is true
0.2.10 / 2014-01-14
==================
* fix #155 Content-Disposition wrong.
0.2.9 / 2014-01-14
==================
* support startkey=c and startkey="c"
* support couch db search api. fixed #153
* fix fork me image link
* support sync by query.name
0.2.8 / 2014-01-14
==================
* dont show err stack on test env
* add download link for package page
0.2.7 / 2014-01-13
==================
* add shasum when nfs.upload and hfs.uploadBuffer, fixed #148
0.2.6 / 2014-01-13
==================
* support custom session store, fixed #146
0.2.5 / 2014-01-13
==================
* add download timeout and unit test
* use downloadStream() first
* nfs download to a writeable stream.
0.2.4 / 2014-01-10
==================
* set main script to index.js, fixed #142
0.2.3 / 2014-01-10
==================
* Dont show sync button on private package
* Sync package as publish with no deps. fixed #138
0.2.2 / 2014-01-10
==================
* keep compatibility
* qnfs upload only callback a url
* compat remove package
* set tarball url
* new npm publish in one request, add _publish_in_cnpm
* support unsure name ufs
* contributors maybe a object
* Object #<Object> has no method 'forEach' fixed #134
* support custom config as a module, fixed issue #132
* support npm new publish flow. fixed #129
* add toString and constructor to test admin
* fix #119 hasOwnProperty check admin bug.
0.2.0 / 2013-12-27
==================

View File

@@ -10,6 +10,7 @@ test: install
@NODE_ENV=test ./node_modules/mocha/bin/mocha \
--reporter $(REPORTER) \
--timeout $(TIMEOUT) \
--require should \
$(MOCHA_OPTS) \
$(TESTS)
@@ -30,4 +31,8 @@ test-all: test test-cov
contributors: install
@./node_modules/contributors/bin/contributors -f plain -o AUTHORS
autod: install
@./node_modules/.bin/autod -w -e public,view,docs,backup
@$(MAKE) install
.PHONY: test

View File

@@ -1,20 +1,21 @@
cnpmjs.org
=======
[![Build Status](https://secure.travis-ci.org/fengmk2/cnpmjs.org.png)](http://travis-ci.org/fengmk2/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/fengmk2/cnpmjs.org/badge.png)](https://coveralls.io/r/fengmk2/cnpmjs.org)[![Dependency Status](https://gemnasium.com/fengmk2/cnpmjs.org.png)](https://gemnasium.com/fengmk2/cnpmjs.org)
[![Build Status](https://secure.travis-ci.org/cnpm/cnpmjs.org.png)](http://travis-ci.org/cnpm/cnpmjs.org) [![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/)
![logo](https://raw.github.com/fengmk2/cnpmjs.org/master/logo.png)
![logo](https://raw.github.com/cnpm/cnpmjs.org/master/logo.png)
## What is this?
Private npm registry and web for Enterprise, base on MySQL and Simple File Store.
Private npm registry and web for Enterprise, base on 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).
![cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=480&h=360)
## Install
```bash
@@ -27,20 +28,26 @@ $ npm install
$ node 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 : 2 weeks
active : 81 days
commits : 205
files : 83
repo age : 7 weeks
active : 132 days
commits : 315
files : 88
authors :
120 fengmk2 58.5%
84 dead_horse 41.0%
1 Alsotang 0.5%
190 fengmk2 60.3%
122 dead_horse 38.7%
2 4simple 0.6%
1 Alsotang 0.3%
```
## License

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

@@ -19,6 +19,8 @@ var config = require('../config');
var client = qn.create(config.qn);
exports._client = client;
/**
* Upload file
*
@@ -32,7 +34,23 @@ var client = qn.create(config.qn);
*/
exports.upload = function (filepath, options, callback) {
client.delete(options.key, function (err, data) {
client.uploadFile(filepath, {key: options.key, size: options.size}, callback);
client.uploadFile(filepath, {key: options.key, size: options.size}, function (err, data) {
if (err) {
return callback(err);
}
callback(null, {url: data.url});
});
});
};
exports.uploadBuffer = function (buf, options, callback) {
client.delete(options.key, function (err, data) {
client.upload(buf, {key: options.key}, function (err, data) {
if (err) {
return callback(err);
}
callback(null, {url: data.url});
});
});
};

View File

@@ -33,7 +33,7 @@ if (config.debug) {
session = connect.session({
key: key,
secret: config.sessionSecret,
store: new RedisStore(config.redis),
store: config.sessionStore || new RedisStore(config.redis),
cookie: cookie,
});
}

View File

@@ -68,9 +68,14 @@ var config = {
user: 'address@gmail.com',
pass: 'your password',
ssl: true,
debug: false
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`
packagePageContributorSearch: true, // package page contributor link to search, default is true
sourceNpmRegistry: 'http://registry.npmjs.org',
enablePrivate: true, // enable private mode, only admin can publish, other use just can sync package from source npm
admins: {
@@ -83,6 +88,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
@@ -98,3 +104,12 @@ mkdirp.sync(config.logdir);
mkdirp.sync(config.uploadDir);
module.exports = config;
config.loadConfig = function (customConfig) {
if (!customConfig) {
return;
}
for (var key in customConfig) {
config[key] = customConfig[key];
}
};

View File

@@ -23,6 +23,9 @@ var utility = require('utility');
var eventproxy = require('eventproxy');
var Bagpipe = require('bagpipe');
var urlparse = require('url').parse;
var mime = require('mime');
var semver = require('semver');
var ms = require('ms');
var config = require('../../config');
var Module = require('../../proxy/module');
var Total = require('../../proxy/total');
@@ -32,7 +35,7 @@ var Log = require('../../proxy/module_log');
var DownloadTotal = require('../../proxy/download');
var SyncModuleWorker = require('../../proxy/sync_module_worker');
var logger = require('../../common/logger');
var semver = require('semver');
var ModuleDeps = require('../../proxy/module_deps');
exports.show = function (req, res, next) {
var name = req.params.name;
@@ -83,7 +86,8 @@ exports.show = function (req, res, next) {
continue;
}
var pkg = row.package;
common.downloadURL(pkg, req);
common.setDownloadURL(pkg, req);
pkg._cnpm_publish_time = row.publish_time;
versions[pkg.version] = pkg;
times[pkg.version] = row.publish_time ? new Date(row.publish_time) : row.gmt_modified;
if ((!distTags.latest && !latestMod) || distTags.latest === row.version) {
@@ -129,7 +133,6 @@ exports.get = function (req, res, next) {
var name = req.params.name;
var tag = req.params.version;
var version = semver.valid(tag);
var ep = eventproxy.create();
ep.fail(next);
@@ -138,7 +141,8 @@ exports.get = function (req, res, next) {
Module[method](name, queryLabel, ep.done(function (mod) {
if (mod) {
common.downloadURL(mod.package, req);
common.setDownloadURL(mod.package, req);
mod.package._cnpm_publish_time = mod.publish_time;
return res.json(mod.package);
}
ep.emit('notFound');
@@ -168,14 +172,86 @@ exports.get = function (req, res, next) {
var _downloads = {};
var DOWNLOAD_TIMEOUT = ms('10m');
exports.download = function (req, res, next) {
var name = req.params.name;
var filename = req.params.filename;
var cdnurl = nfs.url(common.getCDNKey(name, filename));
res.statusCode = 302;
res.setHeader('Location', cdnurl);
res.end();
_downloads[name] = (_downloads[name] || 0) + 1;
var version = filename.slice(name.length + 1, -4);
var ep = eventproxy.create();
ep.fail(next);
Module.get(name, version, ep.doneLater('moduleInfo'));
ep.once('moduleInfo', function (row) {
if (!row || !row.package || !row.package.dist) {
return ep.emit('nodist');
}
var dist = row.package.dist;
if (dist.key) {
return ep.emit('key', dist);
} else {
return ep.emit('url', dist);
}
ep.emit('nodist');
});
ep.once('nodist', function () {
if (!nfs.url) {
return next();
}
ep.emit('url', nfs.url(common.getCDNKey(name, filename)));
});
ep.once('url', function (dist) {
res.statusCode = 302;
res.setHeader('Location', dist.tarball);
res.end();
_downloads[name] = (_downloads[name] || 0) + 1;
});
ep.once('key', function (dist) {
if (!nfs.downloadStream && !nfs.download) {
return next();
}
_downloads[name] = (_downloads[name] || 0) + 1;
if (typeof dist.size === 'number') {
res.setHeader('Content-Length', dist.size);
}
res.setHeader('Content-Type', mime.lookup(dist.key));
res.setHeader('Content-Disposition', 'attachment; filename="' + filename + '"');
res.setHeader('ETag', dist.shasum);
if (nfs.downloadStream) {
nfs.downloadStream(dist.key, res, {timeout: DOWNLOAD_TIMEOUT},
function (err) {
if (err) {
// TODO: just end or send error response?
return next(err);
}
});
return;
}
// use download file api
var tmpPath = path.join(config.uploadDir, utility.randomString() + dist.key);
function cleanup() {
fs.unlink(tmpPath, utility.noop);
}
nfs.download(dist.key, tmpPath, {timeout: DOWNLOAD_TIMEOUT},
function (err) {
if (err) {
cleanup();
return next(err);
}
var tarball = fs.createReadStream(tmpPath);
tarball.on('error', cleanup);
tarball.on('end', cleanup);
tarball.pipe(res);
});
});
};
setInterval(function () {
@@ -241,10 +317,8 @@ exports.upload = function (req, res, next) {
if (err || !mod) {
return next(err);
}
var match = mod.package.maintainers.filter(function (item) {
return item.name === username;
});
if (match.length === 0 || mod.name !== name) {
if (!common.isMaintainer(req, mod.package.maintainers) || mod.name !== name) {
return res.json(403, {
error: 'no_perms',
reason: 'Current user can not publish this module'
@@ -276,8 +350,13 @@ exports.upload = function (req, res, next) {
});
}
shasum = shasum.digest('hex');
var key = common.getCDNKey(name, filename);
nfs.upload(filepath, {key: key, size: length}, function (err, result) {
var options = {
key: common.getCDNKey(name, filename),
size: length,
shasum: shasum
};
nfs.upload(filepath, options, function (err, result) {
// remove tmp file whatever
fs.unlink(filepath, utility.noop);
if (err) {
@@ -285,10 +364,18 @@ exports.upload = function (req, res, next) {
}
var dist = {
tarball: result.url,
shasum: shasum,
size: length
};
// if nfs upload return a key, record it
if (result.url) {
dist.tarball = result.url;
} else if (result.key) {
dist.key = result.key;
dist.tarball = result.key;
}
mod.package.dist = dist;
mod.package.version = version;
debug('%s module: save file to %s, size: %d, sha1: %s, dist: %j, version: %s',
@@ -304,6 +391,18 @@ exports.upload = function (req, res, next) {
});
};
function _addDepsRelations(pkg) {
var dependencies = Object.keys(pkg.dependencies || {});
if (dependencies.length > config.maxDependencies) {
dependencies = dependencies.slice(0, config.maxDependencies);
}
// add deps relations
dependencies.forEach(function (depName) {
ModuleDeps.add(depName, pkg.name, utility.noop);
});
}
exports.updateLatest = function (req, res, next) {
var username = req.session.name;
var name = req.params.name;
@@ -351,10 +450,15 @@ exports.updateLatest = function (req, res, next) {
}
body._publish_on_cnpm = true;
nextMod.package = body;
_addDepsRelations(body);
// reset publish time
nextMod.publish_time = Date.now();
debug('update %s:%s %j', nextMod.package.name, nextMod.package.version, nextMod.package.dist);
// change latest to version
Module.update(nextMod, function (err) {
if (err) {
debug('update nextMod %s error: %s', name, err);
return next(err);
}
// set latest tag
@@ -375,21 +479,142 @@ exports.updateLatest = function (req, res, next) {
});
};
exports.addPackageAndDist = function (req, res, next) {
// 'dist-tags': { latest: '0.0.2' },
// _attachments:
// { 'nae-sandbox-0.0.2.tgz':
// { content_type: 'application/octet-stream',
// data: 'H4sIAAAAA
// length: 9883
var pkg = req.body;
var username = req.session.name;
var name = req.params.name;
var filename = Object.keys(pkg._attachments)[0];
var attachment = pkg._attachments[filename];
var version = filename.match(/\-([\.\d\w\_]+?)\.tgz$/);
if (!version) {
return res.json(403, {
error: 'version_error',
reason: filename + ' version not found',
});
}
version = version[1];
var versionPackage = pkg.versions[version];
versionPackage._publish_on_cnpm = true;
var distTags = pkg['dist-tags'] || {};
var tags = []; // tag, version
for (var t in distTags) {
tags.push([t, distTags[t]]);
}
debug('addPackageAndDist %s:%s, attachment size: %s', name, version, attachment.length);
var ep = eventproxy.create();
ep.fail(next);
Module.get(name, version, ep.done('exists'));
var shasum;
ep.on('exists', function (exists) {
if (exists) {
return res.json(409, {
error: 'conflict',
reason: 'Document update conflict.'
});
}
// upload attachment
var tarballBuffer;
try {
tarballBuffer = new Buffer(attachment.data, 'base64');
} catch (e) {
return next(e);
}
if (tarballBuffer.length !== attachment.length) {
return res.json(403, {
error: 'size_wrong',
reason: 'Attachment size ' + attachment.length + ' not match download size ' + tarballBuffer.length,
});
}
shasum = crypto.createHash('sha1');
shasum.update(tarballBuffer);
shasum = shasum.digest('hex');
var options = {
key: common.getCDNKey(name, filename),
shasum: shasum
};
nfs.uploadBuffer(tarballBuffer, options, ep.done('upload'));
});
ep.on('upload', function (result) {
debug('upload %j', result);
var dist = {
shasum: shasum,
size: attachment.length
};
// if nfs upload return a key, record it
if (result.url) {
dist.tarball = result.url;
} else if (result.key) {
dist.key = result.key;
dist.tarball = result.key;
}
var mod = {
name: name,
version: version,
author: username,
package: versionPackage
};
mod.package.dist = dist;
_addDepsRelations(mod.package);
Module.add(mod, ep.done(function (r) {
debug('%s module: save file to %s, size: %d, sha1: %s, dist: %j, version: %s',
r.id, dist.tarball, dist.size, shasum, dist, version);
ep.emit('saveModule', r.id);
}));
});
ep.on('saveModule', function () {
if (tags.length === 0) {
return ep.emit('saveTags');
}
tags.forEach(function (item) {
Module.addTag(name, item[0], item[1], ep.done('saveTag'));
});
ep.after('saveTag', tags.length, function () {
ep.emit('saveTags');
});
});
ep.all('saveModule', 'saveTags', function (moduleId) {
res.json(201, {ok: true, rev: String(moduleId)});
});
};
exports.add = function (req, res, next) {
var username = req.session.name;
var name = req.params.name;
var pkg = req.body;
var maintainers = pkg.maintainers || [];
var match = maintainers.filter(function (item) {
return item.name === username;
});
if (match.length === 0) {
var pkg = req.body || {};
if (!common.isMaintainer(req, pkg.maintainers)) {
return res.json(403, {
error: 'no_perms',
reason: 'Current user can not publish this module'
});
}
if (pkg._attachments && Object.keys(pkg._attachments).length > 0) {
return exports.addPackageAndDist(req, res, next);
}
var ep = eventproxy.create();
ep.fail(next);
@@ -413,6 +638,7 @@ exports.add = function (req, res, next) {
maintainers: pkg.maintainers,
},
};
debug('add next module: %s', name);
Module.add(nextMod, ep.done(function (result) {
nextMod.id = result.id;
ep.emit('next', nextMod);
@@ -420,19 +646,19 @@ exports.add = function (req, res, next) {
}));
ep.all('latest', 'next', function (latestMod, nextMod) {
var maintainers = latestMod ? latestMod.package.maintainers : nextMod.package.maintainers;
var match = maintainers.filter(function (item) {
return item.name === username;
});
var maintainers = latestMod && latestMod.package.maintainers.length > 0 ?
latestMod.package.maintainers : nextMod.package.maintainers;
if (match.length === 0) {
if (!common.isMaintainer(req, maintainers)) {
return res.json(403, {
error: 'no_perms',
reason: 'Current user can not publish this module'
});
}
if (latestMod || nextMod.exists) {
debug('add %s rev: %s, version: %s', name, nextMod.id, nextMod.version);
if (latestMod || nextMod.version !== 'next') {
return res.json(409, {
error: 'conflict',
reason: 'Document update conflict.'
@@ -455,37 +681,94 @@ exports.removeWithVersions = function (req, res, next) {
var ep = eventproxy.create();
ep.fail(next);
// step1: list all the versions
Module.listByName(name, ep.doneLater('list'));
ep.once('list', function (mods) {
if (!mods || !mods.length) {
return next();
}
//TODO replace this maintainer check
var match = mods[0].package.maintainers.filter(function (item) {
return item.name === username;
});
if (!match.length || mods[0].name !== name) {
// step2: check permission
var firstMod = mods[0];
if (!common.isMaintainer(req, firstMod.package.maintainers) || firstMod.name !== name) {
return res.json(403, {
error: 'no_perms',
reason: 'Current user can not update this module'
});
}
// step3: calculate which versions need to remove and
// which versions need to remain
var removeVersions = [];
var removeVersionMaps = {};
var remainVersions = [];
for (var i = 0; i < mods.length; i++) {
var v = mods[i].version;
if (v !== 'next' && !versions[v]) {
if (v === 'next') {
continue;
}
if (!versions[v]) {
removeVersions.push(v);
removeVersionMaps[v] = true;
} else {
remainVersions.push(v);
}
}
if (!removeVersions.length) {
return res.json(201, {ok: true});
debug('no versions need to remove');
return res.json(201, { ok: true });
}
Module.removeByNameAndVersions(name, removeVersions, ep.done(function () {
res.json(201, {ok: true});
debug('remove versions: %j, remain versions: %j', removeVersions, remainVersions);
// step 4: remove all the versions which need to remove
Module.removeByNameAndVersions(name, removeVersions, ep.done('removeModules'));
// step5: list all the tags
Module.listTags(name, ep.done(function (tags) {
var removeTags = [];
var latestRemoved = false;
tags.forEach(function (tag) {
// this tag need be removed
if (removeVersionMaps[tag.version]) {
removeTags.push(tag.id);
if (tag.tag === 'latest') {
latestRemoved = true;
}
}
});
if (!removeTags.length) {
debug('no tag need be remove');
ep.emit('removeTags');
ep.emit('latest');
return;
}
debug('remove tags: %j', removeTags);
// step 6: remove all the tags which need to remove
Module.removeTagsByIds(removeTags, ep.done('removeTags'));
// step 7: check if latest tag being removed.
// need generate a new latest tag
if (!latestRemoved || !remainVersions[0]) {
ep.emit('latest');
} else {
debug('latest tags removed, generate a new latest tag with new version: %s',
remainVersions[0]);
ep.emit('newLatestVersion', remainVersions[0]);
}
}));
});
ep.all('newLatestVersion', 'removeTags', function (version) {
Module.addTag(name, 'latest', version, ep.done('latest'));
});
ep.all('removeModules', 'removeTags', 'latest', function () {
return res.json(201, { ok: true });
});
};
@@ -503,18 +786,16 @@ exports.removeTar = function (req, res, next) {
if (!mod) {
return next();
}
//TODO replace this maintainer check
var match = mod.package.maintainers.filter(function (item) {
return item.name === username;
});
if (!match.length || mod.name !== name) {
if (!common.isMaintainer(req, mod.package.maintainers) || mod.name !== name) {
return res.json(403, {
error: 'no_perms',
reason: 'Current user can not delete this tarball'
});
}
var key = mod.package.dist && mod.package.dist.key;
key = key || common.getCDNKey(mod.name, filename);
var key = common.getCDNKey(mod.name, filename);
nfs.remove(key, ep.done(function () {
res.json(200, {ok: true});
}));
@@ -532,15 +813,13 @@ exports.removeAll = function (req, res, next) {
Module.listByName(name, ep.doneLater('list'));
ep.once('list', function (mods) {
debug('removeAll module %s: %d', name, mods.length);
var mod = mods[0];
if (!mod) {
return next();
}
//TODO replace this maintainer check
var match = mod.package.maintainers.filter(function (item) {
return item.name === username;
});
if (!match.length || mod.name !== name) {
if (!common.isMaintainer(req, mod.package.maintainers) || mod.name !== name) {
return res.json(403, {
error: 'no_perms',
reason: 'Current user can not delete this tarball'
@@ -559,7 +838,7 @@ exports.removeAll = function (req, res, next) {
}
var queue = new Bagpipe(5);
keys.forEach(function (key) {
queue.push(nfs.remove, key, function () {
queue.push(nfs.remove.bind(nfs), key, function () {
//ignore err here
ep.emit('removeTar');
});
@@ -587,7 +866,7 @@ function parseModsForList(updated, mods, req) {
pkg['dist-tags'] = {
latest: pkg.version
};
common.downloadURL(pkg, req);
common.setDownloadURL(pkg, req);
results[mod.name] = pkg;
}
return results;

View File

@@ -13,13 +13,27 @@
/**
* Module dependencies.
*/
var Log = require('../proxy/module_log');
var SyncModuleWorker = require('../proxy/sync_module_worker');
exports.sync = function (req, res, next) {
var username = req.session.name || 'anonymous';
var name = req.params.name;
SyncModuleWorker.sync(name, username, function (err, result) {
var publish = req.query.publish === 'true';
var noDep = req.query.nodeps === 'true';
if (publish && !req.session.isAdmin) {
return res.json(403, {
error: 'no_perms',
reason: 'Only admin can publish'
});
}
var options = {
publish: publish,
noDep: noDep,
};
SyncModuleWorker.sync(name, username, options, function (err, result) {
if (err) {
return next(err);
}

View File

@@ -14,16 +14,20 @@
* Module dependencies.
*/
var giturl = require('giturl');
var moment = require('moment');
var eventproxy = require('eventproxy');
var semver = require('semver');
var marked = require('marked');
var gravatar = require('gravatar');
var humanize = require('humanize-number');
var config = require('../../config');
var Module = require('../../proxy/module');
var down = require('../download');
var sync = require('../sync');
var Log = require('../../proxy/module_log');
var ModuleDeps = require('../../proxy/module_deps');
var setDownloadURL = require('../../lib/common').setDownloadURL;
exports.display = function (req, res, next) {
var params = req.params;
@@ -45,7 +49,13 @@ exports.display = function (req, res, next) {
down.total(name, ep.done('download'));
ep.all('pkg', 'download', function (pkg, download) {
ModuleDeps.list(name, ep.done(function (rows) {
ep.emit('dependents', rows.map(function (r) {
return r.deps;
}));
}));
ep.all('pkg', 'download', 'dependents', function (pkg, download, dependents) {
if (!pkg || !pkg.package) {
return next();
}
@@ -53,6 +63,10 @@ exports.display = function (req, res, next) {
pkg.package.fromNow = moment(pkg.publish_time).fromNow();
pkg = pkg.package;
pkg.readme = marked(pkg.readme || '');
if (!pkg.readme) {
pkg.readme = pkg.description || '';
}
if (pkg.maintainers) {
for (var i = 0; i < pkg.maintainers.length; i++) {
var maintainer = pkg.maintainers[i];
@@ -63,19 +77,33 @@ exports.display = function (req, res, next) {
}
if (pkg.contributors) {
// registry.cnpmjs.org/compressible
if (!Array.isArray(pkg.contributors)) {
pkg.contributors = [pkg.contributors];
}
for (var i = 0; i < pkg.contributors.length; i++) {
var contributor = pkg.contributors[i];
if (contributor.email) {
contributor.gravatar = gravatar.url(contributor.email, {s: '50', d: 'retro'}, false);
}
if (config.packagePageContributorSearch || !contributor.url) {
contributor.url = '/~' + encodeURIComponent(contributor.name);
}
}
}
if (pkg.repository && pkg.repository.url) {
pkg.repository.weburl = giturl.parse(pkg.repository.url);
}
setLicense(pkg);
for (var k in download) {
download[k] = humanize(download[k]);
}
setDownloadURL(pkg, req, config.registryHost);
pkg.dependents = dependents;
res.render('package', {
title: 'Package - ' + pkg.name,
@@ -88,7 +116,7 @@ exports.display = function (req, res, next) {
exports.search = function (req, res, next) {
var params = req.params;
var word = req.params.word;
Module.search(word, function (err, packages) {
Module.search(word, function (err, result) {
if (err) {
return next(err);
}
@@ -96,13 +124,49 @@ exports.search = function (req, res, next) {
res.render('search', {
title: 'Keyword - ' + word,
keyword: word,
packages: packages || []
packages: result.searchMatchs,
keywords: result.keywordMatchs,
});
});
};
exports.rangeSearch = function (req, res, next) {
var startKey = req.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, result) {
if (err) {
return next(err);
}
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);
}
res.json({
rows: rows
});
});
};
exports.displaySync = function (req, res, next) {
var name = req.params.name;
var name = req.params.name || req.query.name;
res.render('sync', {
name: name,
title: 'Sync - ' + name

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

@@ -1,12 +1,12 @@
# cnpmjs.org: Private npm registry and web for Enterprise
[![Build Status](https://secure.travis-ci.org/fengmk2/cnpmjs.org.png)](http://travis-ci.org/fengmk2/cnpmjs.org) [![Coverage Status](https://coveralls.io/repos/fengmk2/cnpmjs.org/badge.png)](https://coveralls.io/r/fengmk2/cnpmjs.org) [![Dependency Status](https://gemnasium.com/fengmk2/cnpmjs.org.png)](https://gemnasium.com/fengmk2/cnpmjs.org)
[![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/)
## What is this?
> Private npm registry and web for Enterprise, base on MySQL and Simple File Store.
> Private npm registry and web for Enterprise, base on 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).
@@ -16,7 +16,7 @@
## Registry
* Our public registry: [registry.cnpmjs.org](http://registry.cnpmjs.org)
* Our public registry: [r.cnpmjs.org](http://r.cnpmjs.org), syncing from [registry.npmjs.org](http://registry.npmjs.org)
* Current [cnpmjs.org](/) version: <span id="app-version"></span>
<table class="downloads">
@@ -119,13 +119,13 @@ $(function () {
alias it:
```bash
alias cnpm="npm --registry=http://registry.cnpmjs.org \
alias cnpm="npm --registry=http://r.cnpmjs.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=http://dist.u.qiniudn.com \
--userconfig=$HOME/.cnpmrc"
#Or alias it in .bashrc or .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=http://registry.cnpmjs.org \
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=http://r.cnpmjs.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=http://dist.u.qiniudn.com \
--userconfig=$HOME/.cnpmrc"' >> ~/.zshrc && source ~/.zshrc
@@ -139,7 +139,7 @@ $ npm install cnpm -g
### install
Install package from [registry.cnpmjs.org](http://registry.cnpmjs.org). When isntall a package or version not exist, it will try to install from official registry([registry.npmjs.org](http://registry.npmjs.org)), and sync this package to cnpm in the backend.
Install package from [r.cnpmjs.org](http://r.cnpmjs.org). When 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.
```
$ cnpm install [name]
@@ -178,7 +178,7 @@ $ cnpm info cnpm
## TODO list
@see Github [Issues](https://github.com/fengmk2/cnpmjs.org/issues)
@see Github [Issues](https://github.com/cnpm/cnpmjs.org/issues)
## Authors
@@ -188,14 +188,15 @@ Release [History](/history).
$ git summary
project : cnpmjs.org
repo age : 2 weeks
active : 81 days
commits : 205
files : 83
repo age : 7 weeks
active : 132 days
commits : 315
files : 88
authors :
120 fengmk2 58.5%
84 dead_horse 41.0%
1 Alsotang 0.5%
190 fengmk2 60.3%
122 dead_horse 38.7%
2 4simple 0.6%
1 Alsotang 0.3%
```
## npm and cnpm relation

30
index.js Normal file
View File

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

View File

@@ -17,6 +17,7 @@
var crypto = require('crypto');
var path = require('path');
var config = require('../config');
var util = require('util');
exports.getTarballFilepath = function (filename) {
// ensure download file path unique
@@ -28,8 +29,29 @@ exports.getCDNKey = function (name, filename) {
return '/' + name + '/-/' + filename;
};
exports.downloadURL = function (pkg, req) {
if (pkg.dist && pkg.dist.tarball) {
pkg.dist.tarball = 'http://' + req.headers.host + '/' + pkg.name + '/download/' + path.basename(pkg.dist.tarball);
exports.setDownloadURL = function (pkg, req, host) {
if (pkg.dist) {
host = host || req.headers.host;
pkg.dist.tarball = util.format('%s://%s/%s/download/%s-%s.tgz',
req.connection.encrypted ? 'https' : 'http',
host, pkg.name, pkg.name, pkg.version);
}
};
exports.isAdmin = function (username) {
return typeof config.admins[username] === 'string';
};
exports.isMaintainer = function (req, maintainers) {
if (req.session.isAdmin) {
return true;
}
var username = req.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.
@@ -17,14 +17,18 @@
var debug = require('debug')('cnpmjs.org:middleware:auth');
var User = require('../proxy/user');
var config = require('../config');
var common = require('../lib/common');
module.exports = function (options) {
return function auth(req, res, next) {
if (!req.session) {
// redis crash
req.session = {};
return next();
}
req.session.onlySync = config.enablePrivate ? true : false;
if (req.session.name) {
if (config.admins.hasOwnProperty[req.session.name]) {
req.session.isAdmin = true;
}
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();
@@ -34,7 +38,12 @@ module.exports = function (options) {
if (!authorization) {
return next();
}
authorization = new Buffer(authorization, 'base64').toString().split(':');
if (authorization.length !== 2) {
return next();
}
var username = authorization[0];
var password = authorization[1];
@@ -45,16 +54,13 @@ module.exports = function (options) {
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 = null;
req.session.isAdmin = false;
return next();
}
req.session.name = row.name;
if (config.admins.hasOwnProperty(req.session.name)) {
req.session.isAdmin = true;
}
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();

View File

@@ -14,6 +14,7 @@
* Module dependencies.
*/
var debug = require('debug')('cnpmjs.org:middleware:sync_by_install');
var config = require('../config');
/**
@@ -36,5 +37,9 @@ module.exports = function (req, res, next) {
req.session.allowSync = false;
}
// TODO: allow sync will let publish sync package...
req.session.allowSync = false;
debug('%s allowSync: %s', req.session.name, req.session.allowSync);
next();
};

View File

@@ -1,8 +1,8 @@
{
"name": "cnpmjs.org",
"version": "0.2.0",
"description": "Private npm registry and web",
"main": "dispatch.js",
"version": "0.2.26",
"description": "Private npm registry and web for Enterprise, base on MySQL and Simple Store Service",
"main": "index.js",
"scripts": {
"test": "make test-all",
"start": "./bin/nodejsctl start && cp History.md docs/web/history.md",
@@ -30,17 +30,19 @@
"debug": "0.7.4",
"eventproxy": "0.2.6",
"forward": "0.0.4",
"giturl": "0.0.1",
"graceful": "0.0.5",
"gravatar": "1.0.6",
"humanize-number": "0.0.2",
"logfilestream": "0.1.0",
"marked": "0.3.0",
"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-rc2",
"nodemailer": "0.5.15",
"mysql": "2.0.1",
"nodemailer": "0.6.0",
"qn": "0.2.0",
"ready": "0.1.1",
"response-cookie": "0.0.2",
@@ -48,28 +50,29 @@
"semver": "2.2.1",
"urllib": "0.5.5",
"urlrouter": "0.5.4",
"utility": "0.1.9"
"utility": "0.1.10"
},
"devDependencies": {
"autod": ">=0.0.13",
"blanket": "*",
"contributors": "*",
"coveralls": "*",
"mm": "0.1.7",
"mm": "0.1.8",
"mocha": "*",
"mocha-lcov-reporter": "*",
"pedding": "0.0.3",
"should": "2.1.1",
"supertest": "0.8.2",
"should": "3.0.1",
"supertest": "0.9.0",
"travis-cov": "*"
},
"homepage": "https://github.com/fengmk2/cnpmjs.org",
"homepage": "https://github.com/cnpm/cnpmjs.org",
"repository": {
"type": "git",
"url": "git://github.com/fengmk2/cnpmjs.org.git",
"web": "https://github.com/fengmk2/cnpmjs.org"
"url": "git://github.com/cnpm/cnpmjs.org.git",
"web": "https://github.com/cnpm/cnpmjs.org"
},
"bugs": {
"url": "https://github.com/fengmk2/cnpmjs.org/issues",
"url": "https://github.com/cnpm/cnpmjs.org/issues",
"email": "fengmk2@gmail.com"
},
"keywords": [

View File

@@ -29,6 +29,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 +52,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 +128,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 +152,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 +175,7 @@ function parseRow(row) {
}
}
}
exports.parseRow = parseRow;
function stringifyPackage(pkg) {
return encodeURIComponent(JSON.stringify(pkg));
@@ -171,7 +256,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,26 +385,67 @@ exports.listByAuthor = function (author, callback) {
});
};
var SEARCH_SQLS = [
'SELECT module_id FROM tag WHERE name LIKE ? AND tag="latest" ORDER BY name LIMIT 100;',
'SELECT name, description FROM module WHERE id IN (?) ORDER BY name;'
];
exports.search = function (word, callback) {
word = word.replace(/^%/, '') + '%'; //ignore prefix %
var SEARCH_MODULES_SQL = '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;
options = null;
}
options = options || {};
var limit = options.limit || 100;
word = word.replace(/^%/, ''); //ignore prefix %
var ep = eventproxy.create();
ep.fail(callback);
mysql.query(SEARCH_SQLS[0], [word], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
// search flows:
// 1. prefix search by name
// 2. like search by name
// 3. keyword equal search
var ids = {};
mysql.query(SEARCH_MODULES_SQL, [word + '%', limit ], ep.done(function (rows) {
rows = rows || [];
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
}
ep.emit('ids', rows.map(function (r) {
return r.module_id;
}));
if (rows.length >= 20) {
return ep.emit('ids', Object.keys(ids));
}
mysql.query(SEARCH_MODULES_SQL, [ '%' + word + '%', limit ], ep.done('likeSearch'));
}));
ep.on('ids', function (ids) {
mysql.query(SEARCH_SQLS[1], [ids], ep.done(function (modules) {
callback(null, modules);
mysql.query(SEARCH_MODULES_BY_KEYWORD_SQL, [ word, limit ], ep.done('keywordRows'));
ep.on('likeSearch', function (rows) {
rows = rows || [];
if (rows.length > 0) {
for (var i = 0; i < rows.length; i++) {
ids[rows[i].module_id] = 1;
}
}
ep.emit('ids', Object.keys(ids));
});
ep.all('ids', 'keywordRows', function (ids, keywordRows) {
keywordRows = keywordRows || [];
var data = {
keywordMatchs: keywordRows,
searchMatchs: []
};
if (ids.length === 0) {
return callback(null, data);
}
mysql.query(QUERY_MODULES_BY_ID_SQL, [ids], ep.done(function (modules) {
data.searchMatchs = modules;
callback(null, data);
}));
});
};

41
proxy/module_deps.js Normal file
View File

@@ -0,0 +1,41 @@
/**!
* 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 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);
};

View File

@@ -20,15 +20,22 @@ var config = require('../config');
function request(url, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {
dataType: 'json',
timeout: 10000
};
options = null;
}
options = options || {};
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) {

View File

@@ -24,12 +24,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);
@@ -47,6 +49,8 @@ function SyncModuleWorker(options) {
this.username = options.username;
this.concurrency = options.concurrency || 1;
this._publish = options.publish; // _publish_on_cnpm
this.syncingNames = {};
this.nameMap = {};
this.names.forEach(function (name) {
@@ -82,7 +86,8 @@ SyncModuleWorker.prototype.log = function (format, arg1, arg2) {
};
SyncModuleWorker.prototype.start = function () {
this.log('user: %s, sync %s worker start, %d concurrency.', this.username, this.names[0], this.concurrency);
this.log('user: %s, sync %s worker start, %d concurrency, nodeps: %s, publish: %s',
this.username, this.names[0], this.concurrency, this.noDep, this._publish);
for (var i = 0; i < this.concurrency; i++) {
this.next(i);
}
@@ -122,11 +127,11 @@ SyncModuleWorker.prototype.next = function (concurrencyId) {
return that.next(concurrencyId);
}
that.log('[c#%d] [%s] start...', concurrencyId, pkg.name);
that._sync(pkg, function (err, versions) {
that.log('[c#%d] [%s] start...', concurrencyId, name);
that._sync(name, pkg, function (err, versions) {
delete that.syncingNames[name];
if (err) {
that.fails.push(pkg.name);
that.fails.push(name);
that.log('[error] [%s] sync error: %s', name, err.stack);
return that.next(concurrencyId);
}
@@ -139,9 +144,8 @@ SyncModuleWorker.prototype.next = function (concurrencyId) {
});
};
SyncModuleWorker.prototype._sync = function (pkg, callback) {
var username = this.name;
var name = pkg.name;
SyncModuleWorker.prototype._sync = function (name, pkg, callback) {
var username = this.username;
var that = this;
var ep = eventproxy.create();
ep.fail(callback);
@@ -159,7 +163,7 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
if (r.package && r.package._publish_on_cnpm) {
// publish on cnpm, dont sync this version package
that.log(' [%s] publish on local cnpm, don\'t sync.', pkg.name);
that.log(' [%s] publish on local cnpm, don\'t sync', name);
ep.unbind();
callback(null, []);
return;
@@ -192,6 +196,8 @@ SyncModuleWorker.prototype._sync = function (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 || {};
@@ -200,7 +206,7 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
versionNames = Object.keys(pkg.versions);
}
if (versionNames.length === 0) {
that.log(' [%s] no times and no versions, hasModules: %s', pkg.name, hasModules);
that.log(' [%s] no times and no versions, hasModules: %s', name, hasModules);
if (!hasModules) {
// save a next module
var maintainer = pkg.maintainers && pkg.maintainers[0];
@@ -211,11 +217,11 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
maintainer = '-';
}
var nextMod = {
name: pkg.name,
name: name,
version: 'next',
author: maintainer,
package: {
name: pkg.name,
name: name,
version: 'next',
description: pkg.description || '',
readme: pkg.readme || '',
@@ -225,7 +231,7 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
},
};
Module.add(nextMod, function (err, result) {
that.log(' [%s] save next module, %j, error: %s', pkg.name, result, err);
that.log(' [%s] save next module, %j, error: %s', name, result, err);
});
}
}
@@ -237,7 +243,10 @@ SyncModuleWorker.prototype._sync = function (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]) {
@@ -250,7 +259,7 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
// * shasum make sure equal
if ((version.publish_time === exists.publish_time) || (!version.publish_time && exists.publish_time)) {
debug(' [%s] %s publish_time equal: %s, %s',
pkg.name, version.version, version.publish_time, exists.publish_time);
name, version.version, version.publish_time, exists.publish_time);
// * publish_time make sure equal
if (exists.description === null && version.description) {
// * make sure description exists
@@ -259,6 +268,14 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
description: version.description
});
}
if (!exists.package.readme && version.readme) {
// * make sure readme exists
missingReadmes.push({
id: exists.id,
readme: version.readme
});
}
continue;
}
}
@@ -274,7 +291,7 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
}
if (versions.length === 0) {
that.log(' [%s] all versions are exists', pkg.name);
that.log(' [%s] all versions are exists', name);
return ep.emit('syncDone');
}
@@ -282,7 +299,7 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
return a.publish_time - b.publish_time;
});
missingVersions = versions;
that.log(' [%s] %d versions', pkg.name, versions.length);
that.log(' [%s] %d versions', name, versions.length);
ep.emit('syncModule', missingVersions.shift());
});
@@ -311,14 +328,15 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
return ep.emit('descriptionDone');
}
that.log(' [%s] saving %d descriptions', pkg.name, missingDescriptions.length);
that.log(' [%s] saving %d descriptions', name, missingDescriptions.length);
missingDescriptions.forEach(function (item) {
Module.updateDescription(item.id, item.description, function (err, result) {
if (err) {
return that.log(' save error, %s: description %j, error: %s', item.id, item.description, err);
that.log(' 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');
});
});
@@ -332,12 +350,12 @@ SyncModuleWorker.prototype._sync = function (pkg, callback) {
return ep.emit('tagDone');
}
that.log(' [%s] adding %d tags', pkg.name, missingTags.length);
that.log(' [%s] adding %d tags', name, missingTags.length);
// sync tags
missingTags.forEach(function (item) {
Module.addTag(pkg.name, item[0], item[1], ep.done(function (result) {
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');
}));
});
@@ -346,7 +364,29 @@ SyncModuleWorker.prototype._sync = function (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);
});
@@ -371,18 +411,37 @@ SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePacka
callback(err);
});
that.log(' [%s:%d] syncing, version: %s, dist: %j',
sourcePackage.name, versionIndex, sourcePackage.version, sourcePackage.dist);
var dependencies = Object.keys(sourcePackage.dependencies || {});
var devDependencies = Object.keys(sourcePackage.devDependencies || {});
that.log(' [%s:%d] syncing, version: %s, dist: %j, no deps: %s, publish on cnpm: %s, dependencies: %d, devDependencies: %d',
sourcePackage.name, versionIndex, sourcePackage.version,
sourcePackage.dist, that.noDep, that._publish,
dependencies.length, devDependencies.length);
if (dependencies.length > config.maxDependencies) {
dependencies = dependencies.slice(0, config.maxDependencies);
}
if (devDependencies.length > config.maxDependencies) {
devDependencies = devDependencies.slice(0, config.maxDependencies);
}
if (!that.noDep) {
for (var k in sourcePackage.dependencies) {
that.add(k);
for (var i = 0; i < dependencies.length; i++) {
that.add(dependencies[i]);
}
for (var k in sourcePackage.devDependencies) {
that.add(k);
for (var i = 0; i < devDependencies.length; i++) {
that.add(devDependencies[i]);
}
}
// add 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) {
@@ -419,8 +478,12 @@ SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePacka
return ep.emit('error', err);
}
var key = common.getCDNKey(sourcePackage.name, filename);
nfs.upload(filepath, {key: key, size: dataSize}, ep.done('uploadResult'));
var options = {
key: common.getCDNKey(sourcePackage.name, filename),
size: dataSize,
shasum: shasum
};
nfs.upload(filepath, options, ep.done('uploadResult'));
});
}));
@@ -442,24 +505,44 @@ SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePacka
author: author,
publish_time: sourcePackage.publish_time,
};
if (that._publish) {
// sync as publish
mod.package._publish_on_cnpm = true;
}
var dist = {
tarball: result.url,
shasum: shasum,
size: dataSize,
noattachment: dataSize === 0,
};
if (result.url) {
dist.tarball = result.url;
} else if (result.key) {
dist.key = result.key;
dist.tarball = result.key;
}
mod.package.dist = dist;
Module.add(mod, ep.done(function (result) {
that.log(' [%s:%s] done, insertId: %s, author: %s, version: %s, size: %d, publish_time: %j',
that.log(' [%s:%s] done, insertId: %s, author: %s, version: %s, size: %d, publish_time: %j, publish on cnpm: %s',
sourcePackage.name, versionIndex,
result.id,
author, mod.version, dataSize, new Date(mod.publish_time));
author, mod.version, dataSize,
new Date(mod.publish_time),
that._publish);
callback(null, result);
}));
});
};
SyncModuleWorker.sync = function (name, username, callback) {
SyncModuleWorker.sync = function (name, username, options, callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
npm.get(name, function (err, pkg, response) {
if (err) {
return callback(err);
@@ -479,6 +562,8 @@ SyncModuleWorker.sync = function (name, username, callback) {
logId: result.id,
name: name,
username: username,
noDep: options.noDep,
publish: options.publish,
});
worker.start();
callback(null, {

View File

@@ -62,7 +62,7 @@ function routes(app) {
// 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);
app.put('/-/user/org.couchdb.user::name/-rev/:rev', [login], user.update);
// _session
app.post('/_session', user.authSession);

View File

@@ -31,6 +31,9 @@ function routes(app) {
app.get('/sync/:name', pkg.displaySync);
app.put('/sync/:name', sync.sync);
app.get('/sync/:name/log/:id', sync.getSyncLog);
app.get('/sync', pkg.displaySync);
app.get('/_list/search/search', pkg.rangeSearch);
}
module.exports = routes;

View File

@@ -65,9 +65,9 @@ app.use(function (req, res, next) {
app.use(function (err, req, res, next) {
err.url = err.url || req.url;
logger.error(err);
if (process.env.NODE_ENV !== 'test') {
console.error(err.stack);
logger.error(err);
}
if (config.debug) {
return next(err);

View File

@@ -18,6 +18,7 @@
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');
@@ -49,9 +50,16 @@ app.use(connect.json());
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)
.replace('{{logoURL}}', config.logoURL);
fs.writeFileSync(layoutFile, layout);
app.use('/', connectMarkdown({
root: docDir,
layout: path.join(viewDir, 'layout.html'),
layout: layoutFile,
titleHolder: '<%- locals.title %>',
bodyHolder: '<%- locals.body %>',
indexName: 'readme'
@@ -64,7 +72,7 @@ var helpers = {
app.use(render({
root: viewDir,
viewExt: '.html',
layout: 'layout',
layout: '_layout',
cache: config.viewCache,
helpers: helpers
}));
@@ -81,8 +89,10 @@ app.use(urlrouter(routes));
app.use(function (err, req, res, next) {
err.url = err.url || req.url;
console.error(err.stack);
logger.error(err);
if (process.env.NODE_ENV !== 'test') {
console.error(err.stack);
}
if (config.debug) {
return next(err);
}

View File

@@ -25,6 +25,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');
@@ -88,14 +89,17 @@ describe('controllers/registry/module.test.js', function () {
describe('GET /:name/:(version|tag)', function () {
it('should return module@version info', function (done) {
request(app)
.get('/cnpmjs.org/0.0.0')
.get('/cnpmjs.org/0.2.1')
.expect(200, function (err, res) {
should.not.exist(err);
var body = res.body;
body.name.should.equal('cnpmjs.org');
body.version.should.equal('0.0.0');
body._id.should.equal('cnpmjs.org@0.0.0');
body.dist.tarball.should.include('cnpmjs.org-0.0.0.tgz');
body.version.should.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();
});
});
@@ -114,14 +118,14 @@ describe('controllers/registry/module.test.js', function () {
});
});
it('should get cnpmjs.org@0.1.0 with _publish_on_cnpm=true', function (done) {
it('should get cnpmjs.org@0.2.1 with _publish_on_cnpm=true', function (done) {
request(app)
.get('/cnpmjs.org/0.1.0')
.get('/cnpmjs.org/0.2.1')
.expect(200, function (err, res) {
should.not.exist(err);
var body = res.body;
body.name.should.equal('cnpmjs.org');
body.version.should.equal('0.1.0');
body.version.should.equal('0.2.1');
body._publish_on_cnpm.should.equal(true);
done();
});
@@ -137,6 +141,13 @@ describe('controllers/registry/module.test.js', function () {
name: 'cnpmjstest10',
email: 'cnpmjstest10@cnpmjs.org'
}],
keywords: [
'testputmodule', 'test', 'cnpmjstest'
],
dependencies: {
'foo-testputmodule': "*",
'bar-testputmodule': '*'
}
};
var baseauth = 'Basic ' + new Buffer('cnpmjstest10:cnpmjstest10').toString('base64');
var baseauthOther = 'Basic ' + new Buffer('cnpmjstest101:cnpmjstest101').toString('base64');
@@ -172,14 +183,7 @@ describe('controllers/registry/module.test.js', function () {
.put('/' + pkg.name)
.set('authorization', baseauth)
.send(pkg)
.expect(409, function (err, res) {
should.not.exist(err);
res.body.should.eql({
error: 'conflict',
reason: 'Document update conflict.'
});
done();
});
.expect(201, done);
});
it('should try to add return 403 when not module user and only next module exists',
@@ -291,6 +295,7 @@ describe('controllers/registry/module.test.js', function () {
var pkg = require(path.join(fixtures, 'testputmodule.json')).versions['0.1.8'];
pkg.name = 'testputmodule';
pkg.version = '0.1.9';
pkg.dependencies['foo-testputmodule'] = '*';
request(app)
.put('/' + pkg.name + '/' + pkg.version + '/-tag/latest')
.set('authorization', baseauth)
@@ -298,7 +303,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();
});
});
});
@@ -442,6 +456,7 @@ describe('controllers/registry/module.test.js', function () {
it('should remove version ok', function (done) {
//do not really remove it here
mm.empty(Module, 'removeByNameAndVersions');
mm.empty(Module, 'removeTagsByIds');
request(app)
.put('/testputmodule/-rev/' + lastRev)
.set('authorization', baseauth)
@@ -453,6 +468,43 @@ describe('controllers/registry/module.test.js', function () {
});
});
describe('GET /:name/download/:filename', function () {
it('should download a file with 302 redirect', function (done) {
request(app)
.get('/cutter/download/cutter-0.0.2.tgz')
.expect('Location', 'http://qtestbucket.qiniudn.com/cutter/-/cutter-0.0.2.tgz')
.expect(302, done)
});
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 () {
var lastRev;
before(function (done) {

View File

@@ -40,6 +40,54 @@ describe('controllers/sync.test.js', function () {
describe('sync source npm package', function () {
var logIdRegistry;
var logIdWeb;
it('should sync as publish success', function (done) {
request(registryApp)
.del('/utility/-rev/123')
.set('authorization', baseauth)
// .expect(200)
// .expect({ok: true})
.end(function (err, res) {
should.not.exist(err);
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(registryApp)
.put('/utility/sync?publish=true&nodeps=true')
.set('authorization', baseauth)
.end(function (err, res) {
should.not.exist(err);
res.body.should.have.keys('ok', 'logId');
logIdRegistry = res.body.logId;
done();
// 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);
});
});
});
it('should sync as publish 403 when user not admin', function (done) {
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
request(registryApp)
.put('/utility_unit_test/sync?publish=true&nodeps=true')
.expect(403)
.expect({
error: 'no_perms',
reason: 'Only admin can publish'
}, done);
});
it('should sync success', function (done) {
mm.data(Npm, 'get', require(path.join(fixtures, 'utility.json')));
done = pedding(2, done);

View File

@@ -31,6 +31,48 @@ describe('controllers/web/package.test.js', function () {
afterEach(mm.restore);
describe('GET /_list/search/search', function () {
it('should search with "c"', function (done) {
request(app)
.get('/_list/search/search?startkey="c"&limit=2')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('rows');
res.body.rows.length.should.above(0);
res.body.rows.forEach(function (row) {
row.should.have.keys('key', 'count', 'value');
row.value.should.have.keys('name', 'description');
});
done();
});
});
it('should search with c', function (done) {
request(app)
.get('/_list/search/search?startkey=c&limit=2')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.have.keys('rows');
res.body.rows.length.should.above(0);
res.body.rows.forEach(function (row) {
row.should.have.keys('key', 'count', 'value');
row.value.should.have.keys('name', 'description');
});
done();
});
});
it('should search return empty', function (done) {
request(app)
.get('/_list/search/search?startkey="cddddsdasdaasds"&limit=2')
.expect(200, function (err, res) {
should.not.exist(err);
res.body.should.eql({rows: []});
done();
});
});
});
describe('GET /package/:name', function (done) {
it('should get 200', function (done) {
request(app)

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

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

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

View File

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

View File

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

@@ -30,6 +30,8 @@ Log.create({
name: names,
username: 'fengmk2',
concurrency: names.length,
noDep: true,
publish: true,
});
worker.start();

View File

@@ -44,7 +44,7 @@ describe('sync/sync_exist.js', function () {
should.not.exist(err);
data.successes.should.eql(['cnpmjs.org', 'cutter']);
done();
});
});
});
});
});

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

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

View File

@@ -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,26 +45,32 @@
return location.href = '/browse/keyword/' + val;
}
});
$(".nav-tabs li").each(function (index) {
$(this).data("index", index);
})
.on("click", function (e) {
e.preventDefault();
$(".nav-tabs li.active,.nav-cont.active").removeClass("active");
$(this).addClass("active");
$(".nav-cont").eq($(this).data("index")).addClass("active");
});
});
</script>
</head>
<body>
<div class="container">
<div id="content-header">
<a href="/"><img src="http://ww4.sinaimg.cn/large/69c1d4acgw1ebfly5kjlij208202oglr.jpg"></a>
<a href="/"><img src="{{logoURL}}"></a>
<input type="text" id="search-input" class="form-control" placeholder="Search Packages">
</div>
<div id="content-body"><%- locals.body %></div>
<div class="bottom">
<hr/>
<p>
Copyright 2013 - 2014 &copy; cnpmjs.org
|
<a href="/">Home</a>
|
<script>var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_5757157'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s17.cnzz.com/stat.php%3Fid%3D5757157%26online%3D1%26show%3Dline' type='text/javascript'%3E%3C/script%3E"));</script>
{{footer}}
</p>
<a href="https://github.com/fengmk2/cnpmjs.org" id="fork" target="_blank">
<a href="https://github.com/cnpm/cnpmjs.org" id="fork" target="_blank">
<img alt="Fork me on GitHub" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
</a>
</div>

View File

@@ -1,7 +1,13 @@
<div id="package">
<h1>
<%= package.name %>
<small>(<a href="/sync/<%= package.name %>" target="_blank">SYNC</a> missed versions from official npm registry)</small>
<small>
<% if (package._publish_on_cnpm) { %>
(Private package)
<% } else { %>
(<a href="/sync/<%= package.name %>" target="_blank">SYNC</a> missed versions from official npm registry)
<% } %>
</small>
</h1>
<% if (package.deprecated) { %>
@@ -12,193 +18,231 @@
<p class="description"><%= package.description %></p>
<% } %>
<pre class="sh"><code>$ cnpm install <%= package.name %></code></pre>
<table class="downloads">
<tbody>
<tr>
<td class="count"><%- download.today %></td><td> downloads today</td>
<td class="count"><%- download.thisweek %></td><td> downloads in this week</td>
<td class="count"><%- download.thismonth %></td><td> downloads in this month</td>
</tr>
<tr>
<td class="count"><%- download.lastday %></td><td> downloads in the last day</td>
<td class="count"><%- download.lastweek %></td><td> downloads in the last week</td>
<td class="count"><%- download.lastmonth %></td><td> downloads in the last month</td>
</tr>
</tbody>
</table>
<table class="metadata">
<% if (package.maintainers) { %>
<tr>
<th>Maintainers</th>
<td>
<% package.maintainers.forEach(function (m) { %>
<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 (package.contributors) { %>
<tr>
<th>Contributors</th>
<td>
<% package.contributors.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>
<% } %>
</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

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