| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 | module.exports = ownerconst BB = require('bluebird')const log = require('npmlog')const npa = require('libnpm/parse-arg')const npmConfig = require('./config/figgy-config.js')const npmFetch = require('libnpm/fetch')const output = require('./utils/output.js')const otplease = require('./utils/otplease.js')const packument = require('libnpm/packument')const readLocalPkg = BB.promisify(require('./utils/read-local-package.js'))const usage = require('./utils/usage')const whoami = BB.promisify(require('./whoami.js'))owner.usage = usage(  'owner',  'npm owner add <user> [<@scope>/]<pkg>' +  '\nnpm owner rm <user> [<@scope>/]<pkg>' +  '\nnpm owner ls [<@scope>/]<pkg>')owner.completion = function (opts, cb) {  const argv = opts.conf.argv.remain  if (argv.length > 4) return cb()  if (argv.length <= 2) {    var subs = ['add', 'rm']    if (opts.partialWord === 'l') subs.push('ls')    else subs.push('ls', 'list')    return cb(null, subs)  }  BB.try(() => {    const opts = npmConfig()    return whoami([], true).then(username => {      const un = encodeURIComponent(username)      let byUser, theUser      switch (argv[2]) {        case 'ls':          // FIXME: there used to be registry completion here, but it stopped          // making sense somewhere around 50,000 packages on the registry          return        case 'rm':          if (argv.length > 3) {            theUser = encodeURIComponent(argv[3])            byUser = `/-/by-user/${theUser}|${un}`            return npmFetch.json(byUser, opts).then(d => {              return d[theUser].filter(                // kludge for server adminery.                p => un === 'isaacs' || d[un].indexOf(p) === -1              )            })          }          // else fallthrough          /* eslint no-fallthrough:0 */        case 'add':          if (argv.length > 3) {            theUser = encodeURIComponent(argv[3])            byUser = `/-/by-user/${theUser}|${un}`            return npmFetch.json(byUser, opts).then(d => {              var mine = d[un] || []              var theirs = d[theUser] || []              return mine.filter(p => theirs.indexOf(p) === -1)            })          } else {            // just list all users who aren't me.            return npmFetch.json('/-/users', opts).then(list => {              return Object.keys(list).filter(n => n !== un)            })          }        default:          return cb()      }    })  }).nodeify(cb)}function UsageError () {  throw Object.assign(new Error(owner.usage), {code: 'EUSAGE'})}function owner ([action, ...args], cb) {  const opts = npmConfig()  BB.try(() => {    switch (action) {      case 'ls': case 'list': return ls(args[0], opts)      case 'add': return add(args[0], args[1], opts)      case 'rm': case 'remove': return rm(args[0], args[1], opts)      default: UsageError()    }  }).then(    data => cb(null, data),    err => err.code === 'EUSAGE' ? cb(err.message) : cb(err)  )}function ls (pkg, opts) {  if (!pkg) {    return readLocalPkg().then(pkg => {      if (!pkg) { UsageError() }      return ls(pkg, opts)    })  }  const spec = npa(pkg)  return packument(spec, opts.concat({fullMetadata: true})).then(    data => {      var owners = data.maintainers      if (!owners || !owners.length) {        output('admin party!')      } else {        output(owners.map(o => `${o.name} <${o.email}>`).join('\n'))      }      return owners    },    err => {      log.error('owner ls', "Couldn't get owner data", pkg)      throw err    }  )}function add (user, pkg, opts) {  if (!user) { UsageError() }  if (!pkg) {    return readLocalPkg().then(pkg => {      if (!pkg) { UsageError() }      return add(user, pkg, opts)    })  }  log.verbose('owner add', '%s to %s', user, pkg)  const spec = npa(pkg)  return withMutation(spec, user, opts, (u, owners) => {    if (!owners) owners = []    for (var i = 0, l = owners.length; i < l; i++) {      var o = owners[i]      if (o.name === u.name) {        log.info(          'owner add',          'Already a package owner: ' + o.name + ' <' + o.email + '>'        )        return false      }    }    owners.push(u)    return owners  })}function rm (user, pkg, opts) {  if (!user) { UsageError() }  if (!pkg) {    return readLocalPkg().then(pkg => {      if (!pkg) { UsageError() }      return add(user, pkg, opts)    })  }  log.verbose('owner rm', '%s from %s', user, pkg)  const spec = npa(pkg)  return withMutation(spec, user, opts, function (u, owners) {    let found = false    const m = owners.filter(function (o) {      var match = (o.name === user)      found = found || match      return !match    })    if (!found) {      log.info('owner rm', 'Not a package owner: ' + user)      return false    }    if (!m.length) {      throw new Error(        'Cannot remove all owners of a package.  Add someone else first.'      )    }    return m  })}function withMutation (spec, user, opts, mutation) {  return BB.try(() => {    if (user) {      const uri = `/-/user/org.couchdb.user:${encodeURIComponent(user)}`      return npmFetch.json(uri, opts).then(mutate_, err => {        log.error('owner mutate', 'Error getting user data for %s', user)        throw err      })    } else {      return mutate_(null)    }  })  function mutate_ (u) {    if (user && (!u || u.error)) {      throw new Error(        "Couldn't get user data for " + user + ': ' + JSON.stringify(u)      )    }    if (u) u = { name: u.name, email: u.email }    return packument(spec, opts.concat({      fullMetadata: true    })).then(data => {      // save the number of maintainers before mutation so that we can figure      // out if maintainers were added or removed      const beforeMutation = data.maintainers.length      const m = mutation(u, data.maintainers)      if (!m) return // handled      if (m instanceof Error) throw m // error      data = {        _id: data._id,        _rev: data._rev,        maintainers: m      }      const dataPath = `/${spec.escapedName}/-rev/${encodeURIComponent(data._rev)}`      return otplease(opts, opts => {        const reqOpts = opts.concat({          method: 'PUT',          body: data,          spec        })        return npmFetch.json(dataPath, reqOpts)      }).then(data => {        if (data.error) {          throw new Error('Failed to update package metadata: ' + JSON.stringify(data))        } else if (m.length > beforeMutation) {          output('+ %s (%s)', user, spec.name)        } else if (m.length < beforeMutation) {          output('- %s (%s)', user, spec.name)        }        return data      })    })  }}
 |