@heroku/heroku-cli-util 8.0.15 → 9.0.0-beta.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/LICENSE +15 -3
- package/README.md +44 -241
- package/dist/index.d.ts +20 -0
- package/dist/index.js +31 -0
- package/dist/test-helpers/expect-output.d.ts +2 -0
- package/dist/test-helpers/expect-output.js +16 -0
- package/dist/test-helpers/init.d.ts +1 -0
- package/dist/test-helpers/init.js +18 -0
- package/dist/test-helpers/stub-output.d.ts +2 -0
- package/dist/test-helpers/stub-output.js +33 -0
- package/dist/types/errors/ambiguous.d.ts +15 -0
- package/dist/types/errors/ambiguous.js +14 -0
- package/dist/types/errors/not-found.d.ts +5 -0
- package/dist/types/errors/not-found.js +12 -0
- package/dist/types/pg/data-api.d.ts +17 -0
- package/dist/types/pg/data-api.js +2 -0
- package/dist/types/pg/tunnel.d.ts +22 -0
- package/dist/types/pg/tunnel.js +2 -0
- package/dist/utils/addons/resolve.d.ts +9 -0
- package/dist/utils/addons/resolve.js +39 -0
- package/dist/utils/pg/bastion.d.ts +30 -0
- package/dist/utils/pg/bastion.js +122 -0
- package/dist/utils/pg/config-vars.d.ts +8 -0
- package/dist/utils/pg/config-vars.js +34 -0
- package/dist/utils/pg/databases.d.ts +12 -0
- package/dist/utils/pg/databases.js +137 -0
- package/dist/utils/pg/host.d.ts +1 -0
- package/dist/utils/pg/host.js +7 -0
- package/dist/utils/pg/psql.d.ts +28 -0
- package/dist/utils/pg/psql.js +188 -0
- package/dist/ux/confirm.d.ts +1 -0
- package/dist/ux/confirm.js +7 -0
- package/dist/ux/prompt.d.ts +2 -0
- package/dist/ux/prompt.js +7 -0
- package/dist/ux/styled-header.d.ts +1 -0
- package/dist/ux/styled-header.js +7 -0
- package/dist/ux/styled-json.d.ts +1 -0
- package/dist/ux/styled-json.js +7 -0
- package/dist/ux/styled-object.d.ts +1 -0
- package/dist/ux/styled-object.js +7 -0
- package/dist/ux/table.d.ts +2 -0
- package/dist/ux/table.js +7 -0
- package/dist/ux/wait.d.ts +1 -0
- package/dist/ux/wait.js +7 -0
- package/package.json +54 -55
- package/index.js +0 -40
- package/lib/action.js +0 -54
- package/lib/auth.js +0 -207
- package/lib/command.js +0 -171
- package/lib/console.js +0 -105
- package/lib/date.js +0 -18
- package/lib/errors.js +0 -122
- package/lib/exit.js +0 -42
- package/lib/got.js +0 -153
- package/lib/linewrap.js +0 -783
- package/lib/mutex.js +0 -41
- package/lib/open.js +0 -22
- package/lib/preauth.js +0 -26
- package/lib/process.js +0 -14
- package/lib/prompt.js +0 -150
- package/lib/spinner.js +0 -147
- package/lib/spinners.json +0 -739
- package/lib/styled.js +0 -131
- package/lib/table.js +0 -132
- package/lib/util.js +0 -38
- package/lib/vars.js +0 -29
- package/lib/yubikey.js +0 -14
package/package.json
CHANGED
|
@@ -1,70 +1,69 @@
|
|
|
1
1
|
{
|
|
2
|
+
"type": "commonjs",
|
|
2
3
|
"name": "@heroku/heroku-cli-util",
|
|
4
|
+
"version": "9.0.0-beta.1",
|
|
3
5
|
"description": "Set of helpful CLI utilities",
|
|
4
|
-
"version": "8.0.15",
|
|
5
6
|
"author": "Heroku",
|
|
6
|
-
"
|
|
7
|
-
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@heroku-cli/schema": "^2.0.0",
|
|
15
|
+
"@types/chai": "^4.3.13",
|
|
16
|
+
"@types/debug": "^4.1.12",
|
|
17
|
+
"@types/mocha": "^10.0.10",
|
|
18
|
+
"@types/node": "^22.15.3",
|
|
19
|
+
"@types/sinon": "^17.0.4",
|
|
20
|
+
"@types/tunnel-ssh": "4.1.1",
|
|
21
|
+
"chai": "^4.4.1",
|
|
22
|
+
"eslint": "^8.57.0",
|
|
23
|
+
"eslint-config-oclif": "^5.0.0",
|
|
24
|
+
"eslint-config-oclif-typescript": "^3.1.14",
|
|
25
|
+
"eslint-plugin-import": "^2.31.0",
|
|
26
|
+
"eslint-plugin-mocha": "^10.4.3",
|
|
27
|
+
"mocha": "^10.8.2",
|
|
28
|
+
"nyc": "^17.1.0",
|
|
29
|
+
"sinon": "^18.0.1",
|
|
30
|
+
"strip-ansi": "^6",
|
|
31
|
+
"ts-node": "^10.9.2",
|
|
32
|
+
"tsconfig-paths": "^4.2.0",
|
|
33
|
+
"tsheredoc": "^1.0.1",
|
|
34
|
+
"typescript": "^5.4.0"
|
|
8
35
|
},
|
|
9
36
|
"dependencies": {
|
|
10
|
-
"@
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"lodash": "^4.17.10",
|
|
18
|
-
"netrc-parser": "^3.1.4",
|
|
19
|
-
"opn": "^3.0.3",
|
|
20
|
-
"strip-ansi": "^4.0.0",
|
|
21
|
-
"supports-color": "^9.4.0",
|
|
22
|
-
"tslib": "^1.9.0",
|
|
23
|
-
"tunnel-agent": "^0.6.0"
|
|
24
|
-
},
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"chai": "^4.1.2",
|
|
27
|
-
"co": "^4.6.0",
|
|
28
|
-
"hook-std": "^0.4.0",
|
|
29
|
-
"mocha": "^10.2.0",
|
|
30
|
-
"mocha-junit-reporter": "^2.2.1",
|
|
31
|
-
"nock": "^11.9.1",
|
|
32
|
-
"nyc": "^15.1.0",
|
|
33
|
-
"proxyquire": "^1.7.11",
|
|
34
|
-
"sinon": "^18.0.0",
|
|
35
|
-
"standard": "^12.0.1",
|
|
36
|
-
"tmp": "^0.0.33",
|
|
37
|
-
"unexpected": "^13.2.1"
|
|
37
|
+
"@oclif/core": "^2.16.0",
|
|
38
|
+
"@heroku/http-call": "^5.4.0",
|
|
39
|
+
"@heroku-cli/color": "^2.0.4",
|
|
40
|
+
"@heroku-cli/command": "^11.5.0",
|
|
41
|
+
"debug": "^4.4.0",
|
|
42
|
+
"nock": "^13.2.9",
|
|
43
|
+
"tunnel-ssh": "4.1.6"
|
|
38
44
|
},
|
|
39
45
|
"engines": {
|
|
40
|
-
"node": ">=
|
|
46
|
+
"node": ">=20"
|
|
41
47
|
},
|
|
42
|
-
"files": [
|
|
43
|
-
"lib",
|
|
44
|
-
"index.js"
|
|
45
|
-
],
|
|
46
|
-
"homepage": "https://github.com/heroku/heroku-cli-util",
|
|
47
|
-
"license": "ISC",
|
|
48
|
-
"main": "index.js",
|
|
49
48
|
"mocha": {
|
|
50
49
|
"require": [
|
|
51
|
-
"
|
|
52
|
-
|
|
50
|
+
"ts-node/register",
|
|
51
|
+
"source-map-support/register"
|
|
52
|
+
],
|
|
53
|
+
"file": [
|
|
54
|
+
"src/test-helpers/stub-output.ts"
|
|
55
|
+
],
|
|
56
|
+
"watch-extensions": "ts",
|
|
57
|
+
"recursive": true,
|
|
58
|
+
"reporter": "spec",
|
|
59
|
+
"timeout": 360000
|
|
53
60
|
},
|
|
54
|
-
"nyc": {
|
|
55
|
-
"exclude": [
|
|
56
|
-
"lib/linewrap.js"
|
|
57
|
-
]
|
|
58
|
-
},
|
|
59
|
-
"repository": "heroku/heroku-cli-util",
|
|
60
61
|
"scripts": {
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"lib/linewrap.js"
|
|
68
|
-
]
|
|
62
|
+
"build": "tsc",
|
|
63
|
+
"clean": "rm -rf dist",
|
|
64
|
+
"lint": "eslint . --ext .ts --config .eslintrc.js",
|
|
65
|
+
"prepare": "npm run clean && npm run build",
|
|
66
|
+
"test": "nyc mocha --forbid-only \"test/**/*.test.ts\"",
|
|
67
|
+
"test:local": "mocha \"test/**/*.test.ts\""
|
|
69
68
|
}
|
|
70
69
|
}
|
package/index.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
exports.color = require('@heroku-cli/color').default
|
|
4
|
-
|
|
5
|
-
var console = require('./lib/console')
|
|
6
|
-
var errors = require('./lib/errors')
|
|
7
|
-
var prompt = require('./lib/prompt')
|
|
8
|
-
var styled = require('./lib/styled')
|
|
9
|
-
|
|
10
|
-
exports.hush = console.hush
|
|
11
|
-
exports.log = console.log.bind(console)
|
|
12
|
-
exports.formatDate = require('./lib/date').formatDate
|
|
13
|
-
exports.error = errors.error
|
|
14
|
-
exports.action = require('./lib/action')
|
|
15
|
-
exports.warn = exports.action.warn
|
|
16
|
-
exports.errorHandler = errors.errorHandler
|
|
17
|
-
exports.console = console
|
|
18
|
-
exports.yubikey = require('./lib/yubikey')
|
|
19
|
-
exports.prompt = prompt.prompt
|
|
20
|
-
exports.confirmApp = prompt.confirmApp
|
|
21
|
-
exports.preauth = require('./lib/preauth')
|
|
22
|
-
exports.command = require('./lib/command')
|
|
23
|
-
exports.debug = console.debug
|
|
24
|
-
exports.mockConsole = console.mock
|
|
25
|
-
exports.table = require('./lib/table')
|
|
26
|
-
exports.stdout = ''
|
|
27
|
-
exports.stderr = ''
|
|
28
|
-
exports.styledHeader = styled.styledHeader
|
|
29
|
-
exports.styledObject = styled.styledObject
|
|
30
|
-
exports.styledHash = styled.styledObject
|
|
31
|
-
exports.styledNameValues = styled.styledNameValues
|
|
32
|
-
exports.styledJSON = styled.styledJSON
|
|
33
|
-
exports.open = require('./lib/open')
|
|
34
|
-
exports.got = require('./lib/got')
|
|
35
|
-
exports.linewrap = require('./lib/linewrap')
|
|
36
|
-
exports.Spinner = require('./lib/spinner')
|
|
37
|
-
exports.exit = require('./lib/exit').exit
|
|
38
|
-
exports.auth = require('./lib/auth')
|
|
39
|
-
exports.login = exports.auth.login
|
|
40
|
-
exports.logout = exports.auth.logout
|
package/lib/action.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
let cli = require('..')
|
|
4
|
-
let errors = require('./errors')
|
|
5
|
-
|
|
6
|
-
function start (message, options) {
|
|
7
|
-
if (!options) options = {}
|
|
8
|
-
module.exports.task = {
|
|
9
|
-
spinner: new cli.Spinner({ spinner: options.spinner, text: `${message}...` }),
|
|
10
|
-
stream: options.stream
|
|
11
|
-
}
|
|
12
|
-
module.exports.task.spinner.start()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function action (message, options, promise) {
|
|
16
|
-
if (options.then) [options, promise] = [{}, options]
|
|
17
|
-
start(message, options)
|
|
18
|
-
return promise.then(function (result) {
|
|
19
|
-
if (options.success !== false) done(options.success || 'done', options)
|
|
20
|
-
else done(null, options)
|
|
21
|
-
return result
|
|
22
|
-
}).catch(function (err) {
|
|
23
|
-
if (err.body && err.body.id === 'two_factor') done(cli.color.yellow.bold('!'), options)
|
|
24
|
-
else done(cli.color.red.bold('!'), options)
|
|
25
|
-
throw err
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function warn (msg) {
|
|
30
|
-
let task = module.exports.task
|
|
31
|
-
if (task) task.spinner.warn(msg)
|
|
32
|
-
else errors.warn(msg)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function status (status) {
|
|
36
|
-
let task = module.exports.task
|
|
37
|
-
if (task) task.spinner.status = status
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function done (msg, options) {
|
|
41
|
-
options = options || {}
|
|
42
|
-
let task = module.exports.task
|
|
43
|
-
if (task) {
|
|
44
|
-
task.spinner.stop(msg)
|
|
45
|
-
module.exports.task = null
|
|
46
|
-
if (options.clear) task.spinner.clear()
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
module.exports = action
|
|
51
|
-
module.exports.start = start
|
|
52
|
-
module.exports.warn = warn
|
|
53
|
-
module.exports.status = status
|
|
54
|
-
module.exports.done = done
|
package/lib/auth.js
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const cli = require('..')
|
|
4
|
-
const vars = require('./vars')
|
|
5
|
-
|
|
6
|
-
function basicAuth (username, password) {
|
|
7
|
-
let auth = [username, password].join(':')
|
|
8
|
-
auth = Buffer.from(auth).toString('base64')
|
|
9
|
-
return `Basic ${auth}`
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function createOAuthToken (username, password, expiresIn, secondFactor) {
|
|
13
|
-
const os = require('os')
|
|
14
|
-
|
|
15
|
-
let headers = {
|
|
16
|
-
Authorization: basicAuth(username, password)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (secondFactor) headers['Heroku-Two-Factor-Code'] = secondFactor
|
|
20
|
-
|
|
21
|
-
return cli.heroku.post('/oauth/authorizations', {
|
|
22
|
-
headers,
|
|
23
|
-
body: {
|
|
24
|
-
scope: ['global'],
|
|
25
|
-
description: `Heroku CLI login from ${os.hostname()} at ${new Date()}`,
|
|
26
|
-
expires_in: expiresIn || 60 * 60 * 24 * 365 // 1 year
|
|
27
|
-
}
|
|
28
|
-
}).then(function (auth) {
|
|
29
|
-
return { token: auth.access_token.token, email: auth.user.email, expires_in: auth.access_token.expires_in }
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function saveToken ({ email, token }) {
|
|
34
|
-
const netrc = require('netrc-parser').default
|
|
35
|
-
netrc.loadSync()
|
|
36
|
-
const hosts = [vars.apiHost, vars.httpGitHost]
|
|
37
|
-
hosts.forEach(host => {
|
|
38
|
-
if (!netrc.machines[host]) netrc.machines[host] = {}
|
|
39
|
-
netrc.machines[host].login = email
|
|
40
|
-
netrc.machines[host].password = token
|
|
41
|
-
})
|
|
42
|
-
if (netrc.machines._tokens) {
|
|
43
|
-
netrc.machines._tokens.forEach(token => {
|
|
44
|
-
if (hosts.includes(token.host)) {
|
|
45
|
-
token.internalWhitespace = '\n '
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
netrc.saveSync()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function loginUserPass ({ save, expires_in: expiresIn }) {
|
|
53
|
-
const { prompt } = require('./prompt')
|
|
54
|
-
|
|
55
|
-
cli.log('Enter your Heroku credentials:')
|
|
56
|
-
let email = await prompt('Email')
|
|
57
|
-
let password = await prompt('Password', { hide: true })
|
|
58
|
-
|
|
59
|
-
let auth
|
|
60
|
-
try {
|
|
61
|
-
auth = await createOAuthToken(email, password, expiresIn)
|
|
62
|
-
} catch (err) {
|
|
63
|
-
if (!err.body || err.body.id !== 'two_factor') throw err
|
|
64
|
-
let secondFactor = await prompt('Two-factor code', { mask: true })
|
|
65
|
-
auth = await createOAuthToken(email, password, expiresIn, secondFactor)
|
|
66
|
-
}
|
|
67
|
-
if (save) saveToken(auth)
|
|
68
|
-
return auth
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function loginSSO ({ save, browser }) {
|
|
72
|
-
const { prompt } = require('./prompt')
|
|
73
|
-
|
|
74
|
-
let url = process.env['SSO_URL']
|
|
75
|
-
if (!url) {
|
|
76
|
-
let org = process.env['HEROKU_ORGANIZATION']
|
|
77
|
-
if (!org) {
|
|
78
|
-
org = await prompt('Enter your organization name')
|
|
79
|
-
}
|
|
80
|
-
url = `https://sso.heroku.com/saml/${encodeURIComponent(org)}/init?cli=true`
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const open = require('./open')
|
|
84
|
-
|
|
85
|
-
let openError
|
|
86
|
-
await cli.action('Opening browser for login', open(url, browser)
|
|
87
|
-
.catch(function (err) {
|
|
88
|
-
openError = err
|
|
89
|
-
})
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
if (openError) {
|
|
93
|
-
cli.console.error(openError.message)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let token = await prompt('Enter your access token (typing will be hidden)', { hide: true })
|
|
97
|
-
|
|
98
|
-
let account = await cli.heroku.get('/account', {
|
|
99
|
-
headers: {
|
|
100
|
-
Authorization: `Bearer ${token}`
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
if (save) saveToken({ token, email: account.email })
|
|
105
|
-
return { token: token, email: account.email }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function logout () {
|
|
109
|
-
let token = cli.heroku.options.token
|
|
110
|
-
if (token) {
|
|
111
|
-
// for SSO logins we delete the session since those do not show up in
|
|
112
|
-
// authorizations because they are created a trusted client
|
|
113
|
-
let sessionsP = cli.heroku.delete('/oauth/sessions/~')
|
|
114
|
-
.catch(err => {
|
|
115
|
-
if (err.statusCode === 404 && err.body && err.body.id === 'not_found' && err.body.resource === 'session') {
|
|
116
|
-
return null
|
|
117
|
-
}
|
|
118
|
-
if (err.statusCode === 401 && err.body && err.body.id === 'unauthorized') {
|
|
119
|
-
return null
|
|
120
|
-
}
|
|
121
|
-
throw err
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
// grab the default authorization because that is the token shown in the
|
|
125
|
-
// dashboard as API Key and they may be using it for something else and we
|
|
126
|
-
// would unwittingly break an integration that they are depending on
|
|
127
|
-
let defaultAuthorizationP = cli.heroku.get('/oauth/authorizations/~')
|
|
128
|
-
.catch(err => {
|
|
129
|
-
if (err.statusCode === 404 && err.body && err.body.id === 'not_found' && err.body.resource === 'authorization') {
|
|
130
|
-
return null
|
|
131
|
-
}
|
|
132
|
-
if (err.statusCode === 401 && err.body && err.body.id === 'unauthorized') {
|
|
133
|
-
return null
|
|
134
|
-
}
|
|
135
|
-
throw err
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
// grab all the authorizations so that we can delete the token they are
|
|
139
|
-
// using in the CLI. we have to do this rather than delete ~ because
|
|
140
|
-
// the ~ is the API Key, not the authorization that is currently requesting
|
|
141
|
-
let authorizationsP = cli.heroku.get('/oauth/authorizations')
|
|
142
|
-
.catch(err => {
|
|
143
|
-
if (err.statusCode === 401 && err.body && err.body.id === 'unauthorized') {
|
|
144
|
-
return []
|
|
145
|
-
}
|
|
146
|
-
throw err
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
let [, defaultAuthorization, authorizations] = await Promise.all([sessionsP, defaultAuthorizationP, authorizationsP])
|
|
150
|
-
|
|
151
|
-
if (accessToken(defaultAuthorization) !== token) {
|
|
152
|
-
for (let authorization of authorizations) {
|
|
153
|
-
if (accessToken(authorization) === token) {
|
|
154
|
-
// remove the matching access token from core services
|
|
155
|
-
await cli.heroku.delete(`/oauth/authorizations/${authorization.id}`)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const netrc = require('netrc-parser').default
|
|
162
|
-
netrc.loadSync()
|
|
163
|
-
if (netrc.machines[vars.apiHost]) {
|
|
164
|
-
netrc.machines[vars.apiHost] = undefined
|
|
165
|
-
}
|
|
166
|
-
if (netrc.machines[vars.httpGitHost]) {
|
|
167
|
-
netrc.machines[vars.httpGitHost] = undefined
|
|
168
|
-
}
|
|
169
|
-
netrc.saveSync()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function accessToken (authorization) {
|
|
173
|
-
return authorization && authorization.access_token && authorization.access_token.token
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async function login (options = {}) {
|
|
177
|
-
if (!options.skipLogout) await logout()
|
|
178
|
-
|
|
179
|
-
try {
|
|
180
|
-
if (options['sso']) {
|
|
181
|
-
return await loginSSO(options)
|
|
182
|
-
} else {
|
|
183
|
-
return await loginUserPass(options)
|
|
184
|
-
}
|
|
185
|
-
} catch (e) {
|
|
186
|
-
const { PromptMaskError } = require('./prompt')
|
|
187
|
-
const os = require('os')
|
|
188
|
-
if (e instanceof PromptMaskError && os.platform() === 'win32') {
|
|
189
|
-
throw new PromptMaskError('Login is currently incompatible with git bash/Cygwin/MinGW')
|
|
190
|
-
} else {
|
|
191
|
-
throw e
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function token () {
|
|
197
|
-
const netrc = require('netrc-parser').default
|
|
198
|
-
netrc.loadSync()
|
|
199
|
-
if (process.env.HEROKU_API_KEY) return process.env.HEROKU_API_KEY
|
|
200
|
-
return netrc.machines[vars.apiHost] && netrc.machines[vars.apiHost].password
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
module.exports = {
|
|
204
|
-
login: login,
|
|
205
|
-
logout: logout,
|
|
206
|
-
token
|
|
207
|
-
}
|
package/lib/command.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const Heroku = require('heroku-client')
|
|
4
|
-
const cli = require('..')
|
|
5
|
-
const auth = require('./auth')
|
|
6
|
-
const vars = require('./vars')
|
|
7
|
-
const Mutex = require('./mutex')
|
|
8
|
-
|
|
9
|
-
function twoFactorWrapper (options, preauths, context) {
|
|
10
|
-
return function (res, buffer) {
|
|
11
|
-
let body
|
|
12
|
-
try {
|
|
13
|
-
body = this.parseBody(buffer)
|
|
14
|
-
} catch (e) {
|
|
15
|
-
this._handleFailure(res, buffer)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// safety check for if we have already seen this request for preauthing
|
|
19
|
-
// this prevents an infinite loop in case some preauth fails silently
|
|
20
|
-
// and we continue to get two_factor failures
|
|
21
|
-
|
|
22
|
-
// this might be better done with a timer in case a command takes too long
|
|
23
|
-
// and the preauthorization runs out, but that seemed unlikely
|
|
24
|
-
if (res.statusCode === 403 && body.id === 'two_factor' && !preauths.requests.includes(this)) {
|
|
25
|
-
let self = this
|
|
26
|
-
// default preauth to always happen unless explicitly disabled
|
|
27
|
-
if (options.preauth === false || !body.app) {
|
|
28
|
-
twoFactorPrompt(options, preauths, context)
|
|
29
|
-
.then(function (secondFactor) {
|
|
30
|
-
self.options.headers = Object.assign({}, self.options.headers, { 'Heroku-Two-Factor-Code': secondFactor })
|
|
31
|
-
self.request()
|
|
32
|
-
})
|
|
33
|
-
.catch(function (err) {
|
|
34
|
-
self.reject(err)
|
|
35
|
-
})
|
|
36
|
-
} else {
|
|
37
|
-
preauths.requests.push(self)
|
|
38
|
-
|
|
39
|
-
// if multiple requests are run in parallel for the same app, we should
|
|
40
|
-
// only preauth for the first so save the fact we already preauthed
|
|
41
|
-
if (!preauths.promises[body.app.name]) {
|
|
42
|
-
preauths.promises[body.app.name] = twoFactorPrompt(options, preauths, context)
|
|
43
|
-
.then(function (secondFactor) {
|
|
44
|
-
return cli.preauth(body.app.name, heroku(context), secondFactor)
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
preauths.promises[body.app.name].then(function () {
|
|
49
|
-
self.request()
|
|
50
|
-
})
|
|
51
|
-
.catch(function (err) {
|
|
52
|
-
self.reject(err)
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
} else {
|
|
56
|
-
this._handleFailure(res, buffer)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function apiMiddleware (options, preauths, context) {
|
|
62
|
-
let twoFactor = twoFactorWrapper(options, preauths, context)
|
|
63
|
-
return function (response, cb) {
|
|
64
|
-
let warning = response.headers['x-heroku-warning'] || response.headers['warning-message']
|
|
65
|
-
if (warning) cli.action.warn(warning)
|
|
66
|
-
|
|
67
|
-
// override the _handleFailure for this request
|
|
68
|
-
if (!this._handleFailure) {
|
|
69
|
-
this._handleFailure = this.handleFailure
|
|
70
|
-
this.handleFailure = twoFactor.bind(this)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
cb()
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function heroku (context, options) {
|
|
78
|
-
let host = context.apiUrl || vars.apiUrl || 'https://api.heroku.com'
|
|
79
|
-
|
|
80
|
-
let preauths = {
|
|
81
|
-
promises: {},
|
|
82
|
-
requests: [],
|
|
83
|
-
twoFactorMutex: new Mutex()
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let opts = {
|
|
87
|
-
userAgent: context.version,
|
|
88
|
-
debug: context.debug,
|
|
89
|
-
debugHeaders: context.debugHeaders,
|
|
90
|
-
token: context.auth ? context.auth.password : null,
|
|
91
|
-
host: host,
|
|
92
|
-
headers: {},
|
|
93
|
-
rejectUnauthorized: !(process.env.HEROKU_SSL_VERIFY === 'disable' || host.endsWith('herokudev.com')),
|
|
94
|
-
middleware: apiMiddleware(options, preauths, context)
|
|
95
|
-
}
|
|
96
|
-
if (process.env.HEROKU_HEADERS) {
|
|
97
|
-
Object.assign(opts.headers, JSON.parse(process.env.HEROKU_HEADERS))
|
|
98
|
-
}
|
|
99
|
-
if (context.secondFactor) {
|
|
100
|
-
Object.assign(opts.headers, { 'Heroku-Two-Factor-Code': context.secondFactor })
|
|
101
|
-
}
|
|
102
|
-
if (context.reason) {
|
|
103
|
-
Object.assign(opts.headers, { 'X-Heroku-Sudo-Reason': context.reason })
|
|
104
|
-
}
|
|
105
|
-
cli.heroku = new Heroku(opts)
|
|
106
|
-
return cli.heroku
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
let httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy
|
|
110
|
-
|
|
111
|
-
function setupHttpProxy () {
|
|
112
|
-
const url = require('url')
|
|
113
|
-
cli.hush(`proxy set to ${httpsProxy}`)
|
|
114
|
-
let proxy = url.parse(httpsProxy)
|
|
115
|
-
process.env.HEROKU_HTTP_PROXY_HOST = proxy.hostname
|
|
116
|
-
process.env.HEROKU_HTTP_PROXY_PORT = proxy.port
|
|
117
|
-
process.env.HEROKU_HTTP_PROXY_AUTH = proxy.auth
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function relogin () {
|
|
121
|
-
if (process.env.HEROKU_LOGIN_REDIRECT === '1') return auth.login({ save: true })
|
|
122
|
-
process.env.HEROKU_LOGIN_REDIRECT = '1'
|
|
123
|
-
require('child_process').execSync('heroku login', { stdio: 'inherit' })
|
|
124
|
-
return Promise.resolve()
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function twoFactorPrompt (options, preauths, context) {
|
|
128
|
-
cli.yubikey.enable()
|
|
129
|
-
return preauths.twoFactorMutex.synchronize(function () {
|
|
130
|
-
return cli.prompt('Two-factor code', { mask: true })
|
|
131
|
-
.catch(function (err) {
|
|
132
|
-
cli.yubikey.disable()
|
|
133
|
-
throw err
|
|
134
|
-
})
|
|
135
|
-
.then(function (secondFactor) {
|
|
136
|
-
cli.yubikey.disable()
|
|
137
|
-
return secondFactor
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function reasonPrompt (context) {
|
|
143
|
-
return cli.prompt('Reason')
|
|
144
|
-
.then(function (reason) {
|
|
145
|
-
context.reason = reason
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
module.exports = function command (options, fn) {
|
|
150
|
-
return function (context) {
|
|
151
|
-
if (typeof options === 'function') [fn, options] = [options, {}]
|
|
152
|
-
if (httpsProxy) setupHttpProxy()
|
|
153
|
-
cli.color.enabled = context.supportsColor
|
|
154
|
-
let handleErr = cli.errorHandler({ debug: context.debug })
|
|
155
|
-
let run = function () {
|
|
156
|
-
context.auth = { password: auth.token() }
|
|
157
|
-
let p = fn(context, heroku(context, options))
|
|
158
|
-
if (!p.catch) return
|
|
159
|
-
return p.catch(function (err) {
|
|
160
|
-
if (err && err.body && err.body.id === 'unauthorized') {
|
|
161
|
-
cli.error(err.body.message || 'Unauthorized')
|
|
162
|
-
return relogin().then(run).catch(handleErr)
|
|
163
|
-
} else if (err && err.body && err.body.id === 'sudo_reason_required') {
|
|
164
|
-
cli.warn(err.body.message)
|
|
165
|
-
return reasonPrompt(context).then(run).catch(handleErr)
|
|
166
|
-
} else throw err
|
|
167
|
-
}).catch(handleErr)
|
|
168
|
-
}
|
|
169
|
-
return run()
|
|
170
|
-
}
|
|
171
|
-
}
|
package/lib/console.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
var cli = require('..')
|
|
4
|
-
const stripAnsi = require('strip-ansi')
|
|
5
|
-
|
|
6
|
-
var mocking
|
|
7
|
-
|
|
8
|
-
function concatArguments (args) {
|
|
9
|
-
return Array.prototype.map.call(args, function (arg) {
|
|
10
|
-
return arg + ''
|
|
11
|
-
}).join(' ')
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* log is a wrapper for console.log() but can be mocked
|
|
16
|
-
*
|
|
17
|
-
* @param {...Object} obj - objects to be printed to stdout
|
|
18
|
-
*/
|
|
19
|
-
function log () {
|
|
20
|
-
if (mocking) {
|
|
21
|
-
cli.stdout += stripAnsi(concatArguments(arguments) + '\n')
|
|
22
|
-
} else {
|
|
23
|
-
console.log.apply(null, arguments)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* writeLog is a wrapper for process.stdout.write() but can be mocked
|
|
29
|
-
*
|
|
30
|
-
* @param {...Object} obj - objects to be printed to stdout
|
|
31
|
-
*/
|
|
32
|
-
function writeLog () {
|
|
33
|
-
if (mocking) {
|
|
34
|
-
cli.stdout += stripAnsi(concatArguments(arguments))
|
|
35
|
-
} else {
|
|
36
|
-
process.stdout.write.apply(process.stdout, arguments)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function hush () {
|
|
41
|
-
let debug = process.env.HEROKU_DEBUG
|
|
42
|
-
if (debug && (debug === '1' || debug.toUpperCase() === 'TRUE')) {
|
|
43
|
-
console.error.apply(null, arguments)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* error is a wrapper for console.error() but can be mocked
|
|
49
|
-
*
|
|
50
|
-
* @param {...Object} obj - objects to be printed to stderr
|
|
51
|
-
*/
|
|
52
|
-
function error () {
|
|
53
|
-
if (mocking) {
|
|
54
|
-
cli.stderr += stripAnsi(concatArguments(arguments) + '\n')
|
|
55
|
-
} else {
|
|
56
|
-
console.error.apply(null, arguments)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* writeError is a wrapper for process.stderr.write() but can be mocked
|
|
62
|
-
*
|
|
63
|
-
* @param {...Object} obj - objects to be printed to stderr
|
|
64
|
-
*/
|
|
65
|
-
function writeError () {
|
|
66
|
-
if (mocking) {
|
|
67
|
-
cli.stderr += stripAnsi(concatArguments(arguments))
|
|
68
|
-
} else {
|
|
69
|
-
process.stderr.write.apply(process.stderr, arguments)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* mock will make {@link log} and {@link error}
|
|
75
|
-
* stop printing to stdout and stderr and start writing to the
|
|
76
|
-
* stdout and stderr strings.
|
|
77
|
-
*/
|
|
78
|
-
function mock (mock) {
|
|
79
|
-
if (mock === false) {
|
|
80
|
-
mocking = false
|
|
81
|
-
} else {
|
|
82
|
-
mocking = true
|
|
83
|
-
cli.stderr = ''
|
|
84
|
-
cli.stdout = ''
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* debug pretty prints an object.
|
|
90
|
-
* It simply calls console.dir with color enabled.
|
|
91
|
-
*
|
|
92
|
-
* @param {Object} obj - object to be printed
|
|
93
|
-
*/
|
|
94
|
-
function debug (obj) {
|
|
95
|
-
console.dir(obj, { colors: true })
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
exports.hush = hush
|
|
99
|
-
exports.log = log
|
|
100
|
-
exports.writeLog = writeLog
|
|
101
|
-
exports.error = error
|
|
102
|
-
exports.writeError = writeError
|
|
103
|
-
exports.mock = mock
|
|
104
|
-
exports.mocking = () => mocking
|
|
105
|
-
exports.debug = debug
|