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.
- package/CHANGELOG.md +12 -0
- package/README.md +14 -16
- package/bin/run.js +13 -49
- package/dist/commands/domains/add.d.ts +1 -1
- package/dist/commands/pg/backups/schedule.d.ts +1 -1
- package/dist/commands/spaces/index.d.ts +1 -1
- package/dist/commands/spaces/vpn/connections.d.ts +1 -1
- package/dist/hooks/command_not_found/performance_analytics.js +5 -4
- package/dist/hooks/finally/sentry.d.ts +3 -0
- package/dist/hooks/finally/sentry.js +42 -0
- package/dist/hooks/init/performance_analytics.js +5 -4
- package/dist/hooks/postrun/performance_analytics.js +6 -6
- package/dist/hooks/prerun/analytics.js +14 -7
- package/dist/lib/analytics-telemetry/global-telemetry.d.ts +30 -0
- package/dist/lib/analytics-telemetry/global-telemetry.js +103 -0
- package/dist/lib/analytics-telemetry/honeycomb-client.d.ts +15 -0
- package/dist/lib/analytics-telemetry/honeycomb-client.js +135 -0
- package/dist/lib/analytics-telemetry/sentry-client.d.ts +9 -0
- package/dist/lib/analytics-telemetry/sentry-client.js +58 -0
- package/dist/lib/analytics-telemetry/telemetry-utils.d.ts +68 -0
- package/dist/lib/analytics-telemetry/telemetry-utils.js +115 -0
- package/dist/lib/analytics-telemetry/telemetry-worker.d.ts +5 -0
- package/dist/lib/analytics-telemetry/telemetry-worker.js +37 -0
- package/dist/lib/analytics-telemetry/worker-client.d.ts +15 -0
- package/dist/lib/analytics-telemetry/worker-client.js +44 -0
- package/dist/lib/api.d.ts +1 -1
- package/dist/lib/apps/app-transfer.d.ts +1 -1
- package/dist/lib/apps/error_info.d.ts +1 -1
- package/dist/lib/apps/generation.d.ts +3 -3
- package/dist/lib/buildpacks/buildpacks.d.ts +1 -1
- package/dist/lib/container/docker_helper.d.ts +4 -4
- package/dist/lib/data/types.d.ts +37 -37
- package/dist/lib/domains/domains.js +15 -8
- package/dist/lib/pg/download.d.ts +1 -1
- package/dist/lib/pg/push_pull.d.ts +1 -1
- package/dist/lib/pg/setter.d.ts +1 -1
- package/dist/lib/pg/types.d.ts +31 -31
- package/dist/lib/pipelines/setup/validate.d.ts +1 -1
- package/dist/lib/redis/api.d.ts +7 -7
- package/dist/lib/spaces/hosts.d.ts +1 -1
- package/dist/lib/types/app_errors.d.ts +1 -1
- package/dist/lib/types/completion.d.ts +1 -1
- package/dist/lib/types/favorites.d.ts +2 -2
- package/dist/lib/types/notifications.d.ts +3 -3
- package/dist/lib/utils/multisort.d.ts +1 -1
- package/npm-shrinkwrap.json +837 -842
- package/oclif.manifest.json +1018 -1018
- package/package.json +7 -5
- package/dist/global_telemetry.d.ts +0 -62
- 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,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
|
|
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,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
|
|
5
|
-
export
|
|
6
|
-
export
|
|
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,18 +1,18 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type CmdOptions = {
|
|
2
2
|
input?: string;
|
|
3
3
|
output?: boolean;
|
|
4
4
|
};
|
|
5
|
-
export
|
|
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
|
|
12
|
+
export type GroupedDockerJobs = {
|
|
13
13
|
[processType: string]: DockerJob[];
|
|
14
14
|
};
|
|
15
|
-
|
|
15
|
+
type BuildImageParams = {
|
|
16
16
|
arch?: string;
|
|
17
17
|
buildArgs: string[];
|
|
18
18
|
dockerfile: string;
|
package/dist/lib/data/types.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type DeepRequired<T> = T extends object ? {
|
|
2
2
|
[K in keyof T]-?: DeepRequired<T[K]>;
|
|
3
3
|
} : T;
|
|
4
|
-
|
|
4
|
+
type ResourceReference = {
|
|
5
5
|
id: string;
|
|
6
6
|
name: string;
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export
|
|
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
|
|
48
|
+
export type Quotas = {
|
|
49
49
|
items: Array<Quota>;
|
|
50
50
|
};
|
|
51
|
-
export
|
|
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
|
-
|
|
59
|
+
type TableLimit = {
|
|
60
60
|
current: number;
|
|
61
61
|
limit: number;
|
|
62
62
|
name: 'table-limit';
|
|
63
63
|
};
|
|
64
|
-
|
|
64
|
+
type ConnectionLimit = {
|
|
65
65
|
current: number;
|
|
66
66
|
limit: number;
|
|
67
67
|
name: 'connection-limit';
|
|
68
68
|
};
|
|
69
|
-
|
|
69
|
+
type StorageLimitInGb = {
|
|
70
70
|
current: number;
|
|
71
71
|
limit: number;
|
|
72
72
|
name: 'storage-limit-in-gb';
|
|
73
73
|
};
|
|
74
|
-
export
|
|
75
|
-
export
|
|
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
|
|
81
|
+
export type PostgresLevelsResponse = {
|
|
82
82
|
items: Array<PostgresLevelInfo>;
|
|
83
83
|
};
|
|
84
|
-
|
|
84
|
+
type BaseChange = {
|
|
85
85
|
current: boolean | null | number | string;
|
|
86
86
|
name: string;
|
|
87
87
|
previous: boolean | null | number | string;
|
|
88
88
|
};
|
|
89
|
-
|
|
89
|
+
type PoolChange = {
|
|
90
90
|
pool: string;
|
|
91
91
|
} & BaseChange;
|
|
92
|
-
export
|
|
92
|
+
export type ScaleResponse = {
|
|
93
93
|
changes: Array<PoolChange>;
|
|
94
94
|
};
|
|
95
|
-
|
|
96
|
-
export
|
|
95
|
+
type SettingsChange = BaseChange;
|
|
96
|
+
export type SettingsChangeResponse = {
|
|
97
97
|
changes: Array<SettingsChange>;
|
|
98
98
|
};
|
|
99
|
-
export
|
|
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
|
|
107
|
+
export type CreatePoolParameters = {
|
|
108
108
|
count: number;
|
|
109
109
|
level: string;
|
|
110
110
|
name?: string;
|
|
111
111
|
};
|
|
112
|
-
export
|
|
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
|
|
120
|
+
export type ConnectionEndpoint = {
|
|
121
121
|
host: string;
|
|
122
122
|
port: number;
|
|
123
123
|
status: 'available' | 'degraded' | 'deprovisioning' | 'modifying';
|
|
124
124
|
};
|
|
125
|
-
export
|
|
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
|
|
145
|
+
export type CredentialsInfo = {
|
|
146
146
|
items: Array<AdvancedCredentialInfo>;
|
|
147
147
|
};
|
|
148
|
-
export
|
|
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
|
|
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
|
|
172
|
-
export
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
190
|
+
export type ExtendedPostgresLevelInfo = {
|
|
191
191
|
pricing: PricingInfo | undefined;
|
|
192
192
|
} & PostgresLevelInfo;
|
|
193
|
-
export
|
|
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
|
|
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
|
|
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 '
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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[];
|