@heroku/heroku-cli-util 9.0.1 → 9.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/dist/errors/ambiguous.d.ts +16 -0
- package/dist/{types/errors → errors}/ambiguous.js +5 -1
- package/dist/errors/not-found.d.ts +20 -0
- package/dist/errors/not-found.js +24 -0
- package/dist/index.d.ts +16 -23
- package/dist/index.js +24 -26
- package/dist/types/pg/data-api.d.ts +35 -6
- package/dist/types/pg/tunnel.d.ts +11 -8
- package/dist/utils/addons/resolve.d.ts +11 -7
- package/dist/utils/addons/resolve.js +30 -23
- package/dist/utils/pg/bastion.d.ts +57 -25
- package/dist/utils/pg/bastion.js +153 -68
- package/dist/utils/pg/config-vars.d.ts +33 -7
- package/dist/utils/pg/config-vars.js +52 -18
- package/dist/utils/pg/databases.d.ts +74 -10
- package/dist/utils/pg/databases.js +170 -114
- package/dist/utils/pg/psql.d.ts +109 -21
- package/dist/utils/pg/psql.js +222 -144
- package/package.json +9 -21
- package/dist/test-helpers/expect-output.d.ts +0 -2
- package/dist/test-helpers/expect-output.js +0 -16
- package/dist/test-helpers/init.d.ts +0 -1
- package/dist/test-helpers/init.js +0 -17
- package/dist/test-helpers/run-command.d.ts +0 -9
- package/dist/test-helpers/run-command.js +0 -43
- package/dist/test-helpers/stub-output.d.ts +0 -4
- package/dist/test-helpers/stub-output.js +0 -35
- package/dist/types/errors/ambiguous.d.ts +0 -15
- package/dist/types/errors/not-found.d.ts +0 -5
- package/dist/types/errors/not-found.js +0 -12
|
@@ -1,137 +1,193 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
exports.getAttachment = getAttachment;
|
|
5
|
-
exports.getDatabase = getDatabase;
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
6
4
|
const color_1 = require("@heroku-cli/color");
|
|
7
5
|
const api_client_1 = require("@heroku-cli/command/lib/api-client");
|
|
8
|
-
const debug_1 = require("debug");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const resolve_1 = require("../addons/resolve");
|
|
6
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
7
|
+
const ambiguous_1 = require("../../errors/ambiguous");
|
|
8
|
+
const resolve_1 = tslib_1.__importDefault(require("../addons/resolve"));
|
|
12
9
|
const bastion_1 = require("./bastion");
|
|
13
10
|
const config_vars_1 = require("./config-vars");
|
|
14
11
|
const pgDebug = (0, debug_1.default)('pg');
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const { error } = matchesOrError;
|
|
26
|
-
// happy path where the resolver matches just one
|
|
27
|
-
if (matches && matches.length === 1) {
|
|
28
|
-
return matches[0];
|
|
12
|
+
class DatabaseResolver {
|
|
13
|
+
constructor(heroku, getConfigFn = config_vars_1.getConfig, fetchBastionConfigFn = bastion_1.fetchBastionConfig) {
|
|
14
|
+
this.heroku = heroku;
|
|
15
|
+
this.getConfigFn = getConfigFn;
|
|
16
|
+
this.fetchBastionConfigFn = fetchBastionConfigFn;
|
|
17
|
+
this.attachmentHeaders = {
|
|
18
|
+
Accept: 'application/vnd.heroku+json; version=3.sdk',
|
|
19
|
+
'Accept-Inclusion': 'addon:plan,config_vars',
|
|
20
|
+
};
|
|
21
|
+
this.addonAttachmentResolver = new resolve_1.default(this.heroku);
|
|
29
22
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Resolves a database attachment based on the provided database identifier
|
|
25
|
+
* (attachment name, id, or config var name) and namespace (credential).
|
|
26
|
+
*
|
|
27
|
+
* @param appId - The ID of the app to get the attachment for
|
|
28
|
+
* @param attachmentId - The database identifier (defaults to 'DATABASE_URL')
|
|
29
|
+
* @param namespace - Optional namespace/credential for the attachment
|
|
30
|
+
* @returns Promise resolving to the database attachment
|
|
31
|
+
* @throws {Error} When no databases exist or when database identifier is unknown
|
|
32
|
+
* @throws {AmbiguousError} When multiple matching attachments are found
|
|
33
|
+
*/
|
|
34
|
+
async getAttachment(appId, attachmentId = 'DATABASE_URL', namespace) {
|
|
35
|
+
// handle the case where the user passes an app::database format, overriding any app name option values.
|
|
36
|
+
const appConfigMatch = /^(.+?)::(.+)/.exec(attachmentId);
|
|
33
37
|
if (appConfigMatch) {
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
appId = appConfigMatch[1];
|
|
39
|
+
attachmentId = appConfigMatch[2];
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
const { error, matches } = await this.matchesHelper(appId, attachmentId, namespace);
|
|
42
|
+
// happy path where the resolver matches just one
|
|
43
|
+
if (matches && matches.length === 1) {
|
|
44
|
+
return matches[0];
|
|
39
45
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
// handle the case where the resolver didn't find any matches for the given database and show valid options.
|
|
47
|
+
if (!matches) {
|
|
48
|
+
const attachments = await this.allPostgresAttachments(appId);
|
|
49
|
+
if (attachments.length === 0) {
|
|
50
|
+
throw new Error(`${color_1.color.app(appId)} has no databases`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const validOptions = attachments.map(attachment => (0, config_vars_1.getConfigVarName)(attachment.config_vars));
|
|
54
|
+
throw new Error(`Unknown database: ${attachmentId}. Valid options are: ${validOptions.join(', ')}`);
|
|
55
|
+
}
|
|
46
56
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
// handle the case where the resolver found multiple matches for the given database.
|
|
58
|
+
const first = matches[0];
|
|
59
|
+
// return the first attachment when all ambiguous attachments are equivalent (basically target the same database)
|
|
60
|
+
if (matches.every(match => first.addon.id === match.addon.id && first.app.id === match.app.id)) {
|
|
61
|
+
const config = await this.getConfigFn(this.heroku, first.app.name);
|
|
62
|
+
if (matches.every(match => config[(0, config_vars_1.getConfigVarName)(first.config_vars)] === config[(0, config_vars_1.getConfigVarName)(match.config_vars)])) {
|
|
63
|
+
return first;
|
|
64
|
+
}
|
|
51
65
|
}
|
|
66
|
+
throw error;
|
|
52
67
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Returns the connection details for a database attachment resolved through the identifiers passed as
|
|
70
|
+
* arguments: appId, attachmentId and namespace (credential).
|
|
71
|
+
*
|
|
72
|
+
* @param appId - The ID of the app containing the database
|
|
73
|
+
* @param attachmentId - Optional database identifier (defaults to 'DATABASE_URL')
|
|
74
|
+
* @param namespace - Optional namespace/credential for the attachment
|
|
75
|
+
* @returns Promise resolving to connection details with attachment information
|
|
76
|
+
*/
|
|
77
|
+
async getDatabase(appId, attachmentId, namespace) {
|
|
78
|
+
const attached = await this.getAttachment(appId, attachmentId, namespace);
|
|
79
|
+
const config = await this.getConfigFn(this.heroku, attached.app.name);
|
|
80
|
+
const database = this.getConnectionDetails(attached, config);
|
|
81
|
+
// Add bastion configuration if it's a non-shielded Private Space add-on and we still don't have the config.
|
|
82
|
+
if ((0, bastion_1.bastionKeyPlan)(attached) && !database.bastionKey) {
|
|
83
|
+
const bastionConfig = await this.fetchBastionConfigFn(this.heroku, attached.addon);
|
|
84
|
+
Object.assign(database, bastionConfig);
|
|
60
85
|
}
|
|
86
|
+
return database;
|
|
61
87
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// If bastion creds exist, graft it into the payload
|
|
82
|
-
const bastion = (0, bastion_1.getBastion)(configVars, baseName);
|
|
83
|
-
if (bastion) {
|
|
84
|
-
Object.assign(payload, bastion);
|
|
88
|
+
/**
|
|
89
|
+
* Parses a PostgreSQL connection string (or a local database name) into a ConnectionDetails object.
|
|
90
|
+
*
|
|
91
|
+
* @param connStringOrDbName - PostgreSQL connection string or local database name
|
|
92
|
+
* @returns Connection details object with parsed connection information
|
|
93
|
+
*/
|
|
94
|
+
parsePostgresConnectionString(connStringOrDbName) {
|
|
95
|
+
const dbPath = /:\/\//.test(connStringOrDbName) ? connStringOrDbName : `postgres:///${connStringOrDbName}`;
|
|
96
|
+
const url = new URL(dbPath);
|
|
97
|
+
const { hostname, password, pathname, port, username } = url;
|
|
98
|
+
return {
|
|
99
|
+
database: pathname.slice(1), // remove the leading slash from the pathname
|
|
100
|
+
host: hostname,
|
|
101
|
+
password,
|
|
102
|
+
pathname,
|
|
103
|
+
port: port || process.env.PGPORT || (hostname && '5432'),
|
|
104
|
+
url: dbPath,
|
|
105
|
+
user: username,
|
|
106
|
+
};
|
|
85
107
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Fetches all Heroku PostgreSQL add-on attachments for a given app.
|
|
110
|
+
*
|
|
111
|
+
* This is used internally by the `getAttachment` function to get all valid Heroku PostgreSQL add-on attachments
|
|
112
|
+
* to generate a list of possible valid attachments when the user passes a database name that doesn't match any
|
|
113
|
+
* attachments.
|
|
114
|
+
*
|
|
115
|
+
* @param appId - The ID of the app to get the attachments for
|
|
116
|
+
* @returns Promise resolving to array of PostgreSQL add-on attachments
|
|
117
|
+
*/
|
|
118
|
+
async allPostgresAttachments(appId) {
|
|
119
|
+
const addonService = process.env.HEROKU_POSTGRESQL_ADDON_NAME || 'heroku-postgresql';
|
|
120
|
+
const { body: attachments } = await this.heroku.get(`/apps/${appId}/addon-attachments`, {
|
|
121
|
+
headers: this.attachmentHeaders,
|
|
122
|
+
});
|
|
123
|
+
return attachments.filter(a => a.addon.plan.name.split(':', 2)[0] === addonService);
|
|
101
124
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Returns the connection details for a database attachment according to the app config vars.
|
|
127
|
+
*
|
|
128
|
+
* @param attachment - The attachment to get the connection details for
|
|
129
|
+
* @param config - The record of app config vars with their values
|
|
130
|
+
* @returns Connection details with attachment information
|
|
131
|
+
*/
|
|
132
|
+
getConnectionDetails(attachment, config = {}) {
|
|
133
|
+
const connStringVar = (0, config_vars_1.getConfigVarNameFromAttachment)(attachment, config);
|
|
134
|
+
// build the default payload for non-bastion dbs
|
|
135
|
+
pgDebug(`Using "${connStringVar}" to connect to your database…`);
|
|
136
|
+
const conn = this.parsePostgresConnectionString(config[connStringVar]);
|
|
137
|
+
const payload = {
|
|
138
|
+
attachment,
|
|
139
|
+
database: conn.database,
|
|
140
|
+
host: conn.host,
|
|
141
|
+
password: conn.password,
|
|
142
|
+
pathname: conn.pathname,
|
|
143
|
+
port: conn.port,
|
|
144
|
+
url: conn.url,
|
|
145
|
+
user: conn.user,
|
|
146
|
+
};
|
|
147
|
+
// This handles injection of bastion creds into the payload if they exist as config vars (Shield-tier databases).
|
|
148
|
+
const baseName = connStringVar.slice(0, -4);
|
|
149
|
+
const bastion = (0, bastion_1.getBastionConfig)(config, baseName);
|
|
150
|
+
if (bastion) {
|
|
151
|
+
Object.assign(payload, bastion);
|
|
152
|
+
}
|
|
153
|
+
return payload;
|
|
112
154
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Helper function that attempts to find a single addon attachment matching the given database identifier
|
|
157
|
+
* (attachment name, id, or config var name).
|
|
158
|
+
*
|
|
159
|
+
* This is used internally by the `getAttachment` function to handle the lookup of addon attachments.
|
|
160
|
+
* It returns either a single match, multiple matches (for ambiguous cases), or null if no matches are found.
|
|
161
|
+
*
|
|
162
|
+
* The AddonAttachmentResolver uses the Platform API add-on attachment resolver endpoint to get the attachment.
|
|
163
|
+
*
|
|
164
|
+
* @param appId - The ID of the app to search for attachments
|
|
165
|
+
* @param attachmentId - The database identifier to match
|
|
166
|
+
* @param namespace - Optional namespace/credential filter
|
|
167
|
+
* @returns Promise resolving to either a single match, multiple matches with error, or no matches with error
|
|
168
|
+
*/
|
|
169
|
+
async matchesHelper(appId, attachmentId, namespace) {
|
|
170
|
+
(0, debug_1.default)(`fetching ${attachmentId} on ${appId}`);
|
|
171
|
+
const addonService = process.env.HEROKU_POSTGRESQL_ADDON_NAME || 'heroku-postgresql';
|
|
172
|
+
(0, debug_1.default)(`addon service: ${addonService}`);
|
|
173
|
+
try {
|
|
174
|
+
const attached = await this.addonAttachmentResolver.resolve(appId, attachmentId, { addonService, namespace });
|
|
175
|
+
return { error: undefined, matches: [attached] };
|
|
116
176
|
}
|
|
117
|
-
|
|
118
|
-
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof ambiguous_1.AmbiguousError && error.body.id === 'multiple_matches' && error.matches) {
|
|
179
|
+
return { error, matches: error.matches };
|
|
180
|
+
}
|
|
181
|
+
// This handles the case where the resolver returns a 404 error when making the request, but not the case
|
|
182
|
+
// where it returns a NotFound error because there were no matches after filtering by namespace.
|
|
183
|
+
if (error instanceof api_client_1.HerokuAPIError
|
|
184
|
+
&& error.http.statusCode === 404
|
|
185
|
+
&& error.body && error.body.id === 'not_found') {
|
|
186
|
+
return { error, matches: null };
|
|
187
|
+
}
|
|
188
|
+
// This re-throws a NotFound error or any other HerokuAPIError except for the 404 case which is handled above.
|
|
189
|
+
throw error;
|
|
119
190
|
}
|
|
120
|
-
throw error;
|
|
121
191
|
}
|
|
122
192
|
}
|
|
123
|
-
|
|
124
|
-
const dbPath = /:\/\//.test(db) ? db : `postgres:///${db}`;
|
|
125
|
-
const url = new URL(dbPath);
|
|
126
|
-
const { hostname, password, pathname, port, username } = url;
|
|
127
|
-
return {
|
|
128
|
-
database: pathname.charAt(0) === '/' ? pathname.slice(1) : pathname,
|
|
129
|
-
host: hostname,
|
|
130
|
-
password,
|
|
131
|
-
pathname,
|
|
132
|
-
port: port || node_process_1.env.PGPORT || (hostname && '5432'),
|
|
133
|
-
url: dbPath,
|
|
134
|
-
user: username,
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
exports.parsePostgresConnectionString = parsePostgresConnectionString;
|
|
193
|
+
exports.default = DatabaseResolver;
|
package/dist/utils/pg/psql.d.ts
CHANGED
|
@@ -1,28 +1,116 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { EventEmitter } from 'node:events';
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
3
2
|
import { Server } from 'node:net';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
childProcessOptions: SpawnOptionsWithStdioTuple<"ignore", "pipe", "inherit">;
|
|
10
|
-
dbEnv: NodeJS.ProcessEnv;
|
|
11
|
-
psqlArgs: string[];
|
|
12
|
-
};
|
|
13
|
-
export declare function execPSQL({ childProcessOptions, dbEnv, psqlArgs }: {
|
|
14
|
-
childProcessOptions: SpawnOptions;
|
|
15
|
-
dbEnv: NodeJS.ProcessEnv;
|
|
16
|
-
psqlArgs: string[];
|
|
17
|
-
}): ChildProcess;
|
|
18
|
-
export declare function runWithTunnel(db: ConnectionDetails, tunnelConfig: TunnelConfig, options: Parameters<typeof execPSQL>[0]): Promise<string>;
|
|
19
|
-
export declare const trapAndForwardSignalsToChildProcess: (childProcess: ChildProcess) => () => void;
|
|
20
|
-
export declare function waitForPSQLExit(psql: EventEmitter): Promise<void>;
|
|
3
|
+
import { ConnectionDetailsWithAttachment, TunnelConfig } from '../../types/pg/tunnel';
|
|
4
|
+
import { getPsqlConfigs, sshTunnel } from './bastion';
|
|
5
|
+
/**
|
|
6
|
+
* A small wrapper around tunnel-ssh so that other code doesn't have to worry about whether there is or is not a tunnel.
|
|
7
|
+
*/
|
|
21
8
|
export declare class Tunnel {
|
|
22
9
|
private readonly bastionTunnel;
|
|
23
10
|
private readonly events;
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Creates a new Tunnel instance.
|
|
13
|
+
*
|
|
14
|
+
* @param bastionTunnel - The SSH tunnel server or void if no tunnel is needed
|
|
15
|
+
*/
|
|
16
|
+
constructor(bastionTunnel: Server | void);
|
|
17
|
+
/**
|
|
18
|
+
* Creates and connects to an SSH tunnel.
|
|
19
|
+
*
|
|
20
|
+
* @param connectionDetails - The database connection details with attachment information
|
|
21
|
+
* @param tunnelConfig - The tunnel configuration object
|
|
22
|
+
* @param tunnelFn - The function to create the SSH tunnel (default: sshTunnel)
|
|
23
|
+
* @returns Promise that resolves to a new Tunnel instance
|
|
24
|
+
*/
|
|
25
|
+
static connect(connectionDetails: ConnectionDetailsWithAttachment, tunnelConfig: TunnelConfig, tunnelFn: typeof sshTunnel): Promise<Tunnel>;
|
|
26
|
+
/**
|
|
27
|
+
* Closes the tunnel if it exists, or emits a fake close event if no tunnel is needed.
|
|
28
|
+
*
|
|
29
|
+
* @returns void
|
|
30
|
+
*/
|
|
26
31
|
close(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Waits for the tunnel to close.
|
|
34
|
+
*
|
|
35
|
+
* @returns Promise that resolves when the tunnel closes
|
|
36
|
+
* @throws Error if the secure tunnel fails
|
|
37
|
+
*/
|
|
27
38
|
waitForClose(): Promise<void>;
|
|
28
39
|
}
|
|
40
|
+
export default class PsqlService {
|
|
41
|
+
private readonly connectionDetails;
|
|
42
|
+
private readonly getPsqlConfigsFn;
|
|
43
|
+
private readonly spawnFn;
|
|
44
|
+
private readonly tunnelFn;
|
|
45
|
+
constructor(connectionDetails: ConnectionDetailsWithAttachment, getPsqlConfigsFn?: typeof getPsqlConfigs, spawnFn?: typeof spawn, tunnelFn?: typeof sshTunnel);
|
|
46
|
+
/**
|
|
47
|
+
* Executes a PostgreSQL query using the instance's database connection details.
|
|
48
|
+
* It uses the `getPsqlConfigs` function to get the configuration for the database and the tunnel,
|
|
49
|
+
* and then calls the `runWithTunnel` function to execute the query.
|
|
50
|
+
*
|
|
51
|
+
* @param query - The SQL query to execute
|
|
52
|
+
* @param psqlCmdArgs - Additional command-line arguments for psql (default: [])
|
|
53
|
+
* @returns Promise that resolves to the query result as a string
|
|
54
|
+
*/
|
|
55
|
+
execQuery(query: string, psqlCmdArgs?: string[]): Promise<string>;
|
|
56
|
+
/**
|
|
57
|
+
* Consumes a stream and returns its content as a string.
|
|
58
|
+
*
|
|
59
|
+
* @param inputStream - The input stream to consume
|
|
60
|
+
* @returns Promise that resolves to the stream content as a string
|
|
61
|
+
*/
|
|
62
|
+
private consumeStream;
|
|
63
|
+
/**
|
|
64
|
+
* Kills a child process if it hasn't been killed already.
|
|
65
|
+
* According to node.js docs, sending a kill to a process won't cause an error
|
|
66
|
+
* but could have unintended consequences if the PID gets reassigned.
|
|
67
|
+
* To be on the safe side, check if the process was already killed before sending the signal.
|
|
68
|
+
*
|
|
69
|
+
* @param childProcess - The child process to kill
|
|
70
|
+
* @param signal - The signal to send to the process
|
|
71
|
+
* @returns void
|
|
72
|
+
*/
|
|
73
|
+
private kill;
|
|
74
|
+
/**
|
|
75
|
+
* Creates the options for spawning the psql process.
|
|
76
|
+
*
|
|
77
|
+
* @param query - The SQL query to execute
|
|
78
|
+
* @param dbEnv - The database environment variables
|
|
79
|
+
* @param psqlCmdArgs - Additional command-line arguments for psql (default: [])
|
|
80
|
+
* @returns Object containing child process options, database environment, and psql arguments
|
|
81
|
+
*/
|
|
82
|
+
private psqlQueryOptions;
|
|
83
|
+
/**
|
|
84
|
+
* Runs the psql command with tunnel support.
|
|
85
|
+
*
|
|
86
|
+
* @param tunnelConfig - The tunnel configuration object
|
|
87
|
+
* @param options - The options for spawning the psql process
|
|
88
|
+
* @returns Promise that resolves to the query result as a string
|
|
89
|
+
*/
|
|
90
|
+
private runWithTunnel;
|
|
91
|
+
/**
|
|
92
|
+
* Spawns the psql process with the given options.
|
|
93
|
+
*
|
|
94
|
+
* @param options - The options for spawning the psql process
|
|
95
|
+
* @returns The spawned child process
|
|
96
|
+
*/
|
|
97
|
+
private spawnPsql;
|
|
98
|
+
/**
|
|
99
|
+
* Traps SIGINT so that ctrl+c can be used by psql without killing the parent node process.
|
|
100
|
+
* You can use ctrl+c in psql to kill running queries while keeping the psql process open.
|
|
101
|
+
* This code is to stop the parent node process (heroku CLI) from exiting.
|
|
102
|
+
* If the parent Heroku CLI node process exits, then psql will exit as it is a child process.
|
|
103
|
+
*
|
|
104
|
+
* @param childProcess - The child process to forward signals to
|
|
105
|
+
* @returns Function to restore the original signal handlers
|
|
106
|
+
*/
|
|
107
|
+
private trapAndForwardSignalsToChildProcess;
|
|
108
|
+
/**
|
|
109
|
+
* Waits for the psql process to exit and handles any errors.
|
|
110
|
+
*
|
|
111
|
+
* @param psql - The psql process event emitter
|
|
112
|
+
* @throws Error if psql exits with non-zero code or if psql command is not found
|
|
113
|
+
* @returns Promise that resolves to void when psql exits
|
|
114
|
+
*/
|
|
115
|
+
private waitForPSQLExit;
|
|
116
|
+
}
|