heroku 9.3.0-alpha.3 → 9.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/lib/commands/local/run.js +4 -4
- package/lib/commands/pg/promote.js +32 -32
- package/lib/commands/pg/settings/auto-explain/log-format.d.ts +12 -0
- package/lib/commands/pg/settings/auto-explain/log-format.js +26 -0
- package/lib/commands/pg/settings/explain-data-connector-details.d.ts +16 -0
- package/lib/commands/pg/settings/explain-data-connector-details.js +33 -0
- package/lib/commands/pg/settings/log-min-error-statement.d.ts +12 -0
- package/lib/commands/pg/settings/log-min-error-statement.js +27 -0
- package/lib/commands/run/index.js +3 -2
- package/lib/lib/pg/types.d.ts +1 -1
- package/lib/lib/run/helpers.d.ts +0 -1
- package/lib/lib/run/helpers.js +1 -13
- package/oclif.manifest.json +1009 -858
- package/package.json +5 -5
|
@@ -4,14 +4,14 @@ const completions_1 = require("@heroku-cli/command/lib/completions");
|
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
5
|
const color_1 = require("@heroku-cli/color");
|
|
6
6
|
const fork_foreman_1 = require("../../lib/local/fork-foreman");
|
|
7
|
-
const helpers_1 = require("../../lib/run/helpers");
|
|
8
7
|
const fs = require("fs");
|
|
9
8
|
class Run extends core_1.Command {
|
|
10
9
|
async run() {
|
|
11
10
|
const execArgv = ['run'];
|
|
12
11
|
const { argv, flags } = await this.parse(Run);
|
|
13
|
-
const
|
|
14
|
-
|
|
12
|
+
const maybeOptionsIndex = process.argv.indexOf('--');
|
|
13
|
+
const commandArgs = (maybeOptionsIndex === -1 ? argv : process.argv.slice(maybeOptionsIndex + 1));
|
|
14
|
+
if (commandArgs.length === 0) {
|
|
15
15
|
const errorMessage = 'Usage: heroku local:run [COMMAND]\nMust specify command to run';
|
|
16
16
|
this.error(errorMessage, { exit: -1 });
|
|
17
17
|
}
|
|
@@ -24,7 +24,7 @@ class Run extends core_1.Command {
|
|
|
24
24
|
if (flags.port)
|
|
25
25
|
execArgv.push('--port', flags.port);
|
|
26
26
|
execArgv.push('--'); // disable node-foreman flag parsing
|
|
27
|
-
execArgv.push(...
|
|
27
|
+
execArgv.push(...commandArgs); // eslint-disable-line unicorn/no-array-push-push
|
|
28
28
|
await (0, fork_foreman_1.fork)(execArgv);
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -17,36 +17,36 @@ class Promote extends command_1.Command {
|
|
|
17
17
|
core_1.ux.action.start(`Ensuring an alternate alias for existing ${color_1.default.green('DATABASE_URL')}`);
|
|
18
18
|
const { body: attachments } = await this.heroku.get(`/apps/${app}/addon-attachments`);
|
|
19
19
|
const current = attachments.find(a => a.name === 'DATABASE');
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
if (current) {
|
|
21
|
+
// eslint-disable-next-line eqeqeq
|
|
22
|
+
if (((_a = current.addon) === null || _a === void 0 ? void 0 : _a.name) === attachment.addon.name && current.namespace == attachment.namespace) {
|
|
23
|
+
if (attachment.namespace) {
|
|
24
|
+
core_1.ux.error(`${color_1.default.cyan(attachment.name)} is already promoted on ${color_1.default.app(app)}`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
core_1.ux.error(`${color_1.default.addon(attachment.addon.name)} is already promoted on ${color_1.default.app(app)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const existing = attachments.filter(a => { var _a, _b; return ((_a = a.addon) === null || _a === void 0 ? void 0 : _a.id) === ((_b = current.addon) === null || _b === void 0 ? void 0 : _b.id) && a.namespace === current.namespace; })
|
|
31
|
+
.find(a => a.name !== 'DATABASE');
|
|
32
|
+
if (existing) {
|
|
33
|
+
core_1.ux.action.stop(color_1.default.green(existing.name + '_URL'));
|
|
26
34
|
}
|
|
27
35
|
else {
|
|
28
|
-
|
|
36
|
+
// The current add-on occupying the DATABASE attachment has no
|
|
37
|
+
// other attachments. In order to promote this database without
|
|
38
|
+
// error, we can create a secondary attachment, just-in-time.
|
|
39
|
+
const { body: backup } = await this.heroku.post('/addon-attachments', {
|
|
40
|
+
body: {
|
|
41
|
+
app: { name: app },
|
|
42
|
+
addon: { name: (_b = current.addon) === null || _b === void 0 ? void 0 : _b.name },
|
|
43
|
+
namespace: current.namespace,
|
|
44
|
+
confirm: app,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
core_1.ux.action.stop(color_1.default.green(backup.name + '_URL'));
|
|
29
48
|
}
|
|
30
49
|
}
|
|
31
|
-
const existing = attachments.filter(a => { var _a, _b; return ((_a = a.addon) === null || _a === void 0 ? void 0 : _a.id) === ((_b = current.addon) === null || _b === void 0 ? void 0 : _b.id) && a.namespace === current.namespace; })
|
|
32
|
-
.find(a => a.name !== 'DATABASE');
|
|
33
|
-
if (existing) {
|
|
34
|
-
core_1.ux.action.stop(color_1.default.green(existing.name + '_URL'));
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
// The current add-on occupying the DATABASE attachment has no
|
|
38
|
-
// other attachments. In order to promote this database without
|
|
39
|
-
// error, we can create a secondary attachment, just-in-time.
|
|
40
|
-
const { body: backup } = await this.heroku.post('/addon-attachments', {
|
|
41
|
-
body: {
|
|
42
|
-
app: { name: app },
|
|
43
|
-
addon: { name: (_b = current.addon) === null || _b === void 0 ? void 0 : _b.name },
|
|
44
|
-
namespace: current.namespace,
|
|
45
|
-
confirm: app,
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
core_1.ux.action.stop(color_1.default.green(backup.name + '_URL'));
|
|
49
|
-
}
|
|
50
50
|
if (!force) {
|
|
51
51
|
const { body: status } = await this.heroku.get(`/client/v11/databases/${attachment.addon.id}/wait_status`, {
|
|
52
52
|
hostname: (0, host_1.default)(),
|
|
@@ -54,9 +54,9 @@ class Promote extends command_1.Command {
|
|
|
54
54
|
if (status['waiting?']) {
|
|
55
55
|
core_1.ux.error((0, tsheredoc_1.default)(`
|
|
56
56
|
Database cannot be promoted while in state: ${status.message}
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
Promoting this database can lead to application errors and outage. Please run ${color_1.default.cmd('heroku pg:wait')} to wait for database to become available.
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
To ignore this error, you can pass the --force flag to promote the database and risk application issues.
|
|
61
61
|
`));
|
|
62
62
|
}
|
|
@@ -79,7 +79,7 @@ class Promote extends command_1.Command {
|
|
|
79
79
|
},
|
|
80
80
|
});
|
|
81
81
|
core_1.ux.action.stop();
|
|
82
|
-
const currentPooler = attachments.find(a => { var _a, _b; return a.namespace === 'connection-pooling:default' && ((_a = a.addon) === null || _a === void 0 ? void 0 : _a.id) === ((_b = current.addon) === null || _b === void 0 ? void 0 : _b.id) && a.name === 'DATABASE_CONNECTION_POOL'; });
|
|
82
|
+
const currentPooler = attachments.find(a => { var _a, _b; return a.namespace === 'connection-pooling:default' && ((_a = a.addon) === null || _a === void 0 ? void 0 : _a.id) === ((_b = current === null || current === void 0 ? void 0 : current.addon) === null || _b === void 0 ? void 0 : _b.id) && a.name === 'DATABASE_CONNECTION_POOL'; });
|
|
83
83
|
if (currentPooler) {
|
|
84
84
|
core_1.ux.action.start('Reattaching pooler to new leader');
|
|
85
85
|
await this.heroku.post('/addon-attachments', {
|
|
@@ -100,9 +100,9 @@ class Promote extends command_1.Command {
|
|
|
100
100
|
const unfollowLeaderCmd = `heroku pg:unfollow ${attachment.addon.name}`;
|
|
101
101
|
core_1.ux.warn((0, tsheredoc_1.default)(`
|
|
102
102
|
Your database has been promoted but it is currently a follower database in read-only mode.
|
|
103
|
-
|
|
103
|
+
|
|
104
104
|
Promoting a database with ${color_1.default.cmd('heroku pg:promote')} doesn't automatically unfollow its leader.
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
Use ${color_1.default.cmd(unfollowLeaderCmd)} to stop this follower from replicating from its leader (${color_1.default.yellow(promotedDatabaseDetails.leader)}) and convert it into a writable database.
|
|
107
107
|
`));
|
|
108
108
|
}
|
|
@@ -141,7 +141,7 @@ class Promote extends command_1.Command {
|
|
|
141
141
|
msg += 'without an attached DATABASE_URL.';
|
|
142
142
|
}
|
|
143
143
|
else {
|
|
144
|
-
msg += `with ${(_c = current.addon) === null || _c === void 0 ? void 0 : _c.name} attached as DATABASE_URL.`;
|
|
144
|
+
msg += `with ${(_c = current === null || current === void 0 ? void 0 : current.addon) === null || _c === void 0 ? void 0 : _c.name} attached as DATABASE_URL.`;
|
|
145
145
|
}
|
|
146
146
|
msg += ' Check your release phase logs for failure causes.';
|
|
147
147
|
core_1.ux.action.stop(msg);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PGSettingsCommand } from '../../../../lib/pg/setter';
|
|
2
|
+
import { Setting, SettingKey } from '../../../../lib/pg/types';
|
|
3
|
+
export default class LogFormat extends PGSettingsCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static args: {
|
|
6
|
+
database: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
value: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
protected settingKey: SettingKey;
|
|
10
|
+
protected explain(setting: Setting<string>): string;
|
|
11
|
+
protected convertValue(val: string): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const setter_1 = require("../../../../lib/pg/setter");
|
|
5
|
+
const tsheredoc_1 = require("tsheredoc");
|
|
6
|
+
class LogFormat extends setter_1.PGSettingsCommand {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.settingKey = 'auto_explain.log_format';
|
|
10
|
+
}
|
|
11
|
+
explain(setting) {
|
|
12
|
+
return `Auto explain log output will log in ${setting.value} format.`;
|
|
13
|
+
}
|
|
14
|
+
convertValue(val) {
|
|
15
|
+
return val;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.default = LogFormat;
|
|
19
|
+
LogFormat.description = (0, tsheredoc_1.default)(`
|
|
20
|
+
selects the EXPLAIN output format to be used
|
|
21
|
+
The allowed values are text, xml, json, and yaml. The default is text.
|
|
22
|
+
`);
|
|
23
|
+
LogFormat.args = {
|
|
24
|
+
database: core_1.Args.string(),
|
|
25
|
+
value: core_1.Args.string({ options: ['text', 'json', 'yaml', 'xml'] }),
|
|
26
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type BooleanAsString, PGSettingsCommand } from '../../../lib/pg/setter';
|
|
2
|
+
import type { Setting, SettingKey } from '../../../lib/pg/types';
|
|
3
|
+
export default class ExplainDataConnectorDetails extends PGSettingsCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static flags: {
|
|
6
|
+
app: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
7
|
+
remote: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
static args: {
|
|
10
|
+
database: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
11
|
+
value: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
protected settingKey: SettingKey;
|
|
14
|
+
protected convertValue(val: BooleanAsString): boolean;
|
|
15
|
+
protected explain(setting: Setting<boolean>): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const command_1 = require("@heroku-cli/command");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const tsheredoc_1 = require("tsheredoc");
|
|
6
|
+
const setter_1 = require("../../../lib/pg/setter");
|
|
7
|
+
class ExplainDataConnectorDetails extends setter_1.PGSettingsCommand {
|
|
8
|
+
constructor() {
|
|
9
|
+
super(...arguments);
|
|
10
|
+
this.settingKey = 'explain_data_connector_details';
|
|
11
|
+
}
|
|
12
|
+
convertValue(val) {
|
|
13
|
+
return (0, setter_1.booleanConverter)(val);
|
|
14
|
+
}
|
|
15
|
+
explain(setting) {
|
|
16
|
+
if (setting === null || setting === void 0 ? void 0 : setting.value) {
|
|
17
|
+
return 'Data replication slot details will be logged.';
|
|
18
|
+
}
|
|
19
|
+
return 'Data replication slot details will no longer be logged.';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.default = ExplainDataConnectorDetails;
|
|
23
|
+
ExplainDataConnectorDetails.description = (0, tsheredoc_1.default)(`
|
|
24
|
+
displays stats on replication slots on your database, the default value is "off"
|
|
25
|
+
`);
|
|
26
|
+
ExplainDataConnectorDetails.flags = {
|
|
27
|
+
app: command_1.flags.app({ required: true }),
|
|
28
|
+
remote: command_1.flags.remote(),
|
|
29
|
+
};
|
|
30
|
+
ExplainDataConnectorDetails.args = {
|
|
31
|
+
database: core_1.Args.string(),
|
|
32
|
+
value: core_1.Args.string(),
|
|
33
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PGSettingsCommand } from '../../../lib/pg/setter';
|
|
2
|
+
import type { Setting, SettingKey } from '../../../lib/pg/types';
|
|
3
|
+
export default class LogMinErrorStatement extends PGSettingsCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static args: {
|
|
6
|
+
database: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
value: import("@oclif/core/lib/interfaces/parser").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
protected settingKey: SettingKey;
|
|
10
|
+
protected convertValue(val: string): string;
|
|
11
|
+
protected explain(setting: Setting<string>): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const tsheredoc_1 = require("tsheredoc");
|
|
5
|
+
const setter_1 = require("../../../lib/pg/setter");
|
|
6
|
+
class LogMinErrorStatement extends setter_1.PGSettingsCommand {
|
|
7
|
+
constructor() {
|
|
8
|
+
super(...arguments);
|
|
9
|
+
this.settingKey = 'log_min_error_statement';
|
|
10
|
+
}
|
|
11
|
+
convertValue(val) {
|
|
12
|
+
return val;
|
|
13
|
+
}
|
|
14
|
+
explain(setting) {
|
|
15
|
+
return setting.values[setting.value];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.default = LogMinErrorStatement;
|
|
19
|
+
LogMinErrorStatement.description = (0, tsheredoc_1.default)(`
|
|
20
|
+
log-min-error-statement controls the logging of SQL statements that cause an error at a specified severity level.
|
|
21
|
+
This setting is useful to prevent logging SQL queries that might contain sensitive information.
|
|
22
|
+
Use this setting to prevent logging SQL queries that contain sensitive information. Default is "error".
|
|
23
|
+
`);
|
|
24
|
+
LogMinErrorStatement.args = {
|
|
25
|
+
database: core_1.Args.string(),
|
|
26
|
+
value: core_1.Args.string({ options: ['error', 'log', 'fatal', 'panic'] }),
|
|
27
|
+
};
|
|
@@ -10,13 +10,14 @@ const debug = (0, debug_1.default)('heroku:run');
|
|
|
10
10
|
class Run extends command_1.Command {
|
|
11
11
|
async run() {
|
|
12
12
|
const { argv, flags } = await this.parse(Run);
|
|
13
|
-
const
|
|
13
|
+
const maybeOptionsIndex = process.argv.indexOf('--');
|
|
14
|
+
const command = (0, helpers_1.buildCommand)((maybeOptionsIndex === -1 ? argv : process.argv.slice(maybeOptionsIndex + 1)));
|
|
14
15
|
const opts = {
|
|
15
16
|
'exit-code': flags['exit-code'],
|
|
16
17
|
'no-tty': flags['no-tty'],
|
|
17
18
|
app: flags.app,
|
|
18
19
|
attach: true,
|
|
19
|
-
command
|
|
20
|
+
command,
|
|
20
21
|
env: flags.env,
|
|
21
22
|
heroku: this.heroku,
|
|
22
23
|
listen: flags.listen,
|
package/lib/lib/pg/types.d.ts
CHANGED
|
@@ -155,7 +155,7 @@ export declare type PgDatabaseConfig = {
|
|
|
155
155
|
value: boolean;
|
|
156
156
|
};
|
|
157
157
|
};
|
|
158
|
-
export declare type SettingKey = 'log_lock_waits' | 'log_connections' | 'log_min_duration_statement' | 'log_statement' | 'track_functions' | 'pgbouncer_max_client_conn' | 'pg_bouncer_max_db_conns' | 'pg_bouncer_default_pool_size' | 'auto_explain' | 'auto_explain.log_min_duration' | 'auto_explain.log_analyze' | 'auto_explain.log_triggers' | 'auto_explain.log_buffers' | 'auto_explain.log_verbose' | 'auto_explain.log_nested_statements';
|
|
158
|
+
export declare type SettingKey = 'log_lock_waits' | 'log_connections' | 'log_min_duration_statement' | 'log_min_error_statement' | 'log_statement' | 'track_functions' | 'pgbouncer_max_client_conn' | 'pg_bouncer_max_db_conns' | 'pg_bouncer_default_pool_size' | 'explain_data_connector_details' | 'auto_explain' | 'auto_explain.log_min_duration' | 'auto_explain.log_analyze' | 'auto_explain.log_triggers' | 'auto_explain.log_buffers' | 'auto_explain.log_verbose' | 'auto_explain.log_nested_statements' | 'auto_explain.log_format';
|
|
159
159
|
export declare type Setting<T> = {
|
|
160
160
|
value: T;
|
|
161
161
|
values: Record<string, string>;
|
package/lib/lib/run/helpers.d.ts
CHANGED
package/lib/lib/run/helpers.js
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildEnvFromFlag = exports.buildCommand =
|
|
3
|
+
exports.buildEnvFromFlag = exports.buildCommand = void 0;
|
|
4
4
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
5
5
|
const core_1 = require("@oclif/core");
|
|
6
|
-
// this function exists because oclif sorts argv
|
|
7
|
-
function revertSortedArgs(processArgs, argv) {
|
|
8
|
-
const originalInputOrder = [];
|
|
9
|
-
// this reorders the arguments in the order the user inputted
|
|
10
|
-
for (const processArg of processArgs) {
|
|
11
|
-
if (argv.includes(processArg)) {
|
|
12
|
-
originalInputOrder.push(processArg);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return originalInputOrder;
|
|
16
|
-
}
|
|
17
|
-
exports.revertSortedArgs = revertSortedArgs;
|
|
18
6
|
function buildCommand(args) {
|
|
19
7
|
if (args.length === 1) {
|
|
20
8
|
// do not add quotes around arguments if there is only one argument
|