Mini Shell
'use strict'
const cloneDeep = require('lodash.clonedeep')
const figgyPudding = require('figgy-pudding')
const { fixer } = require('normalize-package-data')
const getStream = require('get-stream')
const npa = require('npm-package-arg')
const npmAuth = require('npm-registry-fetch/auth.js')
const npmFetch = require('npm-registry-fetch')
const semver = require('semver')
const ssri = require('ssri')
const url = require('url')
const validate = require('aproba')
const PublishConfig = figgyPudding({
access: {},
algorithms: { default: ['sha512'] },
npmVersion: {},
tag: { default: 'latest' },
Promise: { default: () => Promise }
})
module.exports = publish
function publish (manifest, tarball, opts) {
opts = PublishConfig(opts)
return new opts.Promise(resolve => resolve()).then(() => {
validate('OSO|OOO', [manifest, tarball, opts])
if (manifest.private) {
throw Object.assign(new Error(
'This package has been marked as private\n' +
"Remove the 'private' field from the package.json to publish it."
), { code: 'EPRIVATE' })
}
const spec = npa.resolve(manifest.name, manifest.version)
// NOTE: spec is used to pick the appropriate registry/auth combo.
opts = opts.concat(manifest.publishConfig, { spec })
const reg = npmFetch.pickRegistry(spec, opts)
const auth = npmAuth(reg, opts)
const pubManifest = patchedManifest(spec, auth, manifest, opts)
// registry-frontdoor cares about the access level, which is only
// configurable for scoped packages
if (!spec.scope && opts.access === 'restricted') {
throw Object.assign(
new Error("Can't restrict access to unscoped packages."),
{ code: 'EUNSCOPED' }
)
}
return slurpTarball(tarball, opts).then(tardata => {
const metadata = buildMetadata(
spec, auth, reg, pubManifest, tardata, opts
)
return npmFetch(spec.escapedName, opts.concat({
method: 'PUT',
body: metadata,
ignoreBody: true
})).catch(err => {
if (err.code !== 'E409') { throw err }
return npmFetch.json(spec.escapedName, opts.concat({
query: { write: true }
})).then(
current => patchMetadata(current, metadata, opts)
).then(newMetadata => {
return npmFetch(spec.escapedName, opts.concat({
method: 'PUT',
body: newMetadata,
ignoreBody: true
}))
})
})
})
}).then(() => true)
}
function patchedManifest (spec, auth, base, opts) {
const manifest = cloneDeep(base)
manifest._nodeVersion = process.versions.node
if (opts.npmVersion) {
manifest._npmVersion = opts.npmVersion
}
if (auth.username || auth.email) {
// NOTE: This is basically pointless, but reproduced because it's what
// legacy does: tl;dr `auth.username` and `auth.email` are going to be
// undefined in any auth situation that uses tokens instead of plain
// auth. I can only assume some registries out there decided that
// _npmUser would be of any use to them, but _npmUser in packuments
// currently gets filled in by the npm registry itself, based on auth
// information.
manifest._npmUser = {
name: auth.username,
email: auth.email
}
}
fixer.fixNameField(manifest, { strict: true, allowLegacyCase: true })
const version = semver.clean(manifest.version)
if (!version) {
throw Object.assign(
new Error('invalid semver: ' + manifest.version),
{ code: 'EBADSEMVER' }
)
}
manifest.version = version
return manifest
}
function buildMetadata (spec, auth, registry, manifest, tardata, opts) {
const root = {
_id: manifest.name,
name: manifest.name,
description: manifest.description,
'dist-tags': {},
versions: {},
readme: manifest.readme || ''
}
if (opts.access) root.access = opts.access
if (!auth.token) {
root.maintainers = [{ name: auth.username, email: auth.email }]
manifest.maintainers = JSON.parse(JSON.stringify(root.maintainers))
}
root.versions[ manifest.version ] = manifest
const tag = manifest.tag || opts.tag
root['dist-tags'][tag] = manifest.version
const tbName = manifest.name + '-' + manifest.version + '.tgz'
const tbURI = manifest.name + '/-/' + tbName
const integrity = ssri.fromData(tardata, {
algorithms: [...new Set(['sha1'].concat(opts.algorithms))]
})
manifest._id = manifest.name + '@' + manifest.version
manifest.dist = manifest.dist || {}
// Don't bother having sha1 in the actual integrity field
manifest.dist.integrity = integrity['sha512'][0].toString()
// Legacy shasum support
manifest.dist.shasum = integrity['sha1'][0].hexDigest()
manifest.dist.tarball = url.resolve(registry, tbURI)
.replace(/^https:\/\//, 'http://')
root._attachments = {}
root._attachments[ tbName ] = {
'content_type': 'application/octet-stream',
'data': tardata.toString('base64'),
'length': tardata.length
}
return root
}
function patchMetadata (current, newData, opts) {
const curVers = Object.keys(current.versions || {}).map(v => {
return semver.clean(v, true)
}).concat(Object.keys(current.time || {}).map(v => {
if (semver.valid(v, true)) { return semver.clean(v, true) }
})).filter(v => v)
const newVersion = Object.keys(newData.versions)[0]
if (curVers.indexOf(newVersion) !== -1) {
throw ConflictError(newData.name, newData.version)
}
current.versions = current.versions || {}
current.versions[newVersion] = newData.versions[newVersion]
for (var i in newData) {
switch (i) {
// objects that copy over the new stuffs
case 'dist-tags':
case 'versions':
case '_attachments':
for (var j in newData[i]) {
current[i] = current[i] || {}
current[i][j] = newData[i][j]
}
break
// ignore these
case 'maintainers':
break
// copy
default:
current[i] = newData[i]
}
}
const maint = newData.maintainers && JSON.parse(JSON.stringify(newData.maintainers))
newData.versions[newVersion].maintainers = maint
return current
}
function slurpTarball (tarSrc, opts) {
if (Buffer.isBuffer(tarSrc)) {
return opts.Promise.resolve(tarSrc)
} else if (typeof tarSrc === 'string') {
return opts.Promise.resolve(Buffer.from(tarSrc, 'base64'))
} else if (typeof tarSrc.pipe === 'function') {
return getStream.buffer(tarSrc)
} else {
return opts.Promise.reject(Object.assign(
new Error('invalid tarball argument. Must be a Buffer, a base64 string, or a binary stream'), {
code: 'EBADTAR'
}))
}
}
function ConflictError (pkgid, version) {
return Object.assign(new Error(
`Cannot publish ${pkgid}@${version} over existing version.`
), {
code: 'EPUBLISHCONFLICT',
pkgid,
version
})
}
Zerion Mini Shell 1.0