heroku 11.7.1 → 11.8.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/commands/accounts/add.js +9 -1
- package/dist/commands/auth/logout.js +3 -3
- package/dist/commands/authorizations/index.js +1 -1
- package/dist/commands/clients/index.js +1 -1
- package/dist/commands/data/pg/migrate.d.ts +2 -2
- package/dist/commands/data/pg/migrate.js +137 -89
- package/dist/commands/domains/index.js +1 -1
- package/dist/commands/releases/index.js +1 -1
- package/dist/lib/accounts/accounts.d.ts +2 -2
- package/dist/lib/accounts/accounts.js +10 -4
- package/dist/lib/data/types.d.ts +4 -0
- package/dist/lib/data/types.js +5 -0
- package/dist/lib/utils/table-utils.js +1 -1
- package/npm-shrinkwrap.json +160 -34
- package/oclif.manifest.json +1823 -1824
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
## [11.8.0-beta.0](https://github.com/heroku/cli/compare/v11.7.1...v11.8.0-beta.0) (2026-07-01)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* 'data:pg:migrate' interactive method selection (W-22585064) ([#3791](https://github.com/heroku/cli/issues/3791)) ([914fc06](https://github.com/heroku/cli/commit/914fc06e1dd76cd687ffa3b0973dc0356814a4ae))
|
|
13
|
+
* skip caching token in keychain mode for accounts cache ([#3792](https://github.com/heroku/cli/issues/3792)) ([5d7d95b](https://github.com/heroku/cli/commit/5d7d95bafbcf088c9818690df769d3b8451be369))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* remove keychain accounts cache on logout ([#3797](https://github.com/heroku/cli/issues/3797)) ([70cbd44](https://github.com/heroku/cli/commit/70cbd44fcac9b7d3cd21d8d62b713e63e2030924))
|
|
19
|
+
* update CLI to work with changes to heroku-cli-util table function ([#3796](https://github.com/heroku/cli/issues/3796)) ([a523792](https://github.com/heroku/cli/commit/a523792944869e4ea9fe217c080619e1819b43ae))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Miscellaneous Chores
|
|
23
|
+
|
|
24
|
+
* **deps:** bump @heroku/mcp-server to 1.2.5 and update undici ([#3794](https://github.com/heroku/cli/issues/3794)) ([f5dab06](https://github.com/heroku/cli/commit/f5dab067f0b760a4efd9deee9760b6075a7d913e))
|
|
25
|
+
* **deps:** bump @heroku/mcp-server to 1.2.5, update undici ([09ca009](https://github.com/heroku/cli/commit/09ca0096ae32a27809d688e5e783466fe27d2401))
|
|
26
|
+
|
|
7
27
|
## [11.7.1](https://github.com/heroku/cli/compare/v11.7.0...v11.7.1) (2026-06-24)
|
|
8
28
|
|
|
9
29
|
|
|
@@ -22,6 +22,14 @@ export default class Add extends Command {
|
|
|
22
22
|
ux.error(`Account ${email} already has an alias of ${existingAlias.name}.`);
|
|
23
23
|
}
|
|
24
24
|
const token = this.heroku.auth;
|
|
25
|
-
AccountsModule.
|
|
25
|
+
const config = AccountsModule.getStorageConfig();
|
|
26
|
+
if (config.credentialStore) {
|
|
27
|
+
// Keychain-mode: don't save token to cache file
|
|
28
|
+
AccountsModule.add(name, email);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// Netrc-mode: save token to cache file
|
|
32
|
+
AccountsModule.add(name, email, token);
|
|
33
|
+
}
|
|
26
34
|
}
|
|
27
35
|
}
|
|
@@ -10,7 +10,7 @@ export default class Logout extends Command {
|
|
|
10
10
|
async run() {
|
|
11
11
|
await this.parse(Logout);
|
|
12
12
|
ux.action.start('Logging out');
|
|
13
|
-
const
|
|
13
|
+
const cachedAccount = await AccountsModule.current(this.heroku);
|
|
14
14
|
await this.heroku.logout();
|
|
15
15
|
const git = new Git();
|
|
16
16
|
try {
|
|
@@ -20,8 +20,8 @@ export default class Logout extends Command {
|
|
|
20
20
|
catch {
|
|
21
21
|
// ignore
|
|
22
22
|
}
|
|
23
|
-
if (
|
|
24
|
-
await AccountsModule.remove(
|
|
23
|
+
if (cachedAccount) {
|
|
24
|
+
await AccountsModule.remove(cachedAccount);
|
|
25
25
|
}
|
|
26
26
|
await this.config.runHook('recache', { type: 'logout' });
|
|
27
27
|
ux.action.stop();
|
|
@@ -23,7 +23,7 @@ export default class AuthorizationsIndex extends Command {
|
|
|
23
23
|
Description: { get: (v) => color.name(v.description) },
|
|
24
24
|
ID: { get: (v) => v.id },
|
|
25
25
|
Scope: { get: (v) => v.scope.join(',') },
|
|
26
|
-
}, { sort:
|
|
26
|
+
}, { sort: 'Description' });
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
@@ -6,15 +6,15 @@ export default class DataPgMigrate extends BaseCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static flags: {
|
|
8
8
|
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
method: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
method: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
};
|
|
12
12
|
private advancedDatabases;
|
|
13
13
|
private appName;
|
|
14
14
|
private classicDatabases;
|
|
15
15
|
private extendedLevelsInfo;
|
|
16
|
-
private migrationMethod;
|
|
17
16
|
private migrationTargets;
|
|
17
|
+
private selectedMigrationMethod?;
|
|
18
18
|
createAddon(...args: Parameters<typeof createAddon>): Promise<Heroku.AddOn>;
|
|
19
19
|
prompt<T extends inquirer.Answers>(...args: Parameters<typeof inquirer.prompt<T>>): Promise<T>;
|
|
20
20
|
run(): Promise<void>;
|
|
@@ -7,7 +7,7 @@ import tsheredoc from 'tsheredoc';
|
|
|
7
7
|
import createAddon from '../../../lib/addons/create-addon.js';
|
|
8
8
|
import BaseCommand from '../../../lib/data/base-command.js';
|
|
9
9
|
import PoolConfig from '../../../lib/data/pool-config.js';
|
|
10
|
-
import { DatabaseStatus, MigrationStatus, } from '../../../lib/data/types.js';
|
|
10
|
+
import { DatabaseStatus, MigrationMethod, MigrationStatus, } from '../../../lib/data/types.js';
|
|
11
11
|
import { fetchLevelsAndPricing } from '../../../lib/data/utils.js';
|
|
12
12
|
import { getAttachmentNamesByAddon } from '../../../lib/pg/util.js';
|
|
13
13
|
const heredoc = tsheredoc.default;
|
|
@@ -17,7 +17,6 @@ export default class DataPgMigrate extends BaseCommand {
|
|
|
17
17
|
static flags = {
|
|
18
18
|
app: Flags.app({ required: true }),
|
|
19
19
|
method: Flags.string({
|
|
20
|
-
default: 'snapshot',
|
|
21
20
|
hidden: true,
|
|
22
21
|
options: ['snapshot', 'streaming'],
|
|
23
22
|
}),
|
|
@@ -27,8 +26,8 @@ export default class DataPgMigrate extends BaseCommand {
|
|
|
27
26
|
appName;
|
|
28
27
|
classicDatabases = [];
|
|
29
28
|
extendedLevelsInfo;
|
|
30
|
-
migrationMethod = 'full-load';
|
|
31
29
|
migrationTargets = [];
|
|
30
|
+
selectedMigrationMethod;
|
|
32
31
|
async createAddon(...args) {
|
|
33
32
|
return createAddon(...args);
|
|
34
33
|
}
|
|
@@ -39,7 +38,10 @@ export default class DataPgMigrate extends BaseCommand {
|
|
|
39
38
|
const { flags } = await this.parse(DataPgMigrate);
|
|
40
39
|
const { app, method } = flags;
|
|
41
40
|
this.appName = app;
|
|
42
|
-
|
|
41
|
+
// If --method flag is provided, convert and store
|
|
42
|
+
if (method !== undefined) {
|
|
43
|
+
this.selectedMigrationMethod = method === 'streaming' ? MigrationMethod.CDC : MigrationMethod.FULL_LOAD;
|
|
44
|
+
}
|
|
43
45
|
ux.stdout(heredoc `
|
|
44
46
|
|
|
45
47
|
Migrate existing classic Heroku Postgres databases to Advanced databases
|
|
@@ -148,28 +150,121 @@ export default class DataPgMigrate extends BaseCommand {
|
|
|
148
150
|
let sourceDatabaseId;
|
|
149
151
|
let targetDatabaseId;
|
|
150
152
|
let targetDatabaseName;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
case '__confirm_migration': {
|
|
154
|
-
ux.stdout(color.info(heredoc `
|
|
153
|
+
const confirmMigration = async () => {
|
|
154
|
+
ux.stdout(color.info(heredoc `
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Preparing the migration deletes all the data on the destination database ${color.datastore(targetDatabaseName)}.
|
|
156
|
+
By continuing, we prepare the necessary steps for the migration.
|
|
157
|
+
Your source database is available while we prepare the migration.
|
|
158
|
+
You'll receive an email when the preparation is complete or if there's an error.
|
|
159
|
+
You have 24 hours to begin the migration after the preparation is complete.
|
|
160
|
+
Preparing the migration deletes all the data on the destination database ${color.datastore(targetDatabaseName)}.
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
`));
|
|
163
|
+
const { action } = await this.prompt({
|
|
164
|
+
choices: [
|
|
165
|
+
{ name: 'Confirm', value: '__confirm' },
|
|
166
|
+
{ name: 'Go back', value: '__go_back' },
|
|
167
|
+
],
|
|
168
|
+
message: 'Confirm migration configuration:',
|
|
169
|
+
name: 'action',
|
|
170
|
+
type: 'list',
|
|
171
|
+
});
|
|
172
|
+
return action;
|
|
173
|
+
};
|
|
174
|
+
const selectMethod = async () => {
|
|
175
|
+
ux.stdout(color.info(heredoc `
|
|
176
|
+
|
|
177
|
+
Migration methods:
|
|
178
|
+
· Snapshot: Copies the data from the source database to the destination database. Requires downtime on the source database depending on the size. Best for smaller databases or when a maintenance window is acceptable.
|
|
179
|
+
· Streaming: Replicates changes from the source database to the destination database continuously until you start the migration. Requires minimal downtime. Best for larger databases or when you need near-zero downtime.
|
|
180
|
+
|
|
181
|
+
`));
|
|
182
|
+
const { method } = await this.prompt({
|
|
183
|
+
choices: [
|
|
184
|
+
{ name: 'Snapshot', value: '__snapshot' },
|
|
185
|
+
{ name: 'Streaming', value: '__streaming' },
|
|
186
|
+
new Separator(),
|
|
187
|
+
{ name: 'Go back', value: '__go_back' },
|
|
188
|
+
],
|
|
189
|
+
message: 'Select the migration method:',
|
|
190
|
+
name: 'method',
|
|
191
|
+
type: 'list',
|
|
192
|
+
});
|
|
193
|
+
return method;
|
|
194
|
+
};
|
|
195
|
+
const selectSource = async () => {
|
|
196
|
+
const choices = [];
|
|
197
|
+
for (const database of this.classicDatabases) {
|
|
198
|
+
const name = `${color.datastore(database.name)} as ${database.attachment_names.map(name => color.attachment(name)).join(', ')}`;
|
|
199
|
+
if (this.migrationTargets.some(migration => migration.source_id === database.id && this.isActiveMigration(migration))) {
|
|
200
|
+
choices.push({
|
|
201
|
+
disabled: 'already a source database for an active migration',
|
|
202
|
+
name: color.gray(name),
|
|
203
|
+
value: database.id,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
choices.push({
|
|
208
|
+
name,
|
|
209
|
+
value: database.id,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
choices.push(new Separator(), { name: 'Go back', value: '__go_back' });
|
|
214
|
+
sourceDatabaseId = (await this.prompt({
|
|
215
|
+
choices,
|
|
216
|
+
message: 'Select the source database:',
|
|
217
|
+
name: 'database',
|
|
218
|
+
type: 'list',
|
|
219
|
+
})).database;
|
|
220
|
+
return sourceDatabaseId;
|
|
221
|
+
};
|
|
222
|
+
const selectTarget = async () => {
|
|
223
|
+
const choices = [];
|
|
224
|
+
for (const database of this.advancedDatabases) {
|
|
225
|
+
const name = `${color.datastore(database.name)} as ${database.attachment_names.map(name => color.attachment(name)).join(', ')}`;
|
|
226
|
+
if (this.migrationTargets.some(migration => migration.target_id === database.id && this.isActiveMigration(migration))) {
|
|
227
|
+
choices.push({
|
|
228
|
+
disabled: 'already a destination database for an active migration',
|
|
229
|
+
name: color.gray(name),
|
|
230
|
+
value: database.id,
|
|
172
231
|
});
|
|
232
|
+
}
|
|
233
|
+
else if (database.info?.status === DatabaseStatus.AVAILABLE) {
|
|
234
|
+
choices.push({
|
|
235
|
+
name,
|
|
236
|
+
value: database.id,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
choices.push({
|
|
241
|
+
disabled: 'database isn\'t available',
|
|
242
|
+
name: color.gray(name),
|
|
243
|
+
value: database.id,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (this.advancedDatabases.length === 0) {
|
|
248
|
+
choices.push({
|
|
249
|
+
disabled: true,
|
|
250
|
+
name: color.gray(`No Heroku Postgres Advanced databases available for migration on ${color.app(this.appName)}`),
|
|
251
|
+
value: '__no_advanced_databases',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
choices.push(new Separator(), { name: 'Create a new Advanced database', value: '__create_database' }, { name: 'Go back', value: '__go_back' });
|
|
255
|
+
targetDatabaseId = (await this.prompt({
|
|
256
|
+
choices,
|
|
257
|
+
message: 'Select the destination database:',
|
|
258
|
+
name: 'database',
|
|
259
|
+
type: 'list',
|
|
260
|
+
})).database;
|
|
261
|
+
targetDatabaseName = this.advancedDatabases.find(db => db.id === targetDatabaseId)?.name;
|
|
262
|
+
return targetDatabaseId;
|
|
263
|
+
};
|
|
264
|
+
while (currentStep !== '__exit') {
|
|
265
|
+
switch (currentStep) {
|
|
266
|
+
case '__confirm_migration': {
|
|
267
|
+
const action = await confirmMigration();
|
|
173
268
|
if (action === '__go_back') {
|
|
174
269
|
currentStep = '__select_target';
|
|
175
270
|
}
|
|
@@ -177,81 +272,34 @@ export default class DataPgMigrate extends BaseCommand {
|
|
|
177
272
|
ux.stdout('');
|
|
178
273
|
ux.action.start('Configuring migration');
|
|
179
274
|
await this.dataApi.post(`/data/postgres/v1/${targetDatabaseId}/migrations`, {
|
|
180
|
-
body: {
|
|
275
|
+
body: {
|
|
276
|
+
method: this.selectedMigrationMethod,
|
|
277
|
+
source_id: sourceDatabaseId,
|
|
278
|
+
},
|
|
181
279
|
});
|
|
182
280
|
ux.action.stop();
|
|
183
281
|
currentStep = '__exit';
|
|
184
282
|
}
|
|
185
283
|
break;
|
|
186
284
|
}
|
|
187
|
-
case '
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (this.migrationTargets.some(migration => migration.source_id === database.id && this.isActiveMigration(migration))) {
|
|
192
|
-
choices.push({
|
|
193
|
-
disabled: 'already a source database for an active migration',
|
|
194
|
-
name: color.gray(name),
|
|
195
|
-
value: database.id,
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
choices.push({
|
|
200
|
-
name,
|
|
201
|
-
value: database.id,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
285
|
+
case '__select_method': {
|
|
286
|
+
const method = await selectMethod();
|
|
287
|
+
if (method === '__go_back') {
|
|
288
|
+
currentStep = '__select_target';
|
|
204
289
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
290
|
+
else {
|
|
291
|
+
this.selectedMigrationMethod = method === '__snapshot' ? MigrationMethod.FULL_LOAD : MigrationMethod.CDC;
|
|
292
|
+
currentStep = '__confirm_migration';
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case '__select_source': {
|
|
297
|
+
const sourceDatabaseId = await selectSource();
|
|
212
298
|
currentStep = sourceDatabaseId === '__go_back' ? '__exit' : '__select_target';
|
|
213
299
|
break;
|
|
214
300
|
}
|
|
215
301
|
case '__select_target': {
|
|
216
|
-
|
|
217
|
-
for (const database of this.advancedDatabases) {
|
|
218
|
-
const name = `${color.datastore(database.name)} as ${database.attachment_names.map(name => color.attachment(name)).join(', ')}`;
|
|
219
|
-
if (this.migrationTargets.some(migration => migration.target_id === database.id && this.isActiveMigration(migration))) {
|
|
220
|
-
choices.push({
|
|
221
|
-
disabled: 'already a destination database for an active migration',
|
|
222
|
-
name: color.gray(name),
|
|
223
|
-
value: database.id,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
else if (database.info?.status === DatabaseStatus.AVAILABLE) {
|
|
227
|
-
choices.push({
|
|
228
|
-
name,
|
|
229
|
-
value: database.id,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
choices.push({
|
|
234
|
-
disabled: 'database isn\'t available',
|
|
235
|
-
name: color.gray(name),
|
|
236
|
-
value: database.id,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (this.advancedDatabases.length === 0) {
|
|
241
|
-
choices.push({
|
|
242
|
-
disabled: true,
|
|
243
|
-
name: color.gray(`No Heroku Postgres Advanced databases available for migration on ${color.app(this.appName)}`),
|
|
244
|
-
value: '__no_advanced_databases',
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
choices.push(new Separator(), { name: 'Create a new Advanced database', value: '__create_database' }, { name: 'Go back', value: '__go_back' });
|
|
248
|
-
targetDatabaseId = (await this.prompt({
|
|
249
|
-
choices,
|
|
250
|
-
message: 'Select the destination database:',
|
|
251
|
-
name: 'database',
|
|
252
|
-
type: 'list',
|
|
253
|
-
})).database;
|
|
254
|
-
targetDatabaseName = this.advancedDatabases.find(db => db.id === targetDatabaseId)?.name;
|
|
302
|
+
await selectTarget();
|
|
255
303
|
if (targetDatabaseId === '__go_back') {
|
|
256
304
|
currentStep = '__select_source';
|
|
257
305
|
}
|
|
@@ -260,14 +308,14 @@ export default class DataPgMigrate extends BaseCommand {
|
|
|
260
308
|
if (addon) {
|
|
261
309
|
targetDatabaseId = addon.id;
|
|
262
310
|
targetDatabaseName = addon.name;
|
|
263
|
-
currentStep = '__confirm_migration';
|
|
311
|
+
currentStep = this.selectedMigrationMethod === undefined ? '__select_method' : '__confirm_migration';
|
|
264
312
|
}
|
|
265
313
|
else {
|
|
266
314
|
currentStep = '__select_target';
|
|
267
315
|
}
|
|
268
316
|
}
|
|
269
317
|
else {
|
|
270
|
-
currentStep = '__confirm_migration';
|
|
318
|
+
currentStep = this.selectedMigrationMethod === undefined ? '__select_method' : '__confirm_migration';
|
|
271
319
|
}
|
|
272
320
|
break;
|
|
273
321
|
}
|
|
@@ -185,7 +185,7 @@ www.example.com CNAME www.example.herokudns.com`];
|
|
|
185
185
|
else {
|
|
186
186
|
hux.table(customDomains, tableConfig, {
|
|
187
187
|
...huxTableNoWrapOptions(flags['no-wrap']),
|
|
188
|
-
sort: flags.sort
|
|
188
|
+
sort: flags.sort,
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
}
|
|
@@ -147,7 +147,7 @@ export default class Index extends Command {
|
|
|
147
147
|
}
|
|
148
148
|
hux.styledHeader(header);
|
|
149
149
|
const sortedReleases = releases.sort((a, b) => (b.version ?? 0) - (a.version ?? 0));
|
|
150
|
-
hux.table(sortedReleases, columns);
|
|
150
|
+
hux.table(sortedReleases, columns, { extended });
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -4,7 +4,7 @@ export interface AccountEntry {
|
|
|
4
4
|
username: string;
|
|
5
5
|
}
|
|
6
6
|
export interface IAccountsWrapper {
|
|
7
|
-
add(name: string, username: string, password
|
|
7
|
+
add(name: string, username: string, password?: string): void;
|
|
8
8
|
current(heroku: APIClient): Promise<null | string>;
|
|
9
9
|
currentNetrc(): Promise<null | string>;
|
|
10
10
|
getStorageConfig(): ReturnType<typeof getStorageConfig>;
|
|
@@ -15,7 +15,7 @@ export interface IAccountsWrapper {
|
|
|
15
15
|
}
|
|
16
16
|
export declare class AccountsWrapper implements IAccountsWrapper {
|
|
17
17
|
private netrc;
|
|
18
|
-
add(name: string, username: string, password
|
|
18
|
+
add(name: string, username: string, password?: string): void;
|
|
19
19
|
current(heroku: APIClient): Promise<null | string>;
|
|
20
20
|
currentNetrc(): Promise<null | string>;
|
|
21
21
|
getStorageConfig(): import("@heroku-cli/command").StorageConfig;
|
|
@@ -8,8 +8,10 @@ export class AccountsWrapper {
|
|
|
8
8
|
netrc;
|
|
9
9
|
add(name, username, password) {
|
|
10
10
|
fs.mkdirSync(this.accountsDir(), { recursive: true });
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const content = password === undefined
|
|
12
|
+
? { username }
|
|
13
|
+
: { username, password }; // eslint-disable-line perfectionist/sort-objects
|
|
14
|
+
this.writeAccountFile(name, content);
|
|
13
15
|
}
|
|
14
16
|
async current(heroku) {
|
|
15
17
|
const config = this.getStorageConfig();
|
|
@@ -58,11 +60,15 @@ export class AccountsWrapper {
|
|
|
58
60
|
const config = this.getStorageConfig();
|
|
59
61
|
if (account.name) {
|
|
60
62
|
if (config.credentialStore) {
|
|
63
|
+
// Keychain mode: only update login state, skip netrc
|
|
61
64
|
const email = this.getAliasEmail(account.name);
|
|
62
|
-
if (email) {
|
|
63
|
-
|
|
65
|
+
if (!email) {
|
|
66
|
+
throw new Error(`We can't find the alias file for ${account.name}.`);
|
|
64
67
|
}
|
|
68
|
+
await this.writeLoginState(dataDir, email);
|
|
69
|
+
return;
|
|
65
70
|
}
|
|
71
|
+
// Netrc mode: update both login state and netrc files
|
|
66
72
|
const netrcInstance = await this.initNetrc();
|
|
67
73
|
let current;
|
|
68
74
|
try {
|
package/dist/lib/data/types.d.ts
CHANGED
|
@@ -30,6 +30,10 @@ export declare enum MigrationStatus {
|
|
|
30
30
|
READY = "ready",
|
|
31
31
|
UNKNOWN = "unknown"
|
|
32
32
|
}
|
|
33
|
+
export declare enum MigrationMethod {
|
|
34
|
+
CDC = "cdc",
|
|
35
|
+
FULL_LOAD = "full-load"
|
|
36
|
+
}
|
|
33
37
|
export declare enum PoolStatus {
|
|
34
38
|
AVAILABLE = "available",
|
|
35
39
|
MODIFYING = "modifying",
|
package/dist/lib/data/types.js
CHANGED
|
@@ -34,6 +34,11 @@ export var MigrationStatus;
|
|
|
34
34
|
MigrationStatus["READY"] = "ready";
|
|
35
35
|
MigrationStatus["UNKNOWN"] = "unknown";
|
|
36
36
|
})(MigrationStatus || (MigrationStatus = {}));
|
|
37
|
+
export var MigrationMethod;
|
|
38
|
+
(function (MigrationMethod) {
|
|
39
|
+
MigrationMethod["CDC"] = "cdc";
|
|
40
|
+
MigrationMethod["FULL_LOAD"] = "full-load";
|
|
41
|
+
})(MigrationMethod || (MigrationMethod = {}));
|
|
37
42
|
export var PoolStatus;
|
|
38
43
|
(function (PoolStatus) {
|
|
39
44
|
PoolStatus["AVAILABLE"] = "available";
|
|
@@ -26,7 +26,7 @@ export const constructSortFilterTableOptions = (flags, tableColumns) => {
|
|
|
26
26
|
if (!columnNames.has(lowerCaseSort)) {
|
|
27
27
|
throw new Error(`Invalid sort key: ${sort}`);
|
|
28
28
|
}
|
|
29
|
-
tableOptions.sort =
|
|
29
|
+
tableOptions.sort = lowerCaseSort;
|
|
30
30
|
}
|
|
31
31
|
return tableOptions;
|
|
32
32
|
};
|