heroku 8.2.0 → 8.3.0

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 CHANGED
@@ -56,13 +56,16 @@ For other issues, [submit a support ticket](https://help.heroku.com/).
56
56
  * [`heroku logs`](docs/logs.md) - display recent log output
57
57
  * [`heroku maintenance`](docs/maintenance.md) - enable/disable access to app
58
58
  * [`heroku members`](docs/members.md) - manage organization members
59
+ * [`heroku notifications`](docs/notifications.md) - display notifications
59
60
  * [`heroku orgs`](docs/orgs.md) - manage organizations
60
61
  * [`heroku pg`](docs/pg.md) - manage postgresql databases
61
62
  * [`heroku pipelines`](docs/pipelines.md) - list pipelines you have access to
62
63
  * [`heroku plugins`](docs/plugins.md) - List installed plugins.
63
64
  * [`heroku ps`](docs/ps.md) - Client tools for Heroku Exec
65
+ * [`heroku psql`](docs/psql.md) - open a psql shell to the database
64
66
  * [`heroku redis`](docs/redis.md) - manage heroku redis instances
65
67
  * [`heroku regions`](docs/regions.md) - list available regions for deployment
68
+ * [`heroku releases`](docs/releases.md) - display the releases for an app
66
69
  * [`heroku reviewapps`](docs/reviewapps.md) - disable review apps and/or settings on an existing pipeline
67
70
  * [`heroku run`](docs/run.md) - run a one-off process inside a Heroku dyno
68
71
  * [`heroku sessions`](docs/sessions.md) - OAuth sessions
package/bin/run CHANGED
@@ -10,6 +10,11 @@ const globalTelemetry = require('../lib/global_telemetry')
10
10
 
11
11
  process.once('beforeExit', async code => {
12
12
  // capture as successful exit
13
+ if (global.cliTelemetry.isVersionOrHelp) {
14
+ const cmdStartTime = global.cliTelemetry.commandRunDuration
15
+ global.cliTelemetry.commandRunDuration = globalTelemetry.computeDuration(cmdStartTime)
16
+ }
17
+
13
18
  global.cliTelemetry.exitCode = code
14
19
  global.cliTelemetry.cliRunDuration = globalTelemetry.computeDuration(cliStartTime)
15
20
  const telemetryData = global.cliTelemetry
@@ -18,23 +23,27 @@ process.once('beforeExit', async code => {
18
23
 
19
24
  process.on('SIGINT', async () => {
20
25
  // capture as unsuccessful exit
21
- let error = new Error('Received SIGINT')
26
+ const error = new Error('Received SIGINT')
22
27
  error.cliRunDuration = globalTelemetry.computeDuration(cliStartTime)
23
28
  await globalTelemetry.sendTelemetry(error)
29
+ process.exit(1)
24
30
  })
25
31
 
26
32
  process.on('SIGTERM', async () => {
27
33
  // capture as unsuccessful exit
28
- let error = new Error('Received SIGTERM')
34
+ const error = new Error('Received SIGTERM')
29
35
  error.cliRunDuration = globalTelemetry.computeDuration(cliStartTime)
30
36
  await globalTelemetry.sendTelemetry(error)
37
+ process.exit(1)
31
38
  })
32
39
 
40
+ globalTelemetry.initializeInstrumentation()
41
+
33
42
  const oclif = require('@oclif/core')
34
43
 
35
44
  oclif.run().then(require('@oclif/core/flush')).catch(async error => {
36
45
  // capture any errors raised by oclif
37
- let cliError = error
46
+ const cliError = error
38
47
  cliError.cliRunDuration = globalTelemetry.computeDuration(cliStartTime)
39
48
  await globalTelemetry.sendTelemetry(cliError)
40
49
  console.log(`Error: ${error.message}`)
@@ -1,10 +1,11 @@
1
1
  import 'dotenv/config';
2
+ export declare const processor: any;
2
3
  interface Telemetry {
3
4
  command: string;
4
5
  os: string;
5
6
  version: string;
6
7
  exitCode: number;
7
- exitState: string[];
8
+ exitState: string;
8
9
  cliRunDuration: number;
9
10
  commandRunDuration: number;
10
11
  lifecycleHookCompletion: {
@@ -13,16 +14,21 @@ interface Telemetry {
13
14
  postrun: boolean;
14
15
  command_not_found: boolean;
15
16
  };
17
+ isVersionOrHelp: boolean;
16
18
  }
17
19
  export interface TelemetryGlobal extends NodeJS.Global {
18
20
  cliTelemetry?: Telemetry;
19
21
  }
22
+ interface CLIError extends Error {
23
+ cliRunDuration?: string;
24
+ }
25
+ export declare function initializeInstrumentation(): void;
20
26
  export declare function setupTelemetry(config: any, opts: any): {
21
27
  command: any;
22
28
  os: any;
23
29
  version: any;
24
30
  exitCode: number;
25
- exitState: string[];
31
+ exitState: string;
26
32
  cliRunDuration: number;
27
33
  commandRunDuration: number;
28
34
  lifecycleHookCompletion: {
@@ -31,14 +37,15 @@ export declare function setupTelemetry(config: any, opts: any): {
31
37
  postrun: boolean;
32
38
  command_not_found: boolean;
33
39
  };
34
- };
40
+ isVersionOrHelp: boolean;
41
+ } | undefined;
35
42
  export declare function computeDuration(cmdStartTime: any): number;
36
43
  export declare function reportCmdNotFound(config: any): {
37
44
  command: string;
38
45
  os: any;
39
46
  version: any;
40
47
  exitCode: number;
41
- exitState: string[];
48
+ exitState: string;
42
49
  cliRunDuration: number;
43
50
  commandRunDuration: number;
44
51
  lifecycleHookCompletion: {
@@ -47,7 +54,9 @@ export declare function reportCmdNotFound(config: any): {
47
54
  postrun: boolean;
48
55
  command_not_found: boolean;
49
56
  };
57
+ isVersionOrHelp: boolean;
50
58
  };
51
- export declare function sendTelemetry(currentTelemetry: any): Promise<void>;
52
- export declare function sendToRollbar(data: any): Promise<void>;
59
+ export declare function sendTelemetry(currentTelemetry: any, rollbarCb?: () => void): Promise<void>;
60
+ export declare function sendToHoneycomb(data: Telemetry | CLIError): Promise<void>;
61
+ export declare function sendToRollbar(data: CLIError, rollbarCb?: () => void): Promise<unknown>;
53
62
  export {};
@@ -1,9 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sendToRollbar = exports.sendTelemetry = exports.reportCmdNotFound = exports.computeDuration = exports.setupTelemetry = void 0;
4
- const Rollbar = require("rollbar");
3
+ exports.sendToRollbar = exports.sendToHoneycomb = exports.sendTelemetry = exports.reportCmdNotFound = exports.computeDuration = exports.setupTelemetry = exports.initializeInstrumentation = exports.processor = void 0;
5
4
  require("dotenv/config");
5
+ const Rollbar = require("rollbar");
6
+ const command_1 = require("@heroku-cli/command");
7
+ const core_1 = require("@oclif/core");
8
+ const api_1 = require("@opentelemetry/api");
9
+ const { Resource } = require('@opentelemetry/resources');
10
+ const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');
11
+ const { registerInstrumentations } = require('@opentelemetry/instrumentation');
12
+ const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
13
+ const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');
14
+ const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
15
+ const { version } = require('../../../packages/cli/package.json');
6
16
  const isDev = process.env.IS_DEV_ENVIRONMENT === 'true';
17
+ const path = require('path');
18
+ const root = path.resolve(__dirname, '../../../package.json');
19
+ const config = new core_1.Config({ root });
20
+ const heroku = new command_1.APIClient(config);
21
+ const token = heroku.auth;
7
22
  const debug = require('debug')('global_telemetry');
8
23
  const rollbar = new Rollbar({
9
24
  accessToken: '41f8730238814af69c248e2f7ca59ff2',
@@ -11,24 +26,71 @@ const rollbar = new Rollbar({
11
26
  captureUnhandledRejections: true,
12
27
  environment: isDev ? 'development' : 'production',
13
28
  });
29
+ registerInstrumentations({
30
+ instrumentations: [],
31
+ });
32
+ const resource = Resource
33
+ .default()
34
+ .merge(new Resource({
35
+ [SemanticResourceAttributes.SERVICE_NAME]: 'heroku-cli',
36
+ [SemanticResourceAttributes.SERVICE_VERSION]: version,
37
+ }));
38
+ const provider = new NodeTracerProvider({
39
+ resource,
40
+ });
41
+ const headers = { Authorization: `Bearer ${token}` };
42
+ const exporter = new OTLPTraceExporter({
43
+ url: isDev ? 'https://backboard-staging.herokuapp.com/otel/v1/traces' : 'https://backboard.heroku.com/otel/v1/traces',
44
+ headers,
45
+ compression: 'none',
46
+ });
47
+ exports.processor = new BatchSpanProcessor(exporter);
48
+ provider.addSpanProcessor(exports.processor);
49
+ function initializeInstrumentation() {
50
+ provider.register();
51
+ }
52
+ exports.initializeInstrumentation = initializeInstrumentation;
14
53
  function setupTelemetry(config, opts) {
15
54
  const now = new Date();
16
55
  const cmdStartTime = now.getTime();
17
- return {
18
- command: opts.Command.id,
19
- os: config.platform,
20
- version: config.version,
21
- exitCode: 0,
22
- exitState: [''],
23
- cliRunDuration: 0,
24
- commandRunDuration: cmdStartTime,
25
- lifecycleHookCompletion: {
26
- init: true,
27
- prerun: true,
28
- postrun: false,
29
- command_not_found: false,
30
- },
31
- };
56
+ const isHelpOrVersionCmd = (opts.id === 'version' || opts.id === '--help');
57
+ const isRegularCmd = Boolean(opts.Command);
58
+ if (isHelpOrVersionCmd) {
59
+ return {
60
+ command: opts.id,
61
+ os: config.platform,
62
+ version: config.version,
63
+ exitCode: 0,
64
+ exitState: 'successful',
65
+ cliRunDuration: 0,
66
+ commandRunDuration: cmdStartTime,
67
+ lifecycleHookCompletion: {
68
+ init: true,
69
+ prerun: false,
70
+ postrun: false,
71
+ command_not_found: false,
72
+ },
73
+ isVersionOrHelp: true,
74
+ };
75
+ }
76
+ if (isRegularCmd) {
77
+ return {
78
+ command: opts.Command.id,
79
+ os: config.platform,
80
+ version: config.version,
81
+ exitCode: 0,
82
+ exitState: 'successful',
83
+ cliRunDuration: 0,
84
+ commandRunDuration: cmdStartTime,
85
+ lifecycleHookCompletion: {
86
+ init: true,
87
+ prerun: true,
88
+ postrun: false,
89
+ command_not_found: false,
90
+ },
91
+ isVersionOrHelp: false,
92
+ };
93
+ }
32
94
  }
33
95
  exports.setupTelemetry = setupTelemetry;
34
96
  function computeDuration(cmdStartTime) {
@@ -40,11 +102,11 @@ function computeDuration(cmdStartTime) {
40
102
  exports.computeDuration = computeDuration;
41
103
  function reportCmdNotFound(config) {
42
104
  return {
43
- command: '',
105
+ command: 'invalid_command',
44
106
  os: config.platform,
45
107
  version: config.version,
46
108
  exitCode: 0,
47
- exitState: ['command_not_found'],
109
+ exitState: 'command_not_found',
48
110
  cliRunDuration: 0,
49
111
  commandRunDuration: 0,
50
112
  lifecycleHookCompletion: {
@@ -53,30 +115,81 @@ function reportCmdNotFound(config) {
53
115
  postrun: false,
54
116
  command_not_found: true,
55
117
  },
118
+ isVersionOrHelp: false,
56
119
  };
57
120
  }
58
121
  exports.reportCmdNotFound = reportCmdNotFound;
59
- async function sendTelemetry(currentTelemetry) {
122
+ async function sendTelemetry(currentTelemetry, rollbarCb) {
60
123
  // send telemetry to honeycomb and rollbar
61
- let telemetry = currentTelemetry;
124
+ const telemetry = currentTelemetry;
62
125
  if (telemetry instanceof Error) {
63
- telemetry = { error_message: telemetry.message, error_stack: telemetry.stack };
64
- telemetry.cliRunDuration = currentTelemetry.cliRunDuration;
65
- await sendToRollbar(telemetry);
126
+ await Promise.all([
127
+ sendToRollbar(telemetry, rollbarCb),
128
+ sendToHoneycomb(telemetry),
129
+ ]);
130
+ }
131
+ else {
132
+ await sendToHoneycomb(telemetry);
66
133
  }
67
- // add sendToHoneycomb function here
68
134
  }
69
135
  exports.sendTelemetry = sendTelemetry;
70
- async function sendToRollbar(data) {
136
+ async function sendToHoneycomb(data) {
137
+ try {
138
+ const tracer = api_1.default.trace.getTracer('heroku-cli', version);
139
+ const span = tracer.startSpan('node_app_execution');
140
+ if (data instanceof Error) {
141
+ span.recordException(data);
142
+ span.setStatus({
143
+ code: api_1.SpanStatusCode.ERROR,
144
+ message: data.message,
145
+ });
146
+ }
147
+ else {
148
+ span.setAttribute('heroku_client.command', data.command);
149
+ span.setAttribute('heroku_client.os', data.os);
150
+ span.setAttribute('heroku_client.version', data.version);
151
+ span.setAttribute('heroku_client.exit_code', data.exitCode);
152
+ span.setAttribute('heroku_client.exit_state', data.exitState);
153
+ span.setAttribute('heroku_client.cli_run_duration', data.cliRunDuration);
154
+ span.setAttribute('heroku_client.command_run_duration', data.commandRunDuration);
155
+ span.setAttribute('heroku_client.lifecycle_hook.init', data.lifecycleHookCompletion.init);
156
+ span.setAttribute('heroku_client.lifecycle_hook.prerun', data.lifecycleHookCompletion.prerun);
157
+ span.setAttribute('heroku_client.lifecycle_hook.postrun', data.lifecycleHookCompletion.postrun);
158
+ span.setAttribute('heroku_client.lifecycle_hook.command_not_found', data.lifecycleHookCompletion.command_not_found);
159
+ }
160
+ span.end();
161
+ exports.processor.forceFlush();
162
+ }
163
+ catch (_a) {
164
+ debug('could not send telemetry');
165
+ }
166
+ }
167
+ exports.sendToHoneycomb = sendToHoneycomb;
168
+ async function sendToRollbar(data, rollbarCb) {
169
+ // Make this awaitable so we can wait for it to finish before exiting
170
+ let promiseResolve;
171
+ const rollbarPromise = new Promise((resolve, reject) => {
172
+ promiseResolve = () => {
173
+ if (rollbarCb) {
174
+ try {
175
+ rollbarCb();
176
+ }
177
+ catch (error) {
178
+ reject(error);
179
+ }
180
+ }
181
+ resolve(null);
182
+ };
183
+ });
184
+ const rollbarError = { name: data.name, message: data.message, stack: data.stack, cli_run_duration: data.cliRunDuration };
71
185
  try {
72
186
  // send data to rollbar
73
- rollbar.error('Failed to complete execution', data, () => {
74
- process.exit(1);
75
- });
187
+ rollbar.error('Failed to complete execution', rollbarError, promiseResolve);
76
188
  }
77
189
  catch (_a) {
78
190
  debug('Could not send error report');
79
- process.exit(1);
191
+ return Promise.reject();
80
192
  }
193
+ return rollbarPromise;
81
194
  }
82
195
  exports.sendToRollbar = sendToRollbar;
@@ -0,0 +1,3 @@
1
+ import { Hook } from '@oclif/core';
2
+ declare const performance_analytics: Hook<'init'>;
3
+ export default performance_analytics;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const telemetry = require("../../global_telemetry");
4
+ const performance_analytics = async function (options) {
5
+ global.cliTelemetry = telemetry.setupTelemetry(this.config, options);
6
+ };
7
+ exports.default = performance_analytics;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "8.2.0",
2
+ "version": "8.3.0",
3
3
  "commands": {
4
4
  "console": {
5
5
  "id": "console",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "heroku",
3
3
  "description": "CLI to interact with Heroku",
4
- "version": "8.2.0",
4
+ "version": "8.3.0",
5
5
  "author": "Jeff Dickey @jdxcode",
6
6
  "bin": "./bin/run",
7
7
  "bugs": "https://github.com/heroku/cli/issues",
@@ -33,6 +33,13 @@
33
33
  "@oclif/plugin-version": "^1.2.1",
34
34
  "@oclif/plugin-warn-if-update-available": "2.0.29",
35
35
  "@oclif/plugin-which": "2.2.8",
36
+ "@opentelemetry/api": "^1.4.1",
37
+ "@opentelemetry/exporter-trace-otlp-http": "^0.41.1",
38
+ "@opentelemetry/instrumentation": "^0.41.1",
39
+ "@opentelemetry/resources": "^1.15.1",
40
+ "@opentelemetry/sdk-trace-base": "^1.15.1",
41
+ "@opentelemetry/sdk-trace-node": "^1.15.1",
42
+ "@opentelemetry/semantic-conventions": "^1.15.1",
36
43
  "ansi-escapes": "3.2.0",
37
44
  "async-file": "^2.0.2",
38
45
  "chalk": "^2.4.2",
@@ -254,7 +261,8 @@
254
261
  "hooks": {
255
262
  "init": [
256
263
  "./lib/hooks/init/version",
257
- "./lib/hooks/init/terms-of-service"
264
+ "./lib/hooks/init/terms-of-service",
265
+ "./lib/hooks/init/performance_analytics"
258
266
  ],
259
267
  "prerun": [
260
268
  "./lib/hooks/prerun/analytics"
@@ -324,5 +332,5 @@
324
332
  "version": "oclif readme --multi && git add README.md ../../docs"
325
333
  },
326
334
  "types": "lib/index.d.ts",
327
- "gitHead": "1875102f332f215ad9788b5fc117b4a2bd6bbd8c"
335
+ "gitHead": "16f4f8e933025276b5fc703316c3221149c29eb6"
328
336
  }