-
Notifications
You must be signed in to change notification settings - Fork 3.7k
fix(npm) pass npm context everywhere #2772
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,25 +3,11 @@ const path = require('path') | |
const libaccess = require('libnpmaccess') | ||
const readPackageJson = require('read-package-json-fast') | ||
|
||
const npm = require('./npm.js') | ||
const output = require('./utils/output.js') | ||
const otplease = require('./utils/otplease.js') | ||
const usageUtil = require('./utils/usage.js') | ||
const getIdentity = require('./utils/get-identity.js') | ||
|
||
const usage = usageUtil( | ||
'access', | ||
'npm access public [<package>]\n' + | ||
'npm access restricted [<package>]\n' + | ||
'npm access grant <read-only|read-write> <scope:team> [<package>]\n' + | ||
'npm access revoke <scope:team> [<package>]\n' + | ||
'npm access 2fa-required [<package>]\n' + | ||
'npm access 2fa-not-required [<package>]\n' + | ||
'npm access ls-packages [<user>|<scope>|<scope:team>]\n' + | ||
'npm access ls-collaborators [<package> [<user>]]\n' + | ||
'npm access edit [<package>]' | ||
) | ||
|
||
const subcommands = [ | ||
'public', | ||
'restricted', | ||
|
@@ -34,152 +20,195 @@ const subcommands = [ | |
'2fa-not-required', | ||
] | ||
|
||
const UsageError = (msg) => | ||
Object.assign(new Error(`\nUsage: ${msg}\n\n` + usage), { | ||
code: 'EUSAGE', | ||
}) | ||
|
||
const cmd = (args, cb) => | ||
access(args) | ||
.then(x => cb(null, x)) | ||
.catch(err => err.code === 'EUSAGE' | ||
? cb(err.message) | ||
: cb(err) | ||
class Access { | ||
constructor (npm) { | ||
this.npm = npm | ||
} | ||
|
||
get usage () { | ||
return usageUtil( | ||
'access', | ||
'npm access public [<package>]\n' + | ||
'npm access restricted [<package>]\n' + | ||
'npm access grant <read-only|read-write> <scope:team> [<package>]\n' + | ||
'npm access revoke <scope:team> [<package>]\n' + | ||
'npm access 2fa-required [<package>]\n' + | ||
'npm access 2fa-not-required [<package>]\n' + | ||
'npm access ls-packages [<user>|<scope>|<scope:team>]\n' + | ||
'npm access ls-collaborators [<package> [<user>]]\n' + | ||
'npm access edit [<package>]' | ||
) | ||
} | ||
|
||
const access = async ([cmd, ...args], cb) => { | ||
const fn = subcommands.includes(cmd) && access[cmd] | ||
async completion (opts) { | ||
const argv = opts.conf.argv.remain | ||
if (argv.length === 2) | ||
return subcommands | ||
|
||
switch (argv[2]) { | ||
case 'grant': | ||
if (argv.length === 3) | ||
return ['read-only', 'read-write'] | ||
else | ||
return [] | ||
|
||
case 'public': | ||
case 'restricted': | ||
case 'ls-packages': | ||
case 'ls-collaborators': | ||
case 'edit': | ||
case '2fa-required': | ||
case '2fa-not-required': | ||
case 'revoke': | ||
return [] | ||
default: | ||
throw new Error(argv[2] + ' not recognized') | ||
} | ||
} | ||
|
||
if (!cmd) | ||
throw UsageError('Subcommand is required.') | ||
exec (args, cb) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like you're heading in this direction already, but pretty please can we ditch the callbacks entirely? 😆 I'd love to see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That change was left out of this PR because of the amount of testing changes it would require. This PR is mostly code change, with minimal test changes. Making this function async would be a minimal code change, and a larger test change. It'd be much easier than THIS PR, but still needs to be its own thing. |
||
this.access(args) | ||
.then(x => cb(null, x)) | ||
.catch(err => err.code === 'EUSAGE' | ||
? cb(err.message) | ||
: cb(err) | ||
) | ||
} | ||
|
||
if (!fn) | ||
throw UsageError(`${cmd} is not a recognized subcommand.`) | ||
async access ([cmd, ...args]) { | ||
if (!cmd) | ||
throw this.usageError('Subcommand is required.') | ||
|
||
return fn(args, { ...npm.flatOptions }) | ||
} | ||
if (!subcommands.includes(cmd) || !this[cmd]) | ||
throw this.usageError(`${cmd} is not a recognized subcommand.`) | ||
|
||
const completion = async (opts) => { | ||
const argv = opts.conf.argv.remain | ||
if (argv.length === 2) | ||
return subcommands | ||
return this[cmd](args, { ...this.npm.flatOptions }) | ||
} | ||
|
||
switch (argv[2]) { | ||
case 'grant': | ||
if (argv.length === 3) | ||
return ['read-only', 'read-write'] | ||
else | ||
return [] | ||
public ([pkg], opts) { | ||
return this.modifyPackage(pkg, opts, libaccess.public) | ||
} | ||
|
||
case 'public': | ||
case 'restricted': | ||
case 'ls-packages': | ||
case 'ls-collaborators': | ||
case 'edit': | ||
case '2fa-required': | ||
case '2fa-not-required': | ||
case 'revoke': | ||
return [] | ||
default: | ||
throw new Error(argv[2] + ' not recognized') | ||
restricted ([pkg], opts) { | ||
return this.modifyPackage(pkg, opts, libaccess.restricted) | ||
} | ||
} | ||
|
||
access.public = ([pkg], opts) => | ||
modifyPackage(pkg, opts, libaccess.public) | ||
async grant ([perms, scopeteam, pkg], opts) { | ||
if (!perms || (perms !== 'read-only' && perms !== 'read-write')) | ||
throw this.usageError('First argument must be either `read-only` or `read-write`.') | ||
|
||
access.restricted = ([pkg], opts) => | ||
modifyPackage(pkg, opts, libaccess.restricted) | ||
if (!scopeteam) | ||
throw this.usageError('`<scope:team>` argument is required.') | ||
|
||
access.grant = async ([perms, scopeteam, pkg], opts) => { | ||
if (!perms || (perms !== 'read-only' && perms !== 'read-write')) | ||
throw UsageError('First argument must be either `read-only` or `read-write`.') | ||
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] | ||
|
||
if (!scopeteam) | ||
throw UsageError('`<scope:team>` argument is required.') | ||
if (!scope && !team) { | ||
throw this.usageError( | ||
'Second argument used incorrect format.\n' + | ||
'Example: @example:developers' | ||
) | ||
} | ||
|
||
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] | ||
return this.modifyPackage(pkg, opts, (pkgName, opts) => | ||
libaccess.grant(pkgName, scopeteam, perms, opts), false) | ||
} | ||
|
||
if (!scope && !team) { | ||
throw UsageError( | ||
'Second argument used incorrect format.\n' + | ||
'Example: @example:developers' | ||
) | ||
async revoke ([scopeteam, pkg], opts) { | ||
if (!scopeteam) | ||
throw this.usageError('`<scope:team>` argument is required.') | ||
|
||
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] | ||
|
||
if (!scope || !team) { | ||
throw this.usageError( | ||
'First argument used incorrect format.\n' + | ||
'Example: @example:developers' | ||
) | ||
} | ||
|
||
return this.modifyPackage(pkg, opts, (pkgName, opts) => | ||
libaccess.revoke(pkgName, scopeteam, opts)) | ||
} | ||
|
||
return modifyPackage(pkg, opts, (pkgName, opts) => | ||
libaccess.grant(pkgName, scopeteam, perms, opts), false) | ||
} | ||
get ['2fa-required'] () { | ||
return this.tfaRequired | ||
} | ||
|
||
access.revoke = async ([scopeteam, pkg], opts) => { | ||
if (!scopeteam) | ||
throw UsageError('`<scope:team>` argument is required.') | ||
tfaRequired ([pkg], opts) { | ||
return this.modifyPackage(pkg, opts, libaccess.tfaRequired, false) | ||
} | ||
|
||
const [, scope, team] = scopeteam.match(/^@?([^:]+):(.*)$/) || [] | ||
get ['2fa-not-required'] () { | ||
return this.tfaNotRequired | ||
} | ||
|
||
if (!scope || !team) { | ||
throw UsageError( | ||
'First argument used incorrect format.\n' + | ||
'Example: @example:developers' | ||
) | ||
tfaNotRequired ([pkg], opts) { | ||
return this.modifyPackage(pkg, opts, libaccess.tfaNotRequired, false) | ||
} | ||
|
||
return modifyPackage(pkg, opts, (pkgName, opts) => | ||
libaccess.revoke(pkgName, scopeteam, opts)) | ||
} | ||
get ['ls-packages'] () { | ||
return this.lsPackages | ||
} | ||
|
||
access['2fa-required'] = access.tfaRequired = ([pkg], opts) => | ||
modifyPackage(pkg, opts, libaccess.tfaRequired, false) | ||
async lsPackages ([owner], opts) { | ||
if (!owner) | ||
owner = await getIdentity(this.npm, opts) | ||
|
||
access['2fa-not-required'] = access.tfaNotRequired = ([pkg], opts) => | ||
modifyPackage(pkg, opts, libaccess.tfaNotRequired, false) | ||
const pkgs = await libaccess.lsPackages(owner, opts) | ||
|
||
access['ls-packages'] = access.lsPackages = async ([owner], opts) => { | ||
if (!owner) | ||
owner = await getIdentity(opts) | ||
// TODO - print these out nicely (breaking change) | ||
output(JSON.stringify(pkgs, null, 2)) | ||
} | ||
|
||
const pkgs = await libaccess.lsPackages(owner, opts) | ||
get ['ls-collaborators'] () { | ||
return this.lsCollaborators | ||
} | ||
|
||
// TODO - print these out nicely (breaking change) | ||
output(JSON.stringify(pkgs, null, 2)) | ||
} | ||
async lsCollaborators ([pkg, usr], opts) { | ||
const pkgName = await this.getPackage(pkg, false) | ||
const collabs = await libaccess.lsCollaborators(pkgName, usr, opts) | ||
|
||
access['ls-collaborators'] = access.lsCollaborators = async ([pkg, usr], opts) => { | ||
const pkgName = await getPackage(pkg, false) | ||
const collabs = await libaccess.lsCollaborators(pkgName, usr, opts) | ||
// TODO - print these out nicely (breaking change) | ||
output(JSON.stringify(collabs, null, 2)) | ||
} | ||
|
||
// TODO - print these out nicely (breaking change) | ||
output(JSON.stringify(collabs, null, 2)) | ||
} | ||
async edit () { | ||
throw new Error('edit subcommand is not implemented yet') | ||
} | ||
|
||
access.edit = () => | ||
Promise.reject(new Error('edit subcommand is not implemented yet')) | ||
|
||
const modifyPackage = (pkg, opts, fn, requireScope = true) => | ||
getPackage(pkg, requireScope) | ||
.then(pkgName => otplease(opts, opts => fn(pkgName, opts))) | ||
|
||
const getPackage = async (name, requireScope) => { | ||
if (name && name.trim()) | ||
return name.trim() | ||
else { | ||
try { | ||
const pkg = await readPackageJson(path.resolve(npm.prefix, 'package.json')) | ||
name = pkg.name | ||
} catch (err) { | ||
if (err.code === 'ENOENT') { | ||
throw new Error( | ||
'no package name passed to command and no package.json found' | ||
) | ||
} else | ||
throw err | ||
modifyPackage (pkg, opts, fn, requireScope = true) { | ||
return this.getPackage(pkg, requireScope) | ||
.then(pkgName => otplease(opts, opts => fn(pkgName, opts))) | ||
} | ||
|
||
async getPackage (name, requireScope) { | ||
if (name && name.trim()) | ||
return name.trim() | ||
else { | ||
try { | ||
const pkg = await readPackageJson(path.resolve(this.npm.prefix, 'package.json')) | ||
name = pkg.name | ||
} catch (err) { | ||
if (err.code === 'ENOENT') { | ||
throw new Error( | ||
'no package name passed to command and no package.json found' | ||
) | ||
} else | ||
throw err | ||
} | ||
|
||
if (requireScope && !name.match(/^@[^/]+\/.*$/)) | ||
throw this.usageError('This command is only available for scoped packages.') | ||
else | ||
return name | ||
} | ||
} | ||
|
||
if (requireScope && !name.match(/^@[^/]+\/.*$/)) | ||
throw UsageError('This command is only available for scoped packages.') | ||
else | ||
return name | ||
usageError (msg) { | ||
return Object.assign(new Error(`\nUsage: ${msg}\n\n` + this.usage), { | ||
code: 'EUSAGE', | ||
}) | ||
} | ||
} | ||
|
||
module.exports = Object.assign(cmd, { usage, completion, subcommands }) | ||
module.exports = Access |
Uh oh!
There was an error while loading. Please reload this page.