access.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. const path = require('path')
  2. const libaccess = require('libnpmaccess')
  3. const readPackageJson = require('read-package-json-fast')
  4. const otplease = require('./utils/otplease.js')
  5. const getIdentity = require('./utils/get-identity.js')
  6. const BaseCommand = require('./base-command.js')
  7. const subcommands = [
  8. 'public',
  9. 'restricted',
  10. 'grant',
  11. 'revoke',
  12. 'ls-packages',
  13. 'ls-collaborators',
  14. 'edit',
  15. '2fa-required',
  16. '2fa-not-required',
  17. ]
  18. class Access extends BaseCommand {
  19. static get description () {
  20. return 'Set access level on published packages'
  21. }
  22. static get name () {
  23. return 'access'
  24. }
  25. /* istanbul ignore next - see test/lib/load-all-commands.js */
  26. static get params () {
  27. return [
  28. 'registry',
  29. 'otp',
  30. ]
  31. }
  32. static get usage () {
  33. return [
  34. 'public [<package>]',
  35. 'restricted [<package>]',
  36. 'grant <read-only|read-write> <scope:team> [<package>]',
  37. 'revoke <scope:team> [<package>]',
  38. '2fa-required [<package>]',
  39. '2fa-not-required [<package>]',
  40. 'ls-packages [<user>|<scope>|<scope:team>]',
  41. 'ls-collaborators [<package> [<user>]]',
  42. 'edit [<package>]',
  43. ]
  44. }
  45. async completion (opts) {
  46. const argv = opts.conf.argv.remain
  47. if (argv.length === 2)
  48. return subcommands
  49. switch (argv[2]) {
  50. case 'grant':
  51. if (argv.length === 3)
  52. return ['read-only', 'read-write']
  53. else
  54. return []
  55. case 'public':
  56. case 'restricted':
  57. case 'ls-packages':
  58. case 'ls-collaborators':
  59. case 'edit':
  60. case '2fa-required':
  61. case '2fa-not-required':
  62. case 'revoke':
  63. return []
  64. default:
  65. throw new Error(argv[2] + ' not recognized')
  66. }
  67. }
  68. exec (args, cb) {
  69. this.access(args).then(() => cb()).catch(cb)
  70. }
  71. async access ([cmd, ...args]) {
  72. if (!cmd)
  73. throw this.usageError('Subcommand is required.')
  74. if (!subcommands.includes(cmd) || !this[cmd])
  75. throw this.usageError(`${cmd} is not a recognized subcommand.`)
  76. return this[cmd](args, this.npm.flatOptions)
  77. }
  78. public ([pkg], opts) {
  79. return this.modifyPackage(pkg, opts, libaccess.public)
  80. }
  81. restricted ([pkg], opts) {
  82. return this.modifyPackage(pkg, opts, libaccess.restricted)
  83. }
  84. async grant ([perms, scopeteam, pkg], opts) {
  85. if (!perms || (perms !== 'read-only' && perms !== 'read-write'))
  86. throw this.usageError('First argument must be either `read-only` or `read-write`.')
  87. if (!scopeteam)
  88. throw this.usageError('`<scope:team>` argument is required.')
  89. const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || []
  90. if (!scope && !team) {
  91. throw this.usageError(
  92. 'Second argument used incorrect format.\n' +
  93. 'Example: @example:developers'
  94. )
  95. }
  96. return this.modifyPackage(pkg, opts, (pkgName, opts) =>
  97. libaccess.grant(pkgName, scopeteam, perms, opts), false)
  98. }
  99. async revoke ([scopeteam, pkg], opts) {
  100. if (!scopeteam)
  101. throw this.usageError('`<scope:team>` argument is required.')
  102. const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || []
  103. if (!scope || !team) {
  104. throw this.usageError(
  105. 'First argument used incorrect format.\n' +
  106. 'Example: @example:developers'
  107. )
  108. }
  109. return this.modifyPackage(pkg, opts, (pkgName, opts) =>
  110. libaccess.revoke(pkgName, scopeteam, opts))
  111. }
  112. get ['2fa-required'] () {
  113. return this.tfaRequired
  114. }
  115. tfaRequired ([pkg], opts) {
  116. return this.modifyPackage(pkg, opts, libaccess.tfaRequired, false)
  117. }
  118. get ['2fa-not-required'] () {
  119. return this.tfaNotRequired
  120. }
  121. tfaNotRequired ([pkg], opts) {
  122. return this.modifyPackage(pkg, opts, libaccess.tfaNotRequired, false)
  123. }
  124. get ['ls-packages'] () {
  125. return this.lsPackages
  126. }
  127. async lsPackages ([owner], opts) {
  128. if (!owner)
  129. owner = await getIdentity(this.npm, opts)
  130. const pkgs = await libaccess.lsPackages(owner, opts)
  131. // TODO - print these out nicely (breaking change)
  132. this.npm.output(JSON.stringify(pkgs, null, 2))
  133. }
  134. get ['ls-collaborators'] () {
  135. return this.lsCollaborators
  136. }
  137. async lsCollaborators ([pkg, usr], opts) {
  138. const pkgName = await this.getPackage(pkg, false)
  139. const collabs = await libaccess.lsCollaborators(pkgName, usr, opts)
  140. // TODO - print these out nicely (breaking change)
  141. this.npm.output(JSON.stringify(collabs, null, 2))
  142. }
  143. async edit () {
  144. throw new Error('edit subcommand is not implemented yet')
  145. }
  146. modifyPackage (pkg, opts, fn, requireScope = true) {
  147. return this.getPackage(pkg, requireScope)
  148. .then(pkgName => otplease(opts, opts => fn(pkgName, opts)))
  149. }
  150. async getPackage (name, requireScope) {
  151. if (name && name.trim())
  152. return name.trim()
  153. else {
  154. try {
  155. const pkg = await readPackageJson(path.resolve(this.npm.prefix, 'package.json'))
  156. name = pkg.name
  157. } catch (err) {
  158. if (err.code === 'ENOENT') {
  159. throw new Error(
  160. 'no package name passed to command and no package.json found'
  161. )
  162. } else
  163. throw err
  164. }
  165. if (requireScope && !name.match(/^@[^/]+\/.*$/))
  166. throw this.usageError('This command is only available for scoped packages.')
  167. else
  168. return name
  169. }
  170. }
  171. }
  172. module.exports = Access