11'use strict'
22/* eslint-disable standard/no-callback-literal */
33
4- var resolve = require ( 'path' ) . resolve
4+ const BB = require ( 'bluebird' )
55
6- var readPackageJson = require ( 'read-package-json' )
7- var mapToRegistry = require ( './utils/map-to-registry.js' )
8- var npm = require ( './npm.js' )
9- var output = require ( './utils/output.js' )
10-
11- var whoami = require ( './whoami' )
6+ const figgyPudding = require ( 'figgy-pudding' )
7+ const libaccess = require ( 'libnpm/access' )
8+ const npmConfig = require ( './config/figgy-config.js' )
9+ const output = require ( './utils/output.js' )
10+ const otplease = require ( './utils/otplease.js' )
11+ const path = require ( 'path' )
12+ const prefix = require ( './npm.js' ) . prefix
13+ const readPackageJson = BB . promisify ( require ( 'read-package-json' ) )
14+ const usage = require ( './utils/usage.js' )
15+ const whoami = require ( './whoami.js' )
1216
1317module . exports = access
1418
15- access . usage =
19+ access . usage = usage (
20+ 'npm access' ,
1621 'npm access public [<package>]\n' +
1722 'npm access restricted [<package>]\n' +
1823 'npm access grant <read-only|read-write> <scope:team> [<package>]\n' +
1924 'npm access revoke <scope:team> [<package>]\n' +
25+ 'npm access 2fa-required [<package>]\n' +
26+ 'npm access 2fa-not-required [<package>]\n' +
2027 'npm access ls-packages [<user>|<scope>|<scope:team>]\n' +
2128 'npm access ls-collaborators [<package> [<user>]]\n' +
2229 'npm access edit [<package>]'
30+ )
31+
32+ access . subcommands = [
33+ 'public' , 'restricted' , 'grant' , 'revoke' ,
34+ 'ls-packages' , 'ls-collaborators' , 'edit' ,
35+ '2fa-required' , '2fa-not-required'
36+ ]
37+
38+ const AccessConfig = figgyPudding ( {
39+ json : { }
40+ } )
2341
24- access . subcommands = [ 'public' , 'restricted' , 'grant' , 'revoke' ,
25- 'ls-packages' , 'ls-collaborators' , 'edit' ]
42+ function UsageError ( msg = '' ) {
43+ throw Object . assign ( new Error (
44+ ( msg ? `\nUsage: ${ msg } \n\n` : '' ) +
45+ access . usage
46+ ) , { code : 'EUSAGE' } )
47+ }
2648
2749access . completion = function ( opts , cb ) {
2850 var argv = opts . conf . argv . remain
@@ -42,6 +64,8 @@ access.completion = function (opts, cb) {
4264 case 'ls-packages' :
4365 case 'ls-collaborators' :
4466 case 'edit' :
67+ case '2fa-required' :
68+ case '2fa-not-required' :
4569 return cb ( null , [ ] )
4670 case 'revoke' :
4771 return cb ( null , [ ] )
@@ -50,81 +74,125 @@ access.completion = function (opts, cb) {
5074 }
5175}
5276
53- function access ( args , cb ) {
54- var cmd = args . shift ( )
55- var params
56- return parseParams ( cmd , args , function ( err , p ) {
57- if ( err ) { return cb ( err ) }
58- params = p
59- return mapToRegistry ( params . package , npm . config , invokeCmd )
60- } )
77+ function access ( [ cmd , ...args ] , cb ) {
78+ return BB . try ( ( ) => {
79+ const fn = access . subcommands . includes ( cmd ) && access [ cmd ]
80+ if ( ! cmd ) { UsageError ( 'Subcommand is required.' ) }
81+ if ( ! fn ) { UsageError ( `${ cmd } is not a recognized subcommand.` ) }
6182
62- function invokeCmd ( err , uri , auth , base ) {
63- if ( err ) { return cb ( err ) }
64- params . auth = auth
65- try {
66- return npm . registry . access ( cmd , uri , params , function ( err , data ) {
67- if ( ! err && data ) {
68- output ( JSON . stringify ( data , undefined , 2 ) )
69- }
70- cb ( err , data )
71- } )
72- } catch ( e ) {
73- cb ( e . message + '\n\nUsage:\n' + access . usage )
74- }
75- }
83+ return fn ( args , AccessConfig ( npmConfig ( ) ) )
84+ } ) . then (
85+ x => cb ( null , x ) ,
86+ err => err . code === 'EUSAGE' ? cb ( err . message ) : cb ( err )
87+ )
7688}
7789
78- function parseParams ( cmd , args , cb ) {
79- // mapToRegistry will complain if package is undefined,
80- // but it's not needed for ls-packages
81- var params = { 'package' : '' }
82- if ( cmd === 'grant' ) {
83- params . permissions = args . shift ( )
84- }
85- if ( [ 'grant' , 'revoke' , 'ls-packages' ] . indexOf ( cmd ) !== - 1 ) {
86- var entity = ( args . shift ( ) || '' ) . split ( ':' )
87- params . scope = entity [ 0 ]
88- params . team = entity [ 1 ]
89- }
90+ access . public = ( [ pkg ] , opts ) => {
91+ return modifyPackage ( pkg , opts , libaccess . public )
92+ }
9093
91- if ( cmd === 'ls-packages' ) {
92- if ( ! params . scope ) {
93- whoami ( [ ] , true , function ( err , scope ) {
94- params . scope = scope
95- cb ( err , params )
96- } )
97- } else {
98- cb ( null , params )
94+ access . restricted = ( [ pkg ] , opts ) => {
95+ return modifyPackage ( pkg , opts , libaccess . restricted )
96+ }
97+
98+ access . grant = ( [ perms , scopeteam , pkg ] , opts ) => {
99+ return BB . try ( ( ) => {
100+ if ( ! perms || ( perms !== 'read-only' && perms !== 'read-write' ) ) {
101+ UsageError ( 'First argument must be either `read-only` or `read-write.`' )
99102 }
100- } else {
101- getPackage ( args . shift ( ) , function ( err , pkg ) {
102- if ( err ) return cb ( err )
103- params . package = pkg
103+ if ( ! scopeteam ) {
104+ UsageError ( '`<scope:team>` argument is required.' )
105+ }
106+ const [ , scope , team ] = scopeteam . match ( / ^ @ ? ( [ ^ : ] + ) : ( .* ) $ / ) || [ ]
107+ if ( ! scope && ! team ) {
108+ UsageError (
109+ 'Second argument used incorrect format.\n' +
110+ 'Example: @example:developers'
111+ )
112+ }
113+ return modifyPackage ( pkg , opts , ( pkgName , opts ) => {
114+ return libaccess . grant ( pkgName , scopeteam , perms , opts )
115+ } )
116+ } )
117+ }
104118
105- if ( cmd === 'ls-collaborators' ) params . user = args . shift ( )
106- cb ( null , params )
119+ access . revoke = ( [ scopeteam , pkg ] , opts ) => {
120+ return BB . try ( ( ) => {
121+ if ( ! scopeteam ) {
122+ UsageError ( '`<scope:team>` argument is required.' )
123+ }
124+ const [ , scope , team ] = scopeteam . match ( / ^ @ ? ( [ ^ : ] + ) : ( .* ) $ / ) || [ ]
125+ if ( ! scope || ! team ) {
126+ UsageError (
127+ 'First argument used incorrect format.\n' +
128+ 'Example: @example:developers'
129+ )
130+ }
131+ return modifyPackage ( pkg , opts , ( pkgName , opts ) => {
132+ return libaccess . revoke ( pkgName , scopeteam , opts )
107133 } )
108- }
134+ } )
135+ }
136+
137+ access [ '2fa-required' ] = access . tfaRequired = ( [ pkg ] , opts ) => {
138+ return modifyPackage ( pkg , opts , libaccess . tfaRequired , false )
139+ }
140+
141+ access [ '2fa-not-required' ] = access . tfaNotRequired = ( [ pkg ] , opts ) => {
142+ return modifyPackage ( pkg , opts , libaccess . tfaNotRequired , false )
143+ }
144+
145+ access [ 'ls-packages' ] = access . lsPackages = ( [ owner ] , opts ) => {
146+ return (
147+ owner ? BB . resolve ( owner ) : BB . fromNode ( cb => whoami ( [ ] , true , cb ) )
148+ ) . then ( owner => {
149+ return libaccess . lsPackages ( owner , opts )
150+ } ) . then ( pkgs => {
151+ // TODO - print these out nicely (breaking change)
152+ output ( JSON . stringify ( pkgs , null , 2 ) )
153+ } )
154+ }
155+
156+ access [ 'ls-collaborators' ] = access . lsCollaborators = ( [ pkg , usr ] , opts ) => {
157+ return getPackage ( pkg ) . then ( pkgName =>
158+ libaccess . lsCollaborators ( pkgName , usr , opts )
159+ ) . then ( collabs => {
160+ // TODO - print these out nicely (breaking change)
161+ output ( JSON . stringify ( collabs , null , 2 ) )
162+ } )
109163}
110164
111- function getPackage ( name , cb ) {
112- if ( name && name . trim ( ) ) {
113- cb ( null , name . trim ( ) )
114- } else {
115- readPackageJson (
116- resolve ( npm . prefix , 'package.json' ) ,
117- function ( err , data ) {
118- if ( err ) {
165+ access [ 'edit' ] = ( ) => BB . reject ( new Error ( 'edit subcommand is not implemented yet' ) )
166+
167+ function modifyPackage ( pkg , opts , fn , requireScope = true ) {
168+ return getPackage ( pkg , requireScope ) . then ( pkgName =>
169+ otplease ( opts , opts => fn ( pkgName , opts ) )
170+ )
171+ }
172+
173+ function getPackage ( name , requireScope = true ) {
174+ return BB . try ( ( ) => {
175+ if ( name && name . trim ( ) ) {
176+ return name . trim ( )
177+ } else {
178+ return readPackageJson (
179+ path . resolve ( prefix , 'package.json' )
180+ ) . then (
181+ data => data . name ,
182+ err => {
119183 if ( err . code === 'ENOENT' ) {
120- cb ( new Error ( 'no package name passed to command and no package.json found' ) )
184+ throw new Error ( 'no package name passed to command and no package.json found' )
121185 } else {
122- cb ( err )
186+ throw err
123187 }
124- } else {
125- cb ( null , data . name )
126188 }
127- }
128- )
129- }
189+ )
190+ }
191+ } ) . then ( name => {
192+ if ( requireScope && ! name . match ( / ^ @ [ ^ / ] + \/ .* $ / ) ) {
193+ UsageError ( 'This command is only available for scoped packages.' )
194+ } else {
195+ return name
196+ }
197+ } )
130198}
0 commit comments