@heroku/skynet 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Skynet CLI Plugin
2
+ [![Build Status](https://travis-ci.com/heroku/heroku-skynet-cli.svg?token=Tja9HVA663wD1FA6ZNZn&branch=master)](https://travis-ci.com/heroku/heroku-skynet-cli)
3
+
4
+ Use Skynet from Heroku CLI
5
+
6
+ ### Installation
7
+ ```
8
+ heroku plugins:install heroku-skynet-cli
9
+ ```
10
+
11
+ ### Development
12
+ - `git clone https://github.com/heroku/heroku-skynet-cli.git`
13
+ - Change directories into the cloned repository
14
+ - Link the plugin with `heroku plugins:link .`
15
+
16
+ ### Publishing to `npm`
17
+ 1. Contact `heroku-cli@salesforce.com` to get credentials to Heroku's private NPM registry
18
+
19
+ 2. Make sure you have `np` installed. Install by running `npm install np`
20
+
21
+ 3. When you are ready to publish what you have locally, run `np` and you will see an output like this:
22
+ ```
23
+ λ bargenbright[heroku-skynet-cli/] (git:master)~$ np
24
+
25
+ Publish a new version of heroku-skynet-cli (current: 1.3.5)
26
+
27
+ ? Select semver increment or specify new version minor 1.4.0
28
+
29
+ Commits:
30
+ - Merge pull request #28 from heroku/refactor+appowner f2c5655
31
+ - add app-owner command b07940b
32
+ - adds app-owner command and refactors e914b09
33
+
34
+ ? Will bump from 1.3.5 to 1.4.0. Continue? Yes
35
+ ✔ Prerequisite check
36
+ ✔ Git
37
+ ✔ Cleanup
38
+ ✔ Installing dependencies using Yarn
39
+ ✔ Running tests
40
+ ✔ Bumping version using Yarn
41
+ ✖ Publishing package using Yarn
42
+ → info Visit https://yarnpkg.com/en/docs/cli/publish for documentation about this command.
43
+ Pushing tags
44
+
45
+ ✖ Error: Command failed: yarn publish --new-version 1.4.0
46
+ warning package.json: No license field
47
+ warning package.json: No license field
48
+ error An unexpected error occurred: "No token found and can't prompt for login when running with --non-interactive.".
49
+ ```
50
+
51
+ 4. After you get the `np` error, run `npm publish` to finish pushing the new release.
52
+ ```
53
+ λ bargenbright[heroku-skynet-cli/] (git:master)~$ npm publish
54
+ + heroku-skynet-cli@1.4.0
55
+ ```
56
+
57
+ 5. Then, push the tags to the git repository
58
+ ```
59
+ λ bargenbright[heroku-skynet-cli/] (git:master)~$ git push origin --tags
60
+ Counting objects: 4, done.
61
+ Delta compression using up to 8 threads.
62
+ Compressing objects: 100% (4/4), done.
63
+ Writing objects: 100% (4/4), 416 bytes | 416.00 KiB/s, done.
64
+ Total 4 (delta 2), reused 0 (delta 0)
65
+ remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
66
+ To github.com:heroku/heroku-skynet-cli.git
67
+ * [new tag] v1.4.0 -> v1.4.0
68
+ ```
69
+
70
+ 6. Finally, push the version change commit done by the `np` tool
71
+ ```
72
+ λ bargenbright[heroku-skynet-cli/] (git:master)~$ git push origin master
73
+ Total 0 (delta 0), reused 0 (delta 0)
74
+ To github.com:heroku/heroku-skynet-cli.git
75
+ f2c5655..e8ad793 master -> master
76
+ ```
@@ -0,0 +1,27 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let sudo = require('../lib/sudo')
4
+ let SkynetAPI = require('../lib/skynet')
5
+
6
+ function * run (context) {
7
+ sudo()
8
+ const skynet = new SkynetAPI(context.version, context.auth.password)
9
+ let response = yield skynet.categories()
10
+ response = JSON.parse(response)
11
+ cli.table(response, {
12
+ columns: [
13
+ {key: 'Category'},
14
+ {key: 'Description'}
15
+ ]
16
+ })
17
+ }
18
+
19
+ module.exports = {
20
+ topic: 'skynet',
21
+ command: 'categories',
22
+ description: 'categories to use for suspension and deprovisions',
23
+ help: `Examples:
24
+ $ heroku skynet:categories`,
25
+ flags: [],
26
+ run: cli.command(co.wrap(run))
27
+ }
@@ -0,0 +1,70 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let sudo = require('../lib/sudo')
4
+ let SkynetAPI = require('../lib/skynet')
5
+
6
+ function readlines (file) {
7
+ return new Promise(function (resolve, reject) {
8
+ let split = require('split')
9
+ let fs = require('fs')
10
+
11
+ let users = []
12
+
13
+ fs.createReadStream(file).pipe(split())
14
+ .on('data', function (line) {
15
+ line = line.trim()
16
+ if (line) {
17
+ users.push(line)
18
+ }
19
+ })
20
+ .on('end', function () {
21
+ resolve(users)
22
+ })
23
+ .on('error', function (err) {
24
+ reject(err)
25
+ })
26
+ })
27
+ }
28
+
29
+ function * run (context) {
30
+ sudo()
31
+ const skynet = new SkynetAPI(context.version, context.auth.password)
32
+ let user = context.flags.user || process.env.HEROKU_USER
33
+ let file = context.flags.infile
34
+ let notes = context.flags.notes
35
+ let category = context.flags.category
36
+ let force = context.flags.bypass
37
+
38
+ if (user && file) {
39
+ throw new Error('Either --user USER or --infile must be passed, but not both')
40
+ }
41
+
42
+ if (file) {
43
+ let users = yield readlines(file)
44
+ yield cli.action(`bulk deprovisioning for ${cli.color.cyan(users.length)} users`, skynet.bulkDeprovision(users.join(), notes, category))
45
+ } else {
46
+ if (!user) {
47
+ throw new Error('Required flag: --user USER or --infile FILE')
48
+ }
49
+
50
+ let response = yield cli.action(`deprovisioning ${cli.color.cyan(user)}`, skynet.deprovision(user, notes, category, force))
51
+ response = JSON.parse(response)
52
+ cli.log(`${response.status}. ${response.message}`)
53
+ }
54
+ }
55
+
56
+ module.exports = {
57
+ topic: 'skynet',
58
+ command: 'deprovision',
59
+ description: 'suspends a user and deprovisions all addons',
60
+ help: `Examples:
61
+ $ heroku skynet:deprovision -u foo@bar.com -n "helpful suspend message" -c "spam"`,
62
+ flags: [
63
+ {name: 'user', char: 'u', description: 'user to deprovision', hasValue: true},
64
+ {name: 'infile', char: 'i', description: 'file list of users to deprovision', hasValue: true},
65
+ {name: 'category', char: 'c', description: 'suspension category', hasValue: true, required: true},
66
+ {name: 'notes', char: 'n', description: 'suspend notes', hasValue: true, required: true},
67
+ {name: 'bypass', description: 'bypass the whitelist', hasValue: false, required: false}
68
+ ],
69
+ run: cli.command(co.wrap(run))
70
+ }
@@ -0,0 +1,70 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let sudo = require('../../lib/sudo')
4
+ let SkynetAPI = require('../../lib/skynet')
5
+
6
+ function readlines (file) {
7
+ return new Promise(function (resolve, reject) {
8
+ let split = require('split')
9
+ let fs = require('fs')
10
+
11
+ let users = []
12
+
13
+ fs.createReadStream(file).pipe(split())
14
+ .on('data', function (line) {
15
+ line = line.trim()
16
+ if (line) {
17
+ users.push(line)
18
+ }
19
+ })
20
+ .on('end', function () {
21
+ resolve(users)
22
+ })
23
+ .on('error', function (err) {
24
+ reject(err)
25
+ })
26
+ })
27
+ }
28
+
29
+ function * run (context) {
30
+ sudo()
31
+ const skynet = new SkynetAPI(context.version, context.auth.password)
32
+ let app = context.flags.app
33
+ let file = context.flags.infile
34
+ let notes = context.flags.notes
35
+ let category = context.flags.category
36
+ let force = context.flags.bypass
37
+
38
+ if (app && file) {
39
+ throw new Error('Either --app or --infile must be passed, but not both')
40
+ }
41
+
42
+ if (file) {
43
+ let apps = yield readlines(file)
44
+ yield cli.action(`bulk app-owner suspension for ${cli.color.cyan(apps.length)} apps.`, skynet.bulkSuspendAppOwner(apps.join(), notes, category))
45
+ } else {
46
+ if (!app) {
47
+ throw new Error('Required flag: --owner OWNER or --infile FILE')
48
+ }
49
+
50
+ let response = yield cli.action(`suspending the owner of ${cli.color.cyan(app)}`, skynet.suspendAppOwner(app, notes, category, force))
51
+ response = JSON.parse(response)
52
+ cli.log(`${response.status}. ${response.message}`)
53
+ }
54
+ }
55
+
56
+ module.exports = {
57
+ topic: 'skynet',
58
+ command: 'suspend:app-owner',
59
+ description: 'suspends the owner of a given app',
60
+ help: `Examples:
61
+ $ heroku skynet:suspend:app-owner -a foobar -n "helpful suspend message" -c "ddos"`,
62
+ flags: [
63
+ {name: 'app', char: 'a', description: 'app that requires owner suspension', hasValue: true},
64
+ {name: 'infile', char: 'i', description: 'file of apps that require owner suspension', hasValue: true},
65
+ {name: 'category', char: 'c', description: 'suspension category', hasValue: true, required: true},
66
+ {name: 'notes', char: 'n', description: 'suspend notes', hasValue: true, required: true},
67
+ {name: 'bypass', description: 'bypass the whitelist', hasValue: false, required: false}
68
+ ],
69
+ run: cli.command(co.wrap(run))
70
+ }
@@ -0,0 +1,25 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let sudo = require('../../lib/sudo')
4
+ let SkynetAPI = require('../../lib/skynet')
5
+
6
+ function * run (context) {
7
+ sudo()
8
+ const skynet = new SkynetAPI(context.version, context.auth.password)
9
+ let response = yield skynet.suspendApp(context.flags.app, context.flags.notes)
10
+ response = JSON.parse(response)
11
+ cli.log(`suspending...${cli.color.cyan(response.status)}.\n${response.message}`)
12
+ }
13
+
14
+ module.exports = {
15
+ topic: 'skynet',
16
+ command: 'suspend:app',
17
+ description: 'suspends an app',
18
+ help: `Examples:
19
+ $ heroku skynet:suspend:app -a test-app -n "helpful suspend message"`,
20
+ flags: [
21
+ {name: 'app', char: 'a', description: 'app to suspend', hasValue: true, required: true},
22
+ {name: 'notes', char: 'n', description: 'suspend notes', hasValue: true, required: true}
23
+ ],
24
+ run: cli.command(co.wrap(run))
25
+ }
@@ -0,0 +1,70 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let sudo = require('../../lib/sudo')
4
+ let SkynetAPI = require('../../lib/skynet')
5
+
6
+ function readlines (file) {
7
+ return new Promise(function (resolve, reject) {
8
+ let split = require('split')
9
+ let fs = require('fs')
10
+
11
+ let users = []
12
+
13
+ fs.createReadStream(file).pipe(split())
14
+ .on('data', function (line) {
15
+ line = line.trim()
16
+ if (line) {
17
+ users.push(line)
18
+ }
19
+ })
20
+ .on('end', function () {
21
+ resolve(users)
22
+ })
23
+ .on('error', function (err) {
24
+ reject(err)
25
+ })
26
+ })
27
+ }
28
+
29
+ function * run (context) {
30
+ sudo()
31
+ const skynet = new SkynetAPI(context.version, context.auth.password)
32
+ let user = context.flags.user || process.env.HEROKU_USER
33
+ let file = context.flags.infile
34
+ let notes = context.flags.notes
35
+ let category = context.flags.category
36
+ let force = context.flags.bypass
37
+
38
+ if (user && file) {
39
+ throw new Error('Either --user USER or --infile must be passed, but not both')
40
+ }
41
+
42
+ if (file) {
43
+ let users = yield readlines(file)
44
+ yield cli.action(`bulk user suspension for ${cli.color.cyan(users.length)} users.`, skynet.bulkSuspendUsers(users.join(), notes, category))
45
+ } else {
46
+ if (!user) {
47
+ throw new Error('Required flag: --user USER or --infile FILE')
48
+ }
49
+
50
+ let response = yield cli.action(`suspending ${cli.color.cyan(user)}`, skynet.suspendUser(user, notes, category, force))
51
+ response = JSON.parse(response)
52
+ cli.log(`${response.status}. ${response.message}`)
53
+ }
54
+ }
55
+
56
+ module.exports = {
57
+ topic: 'skynet',
58
+ command: 'suspend:user',
59
+ description: 'suspends a user',
60
+ help: `Examples:
61
+ $ heroku skynet:suspend:user -u foo@bar.com -n "helpful suspend message" -c "ddos"`,
62
+ flags: [
63
+ {name: 'user', char: 'u', description: 'user to suspend', hasValue: true},
64
+ {name: 'infile', char: 'i', description: 'file of users to suspend', hasValue: true},
65
+ {name: 'category', char: 'c', description: 'suspension category', hasValue: true, required: true},
66
+ {name: 'notes', char: 'n', description: 'suspend notes', hasValue: true, required: true},
67
+ {name: 'bypass', description: 'bypass the whitelist', hasValue: false, required: false}
68
+ ],
69
+ run: cli.command(co.wrap(run))
70
+ }
@@ -0,0 +1,24 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let sudo = require('../../lib/sudo')
4
+ let SkynetAPI = require('../../lib/skynet')
5
+
6
+ function * run (context) {
7
+ sudo()
8
+ const skynet = new SkynetAPI(context.version, context.auth.password)
9
+ let response = yield skynet.unsuspendApp(context.flags.app, context.flags.notes)
10
+ response = JSON.parse(response)
11
+ cli.log(`suspending...${cli.color.cyan(response.status)}.\n${response.message}`)
12
+ }
13
+
14
+ module.exports = {
15
+ topic: 'skynet',
16
+ command: 'unsuspend:app',
17
+ description: 'unsuspends an app',
18
+ help: `Examples:
19
+ $ heroku skynet:unsuspend:app -a test-app`,
20
+ flags: [
21
+ {name: 'app', char: 'a', description: 'app to unsuspend', hasValue: true, required: true}
22
+ ],
23
+ run: cli.command(co.wrap(run))
24
+ }
@@ -0,0 +1,26 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let sudo = require('../../lib/sudo')
4
+ let SkynetAPI = require('../../lib/skynet')
5
+
6
+ function * run (context) {
7
+ sudo()
8
+ const skynet = new SkynetAPI(context.version, context.auth.password)
9
+ let user = context.flags.user || process.env.HEROKU_USER
10
+
11
+ let response = yield cli.action(`unsuspending ${cli.color.cyan(user)}`, skynet.unsuspendUser(user))
12
+ response = JSON.parse(response)
13
+ cli.log(`${response.status}. ${response.message}`)
14
+ }
15
+
16
+ module.exports = {
17
+ topic: 'skynet',
18
+ command: 'unsuspend:user',
19
+ description: 'unsuspends a user',
20
+ help: `Examples:
21
+ $ heroku skynet:unsuspend:user -u foo@bar.com`,
22
+ flags: [
23
+ {name: 'user', char: 'u', description: 'user to unsuspend', hasValue: true}
24
+ ],
25
+ run: cli.command(co.wrap(run))
26
+ }
@@ -0,0 +1,21 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let SkynetAPI = require('../../lib/skynet')
4
+
5
+ function * run (context) {
6
+ const skynet = new SkynetAPI(context.version, context.auth.password)
7
+ yield cli.action(`adding ${cli.color.cyan(context.flags.userpass)} to ${cli.color.cyan(context.flags.user)}`, skynet.addUserpass(context.flags.user, context.flags.userpass))
8
+ }
9
+
10
+ module.exports = {
11
+ topic: 'skynet',
12
+ command: 'userpass:add',
13
+ description: 'adds a userpass flag to a given user',
14
+ help: `Examples:
15
+ $ heroku skynet:userpass:add -u foo@bar.com -f cant-install-abused-addons`,
16
+ flags: [
17
+ {name: 'user', char: 'u', description: 'user to apply userpass flag to', hasValue: true, required: true},
18
+ {name: 'userpass', char: 'f', description: 'flag to add to the given user', hasValue: true, required: true}
19
+ ],
20
+ run: cli.command(co.wrap(run))
21
+ }
@@ -0,0 +1,21 @@
1
+ let cli = require('heroku-cli-util')
2
+ let co = require('co')
3
+ let SkynetAPI = require('../../lib/skynet')
4
+
5
+ function * run (context) {
6
+ const skynet = new SkynetAPI(context.version, context.auth.password)
7
+ yield cli.action(`removing ${cli.color.cyan(context.flags.userpass)} from ${cli.color.cyan(context.flags.user)}`, skynet.removeUserpass(context.flags.user, context.flags.userpass))
8
+ }
9
+
10
+ module.exports = {
11
+ topic: 'skynet',
12
+ command: 'userpass:remove',
13
+ description: 'removes given flag from a given user',
14
+ help: `Examples:
15
+ $ heroku skynet:userpass:remove -u foo@bar.com -f cant-install-abused-addons`,
16
+ flags: [
17
+ {name: 'user', char: 'u', description: 'user to remove user_pass from', hasValue: true, required: true},
18
+ {name: 'userpass', char: 'f', description: 'flag to remove from given user', hasValue: true, required: true}
19
+ ],
20
+ run: cli.command(co.wrap(run))
21
+ }
package/index.js ADDED
@@ -0,0 +1,17 @@
1
+ exports.topic = {
2
+ name: 'skynet',
3
+ // this is the help text that shows up under `heroku help`
4
+ description: 'use Skynet from Heroku CLI'
5
+ }
6
+
7
+ exports.commands = [
8
+ require('./commands/userpass/add.js'),
9
+ require('./commands/userpass/remove.js'),
10
+ require('./commands/suspend/app-owner.js'),
11
+ require('./commands/suspend/apps.js'),
12
+ require('./commands/suspend/user.js'),
13
+ require('./commands/unsuspend/apps.js'),
14
+ require('./commands/unsuspend/user.js'),
15
+ require('./commands/deprovision.js'),
16
+ require('./commands/categories.js')
17
+ ]
package/lib/skynet.js ADDED
@@ -0,0 +1,169 @@
1
+ const cli = require('heroku-cli-util')
2
+ const qs = require('querystring')
3
+ const SKYNET_BASE_URL = 'https://skynet.herokai.com/api-h'
4
+
5
+ module.exports = class SkynetAPI {
6
+ constructor (version, token) {
7
+ this.version = version
8
+ this.token = token
9
+ }
10
+
11
+ request (url, options = {}) {
12
+ options.headers = Object.assign({
13
+ Authorization: `Bearer ${this.token}`,
14
+ 'User-Agent': this.version
15
+ })
16
+
17
+ if (['POST', 'PATCH', 'DELETE'].includes(options.method)) {
18
+ options.headers['Content-type'] = 'application/x-www-form-urlencoded'
19
+ }
20
+
21
+ options.json = false
22
+
23
+ return cli.got(SKYNET_BASE_URL + url, options).then((res) => res.body)
24
+ }
25
+
26
+ categories () {
27
+ return this.request(`/categories`, {
28
+ method: 'GET'
29
+ })
30
+ }
31
+
32
+ removeUserpass (user, flag) {
33
+ var body = {
34
+ user: user,
35
+ flag: flag
36
+ }
37
+
38
+ return this.request(`/userpass/remove`, {
39
+ method: 'POST',
40
+ body: qs.stringify(body)
41
+ })
42
+ }
43
+
44
+ addUserpass (user, flag) {
45
+ var body = {
46
+ user: user,
47
+ flag: flag
48
+ }
49
+
50
+ return this.request(`/userpass/add`, {
51
+ method: 'POST',
52
+ body: qs.stringify(body)
53
+ })
54
+ }
55
+
56
+ suspendAppOwner (app, notes, category) {
57
+ var body = {
58
+ value: app,
59
+ reason: notes,
60
+ method: 'skynet-cli',
61
+ category: category
62
+ }
63
+
64
+ return this.request('/suspend/app-owner', {
65
+ method: 'POST',
66
+ body: qs.stringify(body)
67
+ })
68
+ }
69
+
70
+ suspendApp (app, notes, category) {
71
+ var body = {
72
+ value: app,
73
+ reason: notes,
74
+ method: 'skynet-cli',
75
+ category: category
76
+ }
77
+
78
+ return this.request('/suspend/app', {
79
+ method: 'POST',
80
+ body: qs.stringify(body)
81
+ })
82
+ }
83
+
84
+ unsuspendApp (app) {
85
+ return this.request(`/suspensions/app/${app}`, {
86
+ method: 'DELETE'
87
+ })
88
+ }
89
+ suspendUser (user, notes, category, force = false) {
90
+ var body = {
91
+ value: user,
92
+ reason: notes,
93
+ method: 'skynet-cli',
94
+ category: category,
95
+ force: force
96
+ }
97
+
98
+ return this.request(`/suspend/user`, {
99
+ method: 'POST',
100
+ body: qs.stringify(body)
101
+ })
102
+ }
103
+
104
+ unsuspendUser (user) {
105
+ return this.request(`/suspensions/user/${user}`, {
106
+ method: 'DELETE'
107
+ })
108
+ }
109
+
110
+ bulkSuspendUsers (users, notes, category) {
111
+ var body = {
112
+ value: users,
113
+ reason: notes,
114
+ method: 'skynet-cli',
115
+ category: category,
116
+ bulk: 'true'
117
+ }
118
+
119
+ return this.request(`/suspend/user`, {
120
+ method: 'POST',
121
+ body: qs.stringify(body)
122
+ })
123
+ }
124
+
125
+ bulkSuspendAppOwner (apps, notes, category) {
126
+ var body = {
127
+ value: apps,
128
+ reason: notes,
129
+ method: 'skynet-cli',
130
+ category: category,
131
+ bulk: 'true'
132
+ }
133
+
134
+ return this.request(`/suspend/app-owner`, {
135
+ method: 'POST',
136
+ body: qs.stringify(body)
137
+ })
138
+ }
139
+
140
+ deprovision (user, notes, category, force = false) {
141
+ var body = {
142
+ value: user,
143
+ reason: notes,
144
+ method: 'skynet-cli',
145
+ category: category,
146
+ force: force
147
+ }
148
+
149
+ return this.request(`/deprovision`, {
150
+ method: 'POST',
151
+ body: qs.stringify(body)
152
+ })
153
+ }
154
+
155
+ bulkDeprovision (users, notes, category) {
156
+ var body = {
157
+ value: users,
158
+ reason: notes,
159
+ method: 'skynet-cli',
160
+ category: category,
161
+ bulk: 'true'
162
+ }
163
+
164
+ return this.request(`/deprovision`, {
165
+ method: 'POST',
166
+ body: qs.stringify(body)
167
+ })
168
+ }
169
+ }
package/lib/sudo.js ADDED
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ module.exports = function requireSudo () {
4
+ if (process.env.HEROKU_SUDO !== '1') throw new Error('sudo is required for this command')
5
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@heroku/skynet",
3
+ "version": "1.4.1",
4
+ "description": "use Skynet from Heroku CLI",
5
+ "main": "index.js",
6
+ "author": "Bob Argenbright @byt3smith",
7
+ "repository": "heroku/heroku-skynet-cli",
8
+ "bugs": {
9
+ "url": "https://github.com/heroku/heroku-skynet/issues"
10
+ },
11
+ "keywords": [
12
+ "heroku-plugin"
13
+ ],
14
+ "files": [
15
+ "/index.js",
16
+ "/lib",
17
+ "/commands"
18
+ ],
19
+ "dependencies": {
20
+ "co": "^4.6.0",
21
+ "heroku-cli-util": "~6.2.12",
22
+ "split": "^1.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "mocha": "^3.4.2",
26
+ "mockdate": "^2.0.1",
27
+ "nock": "^9.0.13",
28
+ "standard": "^10.0.2",
29
+ "unexpected": "^10.29.0"
30
+ },
31
+ "scripts": {
32
+ "test": "mocha && standard"
33
+ }
34
+ }