Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/package.json
npm-debug.log
sinopia-*.tgz
.DS_Store
Expand All @@ -9,3 +8,9 @@ bin/**
test-storage*

node_modules

# Visual Studio Code
.vscode/*
.jscsrc
.jshintrc
jsconfig.json
1 change: 0 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
node_modules
package.json
npm-debug.log
sinopia-*.tgz

Expand Down
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readYAML('package.yaml'),
pkg: grunt.file.readJSON('package.json'),
browserify: {
dist: {
files: {
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
`verdaccio` is a fork of `sinopia`. It aims to keep backwards compatibility with `sinopia`, while keeping up with npm changes.

`sinopia` - a private/caching npm repository server

[![npm version badge](https://img.shields.io/npm/v/sinopia.svg)](https://www.npmjs.org/package/sinopia)
[![travis badge](http://img.shields.io/travis/rlidwka/sinopia.svg)](https://travis-ci.org/rlidwka/sinopia)
[![downloads badge](http://img.shields.io/npm/dm/sinopia.svg)](https://www.npmjs.org/package/sinopia)
[![travis badge](http://img.shields.io/travis/verdaccio/verdaccio.svg)](https://travis-ci.org/verdaccio/verdaccio)

It allows you to have a local npm registry with zero configuration. You don't have to install and replicate an entire CouchDB database. Sinopia keeps its own small database and, if a package doesn't exist there, it asks npmjs.org for it keeping only those packages you use.

Expand Down
22 changes: 22 additions & 0 deletions conf/full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,25 @@ logs:
# and the highest semver is placed instead.
#ignore_latest_tag: false


# Notify Settings
# Notify was built primarily to use with Slack's Incoming
# webhooks, but will also deliver a simple payload to
# any endpoint. Currently only active for publish / create
# commands.
notify:
# Choose a method. Technically this will accept any HTTP
# request method, but probably stick to GET or POST
method: POST
# If this endpoint requires specific headers, set them here
# as an array of key: value objects.
headers: [{'Content-type': 'application/x-www-form-urlencoded'}]
# set the URL endpoint for this call
endpoint: https://hooks.slack.com/...
# Finally, the content you will be sending in the body.
# This data will first be run through Handlebars to parse
# any Handlebar expressions. All data housed in the metadata object
# is available for use within the expressions.
content: ' {{ handlebar-expression }}'
# For Slack, follow the following format:
# content: '{ "text": "Package *{{ name }}* published to version *{{ dist-tags.latest }}*", "username": "Verdaccio", "icon_emoji": ":package:" }'
9 changes: 5 additions & 4 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ var Path = require('path')
var URL = require('url')
var server = require('./index')
var Utils = require('./utils')
var pkg_file = '../package.yaml'
var pkg = YAML.safeLoad(fs.readFileSync(__dirname+'/'+ pkg_file, 'utf8'))
var pkginfo = require('pkginfo')(module); // eslint-disable-line no-unused-vars
var pkgVersion = module.exports.version
var pkgName = module.exports.name

commander
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
.option('-c, --config <config.yaml>', 'use this configuration file (default: ./config.yaml)')
.version(pkg.version)
.version(pkgVersion)
.parse(process.argv)

if (commander.args.length == 1 && !commander.config) {
Expand Down Expand Up @@ -161,7 +162,7 @@ function afterConfigLoad() {
pathname: '/',
})
),
version: 'Sinopia/'+pkg.version,
version: pkgName + '/' + pkgVersion,
}, 'http address - @{addr}')
})

Expand Down
12 changes: 5 additions & 7 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
var assert = require('assert')
var Crypto = require('crypto')
var fs = require('fs')
var YAML = require('js-yaml')
var Error = require('http-errors')
var minimatch = require('minimatch')
var Path = require('path')
var LocalData = require('./local-data')
var Utils = require('./utils')
var pkg_file = '../package.yaml'
var pkg = YAML.safeLoad(fs.readFileSync(__dirname+'/'+pkg_file, 'utf8'))
var Utils = require('./utils')
var pkginfo = require('pkginfo')(module) // eslint-disable-line no-unused-vars
var pkgVersion = module.exports.version
var pkgName = module.exports.name

// [[a, [b, c]], d] -> [a, b, c, d]
function flatten(array) {
Expand All @@ -28,7 +28,7 @@ function Config(config) {
for (var i in config) {
if (self[i] == null) self[i] = config[i]
}
if (!self.user_agent) self.user_agent = 'Sinopia/'+pkg.version
if (!self.user_agent) self.user_agent = pkgName + '/' + pkgVersion

// some weird shell scripts are valid yaml files parsed as string
assert.equal(typeof(config), 'object', 'CONFIG: it doesn\'t look like a valid config file')
Expand Down Expand Up @@ -142,8 +142,6 @@ function Config(config) {
self.server_id = Crypto.pseudoRandomBytes(6).toString('hex')
}

if (self.ignore_latest_tag == null) self.ignore_latest_tag = false

return self
}

Expand Down
152 changes: 152 additions & 0 deletions lib/file-locking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/**
* file-locking.js - file system locking (replaces fs-ext)
*/

var async = require('async'),
locker = require('lockfile'),
fs = require('fs'),
path = require('path')

// locks a file by creating a lock file
function lockFile(name, next) {
var lockFileName = name + '.lock',
lockOpts = {
wait: 1000, // time (ms) to wait when checking for stale locks
pollPeriod: 100, // how often (ms) to re-check stale locks

stale: 5 * 60 * 1000, // locks are considered stale after 5 minutes

retries: 100, // number of times to attempt to create a lock
retryWait: 100 // time (ms) between tries
}

async.series({

statdir: function (callback) {
// test to see if the directory exists
fs.stat(path.dirname(name), function (err, stats) {
if (err) {
callback(err)
} else if (!stats.isDirectory()) {
callback(new Error(path.dirname(name) + ' is not a directory'))
} else {
callback(null)
}
})
},

statfile: function (callback) {
// test to see if the file to lock exists
fs.stat(name, function (err, stats) {
if (err) {
callback(err)
} else if (!stats.isFile()) {
callback(new Error(path.dirname(name) + ' is not a file'))
} else {
callback(null)
}
});
},

lockfile: function (callback) {
// try to lock the file
locker.lock(lockFileName, lockOpts, callback)
}

}, function (err) {
if (err) {
// lock failed
return next(err)
}

// lock succeeded
return next(null);
})

}

// unlocks file by removing existing lock file
function unlockFile(name, next) {
var lockFileName = name + '.lock'

locker.unlock(lockFileName, function (err) {
if (err) {
return next(err)
}

return next(null)
})
}

/**
* reads a local file, which involves
* optionally taking a lock
* reading the file contents
* optionally parsing JSON contents
*/
function readFile(name, options, next) {
if (typeof options === 'function' && next === null) {
next = options;
options = {}
}

options = options || {}
options.lock = options.lock || false
options.parse = options.parse || false

function lock(callback) {
if (!options.lock) {
return callback(null)
}

lockFile(name, function (err) {
if (err) {
return callback(err)
}
return callback(null)
})
}

function read(callback) {
fs.readFile(name, 'utf8', function (err, contents) {
if (err) {
return callback(err)
}

callback(null, contents)

})
}

function parseJSON(contents, callback) {
if (!options.parse) {
return callback(null, contents)
}

try {
contents = JSON.parse(contents)
return callback(null, contents)
} catch (err) {
return callback(err)
}
}

async.waterfall([
lock,
read,
parseJSON
],

function (err, result) {
if (err) {
return next(err)
} else {
return next(null, result)
}
})
}

exports.lockFile = lockFile;
exports.unlockFile = unlockFile;

exports.readFile = readFile;
8 changes: 5 additions & 3 deletions lib/index-api.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
var Cookies = require('cookies')
var express = require('express')
var expressJson5 = require('express-json5')
var bodyParser = require('body-parser')
var Error = require('http-errors')
var Path = require('path')
var Middleware = require('./middleware')
var Notify = require('./notify')
var Utils = require('./utils')
var expect_json = Middleware.expect_json
var match = Middleware.match
Expand All @@ -14,6 +15,7 @@ var validate_pkg = Middleware.validate_package
module.exports = function(config, auth, storage) {
var app = express.Router()
var can = Middleware.allow(auth)
var notify = Notify.notify;

// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
Expand All @@ -30,7 +32,7 @@ module.exports = function(config, auth, storage) {

app.use(auth.basic_middleware())
//app.use(auth.bearer_middleware())
app.use(expressJson5({ strict: false, limit: config.max_body_size || '10mb' }))
app.use(bodyParser.json({ strict: false, limit: config.max_body_size || '10mb' }))
app.use(Middleware.anti_loop(config))

// encode / in a scoped package name to be matched as a single parameter in routes
Expand Down Expand Up @@ -336,7 +338,7 @@ module.exports = function(config, auth, storage) {

add_tags(metadata['dist-tags'], function(err) {
if (err) return next(err)

notify(metadata, config)
res.status(201)
return next({ ok: ok_message })
})
Expand Down
5 changes: 3 additions & 2 deletions lib/index-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ module.exports = function(config, auth, storage) {
async.filterSeries(packages, function(package, cb) {
auth.allow_access(package.name, req.remote_user, function(err, allowed) {
setImmediate(function () {
cb(!err && allowed)
cb(err, allowed)
})
})
}, function(packages) {
}, function(err, packages) {
if (err) throw err
packages.sort(function(p1, p2) {
if (p1.name < p2.name) {
return -1;
Expand Down
Loading