Skip to content

Commit 4a5dd3a

Browse files
wraithgarruyadorno
authored andcommitted
fix(npm) pass npm context everywhere
Instead of files randomly requiring the npm singleton, we pass it where it needs to go so that tests don't need to do so much require mocking everywhere PR-URL: #2772 Credit: @wraithgar Close: #2772 Reviewed-by: @ruyadorno
1 parent b33c760 commit 4a5dd3a

File tree

165 files changed

+8445
-7469
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

165 files changed

+8445
-7469
lines changed

lib/access.js

+157-128
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,11 @@ const path = require('path')
33
const libaccess = require('libnpmaccess')
44
const readPackageJson = require('read-package-json-fast')
55

6-
const npm = require('./npm.js')
76
const output = require('./utils/output.js')
87
const otplease = require('./utils/otplease.js')
98
const usageUtil = require('./utils/usage.js')
109
const getIdentity = require('./utils/get-identity.js')
1110

12-
const usage = usageUtil(
13-
'access',
14-
'npm access public [<package>]\n' +
15-
'npm access restricted [<package>]\n' +
16-
'npm access grant <read-only|read-write> <scope:team> [<package>]\n' +
17-
'npm access revoke <scope:team> [<package>]\n' +
18-
'npm access 2fa-required [<package>]\n' +
19-
'npm access 2fa-not-required [<package>]\n' +
20-
'npm access ls-packages [<user>|<scope>|<scope:team>]\n' +
21-
'npm access ls-collaborators [<package> [<user>]]\n' +
22-
'npm access edit [<package>]'
23-
)
24-
2511
const subcommands = [
2612
'public',
2713
'restricted',
@@ -34,152 +20,195 @@ const subcommands = [
3420
'2fa-not-required',
3521
]
3622

37-
const UsageError = (msg) =>
38-
Object.assign(new Error(`\nUsage: ${msg}\n\n` + usage), {
39-
code: 'EUSAGE',
40-
})
41-
42-
const cmd = (args, cb) =>
43-
access(args)
44-
.then(x => cb(null, x))
45-
.catch(err => err.code === 'EUSAGE'
46-
? cb(err.message)
47-
: cb(err)
23+
class Access {
24+
constructor (npm) {
25+
this.npm = npm
26+
}
27+
28+
get usage () {
29+
return usageUtil(
30+
'access',
31+
'npm access public [<package>]\n' +
32+
'npm access restricted [<package>]\n' +
33+
'npm access grant <read-only|read-write> <scope:team> [<package>]\n' +
34+
'npm access revoke <scope:team> [<package>]\n' +
35+
'npm access 2fa-required [<package>]\n' +
36+
'npm access 2fa-not-required [<package>]\n' +
37+
'npm access ls-packages [<user>|<scope>|<scope:team>]\n' +
38+
'npm access ls-collaborators [<package> [<user>]]\n' +
39+
'npm access edit [<package>]'
4840
)
41+
}
4942

50-
const access = async ([cmd, ...args], cb) => {
51-
const fn = subcommands.includes(cmd) && access[cmd]
43+
async completion (opts) {
44+
const argv = opts.conf.argv.remain
45+
if (argv.length === 2)
46+
return subcommands
47+
48+
switch (argv[2]) {
49+
case 'grant':
50+
if (argv.length === 3)
51+
return ['read-only', 'read-write']
52+
else
53+
return []
54+
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+
}
5268

53-
if (!cmd)
54-
throw UsageError('Subcommand is required.')
69+
exec (args, cb) {
70+
this.access(args)
71+
.then(x => cb(null, x))
72+
.catch(err => err.code === 'EUSAGE'
73+
? cb(err.message)
74+
: cb(err)
75+
)
76+
}
5577

56-
if (!fn)
57-
throw UsageError(`${cmd} is not a recognized subcommand.`)
78+
async access ([cmd, ...args]) {
79+
if (!cmd)
80+
throw this.usageError('Subcommand is required.')
5881

59-
return fn(args, { ...npm.flatOptions })
60-
}
82+
if (!subcommands.includes(cmd) || !this[cmd])
83+
throw this.usageError(`${cmd} is not a recognized subcommand.`)
6184

62-
const completion = async (opts) => {
63-
const argv = opts.conf.argv.remain
64-
if (argv.length === 2)
65-
return subcommands
85+
return this[cmd](args, { ...this.npm.flatOptions })
86+
}
6687

67-
switch (argv[2]) {
68-
case 'grant':
69-
if (argv.length === 3)
70-
return ['read-only', 'read-write']
71-
else
72-
return []
88+
public ([pkg], opts) {
89+
return this.modifyPackage(pkg, opts, libaccess.public)
90+
}
7391

74-
case 'public':
75-
case 'restricted':
76-
case 'ls-packages':
77-
case 'ls-collaborators':
78-
case 'edit':
79-
case '2fa-required':
80-
case '2fa-not-required':
81-
case 'revoke':
82-
return []
83-
default:
84-
throw new Error(argv[2] + ' not recognized')
92+
restricted ([pkg], opts) {
93+
return this.modifyPackage(pkg, opts, libaccess.restricted)
8594
}
86-
}
8795

88-
access.public = ([pkg], opts) =>
89-
modifyPackage(pkg, opts, libaccess.public)
96+
async grant ([perms, scopeteam, pkg], opts) {
97+
if (!perms || (perms !== 'read-only' && perms !== 'read-write'))
98+
throw this.usageError('First argument must be either `read-only` or `read-write`.')
9099

91-
access.restricted = ([pkg], opts) =>
92-
modifyPackage(pkg, opts, libaccess.restricted)
100+
if (!scopeteam)
101+
throw this.usageError('`<scope:team>` argument is required.')
93102

94-
access.grant = async ([perms, scopeteam, pkg], opts) => {
95-
if (!perms || (perms !== 'read-only' && perms !== 'read-write'))
96-
throw UsageError('First argument must be either `read-only` or `read-write`.')
103+
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || []
97104

98-
if (!scopeteam)
99-
throw UsageError('`<scope:team>` argument is required.')
105+
if (!scope && !team) {
106+
throw this.usageError(
107+
'Second argument used incorrect format.\n' +
108+
'Example: @example:developers'
109+
)
110+
}
100111

101-
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || []
112+
return this.modifyPackage(pkg, opts, (pkgName, opts) =>
113+
libaccess.grant(pkgName, scopeteam, perms, opts), false)
114+
}
102115

103-
if (!scope && !team) {
104-
throw UsageError(
105-
'Second argument used incorrect format.\n' +
106-
'Example: @example:developers'
107-
)
116+
async revoke ([scopeteam, pkg], opts) {
117+
if (!scopeteam)
118+
throw this.usageError('`<scope:team>` argument is required.')
119+
120+
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || []
121+
122+
if (!scope || !team) {
123+
throw this.usageError(
124+
'First argument used incorrect format.\n' +
125+
'Example: @example:developers'
126+
)
127+
}
128+
129+
return this.modifyPackage(pkg, opts, (pkgName, opts) =>
130+
libaccess.revoke(pkgName, scopeteam, opts))
108131
}
109132

110-
return modifyPackage(pkg, opts, (pkgName, opts) =>
111-
libaccess.grant(pkgName, scopeteam, perms, opts), false)
112-
}
133+
get ['2fa-required'] () {
134+
return this.tfaRequired
135+
}
113136

114-
access.revoke = async ([scopeteam, pkg], opts) => {
115-
if (!scopeteam)
116-
throw UsageError('`<scope:team>` argument is required.')
137+
tfaRequired ([pkg], opts) {
138+
return this.modifyPackage(pkg, opts, libaccess.tfaRequired, false)
139+
}
117140

118-
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || []
141+
get ['2fa-not-required'] () {
142+
return this.tfaNotRequired
143+
}
119144

120-
if (!scope || !team) {
121-
throw UsageError(
122-
'First argument used incorrect format.\n' +
123-
'Example: @example:developers'
124-
)
145+
tfaNotRequired ([pkg], opts) {
146+
return this.modifyPackage(pkg, opts, libaccess.tfaNotRequired, false)
125147
}
126148

127-
return modifyPackage(pkg, opts, (pkgName, opts) =>
128-
libaccess.revoke(pkgName, scopeteam, opts))
129-
}
149+
get ['ls-packages'] () {
150+
return this.lsPackages
151+
}
130152

131-
access['2fa-required'] = access.tfaRequired = ([pkg], opts) =>
132-
modifyPackage(pkg, opts, libaccess.tfaRequired, false)
153+
async lsPackages ([owner], opts) {
154+
if (!owner)
155+
owner = await getIdentity(this.npm, opts)
133156

134-
access['2fa-not-required'] = access.tfaNotRequired = ([pkg], opts) =>
135-
modifyPackage(pkg, opts, libaccess.tfaNotRequired, false)
157+
const pkgs = await libaccess.lsPackages(owner, opts)
136158

137-
access['ls-packages'] = access.lsPackages = async ([owner], opts) => {
138-
if (!owner)
139-
owner = await getIdentity(opts)
159+
// TODO - print these out nicely (breaking change)
160+
output(JSON.stringify(pkgs, null, 2))
161+
}
140162

141-
const pkgs = await libaccess.lsPackages(owner, opts)
163+
get ['ls-collaborators'] () {
164+
return this.lsCollaborators
165+
}
142166

143-
// TODO - print these out nicely (breaking change)
144-
output(JSON.stringify(pkgs, null, 2))
145-
}
167+
async lsCollaborators ([pkg, usr], opts) {
168+
const pkgName = await this.getPackage(pkg, false)
169+
const collabs = await libaccess.lsCollaborators(pkgName, usr, opts)
146170

147-
access['ls-collaborators'] = access.lsCollaborators = async ([pkg, usr], opts) => {
148-
const pkgName = await getPackage(pkg, false)
149-
const collabs = await libaccess.lsCollaborators(pkgName, usr, opts)
171+
// TODO - print these out nicely (breaking change)
172+
output(JSON.stringify(collabs, null, 2))
173+
}
150174

151-
// TODO - print these out nicely (breaking change)
152-
output(JSON.stringify(collabs, null, 2))
153-
}
175+
async edit () {
176+
throw new Error('edit subcommand is not implemented yet')
177+
}
154178

155-
access.edit = () =>
156-
Promise.reject(new Error('edit subcommand is not implemented yet'))
157-
158-
const modifyPackage = (pkg, opts, fn, requireScope = true) =>
159-
getPackage(pkg, requireScope)
160-
.then(pkgName => otplease(opts, opts => fn(pkgName, opts)))
161-
162-
const getPackage = async (name, requireScope) => {
163-
if (name && name.trim())
164-
return name.trim()
165-
else {
166-
try {
167-
const pkg = await readPackageJson(path.resolve(npm.prefix, 'package.json'))
168-
name = pkg.name
169-
} catch (err) {
170-
if (err.code === 'ENOENT') {
171-
throw new Error(
172-
'no package name passed to command and no package.json found'
173-
)
174-
} else
175-
throw err
179+
modifyPackage (pkg, opts, fn, requireScope = true) {
180+
return this.getPackage(pkg, requireScope)
181+
.then(pkgName => otplease(opts, opts => fn(pkgName, opts)))
182+
}
183+
184+
async getPackage (name, requireScope) {
185+
if (name && name.trim())
186+
return name.trim()
187+
else {
188+
try {
189+
const pkg = await readPackageJson(path.resolve(this.npm.prefix, 'package.json'))
190+
name = pkg.name
191+
} catch (err) {
192+
if (err.code === 'ENOENT') {
193+
throw new Error(
194+
'no package name passed to command and no package.json found'
195+
)
196+
} else
197+
throw err
198+
}
199+
200+
if (requireScope && !name.match(/^@[^/]+\/.*$/))
201+
throw this.usageError('This command is only available for scoped packages.')
202+
else
203+
return name
176204
}
205+
}
177206

178-
if (requireScope && !name.match(/^@[^/]+\/.*$/))
179-
throw UsageError('This command is only available for scoped packages.')
180-
else
181-
return name
207+
usageError (msg) {
208+
return Object.assign(new Error(`\nUsage: ${msg}\n\n` + this.usage), {
209+
code: 'EUSAGE',
210+
})
182211
}
183212
}
184213

185-
module.exports = Object.assign(cmd, { usage, completion, subcommands })
214+
module.exports = Access

0 commit comments

Comments
 (0)