xref: /third_party/node/deps/npm/lib/commands/access.js (revision 1cb0ef41)
11cb0ef41Sopenharmony_ciconst libnpmaccess = require('libnpmaccess')
21cb0ef41Sopenharmony_ciconst npa = require('npm-package-arg')
31cb0ef41Sopenharmony_ciconst pkgJson = require('@npmcli/package-json')
41cb0ef41Sopenharmony_ciconst localeCompare = require('@isaacs/string-locale-compare')('en')
51cb0ef41Sopenharmony_ci
61cb0ef41Sopenharmony_ciconst otplease = require('../utils/otplease.js')
71cb0ef41Sopenharmony_ciconst getIdentity = require('../utils/get-identity.js')
81cb0ef41Sopenharmony_ciconst BaseCommand = require('../base-command.js')
91cb0ef41Sopenharmony_ci
101cb0ef41Sopenharmony_ciconst commands = [
111cb0ef41Sopenharmony_ci  'get',
121cb0ef41Sopenharmony_ci  'grant',
131cb0ef41Sopenharmony_ci  'list',
141cb0ef41Sopenharmony_ci  'revoke',
151cb0ef41Sopenharmony_ci  'set',
161cb0ef41Sopenharmony_ci]
171cb0ef41Sopenharmony_ci
181cb0ef41Sopenharmony_ciconst setCommands = [
191cb0ef41Sopenharmony_ci  'status=public',
201cb0ef41Sopenharmony_ci  'status=private',
211cb0ef41Sopenharmony_ci  'mfa=none',
221cb0ef41Sopenharmony_ci  'mfa=publish',
231cb0ef41Sopenharmony_ci  'mfa=automation',
241cb0ef41Sopenharmony_ci  '2fa=none',
251cb0ef41Sopenharmony_ci  '2fa=publish',
261cb0ef41Sopenharmony_ci  '2fa=automation',
271cb0ef41Sopenharmony_ci]
281cb0ef41Sopenharmony_ci
291cb0ef41Sopenharmony_ciclass Access extends BaseCommand {
301cb0ef41Sopenharmony_ci  static description = 'Set access level on published packages'
311cb0ef41Sopenharmony_ci  static name = 'access'
321cb0ef41Sopenharmony_ci  static params = [
331cb0ef41Sopenharmony_ci    'json',
341cb0ef41Sopenharmony_ci    'otp',
351cb0ef41Sopenharmony_ci    'registry',
361cb0ef41Sopenharmony_ci  ]
371cb0ef41Sopenharmony_ci
381cb0ef41Sopenharmony_ci  static usage = [
391cb0ef41Sopenharmony_ci    'list packages [<user>|<scope>|<scope:team> [<package>]',
401cb0ef41Sopenharmony_ci    'list collaborators [<package> [<user>]]',
411cb0ef41Sopenharmony_ci    'get status [<package>]',
421cb0ef41Sopenharmony_ci    'set status=public|private [<package>]',
431cb0ef41Sopenharmony_ci    'set mfa=none|publish|automation [<package>]',
441cb0ef41Sopenharmony_ci    'grant <read-only|read-write> <scope:team> [<package>]',
451cb0ef41Sopenharmony_ci    'revoke <scope:team> [<package>]',
461cb0ef41Sopenharmony_ci  ]
471cb0ef41Sopenharmony_ci
481cb0ef41Sopenharmony_ci  static async completion (opts) {
491cb0ef41Sopenharmony_ci    const argv = opts.conf.argv.remain
501cb0ef41Sopenharmony_ci    if (argv.length === 2) {
511cb0ef41Sopenharmony_ci      return commands
521cb0ef41Sopenharmony_ci    }
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci    if (argv.length === 3) {
551cb0ef41Sopenharmony_ci      switch (argv[2]) {
561cb0ef41Sopenharmony_ci        case 'grant':
571cb0ef41Sopenharmony_ci          return ['read-only', 'read-write']
581cb0ef41Sopenharmony_ci        case 'revoke':
591cb0ef41Sopenharmony_ci          return []
601cb0ef41Sopenharmony_ci        case 'list':
611cb0ef41Sopenharmony_ci        case 'ls':
621cb0ef41Sopenharmony_ci          return ['packages', 'collaborators']
631cb0ef41Sopenharmony_ci        case 'get':
641cb0ef41Sopenharmony_ci          return ['status']
651cb0ef41Sopenharmony_ci        case 'set':
661cb0ef41Sopenharmony_ci          return setCommands
671cb0ef41Sopenharmony_ci        default:
681cb0ef41Sopenharmony_ci          throw new Error(argv[2] + ' not recognized')
691cb0ef41Sopenharmony_ci      }
701cb0ef41Sopenharmony_ci    }
711cb0ef41Sopenharmony_ci  }
721cb0ef41Sopenharmony_ci
731cb0ef41Sopenharmony_ci  async exec ([cmd, subcmd, ...args]) {
741cb0ef41Sopenharmony_ci    if (!cmd) {
751cb0ef41Sopenharmony_ci      throw this.usageError()
761cb0ef41Sopenharmony_ci    }
771cb0ef41Sopenharmony_ci    if (!commands.includes(cmd)) {
781cb0ef41Sopenharmony_ci      throw this.usageError(`${cmd} is not a valid access command`)
791cb0ef41Sopenharmony_ci    }
801cb0ef41Sopenharmony_ci    // All commands take at least one more parameter so we can do this check up front
811cb0ef41Sopenharmony_ci    if (!subcmd) {
821cb0ef41Sopenharmony_ci      throw this.usageError()
831cb0ef41Sopenharmony_ci    }
841cb0ef41Sopenharmony_ci
851cb0ef41Sopenharmony_ci    switch (cmd) {
861cb0ef41Sopenharmony_ci      case 'grant':
871cb0ef41Sopenharmony_ci        if (!['read-only', 'read-write'].includes(subcmd)) {
881cb0ef41Sopenharmony_ci          throw this.usageError('grant must be either `read-only` or `read-write`')
891cb0ef41Sopenharmony_ci        }
901cb0ef41Sopenharmony_ci        if (!args[0]) {
911cb0ef41Sopenharmony_ci          throw this.usageError('`<scope:team>` argument is required')
921cb0ef41Sopenharmony_ci        }
931cb0ef41Sopenharmony_ci        return this.#grant(subcmd, args[0], args[1])
941cb0ef41Sopenharmony_ci      case 'revoke':
951cb0ef41Sopenharmony_ci        return this.#revoke(subcmd, args[0])
961cb0ef41Sopenharmony_ci      case 'list':
971cb0ef41Sopenharmony_ci      case 'ls':
981cb0ef41Sopenharmony_ci        if (subcmd === 'packages') {
991cb0ef41Sopenharmony_ci          return this.#listPackages(args[0], args[1])
1001cb0ef41Sopenharmony_ci        }
1011cb0ef41Sopenharmony_ci        if (subcmd === 'collaborators') {
1021cb0ef41Sopenharmony_ci          return this.#listCollaborators(args[0], args[1])
1031cb0ef41Sopenharmony_ci        }
1041cb0ef41Sopenharmony_ci        throw this.usageError(`list ${subcmd} is not a valid access command`)
1051cb0ef41Sopenharmony_ci      case 'get':
1061cb0ef41Sopenharmony_ci        if (subcmd !== 'status') {
1071cb0ef41Sopenharmony_ci          throw this.usageError(`get ${subcmd} is not a valid access command`)
1081cb0ef41Sopenharmony_ci        }
1091cb0ef41Sopenharmony_ci        return this.#getStatus(args[0])
1101cb0ef41Sopenharmony_ci      case 'set':
1111cb0ef41Sopenharmony_ci        if (!setCommands.includes(subcmd)) {
1121cb0ef41Sopenharmony_ci          throw this.usageError(`set ${subcmd} is not a valid access command`)
1131cb0ef41Sopenharmony_ci        }
1141cb0ef41Sopenharmony_ci        return this.#set(subcmd, args[0])
1151cb0ef41Sopenharmony_ci    }
1161cb0ef41Sopenharmony_ci  }
1171cb0ef41Sopenharmony_ci
1181cb0ef41Sopenharmony_ci  async #grant (permissions, scope, pkg) {
1191cb0ef41Sopenharmony_ci    await libnpmaccess.setPermissions(scope, pkg, permissions, this.npm.flatOptions)
1201cb0ef41Sopenharmony_ci  }
1211cb0ef41Sopenharmony_ci
1221cb0ef41Sopenharmony_ci  async #revoke (scope, pkg) {
1231cb0ef41Sopenharmony_ci    await libnpmaccess.removePermissions(scope, pkg, this.npm.flatOptions)
1241cb0ef41Sopenharmony_ci  }
1251cb0ef41Sopenharmony_ci
1261cb0ef41Sopenharmony_ci  async #listPackages (owner, pkg) {
1271cb0ef41Sopenharmony_ci    if (!owner) {
1281cb0ef41Sopenharmony_ci      owner = await getIdentity(this.npm, this.npm.flatOptions)
1291cb0ef41Sopenharmony_ci    }
1301cb0ef41Sopenharmony_ci    const pkgs = await libnpmaccess.getPackages(owner, this.npm.flatOptions)
1311cb0ef41Sopenharmony_ci    this.#output(pkgs, pkg)
1321cb0ef41Sopenharmony_ci  }
1331cb0ef41Sopenharmony_ci
1341cb0ef41Sopenharmony_ci  async #listCollaborators (pkg, user) {
1351cb0ef41Sopenharmony_ci    const pkgName = await this.#getPackage(pkg, false)
1361cb0ef41Sopenharmony_ci    const collabs = await libnpmaccess.getCollaborators(pkgName, this.npm.flatOptions)
1371cb0ef41Sopenharmony_ci    this.#output(collabs, user)
1381cb0ef41Sopenharmony_ci  }
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_ci  async #getStatus (pkg) {
1411cb0ef41Sopenharmony_ci    const pkgName = await this.#getPackage(pkg, false)
1421cb0ef41Sopenharmony_ci    const visibility = await libnpmaccess.getVisibility(pkgName, this.npm.flatOptions)
1431cb0ef41Sopenharmony_ci    this.#output({ [pkgName]: visibility.public ? 'public' : 'private' })
1441cb0ef41Sopenharmony_ci  }
1451cb0ef41Sopenharmony_ci
1461cb0ef41Sopenharmony_ci  async #set (subcmd, pkg) {
1471cb0ef41Sopenharmony_ci    const [subkey, subval] = subcmd.split('=')
1481cb0ef41Sopenharmony_ci    switch (subkey) {
1491cb0ef41Sopenharmony_ci      case 'mfa':
1501cb0ef41Sopenharmony_ci      case '2fa':
1511cb0ef41Sopenharmony_ci        return this.#setMfa(pkg, subval)
1521cb0ef41Sopenharmony_ci      case 'status':
1531cb0ef41Sopenharmony_ci        return this.#setStatus(pkg, subval)
1541cb0ef41Sopenharmony_ci    }
1551cb0ef41Sopenharmony_ci  }
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci  async #setMfa (pkg, level) {
1581cb0ef41Sopenharmony_ci    const pkgName = await this.#getPackage(pkg, false)
1591cb0ef41Sopenharmony_ci    await otplease(this.npm, this.npm.flatOptions, (opts) => {
1601cb0ef41Sopenharmony_ci      return libnpmaccess.setMfa(pkgName, level, opts)
1611cb0ef41Sopenharmony_ci    })
1621cb0ef41Sopenharmony_ci  }
1631cb0ef41Sopenharmony_ci
1641cb0ef41Sopenharmony_ci  async #setStatus (pkg, status) {
1651cb0ef41Sopenharmony_ci    // only scoped packages can have their access changed
1661cb0ef41Sopenharmony_ci    const pkgName = await this.#getPackage(pkg, true)
1671cb0ef41Sopenharmony_ci    if (status === 'private') {
1681cb0ef41Sopenharmony_ci      status = 'restricted'
1691cb0ef41Sopenharmony_ci    }
1701cb0ef41Sopenharmony_ci    await otplease(this.npm, this.npm.flatOptions, (opts) => {
1711cb0ef41Sopenharmony_ci      return libnpmaccess.setAccess(pkgName, status, opts)
1721cb0ef41Sopenharmony_ci    })
1731cb0ef41Sopenharmony_ci    return this.#getStatus(pkgName)
1741cb0ef41Sopenharmony_ci  }
1751cb0ef41Sopenharmony_ci
1761cb0ef41Sopenharmony_ci  async #getPackage (name, requireScope) {
1771cb0ef41Sopenharmony_ci    if (!name) {
1781cb0ef41Sopenharmony_ci      try {
1791cb0ef41Sopenharmony_ci        const { content } = await pkgJson.normalize(this.npm.prefix)
1801cb0ef41Sopenharmony_ci        name = content.name
1811cb0ef41Sopenharmony_ci      } catch (err) {
1821cb0ef41Sopenharmony_ci        if (err.code === 'ENOENT') {
1831cb0ef41Sopenharmony_ci          throw Object.assign(new Error('no package name given and no package.json found'), {
1841cb0ef41Sopenharmony_ci            code: 'ENOENT',
1851cb0ef41Sopenharmony_ci          })
1861cb0ef41Sopenharmony_ci        } else {
1871cb0ef41Sopenharmony_ci          throw err
1881cb0ef41Sopenharmony_ci        }
1891cb0ef41Sopenharmony_ci      }
1901cb0ef41Sopenharmony_ci    }
1911cb0ef41Sopenharmony_ci
1921cb0ef41Sopenharmony_ci    const spec = npa(name)
1931cb0ef41Sopenharmony_ci    if (requireScope && !spec.scope) {
1941cb0ef41Sopenharmony_ci      throw this.usageError('This command is only available for scoped packages.')
1951cb0ef41Sopenharmony_ci    }
1961cb0ef41Sopenharmony_ci    return name
1971cb0ef41Sopenharmony_ci  }
1981cb0ef41Sopenharmony_ci
1991cb0ef41Sopenharmony_ci  #output (items, limiter) {
2001cb0ef41Sopenharmony_ci    const output = {}
2011cb0ef41Sopenharmony_ci    const lookup = {
2021cb0ef41Sopenharmony_ci      __proto__: null,
2031cb0ef41Sopenharmony_ci      read: 'read-only',
2041cb0ef41Sopenharmony_ci      write: 'read-write',
2051cb0ef41Sopenharmony_ci    }
2061cb0ef41Sopenharmony_ci    for (const item in items) {
2071cb0ef41Sopenharmony_ci      const val = items[item]
2081cb0ef41Sopenharmony_ci      output[item] = lookup[val] || val
2091cb0ef41Sopenharmony_ci    }
2101cb0ef41Sopenharmony_ci    if (this.npm.config.get('json')) {
2111cb0ef41Sopenharmony_ci      this.npm.output(JSON.stringify(output, null, 2))
2121cb0ef41Sopenharmony_ci    } else {
2131cb0ef41Sopenharmony_ci      for (const item of Object.keys(output).sort(localeCompare)) {
2141cb0ef41Sopenharmony_ci        if (!limiter || limiter === item) {
2151cb0ef41Sopenharmony_ci          this.npm.output(`${item}: ${output[item]}`)
2161cb0ef41Sopenharmony_ci        }
2171cb0ef41Sopenharmony_ci      }
2181cb0ef41Sopenharmony_ci    }
2191cb0ef41Sopenharmony_ci  }
2201cb0ef41Sopenharmony_ci}
2211cb0ef41Sopenharmony_ci
2221cb0ef41Sopenharmony_cimodule.exports = Access
223