1 module.exports = publish
3 var url = require('url')
4 var semver = require('semver')
5 var crypto = require('crypto')
6 var Stream = require('stream').Stream
7 var assert = require('assert')
8 var fixer = require('normalize-package-data').fixer
9 var concat = require('concat-stream')
11 function escaped (name) {
12 return name.replace('/', '%2f')
15 function publish (uri, params, cb) {
16 assert(typeof uri === 'string', 'must pass registry URI to publish')
17 assert(params && typeof params === 'object', 'must pass params to publish')
18 assert(typeof cb === 'function', 'must pass callback to publish')
20 var access = params.access
22 (!access) || ['public', 'restricted'].indexOf(access) !== -1,
23 "if present, access level must be either 'public' or 'restricted'"
26 var auth = params.auth
27 assert(auth && typeof auth === 'object', 'must pass auth to publish')
29 (auth.password && auth.username && auth.email))) {
30 var er = new Error('auth required for publishing')
35 var metadata = params.metadata
37 metadata && typeof metadata === 'object',
38 'must pass package metadata to publish'
41 fixer.fixNameField(metadata, {strict: true, allowLegacyCase: true})
45 var version = semver.clean(metadata.version)
46 if (!version) return cb(new Error('invalid semver: ' + metadata.version))
47 metadata.version = version
49 var body = params.body
50 assert(body, 'must pass package body to publish')
51 assert(body instanceof Stream, 'package body passed to publish must be a stream')
53 var sink = concat(function (tarbuffer) {
54 putFirst.call(client, uri, metadata, tarbuffer, access, auth, cb)
60 function putFirst (registry, data, tarbuffer, access, auth, cb) {
61 // optimistically try to PUT all in one single atomic thing.
62 // If 409, then GET and merge, try again.
63 // If other error, then fail.
68 description: data.description,
71 readme: data.readme || ''
74 if (access) root.access = access
77 root.maintainers = [{ name: auth.username, email: auth.email }]
78 data.maintainers = JSON.parse(JSON.stringify(root.maintainers))
81 root.versions[ data.version ] = data
82 var tag = data.tag || this.config.defaultTag
83 root['dist-tags'][tag] = data.version
85 var tbName = data.name + '-' + data.version + '.tgz'
86 var tbURI = data.name + '/-/' + tbName
88 data._id = data.name + '@' + data.version
89 data.dist = data.dist || {}
90 data.dist.shasum = crypto.createHash('sha1').update(tarbuffer).digest('hex')
91 data.dist.tarball = url.resolve(registry, tbURI)
92 .replace(/^https:\/\//, 'http://')
94 root._attachments = {}
95 root._attachments[ tbName ] = {
96 'content_type': 'application/octet-stream',
97 'data': tarbuffer.toString('base64'),
98 'length': tarbuffer.length
101 var fixed = url.resolve(registry, escaped(data.name))
108 this.request(fixed, options, function (er, parsed, json, res) {
109 var r409 = 'must supply latest _rev to update existing package'
110 var r409b = 'Document update conflict.'
111 var conflict = res && res.statusCode === 409
112 if (parsed && (parsed.reason === r409 || parsed.reason === r409b)) {
116 // a 409 is typical here. GET the data and merge in.
117 if (er && !conflict) {
118 client.log.error('publish', 'Failed PUT ' + (res && res.statusCode))
122 if (!er && !conflict) return cb(er, parsed, json, res)
124 // let's see what versions are already published.
125 client.request(fixed + '?write=true', { auth: auth }, function (er, current) {
126 if (er) return cb(er)
128 putNext.call(client, registry, data.version, root, current, auth, cb)
133 function putNext (registry, newVersion, root, current, auth, cb) {
134 // already have the tardata on the root object
135 // just merge in existing stuff
136 var curVers = Object.keys(current.versions || {}).map(function (v) {
137 return semver.clean(v, true)
138 }).concat(Object.keys(current.time || {}).map(function (v) {
139 if (semver.valid(v, true)) return semver.clean(v, true)
140 }).filter(function (v) {
144 if (curVers.indexOf(newVersion) !== -1) {
145 return cb(conflictError(root.name, newVersion))
148 current.versions[newVersion] = root.versions[newVersion]
149 current._attachments = current._attachments || {}
150 for (var i in root) {
152 // objects that copy over the new stuffs
156 for (var j in root[i]) {
157 current[i][j] = root[i][j]
170 var maint = JSON.parse(JSON.stringify(root.maintainers))
171 root.versions[newVersion].maintainers = maint
173 var uri = url.resolve(registry, escaped(root.name))
179 this.request(uri, options, cb)
182 function conflictError (pkgid, version) {
183 var e = new Error('cannot modify pre-existing version')
184 e.code = 'EPUBLISHCONFLICT'