1'use strict' 2 3const figgyPudding = require('figgy-pudding') 4const getStream = require('get-stream') 5const npa = require('npm-package-arg') 6const npmFetch = require('npm-registry-fetch') 7const {PassThrough} = require('stream') 8const validate = require('aproba') 9 10const AccessConfig = figgyPudding({ 11 Promise: {default: () => Promise} 12}) 13 14const eu = encodeURIComponent 15const npar = spec => { 16 spec = npa(spec) 17 if (!spec.registry) { 18 throw new Error('`spec` must be a registry spec') 19 } 20 return spec 21} 22 23const cmd = module.exports = {} 24 25cmd.public = (spec, opts) => setAccess(spec, 'public', opts) 26cmd.restricted = (spec, opts) => setAccess(spec, 'restricted', opts) 27function setAccess (spec, access, opts) { 28 opts = AccessConfig(opts) 29 return pwrap(opts, () => { 30 spec = npar(spec) 31 validate('OSO', [spec, access, opts]) 32 const uri = `/-/package/${eu(spec.name)}/access` 33 return npmFetch(uri, opts.concat({ 34 method: 'POST', 35 body: {access}, 36 spec 37 })) 38 }).then(res => res.body.resume() && true) 39} 40 41cmd.grant = (spec, entity, permissions, opts) => { 42 opts = AccessConfig(opts) 43 return pwrap(opts, () => { 44 spec = npar(spec) 45 const {scope, team} = splitEntity(entity) 46 validate('OSSSO', [spec, scope, team, permissions, opts]) 47 if (permissions !== 'read-write' && permissions !== 'read-only') { 48 throw new Error('`permissions` must be `read-write` or `read-only`. Got `' + permissions + '` instead') 49 } 50 const uri = `/-/team/${eu(scope)}/${eu(team)}/package` 51 return npmFetch(uri, opts.concat({ 52 method: 'PUT', 53 body: {package: spec.name, permissions}, 54 scope, 55 spec, 56 ignoreBody: true 57 })) 58 }).then(() => true) 59} 60 61cmd.revoke = (spec, entity, opts) => { 62 opts = AccessConfig(opts) 63 return pwrap(opts, () => { 64 spec = npar(spec) 65 const {scope, team} = splitEntity(entity) 66 validate('OSSO', [spec, scope, team, opts]) 67 const uri = `/-/team/${eu(scope)}/${eu(team)}/package` 68 return npmFetch(uri, opts.concat({ 69 method: 'DELETE', 70 body: {package: spec.name}, 71 scope, 72 spec, 73 ignoreBody: true 74 })) 75 }).then(() => true) 76} 77 78cmd.lsPackages = (entity, opts) => { 79 opts = AccessConfig(opts) 80 return pwrap(opts, () => { 81 return getStream.array( 82 cmd.lsPackages.stream(entity, opts) 83 ).then(data => data.reduce((acc, [key, val]) => { 84 if (!acc) { 85 acc = {} 86 } 87 acc[key] = val 88 return acc 89 }, null)) 90 }) 91} 92 93cmd.lsPackages.stream = (entity, opts) => { 94 validate('SO|SZ', [entity, opts]) 95 opts = AccessConfig(opts) 96 const {scope, team} = splitEntity(entity) 97 let uri 98 if (team) { 99 uri = `/-/team/${eu(scope)}/${eu(team)}/package` 100 } else { 101 uri = `/-/org/${eu(scope)}/package` 102 } 103 opts = opts.concat({ 104 query: {format: 'cli'}, 105 mapJson (value, [key]) { 106 if (value === 'read') { 107 return [key, 'read-only'] 108 } else if (value === 'write') { 109 return [key, 'read-write'] 110 } else { 111 return [key, value] 112 } 113 } 114 }) 115 const ret = new PassThrough({objectMode: true}) 116 npmFetch.json.stream(uri, '*', opts).on('error', err => { 117 if (err.code === 'E404' && !team) { 118 uri = `/-/user/${eu(scope)}/package` 119 npmFetch.json.stream(uri, '*', opts).on( 120 'error', err => ret.emit('error', err) 121 ).pipe(ret) 122 } else { 123 ret.emit('error', err) 124 } 125 }).pipe(ret) 126 return ret 127} 128 129cmd.lsCollaborators = (spec, user, opts) => { 130 if (typeof user === 'object' && !opts) { 131 opts = user 132 user = undefined 133 } 134 opts = AccessConfig(opts) 135 return pwrap(opts, () => { 136 return getStream.array( 137 cmd.lsCollaborators.stream(spec, user, opts) 138 ).then(data => data.reduce((acc, [key, val]) => { 139 if (!acc) { 140 acc = {} 141 } 142 acc[key] = val 143 return acc 144 }, null)) 145 }) 146} 147 148cmd.lsCollaborators.stream = (spec, user, opts) => { 149 if (typeof user === 'object' && !opts) { 150 opts = user 151 user = undefined 152 } 153 opts = AccessConfig(opts) 154 spec = npar(spec) 155 validate('OSO|OZO', [spec, user, opts]) 156 const uri = `/-/package/${eu(spec.name)}/collaborators` 157 return npmFetch.json.stream(uri, '*', opts.concat({ 158 query: {format: 'cli', user: user || undefined}, 159 mapJson (value, [key]) { 160 if (value === 'read') { 161 return [key, 'read-only'] 162 } else if (value === 'write') { 163 return [key, 'read-write'] 164 } else { 165 return [key, value] 166 } 167 } 168 })) 169} 170 171cmd.tfaRequired = (spec, opts) => setRequires2fa(spec, true, opts) 172cmd.tfaNotRequired = (spec, opts) => setRequires2fa(spec, false, opts) 173function setRequires2fa (spec, required, opts) { 174 opts = AccessConfig(opts) 175 return new opts.Promise((resolve, reject) => { 176 spec = npar(spec) 177 validate('OBO', [spec, required, opts]) 178 const uri = `/-/package/${eu(spec.name)}/access` 179 return npmFetch(uri, opts.concat({ 180 method: 'POST', 181 body: {publish_requires_tfa: required}, 182 spec, 183 ignoreBody: true 184 })).then(resolve, reject) 185 }).then(() => true) 186} 187 188cmd.edit = () => { 189 throw new Error('Not implemented yet') 190} 191 192function splitEntity (entity = '') { 193 let [, scope, team] = entity.match(/^@?([^:]+)(?::(.*))?$/) || [] 194 return {scope, team} 195} 196 197function pwrap (opts, fn) { 198 return new opts.Promise((resolve, reject) => { 199 fn().then(resolve, reject) 200 }) 201} 202