summaryrefslogtreecommitdiff
path: root/deps/npm/node_modules/libnpmpublish/publish.js
diff options
context:
space:
mode:
Diffstat (limited to 'deps/npm/node_modules/libnpmpublish/publish.js')
-rw-r--r--deps/npm/node_modules/libnpmpublish/publish.js249
1 files changed, 114 insertions, 135 deletions
diff --git a/deps/npm/node_modules/libnpmpublish/publish.js b/deps/npm/node_modules/libnpmpublish/publish.js
index de5af4f5d3..8a382e4ad9 100644
--- a/deps/npm/node_modules/libnpmpublish/publish.js
+++ b/deps/npm/node_modules/libnpmpublish/publish.js
@@ -1,96 +1,98 @@
'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 cloneDeep = require('lodash.clonedeep')
+const npa = require('npm-package-arg')
+const pack = require('libnpmpack')
const semver = require('semver')
+const { URL } = require('url')
+const util = require('util')
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 }
-})
+const statAsync = util.promisify(require('fs').stat)
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' }
- )
- }
+async function publish (folder, manifest, 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' }
+ )
+ }
- 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
- }))
- })
- })
+ // spec is used to pick the appropriate registry/auth combo
+ const spec = npa.resolve(manifest.name, manifest.version)
+ opts = {
+ defaultTag: 'latest',
+ // if scoped, restricted by default
+ access: spec.scope ? 'restricted' : 'public',
+ algorithms: ['sha512'],
+ ...opts,
+ spec
+ }
+
+ const stat = await statAsync(folder)
+ // checks if it's a dir
+ if (!stat.isDirectory()) {
+ throw Object.assign(
+ new Error('not a directory'),
+ { code: 'ENOTDIR' }
+ )
+ }
+
+ const reg = npmFetch.pickRegistry(spec, opts)
+ const pubManifest = patchManifest(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' }
+ )
+ }
+
+ const tarballData = await pack(`file:${folder}`, { ...opts })
+ const metadata = buildMetadata(reg, pubManifest, tarballData, opts)
+
+ try {
+ return await npmFetch(spec.escapedName, {
+ ...opts,
+ method: 'PUT',
+ body: metadata,
+ ignoreBody: true
})
- }).then(() => true)
+ } catch (err) {
+ if (err.code !== 'E409') { throw err }
+ // if E409, we attempt exactly ONE retry, to protect us
+ // against malicious activity like trying to publish
+ // a bunch of new versions of a package at the same time
+ // and/or spamming the registry
+ const current = await npmFetch.json(spec.escapedName, {
+ ...opts,
+ query: { write: true }
+ })
+ const newMetadata = patchMetadata(current, metadata, opts)
+ return npmFetch(spec.escapedName, {
+ ...opts,
+ method: 'PUT',
+ body: newMetadata,
+ ignoreBody: true
+ })
+ }
}
-function patchedManifest (spec, auth, base, opts) {
- const manifest = cloneDeep(base)
+function patchManifest (_manifest, opts) {
+ const { npmVersion } = opts
+ const manifest = cloneDeep(_manifest)
+
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
- }
+ if (npmVersion) {
+ manifest._npmVersion = npmVersion
}
fixer.fixNameField(manifest, { strict: true, allowLegacyCase: true })
@@ -105,53 +107,52 @@ function patchedManifest (spec, auth, base, opts) {
return manifest
}
-function buildMetadata (spec, auth, registry, manifest, tardata, opts) {
+function buildMetadata (registry, manifest, tarballData, opts) {
+ const { access, defaultTag, algorithms } = opts
const root = {
_id: manifest.name,
name: manifest.name,
description: manifest.description,
'dist-tags': {},
versions: {},
+ access,
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.versions[manifest.version] = manifest
+ const tag = manifest.tag || defaultTag
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))]
+ const tarballName = `${manifest.name}-${manifest.version}.tgz`
+ const tarballURI = `${manifest.name}/-/${tarballName}`
+ const integrity = ssri.fromData(tarballData, {
+ algorithms: [...new Set(['sha1'].concat(algorithms))]
})
- manifest._id = manifest.name + '@' + manifest.version
- manifest.dist = manifest.dist || {}
+ 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()
+ manifest.dist.integrity = integrity.sha512[0].toString()
// Legacy shasum support
- manifest.dist.shasum = integrity['sha1'][0].hexDigest()
- manifest.dist.tarball = url.resolve(registry, tbURI)
+ manifest.dist.shasum = integrity.sha1[0].hexDigest()
+
+ // NB: the CLI always fetches via HTTPS if the registry is HTTPS,
+ // regardless of what's here. This makes it so that installing
+ // from an HTTP-only mirror doesn't cause problems, though.
+ manifest.dist.tarball = new URL(tarballURI, registry).href
.replace(/^https:\/\//, 'http://')
root._attachments = {}
- root._attachments[ tbName ] = {
- 'content_type': 'application/octet-stream',
- 'data': tardata.toString('base64'),
- 'length': tardata.length
+ root._attachments[tarballName] = {
+ content_type: 'application/octet-stream',
+ data: tarballData.toString('base64'),
+ length: tarballData.length
}
return root
}
-function patchMetadata (current, newData, opts) {
+function patchMetadata (current, newData) {
const curVers = Object.keys(current.versions || {}).map(v => {
return semver.clean(v, true)
}).concat(Object.keys(current.time || {}).map(v => {
@@ -161,7 +162,15 @@ function patchMetadata (current, newData, opts) {
const newVersion = Object.keys(newData.versions)[0]
if (curVers.indexOf(newVersion) !== -1) {
- throw ConflictError(newData.name, newData.version)
+ const { name: pkgid, version } = newData
+ throw Object.assign(
+ new Error(
+ `Cannot publish ${pkgid}@${version} over existing version.`
+ ), {
+ code: 'EPUBLISHCONFLICT',
+ pkgid,
+ version
+ })
}
current.versions = current.versions || {}
@@ -178,41 +187,11 @@ function patchMetadata (current, newData, opts) {
}
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
- })
+ return current
}