heroku 11.2.0 → 11.3.0-beta.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/CHANGELOG.md +17 -0
- package/bin/run.js +8 -3
- package/dist/commands/data/pg/upgrade/run.d.ts +15 -0
- package/dist/commands/data/pg/upgrade/run.js +58 -0
- package/dist/commands/data/pg/upgrade/wait.d.ts +6 -0
- package/dist/commands/data/pg/upgrade/wait.js +18 -0
- package/dist/commands/data/pg/wait.d.ts +1 -0
- package/dist/commands/data/pg/wait.js +7 -5
- package/dist/hooks/command_not_found/setup-otel-telemetry.js +4 -1
- package/dist/hooks/finally/send-otel-and-sentry-errors.js +4 -1
- package/dist/hooks/init/setup-otel-telemetry.js +4 -1
- package/dist/hooks/postrun/send-otel-telemetry.js +6 -1
- package/dist/hooks/prerun/collect-and-send-herokulytics.js +4 -1
- package/dist/lib/analytics-telemetry/telemetry-manager.js +0 -1
- package/dist/lib/analytics-telemetry/telemetry-utils.d.ts +6 -0
- package/dist/lib/analytics-telemetry/telemetry-utils.js +22 -3
- package/dist/lib/analytics-telemetry/worker-client.d.ts +5 -3
- package/dist/lib/analytics-telemetry/worker-client.js +20 -6
- package/dist/lib/data/types.d.ts +156 -153
- package/dist/lib/data/types.js +3 -3
- package/npm-shrinkwrap.json +27 -323
- package/oclif.manifest.json +2408 -2273
- package/package.json +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
## [11.3.0-beta.0](https://github.com/heroku/cli/compare/v11.2.0...v11.3.0-beta.0) (2026-04-14)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* adds 'data:pg:upgrade:run/wait' commands (W-21304392) ([#3551](https://github.com/heroku/cli/issues/3551)) ([3297c5e](https://github.com/heroku/cli/commit/3297c5e218fe9105cd106000cba030e784d17a82))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* restore beforeExit handler for version commands and add comprehensive telemetry debug logging ([#3657](https://github.com/heroku/cli/issues/3657)) ([6da79cd](https://github.com/heroku/cli/commit/6da79cd4686133e1ee1f92b59127d8e012739b99))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Miscellaneous Chores
|
|
21
|
+
|
|
22
|
+
* remove @oclif/plugin-legacy dependency ([#3659](https://github.com/heroku/cli/issues/3659)) ([2523d48](https://github.com/heroku/cli/commit/2523d481a79f0d26ab8b6897c6b49d3e5713a218))
|
|
23
|
+
|
|
7
24
|
## [11.2.0](https://github.com/heroku/cli/compare/v11.1.1...v11.2.0) (2026-04-08)
|
|
8
25
|
|
|
9
26
|
|
package/bin/run.js
CHANGED
|
@@ -12,9 +12,11 @@ process.env.HEROKU_UPDATE_INSTRUCTIONS = process.env.HEROKU_UPDATE_INSTRUCTIONS
|
|
|
12
12
|
const now = new Date()
|
|
13
13
|
const cliStartTime = now.getTime()
|
|
14
14
|
|
|
15
|
-
const {isTelemetryEnabled} = await import('../dist/lib/analytics-telemetry/telemetry-utils.js')
|
|
15
|
+
const {isTelemetryEnabled, getTelemetryDisabledReason, telemetryDebug} = await import('../dist/lib/analytics-telemetry/telemetry-utils.js')
|
|
16
|
+
const enableTelemetry = isTelemetryEnabled()
|
|
16
17
|
|
|
17
|
-
if (
|
|
18
|
+
if (enableTelemetry) {
|
|
19
|
+
telemetryDebug('Telemetry enabled: setting up handlers (beforeExit, SIGINT, SIGTERM)')
|
|
18
20
|
// Dynamically import telemetry modules
|
|
19
21
|
const {setupTelemetryHandlers} = await import('../dist/lib/analytics-telemetry/worker-client.js')
|
|
20
22
|
const {computeDuration} = await import('../dist/lib/analytics-telemetry/telemetry-utils.js')
|
|
@@ -23,8 +25,11 @@ if (isTelemetryEnabled()) {
|
|
|
23
25
|
setupTelemetryHandlers({
|
|
24
26
|
cliStartTime,
|
|
25
27
|
computeDuration,
|
|
26
|
-
enableTelemetry
|
|
28
|
+
enableTelemetry,
|
|
27
29
|
})
|
|
30
|
+
} else {
|
|
31
|
+
const reason = getTelemetryDisabledReason()
|
|
32
|
+
telemetryDebug('Telemetry disabled (%s): skipping telemetry handler setup', reason)
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
await execute({dir: import.meta.url})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import BaseCommand from '../../../../lib/data/baseCommand.js';
|
|
2
|
+
export default class DataPgUpgradeRun extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
database: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
confirm: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
version: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { flags as Flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, hux, utils } from '@heroku/heroku-cli-util';
|
|
3
|
+
import { Args, ux } from '@oclif/core';
|
|
4
|
+
import tsheredoc from 'tsheredoc';
|
|
5
|
+
import BaseCommand from '../../../../lib/data/baseCommand.js';
|
|
6
|
+
const heredoc = tsheredoc.default;
|
|
7
|
+
export default class DataPgUpgradeRun extends BaseCommand {
|
|
8
|
+
static args = {
|
|
9
|
+
database: Args.string({
|
|
10
|
+
description: 'database name, database attachment name, or related config var on an app',
|
|
11
|
+
required: true,
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'upgrade the Postgres version on a Postgres Advanced database';
|
|
15
|
+
static examples = [
|
|
16
|
+
heredoc `
|
|
17
|
+
# Upgrade a Postgres Advanced database to version 17
|
|
18
|
+
${color.code('<%= config.bin %> <%= command.id %> DATABASE --version 17 --app my-app')}
|
|
19
|
+
`,
|
|
20
|
+
];
|
|
21
|
+
static flags = {
|
|
22
|
+
app: Flags.app({ required: true }),
|
|
23
|
+
confirm: Flags.string({ char: 'c', description: 'pass in the app name to skip confirmation prompts' }),
|
|
24
|
+
remote: Flags.remote(),
|
|
25
|
+
version: Flags.string({ char: 'v', description: 'Postgres version to upgrade to' }),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { args, flags } = await this.parse(DataPgUpgradeRun);
|
|
29
|
+
const { app, confirm, version } = flags;
|
|
30
|
+
const { database } = args;
|
|
31
|
+
const dbResolver = new utils.pg.DatabaseResolver(this.heroku);
|
|
32
|
+
const { addon } = await dbResolver.getAttachment(app, database);
|
|
33
|
+
if (!utils.pg.isAdvancedDatabase(addon)) {
|
|
34
|
+
ux.error('You can only use this command on Advanced-tier databases.\n'
|
|
35
|
+
+ `Use ${color.code(`heroku pg:upgrade:run ${addon.name} --app ${app}`)} instead.`);
|
|
36
|
+
}
|
|
37
|
+
const { body: databaseInfo } = await this.dataApi.get(`/data/postgres/v1/${addon.id}/info`);
|
|
38
|
+
const { version: currentVersion } = databaseInfo;
|
|
39
|
+
const newVersion = version ?? 'the latest supported Postgres version';
|
|
40
|
+
await hux.confirmCommand({
|
|
41
|
+
comparison: app,
|
|
42
|
+
confirmation: confirm,
|
|
43
|
+
warningMessage: heredoc(`
|
|
44
|
+
This command immediately upgrades your ${color.datastore(addon.name)} database from ${currentVersion} to ${newVersion}.
|
|
45
|
+
Your database will be unavailable until the upgrade is complete.`),
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
ux.action.start(`Upgrading your ${color.datastore(addon.name)} database from ${currentVersion} to ${newVersion}`);
|
|
49
|
+
await this.dataApi.post(`/data/postgres/v1/${addon.id}/upgrade/run`, { body: { version } });
|
|
50
|
+
ux.action.stop();
|
|
51
|
+
ux.stderr(`Upgrade started. Use ${color.code(`heroku data:pg:upgrade:wait ${addon.name} -a ${app}`)} to monitor progress.`);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
ux.action.stop(color.red('!'));
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as color from '@heroku/heroku-cli-util/color';
|
|
2
|
+
import tsheredoc from 'tsheredoc';
|
|
3
|
+
import DataPgWait from '../wait.js';
|
|
4
|
+
const heredoc = tsheredoc.default;
|
|
5
|
+
export default class DataPgUpgradeWait extends DataPgWait {
|
|
6
|
+
static description = 'shows status of an upgrade until it\'s complete';
|
|
7
|
+
static examples = [
|
|
8
|
+
heredoc(`
|
|
9
|
+
# Wait for upgrade to complete
|
|
10
|
+
${color.code('<%= config.bin %> <%= command.id %> DATABASE --app myapp')}
|
|
11
|
+
`),
|
|
12
|
+
heredoc(`
|
|
13
|
+
# Wait with custom polling interval (to avoid rate limiting)
|
|
14
|
+
${color.code('<%= config.bin %> <%= command.id %> DATABASE --app myapp --wait-interval 10')}
|
|
15
|
+
`),
|
|
16
|
+
];
|
|
17
|
+
classicWaitCommand = 'pg:upgrade:wait';
|
|
18
|
+
}
|
|
@@ -12,6 +12,7 @@ export default class DataPgWait extends BaseCommand {
|
|
|
12
12
|
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
13
|
'wait-interval': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
14
|
};
|
|
15
|
+
protected classicWaitCommand: string;
|
|
15
16
|
notify(...args: Parameters<typeof notify>): Promise<void>;
|
|
16
17
|
run(): Promise<void>;
|
|
17
18
|
wait(ms: number): Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { color, utils } from '@heroku/heroku-cli-util';
|
|
2
1
|
import { flags as Flags } from '@heroku-cli/command';
|
|
2
|
+
import * as color from '@heroku/heroku-cli-util/color';
|
|
3
|
+
import { DatabaseResolver, isAdvancedDatabase } from '@heroku/heroku-cli-util/utils';
|
|
3
4
|
import { Args, ux } from '@oclif/core';
|
|
4
5
|
import tsheredoc from 'tsheredoc';
|
|
5
6
|
import BaseCommand from '../../../lib/data/baseCommand.js';
|
|
@@ -16,7 +17,7 @@ export default class DataPgWait extends BaseCommand {
|
|
|
16
17
|
static examples = [
|
|
17
18
|
heredoc(`
|
|
18
19
|
# Wait for database to be available
|
|
19
|
-
${color.
|
|
20
|
+
${color.code('<%= config.bin %> <%= command.id %> DATABASE --app myapp')}
|
|
20
21
|
`),
|
|
21
22
|
];
|
|
22
23
|
static flags = {
|
|
@@ -31,6 +32,7 @@ export default class DataPgWait extends BaseCommand {
|
|
|
31
32
|
min: 1,
|
|
32
33
|
}),
|
|
33
34
|
};
|
|
35
|
+
classicWaitCommand = 'pg:wait';
|
|
34
36
|
async notify(...args) {
|
|
35
37
|
return notify(...args);
|
|
36
38
|
}
|
|
@@ -38,13 +40,13 @@ export default class DataPgWait extends BaseCommand {
|
|
|
38
40
|
const { args, flags } = await this.parse(DataPgWait);
|
|
39
41
|
const { database } = args;
|
|
40
42
|
const { app, 'no-notify': noNotify, 'wait-interval': waitInterval } = flags;
|
|
41
|
-
const databaseResolver = new
|
|
43
|
+
const databaseResolver = new DatabaseResolver(this.heroku);
|
|
42
44
|
const db = await databaseResolver.getAttachment(app, database);
|
|
43
45
|
const { addon } = db;
|
|
44
|
-
if (!
|
|
46
|
+
if (!isAdvancedDatabase(addon)) {
|
|
45
47
|
ux.error(heredoc `
|
|
46
48
|
You can only use this command on Advanced-tier databases.
|
|
47
|
-
|
|
49
|
+
Use ${color.code(`heroku ${this.classicWaitCommand} ${addon.name} -a ${app}`)} instead.`);
|
|
48
50
|
}
|
|
49
51
|
await this.waitFor(addon, waitInterval || 5, noNotify);
|
|
50
52
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const performance_analytics = async function () {
|
|
2
|
-
const { isTelemetryEnabled } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
2
|
+
const { isTelemetryEnabled, getTelemetryDisabledReason, telemetryDebug } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
3
3
|
// Use the consolidated telemetry check
|
|
4
4
|
if (!isTelemetryEnabled()) {
|
|
5
|
+
const reason = getTelemetryDisabledReason();
|
|
6
|
+
telemetryDebug('Telemetry disabled (%s): skipping command_not_found hook, not setting up telemetry for invalid command', reason);
|
|
5
7
|
return;
|
|
6
8
|
}
|
|
9
|
+
telemetryDebug('Telemetry enabled: command_not_found hook setting up telemetry for invalid command');
|
|
7
10
|
const { telemetryManager } = await import('../../lib/analytics-telemetry/telemetry-manager.js');
|
|
8
11
|
const globalAny = global;
|
|
9
12
|
globalAny.cliTelemetry = telemetryManager.reportCmdNotFound(this.config);
|
|
@@ -31,11 +31,14 @@ const finallyHook = async function (options) {
|
|
|
31
31
|
if (isUserError(options.error)) {
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
const { isTelemetryEnabled, spawnTelemetryWorker } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
34
|
+
const { isTelemetryEnabled, getTelemetryDisabledReason, spawnTelemetryWorker, telemetryDebug } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
35
35
|
// Use the consolidated telemetry check
|
|
36
36
|
if (!isTelemetryEnabled()) {
|
|
37
|
+
const reason = getTelemetryDisabledReason();
|
|
38
|
+
telemetryDebug('Telemetry disabled (%s): skipping finally hook, not sending error: %s', reason, options.error.message);
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
41
|
+
telemetryDebug('Telemetry enabled: finally hook spawning worker to send error: %s', options.error.message);
|
|
39
42
|
// Spawn background process to send error without blocking
|
|
40
43
|
spawnTelemetryWorker(options.error);
|
|
41
44
|
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const performance_analytics = async function (options) {
|
|
2
|
-
const { isTelemetryEnabled } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
2
|
+
const { isTelemetryEnabled, getTelemetryDisabledReason, telemetryDebug } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
3
3
|
// Use the consolidated telemetry check
|
|
4
4
|
if (!isTelemetryEnabled()) {
|
|
5
|
+
const reason = getTelemetryDisabledReason();
|
|
6
|
+
telemetryDebug('Telemetry disabled (%s): skipping init hook, not setting up telemetry object', reason);
|
|
5
7
|
return;
|
|
6
8
|
}
|
|
9
|
+
telemetryDebug('Telemetry enabled: init hook setting up telemetry object for command');
|
|
7
10
|
const { telemetryManager } = await import('../../lib/analytics-telemetry/telemetry-manager.js');
|
|
8
11
|
const globalAny = global;
|
|
9
12
|
globalAny.cliTelemetry = telemetryManager.setupTelemetry(this.config, options);
|
|
@@ -3,15 +3,20 @@ const performance_analytics = async function () {
|
|
|
3
3
|
if (!globalAny.cliTelemetry) {
|
|
4
4
|
return;
|
|
5
5
|
}
|
|
6
|
-
const { computeDuration, isTelemetryEnabled, spawnTelemetryWorker } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
6
|
+
const { computeDuration, isTelemetryEnabled, getTelemetryDisabledReason, spawnTelemetryWorker, telemetryDebug } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
7
7
|
// Use the consolidated telemetry check
|
|
8
8
|
if (!isTelemetryEnabled()) {
|
|
9
|
+
const reason = getTelemetryDisabledReason();
|
|
10
|
+
telemetryDebug('Telemetry disabled (%s): skipping postrun hook, not sending telemetry for command: %s', reason, globalAny.cliTelemetry.command);
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
13
|
+
telemetryDebug('Telemetry enabled: postrun hook spawning worker to send telemetry for command: %s', globalAny.cliTelemetry.command);
|
|
11
14
|
const cmdStartTime = globalAny.cliTelemetry.commandRunDuration;
|
|
12
15
|
globalAny.cliTelemetry.commandRunDuration = computeDuration(cmdStartTime);
|
|
13
16
|
globalAny.cliTelemetry.lifecycleHookCompletion.postrun = true;
|
|
14
17
|
// Spawn background process to send telemetry without blocking
|
|
15
18
|
spawnTelemetryWorker(globalAny.cliTelemetry);
|
|
19
|
+
// Mark telemetry as sent to prevent duplicate sends from beforeExit handler
|
|
20
|
+
globalAny.telemetrySent = true;
|
|
16
21
|
};
|
|
17
22
|
export default performance_analytics;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const analytics = async function (options) {
|
|
2
|
-
const { isTelemetryEnabled, spawnTelemetryWorker } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
2
|
+
const { isTelemetryEnabled, getTelemetryDisabledReason, spawnTelemetryWorker, telemetryDebug } = await import('../../lib/analytics-telemetry/telemetry-utils.js');
|
|
3
3
|
// Use the consolidated telemetry check
|
|
4
4
|
if (!isTelemetryEnabled()) {
|
|
5
|
+
const reason = getTelemetryDisabledReason();
|
|
6
|
+
telemetryDebug('Telemetry disabled (%s): skipping prerun hook, not sending Herokulytics for command: %s', reason, options.Command.id);
|
|
5
7
|
return;
|
|
6
8
|
}
|
|
9
|
+
telemetryDebug('Telemetry enabled: prerun hook spawning worker to send Herokulytics for command: %s', options.Command.id);
|
|
7
10
|
const { telemetryManager } = await import('../../lib/analytics-telemetry/telemetry-manager.js');
|
|
8
11
|
const globalAny = global;
|
|
9
12
|
// Only setup telemetry if not already initialized (avoid overwriting init hook data)
|
|
@@ -48,12 +48,17 @@ export interface Telemetry {
|
|
|
48
48
|
export type TelemetryData = CLIError | Telemetry;
|
|
49
49
|
export interface TelemetryGlobal {
|
|
50
50
|
cliTelemetry?: Telemetry;
|
|
51
|
+
telemetrySent?: boolean;
|
|
51
52
|
}
|
|
52
53
|
export type WorkerData = HerokulyticsData | TelemetryData;
|
|
53
54
|
/**
|
|
54
55
|
* Compute duration from a start time to now
|
|
55
56
|
*/
|
|
56
57
|
export declare function computeDuration(cmdStartTime: number): number;
|
|
58
|
+
/**
|
|
59
|
+
* Get the reason why telemetry is disabled (for logging purposes)
|
|
60
|
+
*/
|
|
61
|
+
export declare function getTelemetryDisabledReason(): null | string;
|
|
57
62
|
/**
|
|
58
63
|
* Get authentication token, cached to avoid recreating Config/APIClient
|
|
59
64
|
* Lazy-loads @heroku-cli/command and @oclif/core/config to avoid loading them during CLI init
|
|
@@ -65,6 +70,7 @@ export declare function getToken(): Promise<string | undefined>;
|
|
|
65
70
|
export declare function getVersion(): string;
|
|
66
71
|
/**
|
|
67
72
|
* Check if telemetry is enabled based on environment variables
|
|
73
|
+
* Returns both the enabled status and a reason string for logging
|
|
68
74
|
*/
|
|
69
75
|
export declare function isTelemetryEnabled(): boolean;
|
|
70
76
|
/**
|
|
@@ -21,6 +21,21 @@ export function computeDuration(cmdStartTime) {
|
|
|
21
21
|
const cmdFinishTime = now.getTime();
|
|
22
22
|
return cmdFinishTime - cmdStartTime;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Get the reason why telemetry is disabled (for logging purposes)
|
|
26
|
+
*/
|
|
27
|
+
export function getTelemetryDisabledReason() {
|
|
28
|
+
if (process.env.DISABLE_TELEMETRY === 'true') {
|
|
29
|
+
return 'DISABLE_TELEMETRY=true';
|
|
30
|
+
}
|
|
31
|
+
if (process.platform === 'win32' && process.env.ENABLE_WINDOWS_TELEMETRY !== 'true') {
|
|
32
|
+
return 'Windows platform requires ENABLE_WINDOWS_TELEMETRY=true';
|
|
33
|
+
}
|
|
34
|
+
if (process.env.IS_HEROKU_TEST_ENV === 'true') {
|
|
35
|
+
return 'IS_HEROKU_TEST_ENV=true';
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
24
39
|
/**
|
|
25
40
|
* Get authentication token, cached to avoid recreating Config/APIClient
|
|
26
41
|
* Lazy-loads @heroku-cli/command and @oclif/core/config to avoid loading them during CLI init
|
|
@@ -53,14 +68,18 @@ export function getVersion() {
|
|
|
53
68
|
}
|
|
54
69
|
/**
|
|
55
70
|
* Check if telemetry is enabled based on environment variables
|
|
71
|
+
* Returns both the enabled status and a reason string for logging
|
|
56
72
|
*/
|
|
57
73
|
export function isTelemetryEnabled() {
|
|
58
|
-
if (process.env.DISABLE_TELEMETRY === 'true')
|
|
74
|
+
if (process.env.DISABLE_TELEMETRY === 'true') {
|
|
59
75
|
return false;
|
|
60
|
-
|
|
76
|
+
}
|
|
77
|
+
if (process.platform === 'win32' && process.env.ENABLE_WINDOWS_TELEMETRY !== 'true') {
|
|
61
78
|
return false;
|
|
62
|
-
|
|
79
|
+
}
|
|
80
|
+
if (process.env.IS_HEROKU_TEST_ENV === 'true') {
|
|
63
81
|
return false;
|
|
82
|
+
}
|
|
64
83
|
return true;
|
|
65
84
|
}
|
|
66
85
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TelemetryGlobal } from './telemetry-utils.js';
|
|
2
2
|
declare global {
|
|
3
3
|
var cliTelemetry: TelemetryGlobal['cliTelemetry'];
|
|
4
|
+
var telemetrySent: TelemetryGlobal['telemetrySent'];
|
|
4
5
|
}
|
|
5
6
|
interface SetupTelemetryOptions {
|
|
6
7
|
cliStartTime: number;
|
|
@@ -8,9 +9,10 @@ interface SetupTelemetryOptions {
|
|
|
8
9
|
enableTelemetry: boolean;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
|
-
* Setup telemetry handlers for signal handlers
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Setup telemetry handlers for beforeExit and signal handlers
|
|
13
|
+
* - beforeExit: Fallback for commands where postrun hook doesn't run (e.g., version, --help flags)
|
|
14
|
+
* - postrun hook: Handles regular commands (sets telemetrySent flag to prevent duplicates)
|
|
15
|
+
* - SIGINT/SIGTERM: Handles interrupted commands
|
|
14
16
|
*/
|
|
15
17
|
export declare function setupTelemetryHandlers(options: SetupTelemetryOptions): void;
|
|
16
18
|
export {};
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
/* eslint-disable n/no-process-exit */
|
|
2
|
-
|
|
2
|
+
/* eslint-disable no-var */
|
|
3
|
+
import { spawnTelemetryWorker, telemetryDebug, } from './telemetry-utils.js';
|
|
3
4
|
/**
|
|
4
|
-
* Setup telemetry handlers for signal handlers
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Setup telemetry handlers for beforeExit and signal handlers
|
|
6
|
+
* - beforeExit: Fallback for commands where postrun hook doesn't run (e.g., version, --help flags)
|
|
7
|
+
* - postrun hook: Handles regular commands (sets telemetrySent flag to prevent duplicates)
|
|
8
|
+
* - SIGINT/SIGTERM: Handles interrupted commands
|
|
7
9
|
*/
|
|
8
10
|
export function setupTelemetryHandlers(options) {
|
|
9
11
|
const { cliStartTime, computeDuration, enableTelemetry } = options;
|
|
10
12
|
if (!enableTelemetry)
|
|
11
13
|
return;
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
+
// Fallback handler for commands that don't trigger postrun hook
|
|
15
|
+
// (e.g., version, --version, --help flags handled by oclif)
|
|
16
|
+
process.once('beforeExit', code => {
|
|
17
|
+
// Only send if telemetry wasn't already sent by postrun hook
|
|
18
|
+
if (global.cliTelemetry && !global.telemetrySent) {
|
|
19
|
+
telemetryDebug('Telemetry enabled: beforeExit spawning worker to send telemetry for command: %s (postrun did not run)', global.cliTelemetry.command);
|
|
20
|
+
const cmdStartTime = global.cliTelemetry.commandRunDuration;
|
|
21
|
+
global.cliTelemetry.commandRunDuration = computeDuration(cmdStartTime);
|
|
22
|
+
global.cliTelemetry.exitCode = code;
|
|
23
|
+
global.cliTelemetry.cliRunDuration = computeDuration(cliStartTime);
|
|
24
|
+
spawnTelemetryWorker(global.cliTelemetry);
|
|
25
|
+
global.telemetrySent = true;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
14
28
|
process.on('SIGINT', () => {
|
|
15
29
|
// Spawn background process to send telemetry
|
|
16
30
|
const error = Object.assign(new Error('Received SIGINT'), {
|