Compare commits

...

42 Commits

Author SHA1 Message Date
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
30 changed files with 475 additions and 110 deletions

1
.gitignore vendored
View File

@@ -19,3 +19,4 @@ config/config.js
backup/*.json
backup/*.gz
docs/web/history.md
view/web/_layout.html

View File

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

View File

@@ -1,4 +1,66 @@
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
==================

View File

@@ -9,12 +9,13 @@ 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).
![cnpm](https://docs.google.com/drawings/d/12QeQfGalqjsB77mRnf5Iq5oSXHCIUTvZTwECMonqCmw/pub?w=480&h=360)
## Install
```bash

View File

@@ -19,6 +19,8 @@ var config = require('../config');
var client = qn.create(config.qn);
exports._client = client;
/**
* Upload file
*

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

@@ -71,6 +71,10 @@ var config = {
debug: false
},
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: {

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,6 @@ 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');
exports.show = function (req, res, next) {
var name = req.params.name;
@@ -167,6 +169,8 @@ 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;
@@ -181,9 +185,9 @@ exports.download = function (req, res, next) {
}
var dist = row.package.dist;
if (dist.key) {
return ep.emit('key', dist.key);
return ep.emit('key', dist);
} else {
return ep.emit('url', dist.tarball);
return ep.emit('url', dist);
}
ep.emit('nodist');
});
@@ -195,23 +199,46 @@ exports.download = function (req, res, next) {
ep.emit('url', nfs.url(common.getCDNKey(name, filename)));
});
ep.once('url', function (url) {
ep.once('url', function (dist) {
res.statusCode = 302;
res.setHeader('Location', url);
res.setHeader('Location', dist.tarball);
res.end();
_downloads[name] = (_downloads[name] || 0) + 1;
});
ep.once('key', function (key) {
if (!nfs.download) {
ep.once('key', function (dist) {
if (!nfs.downloadStream && !nfs.download) {
return next();
}
var tmpPath = path.join(config.uploadDir, utility.randomString() + key);
_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(key, tmpPath, function (err) {
nfs.download(dist.key, tmpPath, {timeout: DOWNLOAD_TIMEOUT},
function (err) {
if (err) {
cleanup();
return next(err);
@@ -220,7 +247,6 @@ exports.download = function (req, res, next) {
tarball.on('error', cleanup);
tarball.on('end', cleanup);
tarball.pipe(res);
_downloads[name] = (_downloads[name] || 0) + 1;
});
});
};
@@ -323,8 +349,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) {
@@ -492,9 +523,12 @@ exports.addPackageAndDist = function (req, res, next) {
shasum = crypto.createHash('sha1');
shasum.update(tarballBuffer);
shasum = shasum.digest('hex');
var key = common.getCDNKey(name, filename);
nfs.uploadBuffer(tarballBuffer, {key: key}, ep.done('upload'));
var options = {
key: common.getCDNKey(name, filename),
shasum: shasum
};
nfs.uploadBuffer(tarballBuffer, options, ep.done('upload'));
});
ep.on('upload', function (result) {

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

@@ -20,10 +20,12 @@ 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 setDownloadURL = require('../../lib/common').setDownloadURL;
exports.display = function (req, res, next) {
var params = req.params;
@@ -72,6 +74,9 @@ exports.display = function (req, res, next) {
if (contributor.email) {
contributor.gravatar = gravatar.url(contributor.email, {s: '50', d: 'retro'}, false);
}
if (config.packagePageContributorSearch || !contributor.url) {
contributor.url = '/~' + encodeURIComponent(contributor.name);
}
}
}
@@ -81,6 +86,8 @@ exports.display = function (req, res, next) {
download[k] = humanize(download[k]);
}
setDownloadURL(pkg, req, config.registryHost);
res.render('package', {
title: 'Package - ' + pkg.name,
package: pkg,
@@ -105,8 +112,41 @@ exports.search = function (req, res, next) {
});
};
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, packages) {
if (err) {
return next(err);
}
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

@@ -24,46 +24,36 @@ var workerPath = path.join(__dirname, 'worker.js');
var childProcess = require('child_process');
var syncPath = path.join(__dirname, 'sync');
function start(customConfig) {
config.loadConfig(customConfig);
if (config.enableCluster) {
cluster.setupMaster({
exec: workerPath
});
if (config.enableCluster) {
cluster.setupMaster({
exec: workerPath
});
cluster.on('fork', function (worker) {
console.log('[%s] [worker:%d] new worker start', new Date(), worker.process.pid);
});
cluster.on('fork', function (worker) {
console.log('[%s] [worker:%d] new worker start', new Date(), worker.process.pid);
});
cluster.on('disconnect', function (worker) {
var w = cluster.fork();
console.error('[%s] [master:%s] wroker:%s disconnect! new worker:%s fork',
new Date(), process.pid, worker.process.pid, w.process.pid);
});
cluster.on('disconnect', function (worker) {
var w = cluster.fork();
console.error('[%s] [master:%s] wroker:%s disconnect! new worker:%s fork',
new Date(), process.pid, worker.process.pid, w.process.pid);
});
cluster.on('exit', function (worker, code, signal) {
var exitCode = worker.process.exitCode;
var err = new Error(util.format('worker %s died (code: %s, signal: %s)', worker.process.pid, exitCode, signal));
err.name = 'WorkerDiedError';
console.error(err);
});
cluster.on('exit', function (worker, code, signal) {
var exitCode = worker.process.exitCode;
var err = new Error(util.format('worker %s died (code: %s, signal: %s)', worker.process.pid, exitCode, signal));
err.name = 'WorkerDiedError';
console.error(err);
});
var numCPUs = require('os').cpus().length;
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
childProcess.fork(syncPath);
} else {
require(workerPath);
require(syncPath);
var numCPUs = require('os').cpus().length;
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
}
if (!module.parent) {
start();
childProcess.fork(syncPath);
} else {
module.exports = start;
require(workerPath);
require(syncPath);
}

View File

@@ -6,7 +6,7 @@
## 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]

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

@@ -29,11 +29,12 @@ exports.getCDNKey = function (name, filename) {
return '/' + name + '/-/' + filename;
};
exports.setDownloadURL = function (pkg, req) {
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',
req.headers.host, pkg.name, pkg.name, pkg.version);
host, pkg.name, pkg.name, pkg.version);
}
};

View File

@@ -1,8 +1,8 @@
{
"name": "cnpmjs.org",
"version": "0.2.2",
"description": "Private npm registry and web",
"main": "dispatch.js",
"version": "0.2.13",
"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",
@@ -36,6 +36,7 @@
"logfilestream": "0.1.0",
"marked": "0.3.0",
"microtime": "0.5.1",
"mime": "1.2.11",
"mkdirp": "0.3.5",
"moment": "2.5.0",
"ms": "0.6.2",

View File

@@ -296,14 +296,20 @@ 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 module_id FROM tag WHERE name LIKE ? AND tag="latest" ORDER BY name LIMIT ?;',
'SELECT name, description FROM module WHERE id IN (?) ORDER BY name;'
];
exports.search = function (word, callback) {
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) {
mysql.query(SEARCH_SQLS[0], [word, limit], ep.done(function (rows) {
if (!rows || rows.length === 0) {
return callback(null, []);
}

View File

@@ -20,11 +20,9 @@ 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;

View File

@@ -47,6 +47,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 +84,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 +125,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 +142,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 +161,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;
@@ -200,7 +202,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 +213,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 +227,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);
});
}
}
@@ -250,7 +252,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
@@ -274,7 +276,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 +284,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,7 +313,7 @@ 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) {
@@ -332,10 +334,10 @@ 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');
}));
@@ -371,8 +373,9 @@ SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePacka
callback(err);
});
that.log(' [%s:%d] syncing, version: %s, dist: %j',
sourcePackage.name, versionIndex, sourcePackage.version, sourcePackage.dist);
that.log(' [%s:%d] syncing, version: %s, dist: %j, no deps: %s, publish on cnpm: %s',
sourcePackage.name, versionIndex, sourcePackage.version,
sourcePackage.dist, that.noDep, that._publish);
if (!that.noDep) {
for (var k in sourcePackage.dependencies) {
that.add(k);
@@ -419,8 +422,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,6 +449,12 @@ 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 = {
shasum: shasum,
size: dataSize,
@@ -457,16 +470,23 @@ SyncModuleWorker.prototype._syncOneVersion = function (versionIndex, sourcePacka
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);
@@ -486,6 +506,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

@@ -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,14 @@ 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);
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'
@@ -81,8 +87,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

@@ -446,6 +446,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,53 @@ 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;
setTimeout(function () {
request(registryApp)
.get('/utility')
.expect(200)
.end(function (err, res) {
should.not.exist(err);
Object.keys(res.body.versions).length.should.above(0);
for (var v in res.body.versions) {
var pkg = res.body.versions[v];
pkg.should.have.property('_publish_on_cnpm', true);
}
done();
});
}, 3000);
});
});
});
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.should.eql([
{ key: 'c', count: 1, value: { name: 'c', description: 'Give folders or directories comments and view them easy.' } },
{ key: 'charset', count: 1,
value: { name: 'charset', description: 'Get the content charset from header and html content-type.' } }
]);
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.should.eql([
{ key: 'c', count: 1, value: { name: 'c', description: 'Give folders or directories comments and view them easy.' } },
{ key: 'charset', count: 1,
value: { name: 'charset', description: 'Get the content charset from header and html content-type.' } }
]);
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)

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

@@ -54,13 +54,9 @@
<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,7 +18,7 @@
<p class="description"><%= package.description %></p>
<% } %>
<pre class="sh"><code>$ cnpm install <%= package.name %></code></pre>
<pre class="sh"><code>$ <%- config.npmClientName %> install <%= package.name %></code></pre>
<table class="downloads">
<tbody>
@@ -175,7 +181,7 @@
<td>
<% package.contributors.forEach(function (m) { %>
<span class="user">
<a class="username" href="/~<%= m.name %>">
<a class="username" target="_blank" href="<%= m.url %>">
<% if (m.gravatar) { %>
<img src="<%- m.gravatar %>" class="avatar">
<% } %>
@@ -186,6 +192,17 @@
</td>
</tr>
<% } %>
<% if (package.dist && package.dist.tarball) { %>
<tr>
<th>Download</th>
<td>
<a class="downloadlink" target="_blank" href="<%= package.dist.tarball %>">
<%= package.dist.tarball %>
</a>
</td>
</tr>
<% } %>
</table>
<div class="details">

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) {