heroku 11.5.0-alpha.6 → 11.5.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 CHANGED
@@ -4,47 +4,32 @@ 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.5.0-alpha.6](https://github.com/heroku/cli/compare/v11.4.0...v11.5.0-alpha.6) (2026-06-04)
7
+ ## [11.5.0-beta.0](https://github.com/heroku/cli/compare/v11.4.0...v11.5.0-beta.0) (2026-06-11)
8
8
 
9
9
 
10
10
  ### Features
11
11
 
12
12
  * add hidden --method flag to data:pg:migrate ([#3743](https://github.com/heroku/cli/issues/3743)) ([665b189](https://github.com/heroku/cli/commit/665b1896d50f399cc20cfe39500b603665bc814a))
13
- * implement heroku git:credentials as a git credential helper ([#3683](https://github.com/heroku/cli/issues/3683)) ([781e99e](https://github.com/heroku/cli/commit/781e99e1b9c56339b1245e30087aa9b312c10e69))
14
- * remove cached netrc account on logout ([#3710](https://github.com/heroku/cli/issues/3710)) ([b07137b](https://github.com/heroku/cli/commit/b07137bed5282a1618ae7e2b1e7f603eeb9a70be))
15
- * update accounts and accounts:current commands to use the credential manager ([#3689](https://github.com/heroku/cli/issues/3689)) ([e753d06](https://github.com/heroku/cli/commit/e753d06407fe8ebf6f87b25131d6198d78e824e8))
16
- * update accounts:add to use the credential manager ([#3699](https://github.com/heroku/cli/issues/3699)) ([49e3938](https://github.com/heroku/cli/commit/49e3938f44ec52451870cf24c692d440a7a69701))
17
- * update accounts:remove to work with credential manager ([#3701](https://github.com/heroku/cli/issues/3701)) ([89d7b14](https://github.com/heroku/cli/commit/89d7b148df950429f902691c8348afc5bba07454))
18
- * update accounts:set to work with keychain managers ([#3696](https://github.com/heroku/cli/issues/3696)) ([1f896aa](https://github.com/heroku/cli/commit/1f896aab4b23fb312d444420a06ed9d839ec8cf4))
19
13
 
20
14
 
21
15
  ### Bug Fixes
22
16
 
23
17
  * 'run:inside' args ordering (W-22693654) ([#3727](https://github.com/heroku/cli/issues/3727)) ([355113e](https://github.com/heroku/cli/commit/355113e8253b8547ea86be4ef540287ea263af80))
24
18
  * add missing warning to 'data:pg:migrate' (W-22544849) ([#3716](https://github.com/heroku/cli/issues/3716)) ([400fc6e](https://github.com/heroku/cli/commit/400fc6e26eb7b7fdd634e0e4465fc5c19762303a))
25
- * fix whoami and update heroku-cli-command and heroku-cli-util ([#3719](https://github.com/heroku/cli/issues/3719)) ([7db768f](https://github.com/heroku/cli/commit/7db768f21f0dbe04d8d422f93919bc4f6f9156c5))
19
+ * call _heroku.pg_stat_statements_reset() on Essential and Advanced plans ([#3751](https://github.com/heroku/cli/issues/3751)) ([5414e36](https://github.com/heroku/cli/commit/5414e3621ae732124bafcc9fdd1eabb56fc81c9d))
26
20
  * inherit secrets in reusable workflow ([#3711](https://github.com/heroku/cli/issues/3711)) ([5dd58af](https://github.com/heroku/cli/commit/5dd58af6c14f55ab85fe8584598718ebee03ae4b))
27
21
  * pass empty string to rl.write in repl finally block (W-22295448) ([#3721](https://github.com/heroku/cli/issues/3721)) ([ebdf082](https://github.com/heroku/cli/commit/ebdf082ed317cb74a59945c60dc274615b735b81))
28
- * restore git configuration on logout ([#3697](https://github.com/heroku/cli/issues/3697)) ([5479fa5](https://github.com/heroku/cli/commit/5479fa53c75b152aece718999deee50b94b1f5ce))
29
22
 
30
23
 
31
24
  ### Miscellaneous Chores
32
25
 
33
26
  * add CLAUDE.md and Copilot instructions pointing to AGENTS.md ([#3724](https://github.com/heroku/cli/issues/3724)) ([8abed2f](https://github.com/heroku/cli/commit/8abed2faad739a1897734fe414cc2e1b70ea245a))
34
27
  * consolidate release for trusted publishing ([#3744](https://github.com/heroku/cli/issues/3744)) ([0e81dd8](https://github.com/heroku/cli/commit/0e81dd8429cecba42ea31c64cbbaac9db2bd2f87))
35
- * fix linting errors ([7df4bf0](https://github.com/heroku/cli/commit/7df4bf076497d70c4c0ab3ae672598853f761247))
36
- * merge in main to feature branch ([#3747](https://github.com/heroku/cli/issues/3747)) ([8c100fd](https://github.com/heroku/cli/commit/8c100fd4667a88e414a4c9379ff896792ef07498))
37
- * merge main ([#3737](https://github.com/heroku/cli/issues/3737)) ([cb751e6](https://github.com/heroku/cli/commit/cb751e6a23d11baf44cd6111f7a1741e8477c06d))
28
+ * inline npm publish workflow ([#3750](https://github.com/heroku/cli/issues/3750)) ([5618ee2](https://github.com/heroku/cli/commit/5618ee2e161f71594305de28965d672cf4eba8f3))
29
+ * pass PR title via env in pr-title-check workflow ([#3763](https://github.com/heroku/cli/issues/3763)) ([96310c3](https://github.com/heroku/cli/commit/96310c372b3431aa0c6b4c9313d035b40c7cd0bd))
38
30
  * remove unused workflow file ([#3712](https://github.com/heroku/cli/issues/3712)) ([8860aa4](https://github.com/heroku/cli/commit/8860aa412261f05fae3df19438cfd50a5f4ae67f))
39
- * update CLI analytics to use heroku credential manager ([#3685](https://github.com/heroku/cli/issues/3685)) ([5f86e4c](https://github.com/heroku/cli/commit/5f86e4cf9a6b8ca74d6ef0650abbae1f940fab7d))
40
31
  * upload arm64 win installer to Stampy unsigned bucket (W-22733412) ([#3734](https://github.com/heroku/cli/issues/3734)) ([e171ad5](https://github.com/heroku/cli/commit/e171ad5f670bb4d7aae80d625ae5bf65d77cd1ff))
41
32
 
42
-
43
- ### Tests
44
-
45
- * fix accounts, apps, auth, buildpacks, container, git, and ps-exec tests ([391b6a6](https://github.com/heroku/cli/commit/391b6a6cff1cb754697db642f1c11464a8de6de3))
46
- * update analytics tests to use the credential manager ([#3688](https://github.com/heroku/cli/issues/3688)) ([4fde394](https://github.com/heroku/cli/commit/4fde394ac2351504a727829fd86fd647136afbc8))
47
-
48
33
  ## [11.4.0](https://github.com/heroku/cli/compare/v11.3.0...v11.4.0) (2026-05-13)
49
34
 
50
35
 
@@ -11,13 +11,19 @@ export default class Add extends Command {
11
11
  async run() {
12
12
  const { args } = await this.parse(Add);
13
13
  const { name } = args;
14
- const accounts = await AccountsModule.list();
15
- if (accounts.some(account => account.name === name)) {
14
+ const logInMessage = 'You must be logged in to run this command.';
15
+ if (AccountsModule.list().some(a => a.name === name)) {
16
16
  ux.error(`${name} already exists`);
17
17
  }
18
18
  const { body: account } = await this.heroku.get('/account');
19
- const email = account.email;
20
- const token = this.heroku.auth;
19
+ const email = account.email || '';
20
+ const token = this.heroku.auth || '';
21
+ if (token === '') {
22
+ ux.error(logInMessage);
23
+ }
24
+ if (email === '') {
25
+ ux.error(logInMessage);
26
+ }
21
27
  AccountsModule.add(name, email, token);
22
28
  }
23
29
  }
@@ -8,7 +8,7 @@ export default class Current extends Command {
8
8
  static example = `${color.command('heroku accounts:current')}`;
9
9
  static promptFlagActive = false;
10
10
  async run() {
11
- const accountName = await AccountsModule.current(this.heroku);
11
+ const accountName = await AccountsModule.current();
12
12
  if (accountName) {
13
13
  hux.styledHeader(`Current account is ${color.user(accountName)}`);
14
14
  }
@@ -8,17 +8,16 @@ export default class AccountsIndex extends Command {
8
8
  static example = `${color.command('heroku accounts')}`;
9
9
  static promptFlagActive = false;
10
10
  async run() {
11
- const accounts = await accountsModule.list();
11
+ const accounts = accountsModule.list();
12
12
  if (accounts.length === 0) {
13
13
  ux.error('You don\'t have any accounts in your cache.');
14
14
  }
15
- const current = await accountsModule.current(this.heroku);
16
15
  for (const account of accounts) {
17
- if (account.name === current || account.username === current) {
18
- ux.stdout(`* ${account.name ?? account.username}`);
16
+ if (account.name === await accountsModule.current()) {
17
+ ux.stdout(`* ${account.name}`);
19
18
  }
20
19
  else {
21
- ux.stdout(` ${account.name ?? account.username}`);
20
+ ux.stdout(` ${account.name}`);
22
21
  }
23
22
  }
24
23
  }
@@ -11,12 +11,12 @@ export default class Remove extends Command {
11
11
  async run() {
12
12
  const { args } = await this.parse(Remove);
13
13
  const { name } = args;
14
- if (!(await AccountsModule.list()).some(account => account.name === name || account.username === name)) {
14
+ if (!AccountsModule.list().some(a => a.name === name)) {
15
15
  ux.error(`${name} doesn't exist in your accounts cache.`);
16
16
  }
17
- if (await AccountsModule.current(this.heroku) === name) {
18
- ux.error(`${name} is the current account. To log out, run ${color.command('heroku logout')}.`);
17
+ if (await AccountsModule.current() === name) {
18
+ ux.error(`${name} is the current account.`);
19
19
  }
20
- await AccountsModule.remove(name);
20
+ AccountsModule.remove(name);
21
21
  }
22
22
  }
@@ -4,20 +4,16 @@ import { Args, ux } from '@oclif/core';
4
4
  import AccountsModule from '../../lib/accounts/accounts.js';
5
5
  export default class Set extends Command {
6
6
  static args = {
7
- name: Args.string({ description: 'name or username of account to set', required: true }),
7
+ name: Args.string({ description: 'name of account to set', required: true }),
8
8
  };
9
- static description = 'set the current Heroku account from your accounts cache or system keychain';
9
+ static description = 'set the current Heroku account from your cache';
10
10
  static example = `${color.command('heroku accounts:set my-account')}`;
11
11
  async run() {
12
12
  const { args } = await this.parse(Set);
13
13
  const { name } = args;
14
- const accounts = await AccountsModule.list();
15
- const netrcAccount = accounts.find(account => account.name === name);
16
- const keychainAccount = accounts.find(account => !account.name && account.username === name);
17
- if (!netrcAccount && !keychainAccount) {
18
- ux.error(`${name} does not exist in your accounts cache or system keychain.`);
14
+ if (!AccountsModule.list().some(a => a.name === name)) {
15
+ ux.error(`${name} does not exist in your accounts cache.`);
19
16
  }
20
- const account = netrcAccount ?? keychainAccount;
21
- await AccountsModule.set(account, this.config.dataDir);
17
+ AccountsModule.set(name);
22
18
  }
23
19
  }
@@ -69,7 +69,6 @@ async function configureGitRemote(context, app) {
69
69
  const remoteUrl = git.httpGitUrl(app.name || '');
70
70
  if (!context.flags['no-remote'] && git.inGitRepo()) {
71
71
  await git.createRemote(context.flags.remote || 'heroku', remoteUrl);
72
- await git.configureCredentialHelper();
73
72
  }
74
73
  return remoteUrl;
75
74
  }
@@ -1,6 +1,5 @@
1
1
  import { Command, flags } from '@heroku-cli/command';
2
2
  import * as color from '@heroku/heroku-cli-util/color';
3
- import Git from '../../lib/git/git.js';
4
3
  export default class Login extends Command {
5
4
  static aliases = ['login'];
6
5
  static description = 'login with your Heroku credentials';
@@ -18,14 +17,6 @@ export default class Login extends Command {
18
17
  await this.heroku.login({ browser: flags.browser, expiresIn: flags['expires-in'], method });
19
18
  const { body: account } = await this.heroku.get('/account', { retryAuth: false });
20
19
  this.log(`Logged in as ${color.user(account.email)}`);
21
- const git = new Git();
22
- try {
23
- await git.configureCredentialHelper();
24
- await git.eraseCredentials();
25
- }
26
- catch {
27
- // ignore
28
- }
29
20
  await this.config.runHook('recache', { type: 'login' });
30
21
  }
31
22
  }
@@ -1,28 +1,14 @@
1
1
  import { Command } from '@heroku-cli/command';
2
2
  import { ux } from '@oclif/core/ux';
3
- import AccountsModule from '../../lib/accounts/accounts.js';
4
- import Git from '../../lib/git/git.js';
5
3
  export default class Logout extends Command {
6
4
  static aliases = ['logout'];
7
5
  static baseFlags = Command.baseFlagsWithoutPrompt();
8
6
  static description = 'clears local login credentials and invalidates API session';
9
7
  static promptFlagActive = false;
10
8
  async run() {
11
- await this.parse(Logout);
9
+ this.parse(Logout);
12
10
  ux.action.start('Logging out');
13
- const cachedNetrcAccount = await AccountsModule.currentNetrc();
14
11
  await this.heroku.logout();
15
- const git = new Git();
16
- try {
17
- await git.removeCredentialHelper();
18
- await git.eraseCredentials();
19
- }
20
- catch {
21
- // ignore
22
- }
23
- if (cachedNetrcAccount) {
24
- AccountsModule.removeNetrc(cachedNetrcAccount);
25
- }
26
12
  await this.config.runHook('recache', { type: 'logout' });
27
13
  ux.action.stop();
28
14
  }
@@ -17,7 +17,7 @@ export default class AuthWhoami extends Command {
17
17
  this.log(account.email);
18
18
  }
19
19
  catch (error) {
20
- if (error.http.statusCode === 401)
20
+ if (error.statusCode === 401)
21
21
  this.notloggedin();
22
22
  throw error;
23
23
  }
@@ -27,7 +27,7 @@ export default class CiConfigUnset extends Command {
27
27
  vars[iAmStr] = null;
28
28
  }
29
29
  await ux.action.start(`Unsetting ${Object.keys(vars).join(', ')}`);
30
- await setPipelineConfigVars(this.heroku, pipeline.id, vars);
30
+ setPipelineConfigVars(this.heroku, pipeline.id, vars);
31
31
  ux.action.stop();
32
32
  }
33
33
  }
@@ -24,6 +24,5 @@ remote: Counting objects: 42, done.
24
24
  const directory = args.DIRECTORY || app.name;
25
25
  const remote = flags.remote || 'heroku';
26
26
  await git.spawn(['clone', '-o', remote, git.url(app.name), directory]);
27
- await git.configureCredentialHelper();
28
27
  }
29
28
  }
@@ -6,10 +6,4 @@ export declare class GitCredentials extends Command {
6
6
  static description: string;
7
7
  static hidden: boolean;
8
8
  run(): Promise<void>;
9
- /**
10
- * Reads git-credential input from stdin
11
- * Format: key=value pairs, one per line, terminated by blank line
12
- * Returns parsed object with protocol, host, username, and path
13
- */
14
- private readInput;
15
9
  }
@@ -1,6 +1,5 @@
1
- import { Command, vars } from '@heroku-cli/command';
1
+ import { Command } from '@heroku-cli/command';
2
2
  import { Args, ux } from '@oclif/core';
3
- import * as readline from 'node:readline';
4
3
  export class GitCredentials extends Command {
5
4
  static args = {
6
5
  command: Args.string({ description: 'command name of the git credentials', required: true }),
@@ -17,16 +16,10 @@ export class GitCredentials extends Command {
17
16
  break;
18
17
  }
19
18
  case 'get': {
20
- const { host, protocol } = await this.readInput();
21
- const { httpGitHost } = vars;
22
- if (protocol !== 'https' || host !== httpGitHost) {
23
- return;
24
- }
25
- if (!this.heroku.auth) {
19
+ if (!this.heroku.auth)
26
20
  throw new Error('not logged in');
27
- }
28
21
  ux.stdout(`protocol=https
29
- host=${httpGitHost}
22
+ host=git.heroku.com
30
23
  username=heroku
31
24
  password=${this.heroku.auth}`);
32
25
  break;
@@ -36,32 +29,4 @@ password=${this.heroku.auth}`);
36
29
  }
37
30
  }
38
31
  }
39
- /**
40
- * Reads git-credential input from stdin
41
- * Format: key=value pairs, one per line, terminated by blank line
42
- * Returns parsed object with protocol, host, username, and path
43
- */
44
- async readInput() {
45
- return new Promise(resolve => {
46
- const rl = readline.createInterface({
47
- input: process.stdin,
48
- terminal: false,
49
- });
50
- const input = {};
51
- rl.on('line', (line) => {
52
- if (!line.trim()) {
53
- rl.close();
54
- return;
55
- }
56
- const [key, value] = line.split('=', 2);
57
- if (key && value) {
58
- input[key] = value;
59
- }
60
- });
61
- rl.on('close', () => {
62
- process.stdin.pause();
63
- resolve(input);
64
- });
65
- });
66
- }
67
32
  }
@@ -34,6 +34,5 @@ ${color.command('heroku git:remote --remote heroku-staging -a example-staging')}
34
34
  : git.exec(['remote', 'add', remote, url].concat(argv)));
35
35
  const newRemote = await git.remoteUrl(remote);
36
36
  this.log(`set git remote ${color.cyan(remote)} to ${color.cyan(newRemote)}`);
37
- await git.configureCredentialHelper();
38
37
  }
39
38
  }
@@ -78,7 +78,10 @@ export default class Outliers extends Command {
78
78
  const version = await fetchVersion(db);
79
79
  await this.ensurePGStatStatement();
80
80
  if (reset) {
81
- await this.psqlService.execQuery('SELECT pg_stat_statements_reset();');
81
+ const resetFn = utils.pg.isEssentialDatabase(db.attachment.addon) || utils.pg.isAdvancedDatabase(db.attachment.addon)
82
+ ? '_heroku.pg_stat_statements_reset()'
83
+ : 'pg_stat_statements_reset()';
84
+ await this.psqlService.execQuery(`SELECT ${resetFn};`);
82
85
  return;
83
86
  }
84
87
  let limit = 10;
@@ -1,32 +1,18 @@
1
- import { APIClient, getStorageConfig } from '@heroku-cli/command';
2
- export interface AccountEntry {
3
- name?: string;
4
- username: string;
5
- }
1
+ import * as Heroku from '@heroku-cli/schema';
6
2
  export interface IAccountsWrapper {
7
3
  add(name: string, username: string, password: string): void;
8
- current(heroku: APIClient): Promise<null | string>;
9
- currentNetrc(): Promise<null | string>;
10
- getStorageConfig(): ReturnType<typeof getStorageConfig>;
11
- list(): Promise<AccountEntry[]>;
4
+ current(): Promise<null | string>;
5
+ list(): [] | Heroku.Account[];
12
6
  remove(name: string): void;
13
- removeNetrc(name: string): void;
14
- set(account: AccountEntry, dataDir: string): Promise<void>;
15
- writeLoginState(dataDir: string, name: string): Promise<void>;
7
+ set(name: string): Promise<void>;
16
8
  }
17
9
  export declare class AccountsWrapper implements IAccountsWrapper {
18
10
  private netrc;
19
11
  add(name: string, username: string, password: string): void;
20
- current(heroku: APIClient): Promise<null | string>;
21
- currentNetrc(): Promise<null | string>;
22
- getKeychainAccounts(): Promise<(null | string | undefined)[]>;
23
- getStorageConfig(): import("@heroku-cli/command").StorageConfig;
24
- list(): Promise<AccountEntry[]>;
25
- listNetrc(): AccountEntry[];
26
- remove(name: string): Promise<void>;
27
- removeNetrc(name: string): void;
28
- set(account: AccountEntry, dataDir: string): Promise<void>;
29
- writeLoginState(dataDir: string, name: string): Promise<void>;
12
+ current(): Promise<null | string>;
13
+ list(): [] | Heroku.Account[];
14
+ remove(name: string): void;
15
+ set(name: string): Promise<void>;
30
16
  private account;
31
17
  private configDir;
32
18
  private initNetrc;
@@ -1,5 +1,3 @@
1
- import { getStorageConfig, listKeychainAccounts, writeLoginState, } from '@heroku-cli/command';
2
- import { removeAuth } from '@heroku-cli/command/lib/credential-manager.js';
3
1
  import fs from 'node:fs';
4
2
  import os from 'node:os';
5
3
  import path from 'node:path';
@@ -7,86 +5,41 @@ import { parse, stringify } from 'yaml';
7
5
  export class AccountsWrapper {
8
6
  netrc;
9
7
  add(name, username, password) {
10
- const config = this.getStorageConfig();
11
- if (config.useNetrc) {
12
- const basedir = path.join(this.configDir(), 'accounts');
13
- fs.mkdirSync(basedir, { recursive: true });
14
- fs.writeFileSync(path.join(basedir, name),
15
- // eslint-disable-next-line perfectionist/sort-objects
16
- stringify({ username, password }), 'utf8');
17
- fs.chmodSync(path.join(basedir, name), 0o600);
18
- }
19
- }
20
- async current(heroku) {
21
- const config = this.getStorageConfig();
22
- if (config.credentialStore) {
23
- const authEntry = await heroku.getAuthEntry();
24
- return authEntry?.account ?? null;
25
- }
26
- return this.currentNetrc();
8
+ const basedir = path.join(this.configDir(), 'accounts');
9
+ fs.mkdirSync(basedir, { recursive: true });
10
+ fs.writeFileSync(path.join(basedir, name),
11
+ // eslint-disable-next-line perfectionist/sort-objects
12
+ stringify({ username, password }), 'utf8');
13
+ fs.chmodSync(path.join(basedir, name), 0o600);
27
14
  }
28
- async currentNetrc() {
15
+ async current() {
29
16
  const netrcInstance = await this.initNetrc();
30
17
  if (netrcInstance.machines['api.heroku.com']) {
31
- const current = this.listNetrc().find(a => a.username === netrcInstance.machines['api.heroku.com'].login);
18
+ const current = this.list().find(a => a.username === netrcInstance.machines['api.heroku.com'].login);
32
19
  return current && current.name ? current.name : null;
33
20
  }
34
21
  return null;
35
22
  }
36
- async getKeychainAccounts() {
37
- return listKeychainAccounts();
38
- }
39
- getStorageConfig() {
40
- return getStorageConfig();
41
- }
42
- async list() {
43
- const config = this.getStorageConfig();
44
- if (config.credentialStore) {
45
- const accounts = await this.getKeychainAccounts();
46
- return accounts
47
- .filter((account) => account !== null && account !== undefined)
48
- .map(account => ({ username: account }));
49
- }
50
- return this.listNetrc();
51
- }
52
- listNetrc() {
23
+ list() {
53
24
  const basedir = path.join(this.configDir(), 'accounts');
54
25
  try {
55
26
  return fs.readdirSync(basedir)
56
- .map(name => ({ name, username: this.account(name).username ?? '' }));
27
+ .map(name => Object.assign(this.account(name), { name }));
57
28
  }
58
29
  catch {
59
30
  return [];
60
31
  }
61
32
  }
62
- async remove(name) {
63
- const config = this.getStorageConfig();
64
- if (config.credentialStore) {
65
- await removeAuth(name, ['api.heroku.com', 'git.heroku.com']);
66
- return;
67
- }
68
- this.removeNetrc(name);
69
- }
70
- removeNetrc(name) {
33
+ remove(name) {
71
34
  const basedir = path.join(this.configDir(), 'accounts');
72
35
  fs.unlinkSync(path.join(basedir, name));
73
36
  }
74
- async set(account, dataDir) {
75
- const config = this.getStorageConfig();
76
- if (config.credentialStore && !account.name) {
77
- await this.writeLoginState(dataDir, account.username);
78
- return;
79
- }
80
- if (config.useNetrc && account.name) {
81
- const netrcInstance = await this.initNetrc();
82
- const current = this.account(account.name);
83
- netrcInstance.machines['git.heroku.com'] = { login: current.username, password: current.password };
84
- netrcInstance.machines['api.heroku.com'] = { login: current.username, password: current.password };
85
- await netrcInstance.save();
86
- }
87
- }
88
- async writeLoginState(dataDir, name) {
89
- return writeLoginState(dataDir, name);
37
+ async set(name) {
38
+ const netrcInstance = await this.initNetrc();
39
+ const current = this.account(name);
40
+ netrcInstance.machines['git.heroku.com'] = { login: current.username, password: current.password };
41
+ netrcInstance.machines['api.heroku.com'] = { login: current.username, password: current.password };
42
+ await netrcInstance.save();
90
43
  }
91
44
  account(name) {
92
45
  const basedir = path.join(this.configDir(), 'accounts');
@@ -26,11 +26,15 @@ export default class BackboardHerokulyticsClient {
26
26
  config: Interfaces.Config;
27
27
  http: ReturnType<typeof HTTP.create>;
28
28
  userConfig: HerokulyticsConfig;
29
- private heroku;
30
29
  private isInitialized;
30
+ private netrc;
31
31
  constructor(config: Interfaces.Config);
32
32
  get authorizationToken(): string | undefined;
33
+ get netrcLogin(): string | undefined;
34
+ get netrcToken(): string | undefined;
33
35
  get url(): string;
36
+ get user(): string | undefined;
37
+ get usingHerokuAPIKey(): boolean;
34
38
  _acAnalytics(id: string): Promise<number>;
35
39
  send(opts: RecordOpts): Promise<void>;
36
40
  private ensureInitialized;
@@ -1,4 +1,4 @@
1
- import { APIClient } from '@heroku-cli/command';
1
+ import { vars } from '@heroku-cli/command';
2
2
  import { HTTP } from '@heroku/http-call';
3
3
  import fs from 'fs-extra';
4
4
  import path from 'node:path';
@@ -8,8 +8,8 @@ export default class BackboardHerokulyticsClient {
8
8
  config;
9
9
  http;
10
10
  userConfig;
11
- heroku;
12
11
  isInitialized = false;
12
+ netrc;
13
13
  constructor(config) {
14
14
  this.config = config;
15
15
  this.http = HTTP.create({
@@ -17,11 +17,26 @@ export default class BackboardHerokulyticsClient {
17
17
  });
18
18
  }
19
19
  get authorizationToken() {
20
- return this.heroku.auth;
20
+ return process.env.HEROKU_API_KEY || this.netrcToken;
21
+ }
22
+ get netrcLogin() {
23
+ return this.netrc?.machines[vars.apiHost]?.login;
24
+ }
25
+ get netrcToken() {
26
+ return this.netrc?.machines[vars.apiHost]?.password;
21
27
  }
22
28
  get url() {
23
29
  return process.env.HEROKU_ANALYTICS_URL || 'https://backboard.heroku.com/hamurai';
24
30
  }
31
+ get user() {
32
+ if (this.usingHerokuAPIKey)
33
+ return undefined;
34
+ return this.netrcLogin;
35
+ }
36
+ get usingHerokuAPIKey() {
37
+ const k = process.env.HEROKU_API_KEY;
38
+ return Boolean(k && k.length > 0);
39
+ }
25
40
  async _acAnalytics(id) {
26
41
  if (id === 'autocomplete:options')
27
42
  return 0;
@@ -95,8 +110,10 @@ export default class BackboardHerokulyticsClient {
95
110
  }
96
111
  telemetryDebug('Initializing Herokulytics client...');
97
112
  this.isInitialized = true;
98
- this.heroku = new APIClient(this.config);
99
- await this.heroku.getAuth();
113
+ const NetrcModule = await import('netrc-parser');
114
+ const NetrcClass = NetrcModule.Netrc || NetrcModule.default.constructor;
115
+ this.netrc = new NetrcClass();
116
+ await this.netrc.load();
100
117
  this.userConfig = new HerokulyticsConfig(this.config);
101
118
  await this.userConfig.init();
102
119
  telemetryDebug('Herokulytics client initialized (install_id: %s)', this.userConfig.install);
@@ -1,20 +1,12 @@
1
- import cp from 'node:child_process';
2
1
  export default class Git {
3
- private readonly execFile;
4
- /** Configures `heroku git:credentials` as a Git credential helper
5
- * that is URL-scoped to Heroku Git operations only.
6
- */
7
- configureCredentialHelper(): Promise<void>;
8
2
  createRemote(remote: string, url: string): Promise<string | null>;
9
- /** Erases stored credentials for the Heroku Git host */
10
- eraseCredentials(): Promise<void>;
11
3
  exec(args: string[]): Promise<string>;
12
4
  getBranch(symbolicRef: string): Promise<string>;
13
5
  getCommitTitle(ref: string): Promise<string>;
14
6
  getRef(branch: string): Promise<string>;
15
7
  hasGitRemote(remote: string): Promise<boolean>;
16
8
  httpGitUrl(app: string): string;
17
- inGitRepo(): boolean;
9
+ inGitRepo(): true | undefined;
18
10
  readCommit(commit: string): Promise<{
19
11
  branch: string;
20
12
  message: string;
@@ -22,11 +14,6 @@ export default class Git {
22
14
  }>;
23
15
  remoteFromGitConfig(): Promise<string | void>;
24
16
  remoteUrl(name: string): Promise<string>;
25
- /** Removes `heroku git:credentials` from the global config */
26
- removeCredentialHelper(): Promise<void>;
27
- spawn(args: string[], options?: {
28
- input?: string;
29
- stdio?: cp.StdioOptions;
30
- }): Promise<unknown>;
17
+ spawn(args: string[]): Promise<unknown>;
31
18
  url(app: string): string;
32
19
  }