Skip to content

Commit

Permalink
publish: stop using npm-registry-client
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Dec 10, 2018
1 parent 90a069e commit b24ed5f
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 126 deletions.
2 changes: 2 additions & 0 deletions lib/config/figgy-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ function mkConfig (...providers) {
dmode: npm.modes.exec,
fmode: npm.modes.file,
umask: npm.modes.umask,
npmVersion: npm.version,
tmp: npm.tmp,
Promise: BB
})
const ownerStats = calculateOwner()
Expand Down
188 changes: 65 additions & 123 deletions lib/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@
const BB = require('bluebird')

const cacache = require('cacache')
const createReadStream = require('graceful-fs').createReadStream
const getPublishConfig = require('./utils/get-publish-config.js')
const figgyPudding = require('figgy-pudding')
const libpub = require('libnpm/publish')
const libunpub = require('libnpm/unpublish')
const lifecycle = BB.promisify(require('./utils/lifecycle.js'))
const log = require('npmlog')
const mapToRegistry = require('./utils/map-to-registry.js')
const npa = require('npm-package-arg')
const npm = require('./npm.js')
const npa = require('libnpm/parse-arg')
const npmConfig = require('./config/figgy-config.js')
const output = require('./utils/output.js')
const otplease = require('./utils/otplease.js')
const pack = require('./pack')
const pacote = require('pacote')
const { tarball, extract } = require('libnpm')
const pacoteOpts = require('./config/pacote')
const path = require('path')
const readFileAsync = BB.promisify(require('graceful-fs').readFile)
const readJson = BB.promisify(require('read-package-json'))
const readUserInfo = require('./utils/read-user-info.js')
const semver = require('semver')
const statAsync = BB.promisify(require('graceful-fs').stat)

Expand All @@ -31,6 +32,16 @@ publish.completion = function (opts, cb) {
return cb()
}

const PublishConfig = figgyPudding({
dryRun: 'dry-run',
'dry-run': { default: false },
force: { default: false },
json: { default: false },
Promise: { default: () => Promise },
tag: { default: 'latest' },
tmp: {}
})

module.exports = publish
function publish (args, isRetry, cb) {
if (typeof cb !== 'function') {
Expand All @@ -42,15 +53,16 @@ function publish (args, isRetry, cb) {

log.verbose('publish', args)

const t = npm.config.get('tag').trim()
const opts = PublishConfig(npmConfig())
const t = opts.tag.trim()
if (semver.validRange(t)) {
return cb(new Error('Tag name must not be a valid SemVer range: ' + t))
}

return publish_(args[0])
return publish_(args[0], opts)
.then((tarball) => {
const silent = log.level === 'silent'
if (!silent && npm.config.get('json')) {
if (!silent && opts.json) {
output(JSON.stringify(tarball, null, 2))
} else if (!silent) {
output(`+ ${tarball.id}`)
Expand All @@ -59,7 +71,7 @@ function publish (args, isRetry, cb) {
.nodeify(cb)
}

function publish_ (arg) {
function publish_ (arg, opts) {
return statAsync(arg).then((stat) => {
if (stat.isDirectory()) {
return stat
Expand All @@ -69,17 +81,17 @@ function publish_ (arg) {
throw err
}
}).then(() => {
return publishFromDirectory(arg)
return publishFromDirectory(arg, opts)
}, (err) => {
if (err.code !== 'ENOENT' && err.code !== 'ENOTDIR') {
throw err
} else {
return publishFromPackage(arg)
return publishFromPackage(arg, opts)
}
})
}

function publishFromDirectory (arg) {
function publishFromDirectory (arg, opts) {
// All this readJson is because any of the given scripts might modify the
// package.json in question, so we need to refresh after every step.
let contents
Expand All @@ -90,12 +102,12 @@ function publishFromDirectory (arg) {
}).then(() => {
return readJson(path.join(arg, 'package.json'))
}).then((pkg) => {
return cacache.tmp.withTmp(npm.tmp, {tmpPrefix: 'fromDir'}, (tmpDir) => {
return cacache.tmp.withTmp(opts.tmp, {tmpPrefix: 'fromDir'}, (tmpDir) => {
const target = path.join(tmpDir, 'package.tgz')
return pack.packDirectory(pkg, arg, target, null, true)
.tap((c) => { contents = c })
.then((c) => !npm.config.get('json') && pack.logContents(c))
.then(() => upload(arg, pkg, false, target))
.then((c) => !opts.json && pack.logContents(c))
.then(() => upload(pkg, false, target, opts))
})
}).then(() => {
return readJson(path.join(arg, 'package.json'))
Expand All @@ -107,121 +119,51 @@ function publishFromDirectory (arg) {
.then(() => contents)
}

function publishFromPackage (arg) {
return cacache.tmp.withTmp(npm.tmp, {tmpPrefix: 'fromPackage'}, (tmp) => {
function publishFromPackage (arg, opts) {
return cacache.tmp.withTmp(opts.tmp, {tmpPrefix: 'fromPackage'}, tmp => {
const extracted = path.join(tmp, 'package')
const target = path.join(tmp, 'package.json')
const opts = pacoteOpts()
return pacote.tarball.toFile(arg, target, opts)
.then(() => pacote.extract(arg, extracted, opts))
const pacOpts = pacoteOpts()
return tarball.toFile(arg, target, pacOpts)
.then(() => extract(arg, extracted, pacOpts))
.then(() => readJson(path.join(extracted, 'package.json')))
.then((pkg) => {
return BB.resolve(pack.getContents(pkg, target))
.tap((c) => !npm.config.get('json') && pack.logContents(c))
.tap(() => upload(arg, pkg, false, target))
.tap((c) => !opts.json && pack.logContents(c))
.tap(() => upload(pkg, false, target, opts))
})
})
}

function upload (arg, pkg, isRetry, cached) {
if (!pkg) {
return BB.reject(new Error('no package.json file found'))
}
if (pkg.private) {
return BB.reject(new Error(
'This package has been marked as private\n' +
"Remove the 'private' field from the package.json to publish it."
))
}
const mappedConfig = getPublishConfig(
pkg.publishConfig,
npm.config,
npm.registry
)
const config = mappedConfig.config
const registry = mappedConfig.client

pkg._npmVersion = npm.version
pkg._nodeVersion = process.versions.node

delete pkg.modules

return BB.fromNode((cb) => {
mapToRegistry(pkg.name, config, (err, registryURI, auth, registryBase) => {
if (err) { return cb(err) }
cb(null, [registryURI, auth, registryBase])
})
}).spread((registryURI, auth, registryBase) => {
// we just want the base registry URL in this case
log.verbose('publish', 'registryBase', registryBase)
log.silly('publish', 'uploading', cached)

pkg._npmUser = {
name: auth.username,
email: auth.email
}

const params = {
metadata: pkg,
body: !npm.config.get('dry-run') && createReadStream(cached),
auth: auth
}

function closeFile () {
if (!npm.config.get('dry-run')) {
params.body.close()
}
}

// registry-frontdoor cares about the access level, which is only
// configurable for scoped packages
if (config.get('access')) {
if (!npa(pkg.name).scope && config.get('access') === 'restricted') {
throw new Error("Can't restrict access to unscoped packages.")
}

params.access = config.get('access')
}

if (npm.config.get('dry-run')) {
log.verbose('publish', '--dry-run mode enabled. Skipping upload.')
return BB.resolve()
}

log.showProgress('publish:' + pkg._id)
return BB.fromNode((cb) => {
registry.publish(registryBase, params, cb)
}).catch((err) => {
if (
err.code === 'EPUBLISHCONFLICT' &&
npm.config.get('force') &&
!isRetry
) {
log.warn('publish', 'Forced publish over ' + pkg._id)
return BB.fromNode((cb) => {
npm.commands.unpublish([pkg._id], cb)
}).finally(() => {
// close the file we are trying to upload, we will open it again.
closeFile()
// ignore errors. Use the force. Reach out with your feelings.
return upload(arg, pkg, true, cached).catch(() => {
// but if it fails again, then report the first error.
throw err
function upload (pkg, isRetry, cached, opts) {
if (!opts.dryRun) {
return readFileAsync(cached).then(tarball => {
return otplease(opts, opts => {
return libpub(pkg, tarball, opts)
}).catch(err => {
if (
err.code === 'EPUBLISHCONFLICT' &&
opts.force &&
!isRetry
) {
log.warn('publish', 'Forced publish over ' + pkg._id)
return otplease(opts, opts => libunpub(
npa.resolve(pkg.name, pkg.version), opts
)).finally(() => {
// ignore errors. Use the force. Reach out with your feelings.
return otplease(opts, opts => {
return upload(pkg, true, tarball, opts)
}).catch(() => {
// but if it fails again, then report the first error.
throw err
})
})
})
} else {
// close the file we are trying to upload, all attempts to resume will open it again
closeFile()
throw err
}
})
}).catch((err) => {
if (err.code !== 'EOTP' && !(err.code === 'E401' && /one-time pass/.test(err.message))) throw err
// we prompt on stdout and read answers from stdin, so they need to be ttys.
if (!process.stdin.isTTY || !process.stdout.isTTY) throw err
return readUserInfo.otp().then((otp) => {
npm.config.set('otp', otp)
return upload(arg, pkg, isRetry, cached)
} else {
throw err
}
})
})
})
} else {
return opts.Promise.resolve(true)
}
}
12 changes: 9 additions & 3 deletions test/tap/publish-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ test(function (t) {
// itself functions normally.
//
// Make sure that we don't sit around waiting for lock files
child = common.npm(['publish', '--userconfig=' + pkg + '/fixture_npmrc', '--tag=beta'], {
child = common.npm([
'publish',
'--userconfig=' + pkg + '/fixture_npmrc',
'--tag=beta',
'--loglevel', 'error'
], {
cwd: pkg,
stdio: 'inherit',
env: {
'npm_config_cache_lock_stale': 1000,
'npm_config_cache_lock_wait': 1000,
Expand All @@ -58,7 +62,9 @@ test(function (t) {
PATH: process.env.PATH,
USERPROFILE: osenv.home()
}
}, function (err, code) {
}, function (err, code, stdout, stderr) {
t.comment(stdout)
t.comment(stderr)
t.ifError(err, 'publish command finished successfully')
t.notOk(code, 'npm install exited with code 0')
})
Expand Down

0 comments on commit b24ed5f

Please sign in to comment.