Skip to content

Commit b37a665

Browse files
committed
org: add npm org command
Credit: @chrisdickinson Credit: @zkat
1 parent ab21553 commit b37a665

File tree

3 files changed

+267
-1
lines changed

3 files changed

+267
-1
lines changed

lib/config/cmd-list.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ var affordances = {
5050
'rm': 'uninstall',
5151
'r': 'uninstall',
5252
'rum': 'run-script',
53-
'sit': 'cit'
53+
'sit': 'cit',
54+
'urn': 'run-script',
55+
'ogr': 'org'
5456
}
5557

5658
// these are filenames in .
@@ -89,6 +91,7 @@ var cmdList = [
8991
'token',
9092
'profile',
9193
'audit',
94+
'org',
9295

9396
'help',
9497
'help-search',

lib/org.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
'use strict'
2+
3+
const BB = require('bluebird')
4+
5+
const figgyPudding = require('figgy-pudding')
6+
const liborg = require('libnpm/org')
7+
const npmConfig = require('./config/figgy-config.js')
8+
const output = require('./utils/output.js')
9+
const otplease = require('./utils/otplease.js')
10+
const Table = require('cli-table3')
11+
12+
module.exports = org
13+
14+
org.subcommands = ['set', 'rm', 'ls']
15+
16+
org.usage =
17+
'npm org set orgname username [developer | admin | owner]\n' +
18+
'npm org rm orgname username\n' +
19+
'npm org ls orgname'
20+
21+
const OrgConfig = figgyPudding({
22+
json: {},
23+
loglevel: {},
24+
parseable: {},
25+
silent: {}
26+
})
27+
28+
org.completion = function (opts, cb) {
29+
var argv = opts.conf.argv.remain
30+
if (argv.length === 2) {
31+
return cb(null, org.subcommands)
32+
}
33+
switch (argv[2]) {
34+
case 'ls':
35+
case 'add':
36+
case 'rm':
37+
case 'set':
38+
return cb(null, [])
39+
default:
40+
return cb(new Error(argv[2] + ' not recognized'))
41+
}
42+
}
43+
44+
function UsageError () {
45+
throw Object.assign(new Error(org.usage), {code: 'EUSAGE'})
46+
}
47+
48+
function org ([cmd, orgname, username, role], cb) {
49+
otplease(npmConfig(), opts => {
50+
opts = OrgConfig(opts)
51+
switch (cmd) {
52+
case 'add':
53+
case 'set':
54+
return orgSet(orgname, username, role, opts)
55+
case 'rm':
56+
return orgRm(orgname, username, opts)
57+
case 'ls':
58+
return orgList(orgname, opts)
59+
default:
60+
UsageError()
61+
}
62+
}).then(
63+
x => cb(null, x),
64+
err => err.code === 'EUSAGE' ? err.message : err
65+
)
66+
}
67+
68+
function orgSet (org, user, role, opts) {
69+
return liborg.set(org, user, role, opts).then(memDeets => {
70+
if (opts.json) {
71+
output(JSON.stringify(memDeets, null, 2))
72+
} else if (opts.parseable) {
73+
output(['org', 'orgsize', 'user', 'role'].join('\t'))
74+
output([
75+
memDeets.org.name,
76+
memDeets.org.size,
77+
memDeets.user,
78+
memDeets.role
79+
])
80+
} else if (!opts.silent && opts.loglevel !== 'silent') {
81+
output(`Added ${memDeets.user} as ${memDeets.role} to ${memDeets.org.name}. You now ${memDeets.org.size} member${memDeets.org.size === 1 ? '' : 's'} in this org.`)
82+
}
83+
return memDeets
84+
})
85+
}
86+
87+
function orgRm (org, user, opts) {
88+
return liborg.rm(org, user, opts).then(() => {
89+
return liborg.ls(org, opts)
90+
}).then(roster => {
91+
user = user.replace(/^[~@]?/, '')
92+
org = org.replace(/^[~@]?/, '')
93+
const userCount = Object.keys(roster).length
94+
if (opts.json) {
95+
output(JSON.stringify({
96+
user,
97+
org,
98+
userCount,
99+
deleted: true
100+
}))
101+
} else if (opts.parseable) {
102+
output(['user', 'org', 'userCount', 'deleted'].join('\t'))
103+
output([user, org, userCount, true].join('\t'))
104+
} else if (!opts.silent && opts.loglevel !== 'silent') {
105+
output(`Successfully removed ${user} from ${org}. You now have ${userCount} member${userCount === 1 ? '' : 's'} in this org.`)
106+
}
107+
})
108+
}
109+
110+
function orgList (org, opts) {
111+
return liborg.ls(org, opts).then(roster => {
112+
if (opts.json) {
113+
output(JSON.stringify(roster, null, 2))
114+
} else if (opts.parseable) {
115+
output(['user', 'role'].join('\t'))
116+
Object.keys(roster).forEach(user => {
117+
output([user, roster[user]].join('\t'))
118+
})
119+
} else if (!opts.silent && opts.loglevel !== 'silent') {
120+
const table = new Table({head: ['user', 'role']})
121+
Object.keys(roster).sort().forEach(user => {
122+
table.push([user, roster[user]])
123+
})
124+
output(table.toString())
125+
}
126+
})
127+
}

test/tap/org.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
'use strict'
2+
3+
var mr = require('npm-registry-mock')
4+
5+
var test = require('tap').test
6+
var common = require('../common-tap.js')
7+
8+
var server
9+
10+
test('setup', function (t) {
11+
mr({port: common.port}, function (err, s) {
12+
t.ifError(err, 'registry mocked successfully')
13+
server = s
14+
t.end()
15+
})
16+
})
17+
18+
const names = ['add', 'set']
19+
const roles = ['developer', 'admin', 'owner']
20+
21+
names.forEach(function (name) {
22+
test('org ' + name + ' [orgname] [username]: defaults to developer', function (t) {
23+
const membershipData = {
24+
org: {
25+
name: 'myorg',
26+
size: 1
27+
},
28+
user: 'myuser',
29+
role: 'developer'
30+
}
31+
server.put('/-/org/myorg/user', JSON.stringify({
32+
user: 'myuser'
33+
})).reply(200, membershipData)
34+
common.npm([
35+
'org', 'add', 'myorg', 'myuser',
36+
'--json',
37+
'--registry', common.registry,
38+
'--loglevel', 'silent'
39+
], {}, function (err, code, stdout, stderr) {
40+
t.ifError(err, 'npm org')
41+
42+
t.equal(code, 0, 'exited OK')
43+
t.equal(stderr, '', 'no error output')
44+
45+
t.same(JSON.parse(stdout), membershipData)
46+
t.end()
47+
})
48+
})
49+
50+
roles.forEach(function (role) {
51+
test('org ' + name + ' [orgname] [username]: accepts role ' + role, function (t) {
52+
const membershipData = {
53+
org: {
54+
name: 'myorg',
55+
size: 1
56+
},
57+
user: 'myuser',
58+
role: role
59+
}
60+
server.put('/-/org/myorg/user', JSON.stringify({
61+
user: 'myuser'
62+
})).reply(200, membershipData)
63+
common.npm([
64+
'org', name, 'myorg', 'myuser',
65+
'--json',
66+
'--registry', common.registry,
67+
'--loglevel', 'silent'
68+
], {}, function (err, code, stdout, stderr) {
69+
t.ifError(err, 'npm org')
70+
71+
t.equal(code, 0, 'exited OK')
72+
t.equal(stderr, '', 'no error output')
73+
74+
t.same(JSON.parse(stdout), membershipData)
75+
t.end()
76+
})
77+
})
78+
})
79+
})
80+
81+
test('org rm [orgname] [username]', function (t) {
82+
const membershipData = {
83+
otheruser: 'admin'
84+
}
85+
server.delete('/-/org/myorg/user', JSON.stringify({
86+
user: 'myuser'
87+
})).reply(204, {})
88+
server.get('/-/org/myorg/user')
89+
.reply(200, membershipData)
90+
common.npm([
91+
'org', 'rm', 'myorg', 'myuser',
92+
'--json',
93+
'--registry', common.registry,
94+
'--loglevel', 'silent'
95+
], {}, function (err, code, stdout, stderr) {
96+
t.ifError(err, 'npm org')
97+
98+
t.equal(code, 0, 'exited OK')
99+
t.equal(stderr, '', 'no error output')
100+
t.deepEqual(JSON.parse(stdout), {
101+
user: 'myuser',
102+
org: 'myorg',
103+
deleted: true,
104+
userCount: 1
105+
}, 'got useful info')
106+
t.end()
107+
})
108+
})
109+
110+
test('org ls [orgname]', function (t) {
111+
const membershipData = {
112+
username: 'admin',
113+
username2: 'foo'
114+
}
115+
server.get('/-/org/myorg/user')
116+
.reply(200, membershipData)
117+
common.npm([
118+
'org', 'ls', 'myorg',
119+
'--json',
120+
'--registry', common.registry,
121+
'--loglevel', 'silent'
122+
], {}, function (err, code, stdout, stderr) {
123+
t.ifError(err, 'npm org')
124+
t.equal(code, 0, 'exited OK')
125+
t.equal(stderr, '', 'no error output')
126+
t.same(JSON.parse(stdout), membershipData, 'outputs members')
127+
t.end()
128+
})
129+
})
130+
131+
test('cleanup', function (t) {
132+
t.pass('cleaned up')
133+
server.done()
134+
server.close()
135+
t.end()
136+
})

0 commit comments

Comments
 (0)