@heroku/heroku-cli-util 8.0.12

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/lib/command.js ADDED
@@ -0,0 +1,171 @@
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 ADDED
@@ -0,0 +1,105 @@
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
package/lib/date.js ADDED
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * formatDate will format a date in a standard Heroku format
5
+ *
6
+ * @example
7
+ * let cli = require('heroku-cli-util')
8
+ * var d = new Date()
9
+ * cli.formatDate(d); // '2015-05-14T18:03:10.034Z'
10
+ *
11
+ * @param {Date} date the date to format
12
+ * @return {String} string representing the date
13
+ */
14
+ function formatDate (date) {
15
+ return date.toISOString()
16
+ }
17
+
18
+ exports.formatDate = formatDate
package/lib/errors.js ADDED
@@ -0,0 +1,122 @@
1
+ 'use strict'
2
+
3
+ const stripAnsi = require('strip-ansi')
4
+ let cli = require('..')
5
+ let console = require('./console')
6
+ let linewrap = require('./linewrap')
7
+ let path = require('path')
8
+ let os = require('os')
9
+
10
+ function errtermwidth () {
11
+ if (global.columns) return global.columns
12
+ if (!process.stderr.isTTY || !process.stderr.getWindowSize) return 80
13
+ let width = process.stderr.getWindowSize()[0]
14
+ return width < 30 ? 30 : width
15
+ }
16
+
17
+ function wrap (msg) {
18
+ return linewrap(6,
19
+ errtermwidth(), {
20
+ skipScheme: 'ansi-color',
21
+ skip: /^\$ .*$/
22
+ })(msg || '')
23
+ }
24
+
25
+ function bangify (msg, c) {
26
+ let lines = msg.split('\n')
27
+ for (let i = 0; i < lines.length; i++) {
28
+ let line = lines[i]
29
+ lines[i] = ' ' + c + line.substr(2, line.length)
30
+ }
31
+ return lines.join('\n')
32
+ }
33
+
34
+ function getErrorMessage (err) {
35
+ if (err.body) {
36
+ // API error
37
+ if (err.body.message) {
38
+ return err.body.message
39
+ } else if (err.body.error) {
40
+ return err.body.error
41
+ }
42
+ }
43
+ // Unhandled error
44
+ if (err.message && err.code) {
45
+ return `${err.code}: ${err.message}`
46
+ } else if (err.message) {
47
+ return err.message
48
+ }
49
+ return err
50
+ }
51
+
52
+ let arrow = process.platform === 'win32' ? '!' : '▸'
53
+
54
+ function error (err) {
55
+ console.error(bangify(wrap(getErrorMessage(err)), cli.color.red(arrow)))
56
+ }
57
+
58
+ function warn (msg) {
59
+ console.error(renderWarning(msg))
60
+ }
61
+
62
+ function renderWarning (msg) {
63
+ return bangify(wrap(msg), cli.color.yellow(arrow))
64
+ }
65
+
66
+ function logtimestamp () {
67
+ return new Date().toISOString()
68
+ .replace(/T/, ' ')
69
+ .replace(/-/g, '/')
70
+ .replace(/\..+/, '')
71
+ }
72
+
73
+ function cacheHome () {
74
+ let base
75
+ if (process.env.XDG_CACHE_HOME) base = process.env.XDG_CACHE_HOME
76
+ if (!base) {
77
+ if (process.platform === 'darwin') {
78
+ base = path.join(os.homedir(), 'Library', 'Caches')
79
+ } else if (process.platform === 'win32' && process.env.LOCALAPPDATA) {
80
+ base = process.env.LOCALAPPDATA
81
+ } else {
82
+ base = path.join(os.homedir(), '.cache')
83
+ }
84
+ }
85
+ return path.join(base, 'heroku')
86
+ }
87
+
88
+ function log (msg) {
89
+ let fs = require('fs')
90
+ let logPath = path.join(cacheHome(), 'error.log')
91
+ fs.appendFileSync(logPath, logtimestamp() + ' ' + stripAnsi(msg) + '\n')
92
+ }
93
+
94
+ function errorHandler (options) {
95
+ options = options || {}
96
+ function exit () {
97
+ if (options.exit !== false) {
98
+ process.exit(1)
99
+ }
100
+ }
101
+ return function handleErr (err) {
102
+ if (cli.raiseErrors) throw err
103
+ try {
104
+ if (err !== '') error(err)
105
+ if (err.stack) {
106
+ log(err.stack)
107
+ if (options.debug) console.error(err.stack)
108
+ }
109
+ if (err.body) log(JSON.stringify(err.body))
110
+ exit()
111
+ } catch (err) {
112
+ console.error(err.stack)
113
+ process.exit(-1)
114
+ }
115
+ }
116
+ }
117
+
118
+ module.exports.error = error
119
+ module.exports.warn = warn
120
+ module.exports.errorHandler = errorHandler
121
+ module.exports.renderWarning = renderWarning
122
+ module.exports.errtermwidth = errtermwidth
package/lib/exit.js ADDED
@@ -0,0 +1,42 @@
1
+ 'use strict'
2
+
3
+ var util = require('util')
4
+ var cli = require('./errors')
5
+
6
+ function ErrorExit (code, message) {
7
+ Error.call(this)
8
+ Error.captureStackTrace(this, this.constructor)
9
+ this.name = this.constructor.name
10
+
11
+ this.code = code
12
+ this.message = message
13
+ }
14
+
15
+ util.inherits(ErrorExit, Error)
16
+
17
+ var mocking
18
+
19
+ function exit (code, message) {
20
+ if (message) {
21
+ cli.error(message)
22
+ }
23
+ if (mocking) {
24
+ throw new ErrorExit(code, message)
25
+ } else {
26
+ process.exit(code)
27
+ }
28
+ }
29
+
30
+ exit.mock = function (mock) {
31
+ if (mock === false) {
32
+ mocking = false
33
+ } else {
34
+ mocking = true
35
+ }
36
+ }
37
+
38
+ exit.ErrorExit = ErrorExit
39
+
40
+ module.exports = {
41
+ exit
42
+ }
package/lib/got.js ADDED
@@ -0,0 +1,153 @@
1
+ 'use strict'
2
+
3
+ let hush = require('./console').hush
4
+
5
+ function findProxy (urlParsed) {
6
+ let httpProxy = process.env.HTTP_PROXY || process.env.http_proxy
7
+ let httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy
8
+
9
+ if (urlParsed.protocol === 'https:') {
10
+ return httpsProxy || httpProxy
11
+ } else {
12
+ return httpProxy
13
+ }
14
+ }
15
+
16
+ function findTunnel (urlParsed) {
17
+ let tunnel = require('tunnel-agent')
18
+
19
+ if (urlParsed.protocol === 'https:') {
20
+ return tunnel.httpsOverHttp
21
+ } else {
22
+ return tunnel.httpOverHttp
23
+ }
24
+ }
25
+
26
+ function agent (urlParsed, proxyParsed, certs) {
27
+ let tunnelMethod = findTunnel(urlParsed)
28
+ let opts = {
29
+ proxy: {
30
+ host: proxyParsed.hostname,
31
+ port: proxyParsed.port || '8080'
32
+ }
33
+ }
34
+
35
+ if (proxyParsed.auth) {
36
+ opts.proxy.proxyAuth = proxyParsed.auth
37
+ }
38
+
39
+ if (certs.length > 0) {
40
+ opts.ca = certs
41
+ }
42
+
43
+ let tunnelAgent = tunnelMethod(opts)
44
+ if (urlParsed.protocol === 'https:') {
45
+ tunnelAgent.defaultPort = 443
46
+ }
47
+ return tunnelAgent
48
+ }
49
+
50
+ function sslCertFile () {
51
+ return process.env.SSL_CERT_FILE ? [process.env.SSL_CERT_FILE] : []
52
+ }
53
+
54
+ function sslCertDir () {
55
+ let certDir = process.env.SSL_CERT_DIR
56
+ if (certDir) {
57
+ const fs = require('fs')
58
+ const path = require('path')
59
+ return fs.readdirSync(certDir).map(f => path.join(certDir, f))
60
+ } else {
61
+ return []
62
+ }
63
+ }
64
+
65
+ function getCerts () {
66
+ let filenames = sslCertFile().concat(sslCertDir())
67
+
68
+ if (filenames.length > 0) {
69
+ hush('Adding the following trusted certificate authorities')
70
+ }
71
+
72
+ return filenames.map(function (filename) {
73
+ const fs = require('fs')
74
+ hush(' ' + filename)
75
+ return fs.readFileSync(filename)
76
+ })
77
+ }
78
+
79
+ function addToOpts (url, opts) {
80
+ const urlLib = require('url')
81
+
82
+ let urlParsed = urlLib.parse(url)
83
+ let proxy = findProxy(urlParsed)
84
+
85
+ let certs = getCerts()
86
+
87
+ if (proxy) {
88
+ let proxyParsed = urlLib.parse(proxy)
89
+ opts = Object.assign({}, opts, { agent: agent(urlParsed, proxyParsed, certs) })
90
+ }
91
+
92
+ if (certs.length > 0) {
93
+ opts = Object.assign({}, opts, { ca: certs })
94
+ }
95
+
96
+ return opts
97
+ }
98
+
99
+ let loadErrors
100
+
101
+ function got (url, opts) {
102
+ const gotDelegate = require('got')
103
+ loadErrors()
104
+ return gotDelegate(url, addToOpts(url, opts))
105
+ }
106
+
107
+ got.stream = function (url, opts) {
108
+ const gotDelegate = require('got')
109
+ loadErrors()
110
+ return gotDelegate.stream(url, addToOpts(url, opts))
111
+ }
112
+
113
+ const helpers = [
114
+ 'get',
115
+ 'post',
116
+ 'put',
117
+ 'patch',
118
+ 'head',
119
+ 'delete'
120
+ ]
121
+
122
+ helpers.forEach(el => {
123
+ got[el] = (url, opts) => got(url, Object.assign({}, opts, { method: el }))
124
+ })
125
+
126
+ helpers.forEach(el => {
127
+ got.stream[el] = function (url, opts) {
128
+ return got.stream(url, Object.assign({}, opts, { method: el }))
129
+ }
130
+ })
131
+
132
+ loadErrors = () => {
133
+ const gotDelegate = require('got')
134
+
135
+ const errors = [
136
+ 'CacheError',
137
+ 'CancelError',
138
+ 'UnsupportedProtocolError',
139
+ 'HTTPError',
140
+ 'MaxRedirectsError',
141
+ 'ParseError',
142
+ 'ReadError',
143
+ 'RequestError',
144
+ 'TimeoutError',
145
+ 'UploadError'
146
+ ]
147
+
148
+ errors.forEach(el => {
149
+ got[el] = gotDelegate[el]
150
+ })
151
+ }
152
+
153
+ module.exports = got