heroku 11.0.2 → 11.1.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +14 -16
  3. package/bin/run.js +13 -49
  4. package/dist/commands/domains/add.d.ts +1 -1
  5. package/dist/commands/pg/backups/schedule.d.ts +1 -1
  6. package/dist/commands/spaces/index.d.ts +1 -1
  7. package/dist/commands/spaces/vpn/connections.d.ts +1 -1
  8. package/dist/hooks/command_not_found/performance_analytics.js +5 -4
  9. package/dist/hooks/finally/sentry.d.ts +3 -0
  10. package/dist/hooks/finally/sentry.js +42 -0
  11. package/dist/hooks/init/performance_analytics.js +5 -4
  12. package/dist/hooks/postrun/performance_analytics.js +6 -6
  13. package/dist/hooks/prerun/analytics.js +14 -7
  14. package/dist/lib/analytics-telemetry/global-telemetry.d.ts +30 -0
  15. package/dist/lib/analytics-telemetry/global-telemetry.js +103 -0
  16. package/dist/lib/analytics-telemetry/honeycomb-client.d.ts +15 -0
  17. package/dist/lib/analytics-telemetry/honeycomb-client.js +135 -0
  18. package/dist/lib/analytics-telemetry/sentry-client.d.ts +9 -0
  19. package/dist/lib/analytics-telemetry/sentry-client.js +58 -0
  20. package/dist/lib/analytics-telemetry/telemetry-utils.d.ts +68 -0
  21. package/dist/lib/analytics-telemetry/telemetry-utils.js +115 -0
  22. package/dist/lib/analytics-telemetry/telemetry-worker.d.ts +5 -0
  23. package/dist/lib/analytics-telemetry/telemetry-worker.js +37 -0
  24. package/dist/lib/analytics-telemetry/worker-client.d.ts +15 -0
  25. package/dist/lib/analytics-telemetry/worker-client.js +44 -0
  26. package/dist/lib/api.d.ts +1 -1
  27. package/dist/lib/apps/app-transfer.d.ts +1 -1
  28. package/dist/lib/apps/error_info.d.ts +1 -1
  29. package/dist/lib/apps/generation.d.ts +3 -3
  30. package/dist/lib/buildpacks/buildpacks.d.ts +1 -1
  31. package/dist/lib/container/docker_helper.d.ts +4 -4
  32. package/dist/lib/data/types.d.ts +37 -37
  33. package/dist/lib/domains/domains.js +15 -8
  34. package/dist/lib/pg/download.d.ts +1 -1
  35. package/dist/lib/pg/push_pull.d.ts +1 -1
  36. package/dist/lib/pg/setter.d.ts +1 -1
  37. package/dist/lib/pg/types.d.ts +31 -31
  38. package/dist/lib/pipelines/setup/validate.d.ts +1 -1
  39. package/dist/lib/redis/api.d.ts +7 -7
  40. package/dist/lib/spaces/hosts.d.ts +1 -1
  41. package/dist/lib/types/app_errors.d.ts +1 -1
  42. package/dist/lib/types/completion.d.ts +1 -1
  43. package/dist/lib/types/favorites.d.ts +2 -2
  44. package/dist/lib/types/notifications.d.ts +3 -3
  45. package/dist/lib/utils/multisort.d.ts +1 -1
  46. package/npm-shrinkwrap.json +837 -842
  47. package/oclif.manifest.json +1018 -1018
  48. package/package.json +7 -5
  49. package/dist/global_telemetry.d.ts +0 -62
  50. package/dist/global_telemetry.js +0 -216
@@ -0,0 +1,58 @@
1
+ import * as Sentry from '@sentry/node';
2
+ import { PII_PATTERNS } from '../data-scrubber/patterns.js';
3
+ import { GDPR_FIELDS, HEROKU_FIELDS, PCI_FIELDS } from '../data-scrubber/presets.js';
4
+ import { Scrubber } from '../data-scrubber/scrubber.js';
5
+ import { getVersion, isDev, isTelemetryDisabled, telemetryDebug, } from './telemetry-utils.js';
6
+ // Lazy-loaded state
7
+ let isSentryInitialized = false;
8
+ let sentryClient;
9
+ /**
10
+ * Ensure Sentry is initialized (lazy initialization)
11
+ */
12
+ export function ensureSentryInitialized() {
13
+ if (isSentryInitialized || isTelemetryDisabled) {
14
+ return;
15
+ }
16
+ telemetryDebug('Initializing Sentry...');
17
+ isSentryInitialized = true;
18
+ const scrubber = new Scrubber({
19
+ fields: [...HEROKU_FIELDS, ...GDPR_FIELDS, ...PCI_FIELDS],
20
+ patterns: [...PII_PATTERNS],
21
+ });
22
+ sentryClient = Sentry.init({
23
+ beforeSend(event) {
24
+ return scrubber.scrub(event).data;
25
+ },
26
+ dsn: 'https://76530569188e7ee2961373f37951d916@o4508609692368896.ingest.us.sentry.io/4508767754846208',
27
+ environment: isDev ? 'development' : 'production',
28
+ release: getVersion(),
29
+ skipOpenTelemetrySetup: true,
30
+ tracesSampleRate: 1, // needed to ensure we send OTEL data to Honeycomb
31
+ });
32
+ telemetryDebug('Sentry initialized (environment: %s, release: %s)', isDev ? 'development' : 'production', getVersion());
33
+ }
34
+ /**
35
+ * Send error data to Sentry
36
+ */
37
+ export async function sendToSentry(data) {
38
+ // Lazy-load Sentry only when we actually need to report an error
39
+ ensureSentryInitialized();
40
+ try {
41
+ telemetryDebug('Sentry payload: %O', {
42
+ code: data.code,
43
+ context: data.context,
44
+ message: data.message,
45
+ name: data.name,
46
+ stack: data.stack,
47
+ statusCode: data.statusCode,
48
+ });
49
+ telemetryDebug('Capturing exception in Sentry: %s', data.message);
50
+ Sentry.captureException(data);
51
+ // ensures all events are sent to Sentry before exiting.
52
+ await Sentry.flush();
53
+ telemetryDebug('Successfully flushed error to Sentry');
54
+ }
55
+ catch (error) {
56
+ telemetryDebug('Error sending to Sentry: %O', error);
57
+ }
58
+ }
@@ -0,0 +1,68 @@
1
+ import debug from 'debug';
2
+ export declare const telemetryDebug: debug.Debugger;
3
+ export declare const isDev: boolean;
4
+ export declare const isTelemetryDisabled: boolean;
5
+ export interface CLIError extends Error {
6
+ cliRunDuration?: number | string;
7
+ code?: string;
8
+ context?: {
9
+ isTTY?: boolean;
10
+ };
11
+ http?: {
12
+ statusCode?: number;
13
+ };
14
+ oclif?: {
15
+ exit?: number;
16
+ };
17
+ statusCode?: number;
18
+ }
19
+ export interface Telemetry {
20
+ cliRunDuration: number;
21
+ command: string;
22
+ commandRunDuration: number;
23
+ exitCode: number;
24
+ exitState: string;
25
+ isTTY: boolean | undefined;
26
+ isVersionOrHelp: boolean;
27
+ lifecycleHookCompletion: {
28
+ command_not_found: boolean;
29
+ init: boolean;
30
+ postrun: boolean;
31
+ prerun: boolean;
32
+ };
33
+ os: string;
34
+ version: string;
35
+ }
36
+ export type TelemetryData = CLIError | Telemetry;
37
+ export interface TelemetryGlobal {
38
+ cliTelemetry?: Telemetry;
39
+ }
40
+ /**
41
+ * Compute duration from a start time to now
42
+ */
43
+ export declare function computeDuration(cmdStartTime: number): number;
44
+ /**
45
+ * Get authentication token, cached to avoid recreating Config/APIClient
46
+ */
47
+ export declare function getToken(): string | undefined;
48
+ /**
49
+ * Get CLI version
50
+ */
51
+ export declare function getVersion(): string;
52
+ /**
53
+ * Check if telemetry is enabled based on environment variables
54
+ */
55
+ export declare function isTelemetryEnabled(): boolean;
56
+ /**
57
+ * Serialize data for telemetry worker, handling Error objects specially
58
+ */
59
+ export declare function serializeTelemetryData(data: TelemetryData): string;
60
+ /**
61
+ * Set CLI version (called once during setup)
62
+ */
63
+ export declare function setVersion(v: string): void;
64
+ /**
65
+ * Spawn telemetry worker process in background
66
+ * This avoids blocking the main CLI process with telemetry overhead
67
+ */
68
+ export declare function spawnTelemetryWorker(data: TelemetryData): void;
@@ -0,0 +1,115 @@
1
+ import { APIClient } from '@heroku-cli/command';
2
+ import { Config } from '@oclif/core/config';
3
+ import debug from 'debug';
4
+ import { spawn } from 'node:child_process';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const root = path.resolve(__dirname, '../../../package.json');
10
+ // Debug instance for telemetry operations
11
+ export const telemetryDebug = debug('analytics-telemetry');
12
+ // Environment flags
13
+ export const isDev = process.env.IS_DEV_ENVIRONMENT === 'true';
14
+ export const isTelemetryDisabled = process.env.DISABLE_TELEMETRY === 'true';
15
+ // Cached values
16
+ let version;
17
+ let cachedToken;
18
+ /**
19
+ * Compute duration from a start time to now
20
+ */
21
+ export function computeDuration(cmdStartTime) {
22
+ const now = new Date();
23
+ const cmdFinishTime = now.getTime();
24
+ return cmdFinishTime - cmdStartTime;
25
+ }
26
+ /**
27
+ * Get authentication token, cached to avoid recreating Config/APIClient
28
+ */
29
+ export function getToken() {
30
+ if (cachedToken !== undefined) {
31
+ return cachedToken;
32
+ }
33
+ try {
34
+ const config = new Config({ root });
35
+ const heroku = new APIClient(config);
36
+ cachedToken = heroku.auth;
37
+ return cachedToken;
38
+ }
39
+ catch {
40
+ // If config initialization fails, return empty string
41
+ cachedToken = '';
42
+ return cachedToken;
43
+ }
44
+ }
45
+ /**
46
+ * Get CLI version
47
+ */
48
+ export function getVersion() {
49
+ return version || 'unknown';
50
+ }
51
+ /**
52
+ * Check if telemetry is enabled based on environment variables
53
+ */
54
+ export function isTelemetryEnabled() {
55
+ if (process.env.DISABLE_TELEMETRY === 'true')
56
+ return false;
57
+ if (process.platform === 'win32' && process.env.ENABLE_WINDOWS_TELEMETRY !== 'true')
58
+ return false;
59
+ if (process.env.IS_HEROKU_TEST_ENV === 'true')
60
+ return false;
61
+ return true;
62
+ }
63
+ /**
64
+ * Serialize data for telemetry worker, handling Error objects specially
65
+ */
66
+ export function serializeTelemetryData(data) {
67
+ // If it's an Error object, convert to plain object with all properties
68
+ if (data instanceof Error) {
69
+ const errorData = data;
70
+ return JSON.stringify({
71
+ // Include any other enumerable properties first
72
+ ...data,
73
+ // Then override with important properties to ensure they're captured
74
+ cliRunDuration: errorData.cliRunDuration,
75
+ code: errorData.code,
76
+ http: errorData.http,
77
+ message: errorData.message,
78
+ name: errorData.name,
79
+ oclif: errorData.oclif,
80
+ stack: errorData.stack,
81
+ statusCode: errorData.statusCode,
82
+ });
83
+ }
84
+ return JSON.stringify(data);
85
+ }
86
+ /**
87
+ * Set CLI version (called once during setup)
88
+ */
89
+ export function setVersion(v) {
90
+ version = v;
91
+ }
92
+ /**
93
+ * Spawn telemetry worker process in background
94
+ * This avoids blocking the main CLI process with telemetry overhead
95
+ */
96
+ export function spawnTelemetryWorker(data) {
97
+ try {
98
+ const workerPath = path.join(__dirname, '..', '..', '..', 'dist', 'lib', 'analytics-telemetry', 'telemetry-worker.js');
99
+ const child = spawn(process.execPath, [workerPath], {
100
+ detached: true,
101
+ // Keep stderr attached to see DEBUG output, but ignore stdout
102
+ stdio: ['pipe', 'ignore', 'inherit'],
103
+ // On Windows, prevent console window from appearing
104
+ windowsHide: true,
105
+ });
106
+ // Send data via stdin
107
+ child.stdin.write(serializeTelemetryData(data));
108
+ child.stdin.end();
109
+ // Detach from parent so it can exit immediately
110
+ child.unref();
111
+ }
112
+ catch {
113
+ // Silently fail - don't let telemetry errors affect user experience
114
+ }
115
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Telemetry worker process - runs in background to send telemetry data
3
+ * This runs as a separate process to avoid blocking the main CLI
4
+ */
5
+ export {};
@@ -0,0 +1,37 @@
1
+ /* eslint-disable n/no-process-exit */
2
+ /**
3
+ * Telemetry worker process - runs in background to send telemetry data
4
+ * This runs as a separate process to avoid blocking the main CLI
5
+ */
6
+ import { sendTelemetry } from './global-telemetry.js';
7
+ // Read telemetry data from stdin
8
+ let inputData = '';
9
+ process.stdin.setEncoding('utf8');
10
+ process.stdin.on('data', chunk => {
11
+ inputData += chunk;
12
+ });
13
+ process.stdin.on('end', async () => {
14
+ try {
15
+ const parsed = JSON.parse(inputData);
16
+ // If the data looks like an error, reconstruct it as an Error instance
17
+ let telemetryData = parsed;
18
+ if (parsed.message && parsed.stack && (parsed.name === 'Error' || parsed.name)) {
19
+ const error = new Error(parsed.message);
20
+ error.name = parsed.name;
21
+ error.stack = parsed.stack;
22
+ // Copy over additional properties
23
+ Object.assign(error, parsed);
24
+ telemetryData = error;
25
+ }
26
+ // Send telemetry (this will initialize OpenTelemetry and Sentry if needed)
27
+ await sendTelemetry(telemetryData);
28
+ process.exit(0);
29
+ }
30
+ catch {
31
+ // Silently fail - don't let telemetry errors affect user experience
32
+ process.exit(1);
33
+ }
34
+ });
35
+ // Handle errors silently
36
+ process.on('uncaughtException', () => process.exit(1));
37
+ process.on('unhandledRejection', () => process.exit(1));
@@ -0,0 +1,15 @@
1
+ import { TelemetryGlobal } from './telemetry-utils.js';
2
+ declare global {
3
+ var cliTelemetry: TelemetryGlobal['cliTelemetry'];
4
+ }
5
+ interface SetupTelemetryOptions {
6
+ cliStartTime: number;
7
+ computeDuration: (startTime: number) => number;
8
+ enableTelemetry: boolean;
9
+ }
10
+ /**
11
+ * Setup telemetry handlers for beforeExit and signal handlers
12
+ * This centralizes all telemetry worker spawning logic
13
+ */
14
+ export declare function setupTelemetryHandlers(options: SetupTelemetryOptions): void;
15
+ export {};
@@ -0,0 +1,44 @@
1
+ /* eslint-disable n/no-process-exit */
2
+ import { spawnTelemetryWorker } from './telemetry-utils.js';
3
+ /**
4
+ * Setup telemetry handlers for beforeExit and signal handlers
5
+ * This centralizes all telemetry worker spawning logic
6
+ */
7
+ export function setupTelemetryHandlers(options) {
8
+ const { cliStartTime, computeDuration, enableTelemetry } = options;
9
+ if (!enableTelemetry)
10
+ return;
11
+ process.once('beforeExit', code => {
12
+ // capture as successful exit
13
+ if (global.cliTelemetry) {
14
+ if (global.cliTelemetry.isVersionOrHelp) {
15
+ const cmdStartTime = global.cliTelemetry.commandRunDuration;
16
+ global.cliTelemetry.commandRunDuration = computeDuration(cmdStartTime);
17
+ }
18
+ global.cliTelemetry.exitCode = code;
19
+ global.cliTelemetry.cliRunDuration = computeDuration(cliStartTime);
20
+ const telemetryData = global.cliTelemetry;
21
+ // Spawn background process to send telemetry without blocking exit
22
+ spawnTelemetryWorker(telemetryData);
23
+ }
24
+ });
25
+ process.on('SIGINT', () => {
26
+ // Spawn background process to send telemetry
27
+ const error = Object.assign(new Error('Received SIGINT'), {
28
+ cliRunDuration: computeDuration(cliStartTime),
29
+ context: {
30
+ isTTY: process.stdin.isTTY,
31
+ },
32
+ });
33
+ spawnTelemetryWorker(error);
34
+ process.exit(1);
35
+ });
36
+ process.on('SIGTERM', () => {
37
+ // Spawn background process to send telemetry
38
+ const error = Object.assign(new Error('Received SIGTERM'), {
39
+ cliRunDuration: computeDuration(cliStartTime),
40
+ });
41
+ spawnTelemetryWorker(error);
42
+ process.exit(1);
43
+ });
44
+ }
package/dist/lib/api.d.ts CHANGED
@@ -5,7 +5,7 @@ export declare const V3_HEADER = "application/vnd.heroku+json; version=3";
5
5
  export declare const SDK_HEADER = "application/vnd.heroku+json; version=3.sdk";
6
6
  export declare const FILTERS_HEADER: string;
7
7
  export declare const PIPELINES_HEADER: string;
8
- export declare type Owner = Pick<Heroku.Account, 'id' | 'type'> | Pick<Heroku.Team, 'id' | 'type'>;
8
+ export type Owner = Pick<Heroku.Account, 'id' | 'type'> | Pick<Heroku.Team, 'id' | 'type'>;
9
9
  export declare function createAppSetup(heroku: APIClient, body: {
10
10
  body: any;
11
11
  }): Promise<import("@heroku/http-call").HTTP<Heroku.AppSetup>>;
@@ -1,5 +1,5 @@
1
1
  import { APIClient } from '@heroku-cli/command';
2
- declare type Options = {
2
+ type Options = {
3
3
  appName: string;
4
4
  bulk: boolean;
5
5
  heroku: APIClient;
@@ -1,4 +1,4 @@
1
- export declare type ErrorInfo = {
1
+ export type ErrorInfo = {
2
2
  name: string;
3
3
  title: string;
4
4
  level: string;
@@ -1,9 +1,9 @@
1
1
  import { APIClient } from '@heroku-cli/command';
2
2
  import { App, Space, DynoSize, TeamApp, Pipeline, Generation, AppGeneration, DynoSizeGeneration, PipelineGeneration } from '../types/fir.js';
3
3
  import Dyno from '../run/dyno.js';
4
- export declare type GenerationKind = 'fir' | 'cedar';
5
- export declare type GenerationLike = Generation | AppGeneration | DynoSizeGeneration | PipelineGeneration | Dyno;
6
- export declare type GenerationCapable = App | Space | DynoSize | TeamApp | Pipeline;
4
+ export type GenerationKind = 'fir' | 'cedar';
5
+ export type GenerationLike = Generation | AppGeneration | DynoSizeGeneration | PipelineGeneration | Dyno;
6
+ export type GenerationCapable = App | Space | DynoSize | TeamApp | Pipeline;
7
7
  /**
8
8
  * Get the generation of an object
9
9
  *
@@ -1,6 +1,6 @@
1
1
  import { APIClient } from '@heroku-cli/command';
2
2
  import { BuildpackRegistry } from '@heroku/buildpack-registry';
3
- export declare type BuildpackResponse = {
3
+ export type BuildpackResponse = {
4
4
  buildpack: {
5
5
  name: string;
6
6
  url: string;
@@ -1,18 +1,18 @@
1
- export declare type CmdOptions = {
1
+ export type CmdOptions = {
2
2
  input?: string;
3
3
  output?: boolean;
4
4
  };
5
- export declare type DockerJob = {
5
+ export type DockerJob = {
6
6
  depth: number;
7
7
  dockerfile: string;
8
8
  name: string;
9
9
  postfix: number;
10
10
  resource: string;
11
11
  };
12
- export declare type GroupedDockerJobs = {
12
+ export type GroupedDockerJobs = {
13
13
  [processType: string]: DockerJob[];
14
14
  };
15
- declare type BuildImageParams = {
15
+ type BuildImageParams = {
16
16
  arch?: string;
17
17
  buildArgs: string[];
18
18
  dockerfile: string;
@@ -1,15 +1,15 @@
1
- export declare type DeepRequired<T> = T extends object ? {
1
+ export type DeepRequired<T> = T extends object ? {
2
2
  [K in keyof T]-?: DeepRequired<T[K]>;
3
3
  } : T;
4
- declare type ResourceReference = {
4
+ type ResourceReference = {
5
5
  id: string;
6
6
  name: string;
7
7
  };
8
- declare type AppReference = ResourceReference;
9
- declare type AddonReference = ResourceReference;
10
- declare type CommonRuntimeRegion = 'eu' | 'us';
11
- declare type PrivateSpaceRegion = 'california' | 'dublin' | 'frankfurt' | 'london' | 'montreal' | 'mumbai' | 'ohio' | 'oregon' | 'paris' | 'singapore' | 'sydney' | 'tokyo' | 'virginia';
12
- export declare type InfoResponse = {
8
+ type AppReference = ResourceReference;
9
+ type AddonReference = ResourceReference;
10
+ type CommonRuntimeRegion = 'eu' | 'us';
11
+ type PrivateSpaceRegion = 'california' | 'dublin' | 'frankfurt' | 'london' | 'montreal' | 'mumbai' | 'ohio' | 'oregon' | 'paris' | 'singapore' | 'sydney' | 'tokyo' | 'virginia';
12
+ export type InfoResponse = {
13
13
  addon: AddonReference;
14
14
  app: AppReference;
15
15
  created_at: string;
@@ -45,10 +45,10 @@ export declare type InfoResponse = {
45
45
  tier: 'advanced';
46
46
  version: string;
47
47
  };
48
- export declare type Quotas = {
48
+ export type Quotas = {
49
49
  items: Array<Quota>;
50
50
  };
51
- export declare type Quota = {
51
+ export type Quota = {
52
52
  critical_gb: null | number;
53
53
  current_gb: null | number;
54
54
  enforcement_action: 'none' | 'notify' | 'restrict';
@@ -56,47 +56,47 @@ export declare type Quota = {
56
56
  type: string;
57
57
  warning_gb: null | number;
58
58
  };
59
- declare type TableLimit = {
59
+ type TableLimit = {
60
60
  current: number;
61
61
  limit: number;
62
62
  name: 'table-limit';
63
63
  };
64
- declare type ConnectionLimit = {
64
+ type ConnectionLimit = {
65
65
  current: number;
66
66
  limit: number;
67
67
  name: 'connection-limit';
68
68
  };
69
- declare type StorageLimitInGb = {
69
+ type StorageLimitInGb = {
70
70
  current: number;
71
71
  limit: number;
72
72
  name: 'storage-limit-in-gb';
73
73
  };
74
- export declare type PlanLimit = ConnectionLimit | StorageLimitInGb | TableLimit;
75
- export declare type PostgresLevelInfo = {
74
+ export type PlanLimit = ConnectionLimit | StorageLimitInGb | TableLimit;
75
+ export type PostgresLevelInfo = {
76
76
  connection_limit: number;
77
77
  memory_in_gb: number;
78
78
  name: string;
79
79
  vcpu: number;
80
80
  };
81
- export declare type PostgresLevelsResponse = {
81
+ export type PostgresLevelsResponse = {
82
82
  items: Array<PostgresLevelInfo>;
83
83
  };
84
- declare type BaseChange = {
84
+ type BaseChange = {
85
85
  current: boolean | null | number | string;
86
86
  name: string;
87
87
  previous: boolean | null | number | string;
88
88
  };
89
- declare type PoolChange = {
89
+ type PoolChange = {
90
90
  pool: string;
91
91
  } & BaseChange;
92
- export declare type ScaleResponse = {
92
+ export type ScaleResponse = {
93
93
  changes: Array<PoolChange>;
94
94
  };
95
- declare type SettingsChange = BaseChange;
96
- export declare type SettingsChangeResponse = {
95
+ type SettingsChange = BaseChange;
96
+ export type SettingsChangeResponse = {
97
97
  changes: Array<SettingsChange>;
98
98
  };
99
- export declare type SettingsResponse = {
99
+ export type SettingsResponse = {
100
100
  items: Array<{
101
101
  current: boolean | null | number | string;
102
102
  default: boolean | null | number | string;
@@ -104,12 +104,12 @@ export declare type SettingsResponse = {
104
104
  reboot_required: boolean;
105
105
  }>;
106
106
  };
107
- export declare type CreatePoolParameters = {
107
+ export type CreatePoolParameters = {
108
108
  count: number;
109
109
  level: string;
110
110
  name?: string;
111
111
  };
112
- export declare type ComputeInstance = {
112
+ export type ComputeInstance = {
113
113
  id: string;
114
114
  level: string;
115
115
  name: string;
@@ -117,12 +117,12 @@ export declare type ComputeInstance = {
117
117
  status: string;
118
118
  updated_at: string;
119
119
  };
120
- export declare type ConnectionEndpoint = {
120
+ export type ConnectionEndpoint = {
121
121
  host: string;
122
122
  port: number;
123
123
  status: 'available' | 'degraded' | 'deprovisioning' | 'modifying';
124
124
  };
125
- export declare type PoolInfoResponse = {
125
+ export type PoolInfoResponse = {
126
126
  compute_instances: Array<ComputeInstance>;
127
127
  connections_used: null | number;
128
128
  endpoints: Array<ConnectionEndpoint>;
@@ -142,10 +142,10 @@ export declare type PoolInfoResponse = {
142
142
  waiting: boolean;
143
143
  };
144
144
  };
145
- export declare type CredentialsInfo = {
145
+ export type CredentialsInfo = {
146
146
  items: Array<AdvancedCredentialInfo>;
147
147
  };
148
- export declare type CredentialInfo = AdvancedCredentialInfo | NonAdvancedCredentialInfo;
148
+ export type CredentialInfo = AdvancedCredentialInfo | NonAdvancedCredentialInfo;
149
149
  export interface AdvancedCredentialInfo extends Record<string, unknown> {
150
150
  database: string;
151
151
  host: string;
@@ -161,23 +161,23 @@ export interface AdvancedCredentialInfo extends Record<string, unknown> {
161
161
  type: 'additional' | 'owner';
162
162
  }
163
163
  export declare function isAdvancedCredentialInfo(credential: CredentialInfo): credential is AdvancedCredentialInfo;
164
- export declare type PricingInfo = {
164
+ export type PricingInfo = {
165
165
  billing_period: 'month';
166
166
  billing_unit: 'compute' | 'gigabyte';
167
167
  included_units?: number;
168
168
  product_description: string;
169
169
  rate: number;
170
170
  };
171
- export declare type TierPricingInfo = Record<string, PricingInfo>;
172
- export declare type PricingInfoResponse = Record<string, TierPricingInfo>;
173
- declare type NonAdvancedCredentialState = 'active' | 'archived' | 'enabling' | 'revoked' | 'revoking';
174
- declare type NonAdvancedCredential = {
171
+ export type TierPricingInfo = Record<string, PricingInfo>;
172
+ export type PricingInfoResponse = Record<string, TierPricingInfo>;
173
+ type NonAdvancedCredentialState = 'active' | 'archived' | 'enabling' | 'revoked' | 'revoking';
174
+ type NonAdvancedCredential = {
175
175
  connections?: null | number;
176
176
  password: string;
177
177
  state: NonAdvancedCredentialState;
178
178
  user: string;
179
179
  };
180
- declare type NonAdvancedCredentialStoreState = 'active' | 'archived' | 'provisioning' | 'revoking' | 'rotating' | 'rotation_completed' | 'wait_for_provisioning';
180
+ type NonAdvancedCredentialStoreState = 'active' | 'archived' | 'provisioning' | 'revoking' | 'rotating' | 'rotation_completed' | 'wait_for_provisioning';
181
181
  export interface NonAdvancedCredentialInfo extends Record<string, unknown> {
182
182
  credentials: Array<NonAdvancedCredential>;
183
183
  database: string;
@@ -187,10 +187,10 @@ export interface NonAdvancedCredentialInfo extends Record<string, unknown> {
187
187
  state: NonAdvancedCredentialStoreState;
188
188
  uuid: string;
189
189
  }
190
- export declare type ExtendedPostgresLevelInfo = {
190
+ export type ExtendedPostgresLevelInfo = {
191
191
  pricing: PricingInfo | undefined;
192
192
  } & PostgresLevelInfo;
193
- export declare type Maintenance = {
193
+ export type Maintenance = {
194
194
  'addon': {
195
195
  'attachments': string[];
196
196
  'kind': string;
@@ -215,7 +215,7 @@ export declare type Maintenance = {
215
215
  'status': MaintenanceStatus;
216
216
  'window': null | string;
217
217
  };
218
- export declare type Window = {
218
+ export type Window = {
219
219
  previous_window: null | string;
220
220
  previously_scheduled_at: null | string;
221
221
  scheduled_at: null | string;
@@ -229,7 +229,7 @@ export declare enum MaintenanceStatus {
229
229
  ready = "ready",
230
230
  running = "running"
231
231
  }
232
- export declare type WaitStatus = {
232
+ export type WaitStatus = {
233
233
  message: null | string;
234
234
  waiting: boolean;
235
235
  };
@@ -1,6 +1,6 @@
1
1
  import { color, hux } from '@heroku/heroku-cli-util';
2
2
  import { ux } from '@oclif/core/ux';
3
- import { parse } from 'psl';
3
+ import { parse } from 'tldts';
4
4
  const wait = function (ms) {
5
5
  return new Promise(resolve => {
6
6
  setTimeout(resolve, ms);
@@ -87,13 +87,20 @@ export async function waitForDomains(heroku, app) {
87
87
  }
88
88
  return apiDomains;
89
89
  }
90
- function isParseError(parsed) {
91
- return parsed.error !== undefined;
92
- }
93
90
  function type(domain) {
94
- const parsed = parse(domain.hostname);
95
- if (isParseError(parsed)) {
96
- throw new Error(parsed.error.message);
91
+ // Wildcard domains (*.example.com) are always treated as subdomains (CNAME)
92
+ // This must be checked before parsing since wildcards aren't valid hostnames
93
+ if (domain.hostname.includes('*')) {
94
+ return 'CNAME';
95
+ }
96
+ // Parse the domain with private domains enabled (for .herokuapp.com, etc.)
97
+ const result = parse(domain.hostname, { allowPrivateDomains: true });
98
+ // Reject invalid or unparsable hostnames (e.g., "notadomain", "localhost", IPs, empty strings)
99
+ // All of these result in domain === null
100
+ if (result.domain === null) {
101
+ throw new Error(`Invalid hostname: ${domain.hostname}`);
97
102
  }
98
- return parsed.subdomain === null ? 'ALIAS/ANAME' : 'CNAME';
103
+ // Empty string subdomain means root domain ALIAS/ANAME
104
+ // Non-empty subdomain means has subdomain → CNAME
105
+ return result.subdomain ? 'CNAME' : 'ALIAS/ANAME';
99
106
  }
@@ -1,4 +1,4 @@
1
- declare type downloadOptions = {
1
+ type downloadOptions = {
2
2
  progress: boolean;
3
3
  };
4
4
  export default function download(url: string, path: string, opts: downloadOptions): Promise<unknown>;
@@ -3,7 +3,7 @@
3
3
  import type { pg } from '@heroku/heroku-cli-util';
4
4
  import { ChildProcess } from 'node:child_process';
5
5
  export declare const parseExclusions: (rawExcludeList: string | undefined) => Array<string>;
6
- declare type ExecFn = typeof exec;
6
+ type ExecFn = typeof exec;
7
7
  export declare const prepare: (target: pg.ConnectionDetails, execFn?: ExecFn) => Promise<void>;
8
8
  export declare const maybeTunnel: (herokuDb: pg.ConnectionDetails) => Promise<pg.ConnectionDetails>;
9
9
  export declare const connArgs: (uri: pg.ConnectionDetails, skipDFlag?: boolean) => string[];