heroku 11.1.1 → 11.2.0-beta.1
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 +26 -0
- package/bin/run.js +3 -6
- package/dist/commands/access/index.js +9 -8
- package/dist/commands/addons/index.d.ts +2 -1
- package/dist/commands/addons/index.js +70 -70
- package/dist/commands/apps/create.js +2 -1
- package/dist/commands/apps/diff.js +3 -2
- package/dist/commands/apps/index.js +4 -3
- package/dist/commands/apps/info.js +83 -83
- package/dist/commands/apps/transfer.d.ts +1 -4
- package/dist/commands/apps/transfer.js +4 -3
- package/dist/commands/auth/token.js +2 -1
- package/dist/commands/certs/add.d.ts +3 -12
- package/dist/commands/certs/add.js +8 -7
- package/dist/commands/certs/auto/index.js +2 -1
- package/dist/commands/certs/generate.d.ts +1 -1
- package/dist/commands/certs/generate.js +4 -3
- package/dist/commands/config/edit.d.ts +1 -1
- package/dist/commands/config/edit.js +52 -51
- package/dist/commands/dashboard.js +31 -30
- package/dist/commands/data/maintenances/info.d.ts +1 -1
- package/dist/commands/data/maintenances/info.js +4 -3
- package/dist/commands/data/maintenances/run.js +2 -2
- package/dist/commands/data/maintenances/schedule.d.ts +1 -1
- package/dist/commands/data/maintenances/schedule.js +4 -3
- package/dist/commands/data/pg/create.js +9 -9
- package/dist/commands/data/pg/credentials/create.js +2 -2
- package/dist/commands/data/pg/credentials/index.d.ts +1 -0
- package/dist/commands/data/pg/credentials/index.js +3 -3
- package/dist/commands/data/pg/fork.d.ts +1 -1
- package/dist/commands/data/pg/fork.js +4 -3
- package/dist/commands/data/pg/info.js +40 -40
- package/dist/commands/data/pg/settings.js +3 -3
- package/dist/commands/data/pg/update.js +8 -8
- package/dist/commands/domains/add.d.ts +2 -2
- package/dist/commands/domains/add.js +6 -5
- package/dist/commands/domains/index.d.ts +2 -1
- package/dist/commands/domains/index.js +13 -11
- package/dist/commands/keys/add.js +13 -12
- package/dist/commands/local/index.d.ts +2 -0
- package/dist/commands/local/index.js +46 -11
- package/dist/commands/local/run.js +10 -2
- package/dist/commands/local/version.js +10 -2
- package/dist/commands/notifications/index.js +14 -14
- package/dist/commands/pg/backups/download.js +12 -12
- package/dist/commands/pg/backups/restore.js +15 -15
- package/dist/commands/pg/backups/schedule.d.ts +6 -6
- package/dist/commands/pg/backups/schedule.js +18 -18
- package/dist/commands/pg/backups/url.js +2 -2
- package/dist/commands/pg/credentials/create.js +5 -5
- package/dist/commands/pg/credentials/destroy.js +2 -2
- package/dist/commands/pg/credentials/rotate.js +2 -2
- package/dist/commands/pg/credentials.d.ts +1 -0
- package/dist/commands/pg/credentials.js +3 -3
- package/dist/commands/pipelines/add.js +2 -1
- package/dist/commands/pipelines/create.js +2 -1
- package/dist/commands/ps/copy.js +3 -3
- package/dist/commands/ps/exec.js +2 -2
- package/dist/commands/ps/forward.js +1 -1
- package/dist/commands/ps/index.d.ts +1 -0
- package/dist/commands/ps/index.js +115 -115
- package/dist/commands/ps/scale.js +2 -1
- package/dist/commands/ps/type.js +6 -5
- package/dist/commands/releases/index.js +2 -1
- package/dist/commands/releases/rollback.js +2 -2
- package/dist/commands/spaces/destroy.js +7 -7
- package/dist/commands/spaces/drains/set.js +2 -2
- package/dist/commands/spaces/peerings/accept.js +1 -1
- package/dist/commands/spaces/ps.js +2 -2
- package/dist/commands/spaces/trusted-ips/add.js +2 -2
- package/dist/commands/spaces/trusted-ips/remove.js +1 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/version/info.js +2 -6
- package/dist/hooks/command_not_found/{performance_analytics.js → setup-otel-telemetry.js} +3 -2
- package/dist/hooks/init/{performance_analytics.js → setup-otel-telemetry.js} +3 -2
- package/dist/hooks/init/terms-of-service.js +1 -1
- package/dist/hooks/init/version.js +9 -6
- package/dist/hooks/postrun/{performance_analytics.js → send-otel-telemetry.js} +3 -2
- package/dist/hooks/prerun/collect-and-send-herokulytics.js +34 -0
- package/dist/{analytics.d.ts → lib/analytics-telemetry/backboard-herokulytics-client.d.ts} +20 -19
- package/dist/{analytics.js → lib/analytics-telemetry/backboard-herokulytics-client.js} +72 -54
- package/dist/lib/analytics-telemetry/backboard-otel-client.d.ts +16 -0
- package/dist/lib/analytics-telemetry/backboard-otel-client.js +121 -0
- package/dist/{user-config.d.ts → lib/analytics-telemetry/herokulytics-config.d.ts} +9 -9
- package/dist/{user-config.js → lib/analytics-telemetry/herokulytics-config.js} +40 -40
- package/dist/lib/analytics-telemetry/sentry-client.d.ts +14 -8
- package/dist/lib/analytics-telemetry/sentry-client.js +51 -50
- package/dist/lib/analytics-telemetry/telemetry-manager.d.ts +43 -0
- package/dist/lib/analytics-telemetry/telemetry-manager.js +133 -0
- package/dist/lib/analytics-telemetry/telemetry-utils.d.ts +18 -4
- package/dist/lib/analytics-telemetry/telemetry-utils.js +13 -9
- package/dist/lib/analytics-telemetry/telemetry-worker.js +56 -8
- package/dist/lib/analytics-telemetry/worker-client.d.ts +3 -2
- package/dist/lib/analytics-telemetry/worker-client.js +5 -16
- package/dist/lib/data/poolConfig.js +2 -2
- package/dist/lib/data/utils.d.ts +1 -1
- package/dist/lib/data/utils.js +1 -1
- package/dist/lib/lazy-module-loader.d.ts +58 -0
- package/dist/lib/lazy-module-loader.js +100 -0
- package/dist/lib/local/fork-foreman.d.ts +5 -0
- package/dist/lib/local/fork-foreman.js +18 -5
- package/dist/lib/local/load-foreman-procfile.js +3 -0
- package/dist/lib/ps-exec/exec.js +2 -2
- package/dist/lib/releases/status_helper.d.ts +1 -1
- package/dist/lib/run/colorize.js +10 -10
- package/dist/lib/utils/tableUtils.d.ts +4 -0
- package/dist/lib/utils/tableUtils.js +6 -0
- package/npm-shrinkwrap.json +336 -249
- package/oclif.manifest.json +3774 -3737
- package/package.json +9 -9
- package/dist/deps.d.ts +0 -9
- package/dist/deps.js +0 -14
- package/dist/hooks/prerun/analytics.js +0 -23
- package/dist/lib/analytics-telemetry/global-telemetry.d.ts +0 -30
- package/dist/lib/analytics-telemetry/global-telemetry.js +0 -103
- package/dist/lib/analytics-telemetry/honeycomb-client.d.ts +0 -15
- package/dist/lib/analytics-telemetry/honeycomb-client.js +0 -135
- /package/dist/hooks/command_not_found/{performance_analytics.d.ts → setup-otel-telemetry.d.ts} +0 -0
- /package/dist/hooks/finally/{sentry.d.ts → send-otel-and-sentry-errors.d.ts} +0 -0
- /package/dist/hooks/finally/{sentry.js → send-otel-and-sentry-errors.js} +0 -0
- /package/dist/hooks/init/{performance_analytics.d.ts → setup-otel-telemetry.d.ts} +0 -0
- /package/dist/hooks/postrun/{performance_analytics.d.ts → send-otel-telemetry.d.ts} +0 -0
- /package/dist/hooks/prerun/{analytics.d.ts → collect-and-send-herokulytics.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +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.2.0-beta.1](https://github.com/heroku/cli/compare/v11.1.1...v11.2.0-beta.1) (2026-04-08)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* adding --start-cmd flag to heroku local when no Procfile is present ([#3638](https://github.com/heroku/cli/issues/3638)) ([b9b8fed](https://github.com/heroku/cli/commit/b9b8fed8f44ad88946edc4141ec1fe41f157baac))
|
|
13
|
+
* include a --no-wrap flag for table displays ([#3613](https://github.com/heroku/cli/issues/3613)) ([1d27c09](https://github.com/heroku/cli/commit/1d27c09f3973c81bb610583c3afe418ff0c867a6))
|
|
14
|
+
* update color usage, telemetry activation logic, and command/util dependencies ([#3646](https://github.com/heroku/cli/issues/3646)) ([ede6655](https://github.com/heroku/cli/commit/ede665557ca3d387e3e57f5a438cd7ab7c27b1b3))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* flaky apps:diff tests ([#3649](https://github.com/heroku/cli/issues/3649)) ([8c4f8c3](https://github.com/heroku/cli/commit/8c4f8c3e5c99f688cbbefcf91a44ed234ba6c65b))
|
|
20
|
+
* improve telemetry worker stderr cleanup ([#3648](https://github.com/heroku/cli/issues/3648)) ([150e74a](https://github.com/heroku/cli/commit/150e74abe77b7c51b6b9ea84c8e246fe3165c6b5))
|
|
21
|
+
* procfile comment parsing ([#3641](https://github.com/heroku/cli/issues/3641)) ([aae3751](https://github.com/heroku/cli/commit/aae37512b54bdcf2b832429254f0d2c0fe1f42d3))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Code Refactoring
|
|
25
|
+
|
|
26
|
+
* unify telemetry architecture and use background workers for all telemetry ([#3642](https://github.com/heroku/cli/issues/3642)) ([c13074d](https://github.com/heroku/cli/commit/c13074da5f0892fa412ce6bc7c4b1d998eae3366))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Tests
|
|
30
|
+
|
|
31
|
+
* this fixes the mocking difference causing flappy tests and adds chore to PR release title ([#3640](https://github.com/heroku/cli/issues/3640)) ([32920f9](https://github.com/heroku/cli/commit/32920f9600cc6b06815aa7db532dd8bb352d90db))
|
|
32
|
+
|
|
7
33
|
## [11.1.1](https://github.com/heroku/cli/compare/v11.1.0...v11.1.1) (2026-04-01)
|
|
8
34
|
|
|
9
35
|
|
package/bin/run.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-deprecation
|
|
2
2
|
|
|
3
|
-
/* eslint-disable n/no-unpublished-bin */
|
|
4
|
-
|
|
5
3
|
import {execute, settings} from '@oclif/core'
|
|
6
4
|
|
|
7
5
|
// Enable performance tracking when oclif:perf is specified in DEBUG
|
|
@@ -14,10 +12,9 @@ process.env.HEROKU_UPDATE_INSTRUCTIONS = process.env.HEROKU_UPDATE_INSTRUCTIONS
|
|
|
14
12
|
const now = new Date()
|
|
15
13
|
const cliStartTime = now.getTime()
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
const enableTelemetry = process.platform !== 'win32' || process.env.ENABLE_WINDOWS_TELEMETRY === 'true'
|
|
15
|
+
const {isTelemetryEnabled} = await import('../dist/lib/analytics-telemetry/telemetry-utils.js')
|
|
19
16
|
|
|
20
|
-
if (
|
|
17
|
+
if (isTelemetryEnabled()) {
|
|
21
18
|
// Dynamically import telemetry modules
|
|
22
19
|
const {setupTelemetryHandlers} = await import('../dist/lib/analytics-telemetry/worker-client.js')
|
|
23
20
|
const {computeDuration} = await import('../dist/lib/analytics-telemetry/telemetry-utils.js')
|
|
@@ -26,7 +23,7 @@ if (enableTelemetry) {
|
|
|
26
23
|
setupTelemetryHandlers({
|
|
27
24
|
cliStartTime,
|
|
28
25
|
computeDuration,
|
|
29
|
-
enableTelemetry,
|
|
26
|
+
enableTelemetry: isTelemetryEnabled(),
|
|
30
27
|
})
|
|
31
28
|
}
|
|
32
29
|
|
|
@@ -2,7 +2,7 @@ import { Command, flags } from '@heroku-cli/command';
|
|
|
2
2
|
import { HerokuAPIError } from '@heroku-cli/command/lib/api-client.js';
|
|
3
3
|
import { color, hux } from '@heroku/heroku-cli-util';
|
|
4
4
|
import { ux } from '@oclif/core/ux';
|
|
5
|
-
import
|
|
5
|
+
import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
|
|
6
6
|
import { getOwner, isTeamApp } from '../../lib/teamUtils.js';
|
|
7
7
|
export default class AccessIndex extends Command {
|
|
8
8
|
static description = 'list who has access to an app';
|
|
@@ -13,6 +13,7 @@ export default class AccessIndex extends Command {
|
|
|
13
13
|
};
|
|
14
14
|
static topic = 'access';
|
|
15
15
|
async run() {
|
|
16
|
+
const _ = await lazyModuleLoader.loadLodash();
|
|
16
17
|
const { flags } = await this.parse(AccessIndex);
|
|
17
18
|
const { app: appName, json } = flags;
|
|
18
19
|
const { body: app } = await this.heroku.get(`/apps/${appName}`);
|
|
@@ -28,7 +29,7 @@ export default class AccessIndex extends Command {
|
|
|
28
29
|
admin.permissions = adminPermissions;
|
|
29
30
|
return admin;
|
|
30
31
|
});
|
|
31
|
-
collaborators = buildCollaboratorsArray(collaborators, admins);
|
|
32
|
+
collaborators = buildCollaboratorsArray(collaborators, admins, _);
|
|
32
33
|
}
|
|
33
34
|
catch (error) {
|
|
34
35
|
if (!(error instanceof HerokuAPIError && error.http.statusCode === 403))
|
|
@@ -38,10 +39,10 @@ export default class AccessIndex extends Command {
|
|
|
38
39
|
if (json)
|
|
39
40
|
printJSON(collaborators);
|
|
40
41
|
else
|
|
41
|
-
printAccess(app, collaborators);
|
|
42
|
+
printAccess(app, collaborators, _);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
function buildCollaboratorsArray(collaboratorsRaw, admins) {
|
|
45
|
+
function buildCollaboratorsArray(collaboratorsRaw, admins, _) {
|
|
45
46
|
const collaboratorsNoAdmins = _.reject(collaboratorsRaw, { role: 'admin' });
|
|
46
47
|
return _.union(collaboratorsNoAdmins, admins);
|
|
47
48
|
}
|
|
@@ -62,12 +63,12 @@ function buildTableColumns(showPermissions) {
|
|
|
62
63
|
}
|
|
63
64
|
return baseColumns;
|
|
64
65
|
}
|
|
65
|
-
function printAccess(app, collaborators) {
|
|
66
|
+
function printAccess(app, collaborators, _) {
|
|
66
67
|
const showPermissions = isTeamApp(app.owner?.email);
|
|
67
68
|
collaborators = _.chain(collaborators)
|
|
68
|
-
.sortBy(c => c.email || c.user.email)
|
|
69
|
-
.reject(c => /herokumanager\.com$/.test(c.user.email))
|
|
70
|
-
.map(collab => {
|
|
69
|
+
.sortBy((c) => c.email || c.user.email)
|
|
70
|
+
.reject((c) => /herokumanager\.com$/.test(c.user.email))
|
|
71
|
+
.map((collab) => {
|
|
71
72
|
const { email } = collab.user;
|
|
72
73
|
const { permissions, role } = collab;
|
|
73
74
|
const data = { email, role: role || 'collaborator' };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from '@heroku-cli/command';
|
|
2
2
|
import * as Heroku from '@heroku-cli/schema';
|
|
3
|
-
export declare function renderAttachment(attachment: Heroku.AddOnAttachment, app: string, isLast?: boolean): string;
|
|
4
3
|
export default class Addons extends Command {
|
|
5
4
|
static description: string;
|
|
6
5
|
static examples: string[];
|
|
@@ -8,9 +7,11 @@ export default class Addons extends Command {
|
|
|
8
7
|
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
8
|
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
9
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
'no-wrap': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
};
|
|
13
13
|
static topic: string;
|
|
14
14
|
static usage: string;
|
|
15
15
|
run(): Promise<void>;
|
|
16
16
|
}
|
|
17
|
+
export declare function renderAttachment(attachment: Heroku.AddOnAttachment, app: string, isLast?: boolean): string;
|
|
@@ -1,9 +1,55 @@
|
|
|
1
|
-
import { color, hux } from '@heroku/heroku-cli-util';
|
|
2
1
|
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, hux } from '@heroku/heroku-cli-util';
|
|
3
3
|
import { ux } from '@oclif/core/ux';
|
|
4
4
|
import _ from 'lodash';
|
|
5
5
|
import { formatPrice, formatState, grandfatheredPrice } from '../../lib/addons/util.js';
|
|
6
|
+
import { huxTableNoWrapOptions } from '../../lib/utils/tableUtils.js';
|
|
6
7
|
const topic = 'addons';
|
|
8
|
+
export default class Addons extends Command {
|
|
9
|
+
static description = `Lists your add-ons and attachments.
|
|
10
|
+
|
|
11
|
+
The default filter applied depends on whether you are in a Heroku app
|
|
12
|
+
directory. If so, the --app flag is implied. If not, the default of --all
|
|
13
|
+
is implied. Explicitly providing either flag overrides the default
|
|
14
|
+
behavior.
|
|
15
|
+
`;
|
|
16
|
+
static examples = [
|
|
17
|
+
`${color.command(`heroku ${topic} --all`)}`,
|
|
18
|
+
`${color.command(`heroku ${topic} --app acme-inc-www`)}`,
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
all: flags.boolean({ char: 'A', description: 'show add-ons and attachments for all accessible apps' }),
|
|
22
|
+
app: flags.app(),
|
|
23
|
+
json: flags.boolean({ description: 'return add-ons in json format' }),
|
|
24
|
+
'no-wrap': flags.noWrap(),
|
|
25
|
+
remote: flags.remote(),
|
|
26
|
+
};
|
|
27
|
+
static topic = topic;
|
|
28
|
+
static usage = 'addons [--all|--app APP]';
|
|
29
|
+
async run() {
|
|
30
|
+
const { flags } = await this.parse(Addons);
|
|
31
|
+
const { all, app, json } = flags;
|
|
32
|
+
if (!all && app) {
|
|
33
|
+
const addons = await addonGetter(this.heroku, app);
|
|
34
|
+
if (json)
|
|
35
|
+
displayJSON(addons);
|
|
36
|
+
else
|
|
37
|
+
displayForApp(app, addons, flags['no-wrap']);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const addons = await addonGetter(this.heroku);
|
|
41
|
+
if (json)
|
|
42
|
+
displayJSON(addons);
|
|
43
|
+
else
|
|
44
|
+
displayAll(addons, flags['no-wrap']);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function renderAttachment(attachment, app, isLast = false) {
|
|
49
|
+
const line = isLast ? '\u2514\u2500' : '\u251C\u2500';
|
|
50
|
+
const attName = formatAttachment(attachment, attachment.app?.name !== app);
|
|
51
|
+
return ` ${color.gray(line)} ${attName}`;
|
|
52
|
+
}
|
|
7
53
|
async function addonGetter(api, app) {
|
|
8
54
|
let attachmentsResponse = null;
|
|
9
55
|
let addonsResponse;
|
|
@@ -67,7 +113,7 @@ async function addonGetter(api, app) {
|
|
|
67
113
|
});
|
|
68
114
|
return addons;
|
|
69
115
|
}
|
|
70
|
-
function displayAll(addons) {
|
|
116
|
+
function displayAll(addons, noWrap = false) {
|
|
71
117
|
addons = _.sortBy(addons, 'app.name', 'plan.name', 'addon.name');
|
|
72
118
|
if (addons.length === 0) {
|
|
73
119
|
ux.stdout('No add-ons.');
|
|
@@ -84,21 +130,21 @@ function displayAll(addons) {
|
|
|
84
130
|
Plan: {
|
|
85
131
|
get({ plan }) {
|
|
86
132
|
if (plan === undefined)
|
|
87
|
-
return color.
|
|
133
|
+
return color.inactive('?');
|
|
88
134
|
return plan.name;
|
|
89
135
|
},
|
|
90
136
|
},
|
|
91
137
|
Price: {
|
|
92
138
|
get({ plan }) {
|
|
93
139
|
if (plan?.price === undefined)
|
|
94
|
-
return color.
|
|
140
|
+
return color.inactive('?');
|
|
95
141
|
return formatPrice({ hourly: true, price: plan?.price });
|
|
96
142
|
},
|
|
97
143
|
},
|
|
98
144
|
'Max Price': {
|
|
99
145
|
get({ plan }) {
|
|
100
146
|
if (plan?.price === undefined)
|
|
101
|
-
return color.
|
|
147
|
+
return color.inactive('?');
|
|
102
148
|
return formatPrice({ hourly: false, price: plan?.price });
|
|
103
149
|
},
|
|
104
150
|
},
|
|
@@ -121,26 +167,10 @@ function displayAll(addons) {
|
|
|
121
167
|
return result;
|
|
122
168
|
},
|
|
123
169
|
},
|
|
124
|
-
},
|
|
125
|
-
overflow: 'wrap',
|
|
126
|
-
});
|
|
170
|
+
}, huxTableNoWrapOptions(noWrap));
|
|
127
171
|
/* eslint-enable perfectionist/sort-objects */
|
|
128
172
|
}
|
|
129
|
-
function
|
|
130
|
-
const attName = color.attachment(attachment.name || '');
|
|
131
|
-
const output = [color.dim('as'), attName];
|
|
132
|
-
if (showApp) {
|
|
133
|
-
const appInfo = `on ${color.app(attachment.app?.name || '')} app`;
|
|
134
|
-
output.push(color.dim(appInfo));
|
|
135
|
-
}
|
|
136
|
-
return output.join(' ');
|
|
137
|
-
}
|
|
138
|
-
export function renderAttachment(attachment, app, isLast = false) {
|
|
139
|
-
const line = isLast ? '\u2514\u2500' : '\u251C\u2500';
|
|
140
|
-
const attName = formatAttachment(attachment, attachment.app?.name !== app);
|
|
141
|
-
return ` ${color.dim(line)} ${attName}`;
|
|
142
|
-
}
|
|
143
|
-
function displayForApp(app, addons) {
|
|
173
|
+
function displayForApp(app, addons, noWrap = false) {
|
|
144
174
|
if (addons.length === 0) {
|
|
145
175
|
ux.stdout(`No add-ons for app ${app}.`);
|
|
146
176
|
return;
|
|
@@ -150,17 +180,19 @@ function displayForApp(app, addons) {
|
|
|
150
180
|
const name = color.addon(addon.name || '');
|
|
151
181
|
let service = addon.addon_service?.name;
|
|
152
182
|
if (service === undefined) {
|
|
153
|
-
service = color.
|
|
183
|
+
service = color.gray('?');
|
|
154
184
|
}
|
|
155
185
|
const addonLine = `${service} (${name})`;
|
|
156
186
|
const atts = _.sortBy(addon.attachments, isForeignApp, 'app.name', 'name');
|
|
157
|
-
// render each attachment under the add-on
|
|
158
187
|
const attLines = atts.map((attachment, idx) => {
|
|
159
188
|
const isLast = (idx === addon.attachments.length - 1);
|
|
160
189
|
return renderAttachment(attachment, app, isLast);
|
|
161
190
|
});
|
|
162
|
-
|
|
163
|
-
|
|
191
|
+
const lines = [addonLine, ...attLines];
|
|
192
|
+
if (noWrap) {
|
|
193
|
+
return lines.join(' ');
|
|
194
|
+
}
|
|
195
|
+
return `${lines.join('\n')}\n`;
|
|
164
196
|
}
|
|
165
197
|
addons = _.sortBy(addons, isForeignApp, 'plan.name', 'name');
|
|
166
198
|
ux.stdout();
|
|
@@ -169,14 +201,14 @@ function displayForApp(app, addons) {
|
|
|
169
201
|
Plan: {
|
|
170
202
|
get: ({ plan }) => plan && plan.name !== undefined
|
|
171
203
|
? plan.name.replace(/^[^:]+:/, '')
|
|
172
|
-
: color.
|
|
204
|
+
: color.inactive('?'),
|
|
173
205
|
},
|
|
174
206
|
Price: {
|
|
175
207
|
get(addon) {
|
|
176
208
|
if (addon.app?.name === app) {
|
|
177
209
|
return formatPrice({ hourly: true, price: addon.plan?.price });
|
|
178
210
|
}
|
|
179
|
-
return color.
|
|
211
|
+
return color.gray(`(billed to ${color.app(addon.app?.name || '')} app)`);
|
|
180
212
|
},
|
|
181
213
|
},
|
|
182
214
|
// eslint-disable-next-line perfectionist/sort-objects
|
|
@@ -185,56 +217,24 @@ function displayForApp(app, addons) {
|
|
|
185
217
|
if (addon.app?.name === app) {
|
|
186
218
|
return formatPrice({ hourly: false, price: addon.plan?.price });
|
|
187
219
|
}
|
|
188
|
-
return color.
|
|
220
|
+
return color.gray(`(billed to ${color.app(addon.app?.name || '')} app)`);
|
|
189
221
|
},
|
|
190
222
|
},
|
|
191
223
|
State: {
|
|
192
224
|
get: ({ state }) => formatState(state || ''),
|
|
193
225
|
},
|
|
194
|
-
},
|
|
195
|
-
overflow: 'wrap',
|
|
196
|
-
});
|
|
226
|
+
}, huxTableNoWrapOptions(noWrap));
|
|
197
227
|
ux.stdout(`The table above shows add-ons and the attachments to the current app (${color.app(app)}) or other apps.\n `);
|
|
198
228
|
}
|
|
199
229
|
function displayJSON(addons) {
|
|
200
230
|
ux.stdout(JSON.stringify(addons, null, 2));
|
|
201
231
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
behavior.
|
|
209
|
-
`;
|
|
210
|
-
static examples = [
|
|
211
|
-
`${color.command(`heroku ${topic} --all`)}`,
|
|
212
|
-
`${color.command(`heroku ${topic} --app acme-inc-www`)}`,
|
|
213
|
-
];
|
|
214
|
-
static flags = {
|
|
215
|
-
all: flags.boolean({ char: 'A', description: 'show add-ons and attachments for all accessible apps' }),
|
|
216
|
-
app: flags.app(),
|
|
217
|
-
json: flags.boolean({ description: 'return add-ons in json format' }),
|
|
218
|
-
remote: flags.remote(),
|
|
219
|
-
};
|
|
220
|
-
static topic = topic;
|
|
221
|
-
static usage = 'addons [--all|--app APP]';
|
|
222
|
-
async run() {
|
|
223
|
-
const { flags } = await this.parse(Addons);
|
|
224
|
-
const { all, app, json } = flags;
|
|
225
|
-
if (!all && app) {
|
|
226
|
-
const addons = await addonGetter(this.heroku, app);
|
|
227
|
-
if (json)
|
|
228
|
-
displayJSON(addons);
|
|
229
|
-
else
|
|
230
|
-
displayForApp(app, addons);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
const addons = await addonGetter(this.heroku);
|
|
234
|
-
if (json)
|
|
235
|
-
displayJSON(addons);
|
|
236
|
-
else
|
|
237
|
-
displayAll(addons);
|
|
238
|
-
}
|
|
232
|
+
function formatAttachment(attachment, showApp = true) {
|
|
233
|
+
const attName = color.attachment(attachment.name || '');
|
|
234
|
+
const output = [color.gray('as'), attName];
|
|
235
|
+
if (showApp) {
|
|
236
|
+
const appInfo = `on ${color.app(attachment.app?.name || '')} app`;
|
|
237
|
+
output.push(color.gray(appInfo));
|
|
239
238
|
}
|
|
239
|
+
return output.join(' ');
|
|
240
240
|
}
|
|
@@ -3,8 +3,8 @@ import { Command, flags } from '@heroku-cli/command';
|
|
|
3
3
|
import { BuildpackCompletion, RegionCompletion, SpaceCompletion, StackCompletion, } from '@heroku-cli/command/lib/completions.js';
|
|
4
4
|
import { Args, ux } from '@oclif/core';
|
|
5
5
|
import fs from 'fs-extra';
|
|
6
|
-
import { parse } from 'yaml';
|
|
7
6
|
import Git from '../../lib/git/git.js';
|
|
7
|
+
import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
|
|
8
8
|
const git = new Git();
|
|
9
9
|
function createText(name, space) {
|
|
10
10
|
let text = `Creating ${name ? color.app(name) : 'app'}`;
|
|
@@ -157,6 +157,7 @@ ${color.command('heroku apps:create --region eu')}`];
|
|
|
157
157
|
};
|
|
158
158
|
static hiddenAliases = ['create'];
|
|
159
159
|
async readManifest() {
|
|
160
|
+
const { parse } = await lazyModuleLoader.loadYaml();
|
|
160
161
|
const buffer = await fs.readFile('heroku.yml');
|
|
161
162
|
return parse(buffer.toString());
|
|
162
163
|
}
|
|
@@ -28,8 +28,9 @@ async function checksum(heroku, app) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
async function diffFiles(heroku, app1, app2) {
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const sum1 = await checksum(heroku, app1);
|
|
32
|
+
const sum2 = await checksum(heroku, app2);
|
|
33
|
+
return sum1 === sum2 ? [] : [{ prop: 'slug (checksum)', app1: sum1 ?? undefined, app2: sum2 ?? undefined }];
|
|
33
34
|
}
|
|
34
35
|
async function diffEnv(heroku, app1, app2) {
|
|
35
36
|
const [res1, res2] = await Promise.all([
|
|
@@ -2,7 +2,7 @@ import { Command, flags } from '@heroku-cli/command';
|
|
|
2
2
|
import { SpaceCompletion } from '@heroku-cli/command/lib/completions.js';
|
|
3
3
|
import { color, hux } from '@heroku/heroku-cli-util';
|
|
4
4
|
import { ux } from '@oclif/core/ux';
|
|
5
|
-
import
|
|
5
|
+
import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
|
|
6
6
|
export default class AppsIndex extends Command {
|
|
7
7
|
static description = 'list your apps';
|
|
8
8
|
static examples = [
|
|
@@ -23,6 +23,7 @@ export default class AppsIndex extends Command {
|
|
|
23
23
|
static hiddenAliases = ['list', 'apps:list'];
|
|
24
24
|
static topic = 'apps';
|
|
25
25
|
async run() {
|
|
26
|
+
const _ = await lazyModuleLoader.loadLodash();
|
|
26
27
|
const { flags } = await this.parse(AppsIndex);
|
|
27
28
|
const teamIdentifier = flags.team;
|
|
28
29
|
let team = (!flags.personal && teamIdentifier) ? teamIdentifier : null;
|
|
@@ -54,7 +55,7 @@ export default class AppsIndex extends Command {
|
|
|
54
55
|
hux.styledJSON(apps);
|
|
55
56
|
}
|
|
56
57
|
else {
|
|
57
|
-
print(apps, user, space, team);
|
|
58
|
+
print(apps, user, space, team, _);
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -74,7 +75,7 @@ function annotateAppName(app) {
|
|
|
74
75
|
function listApps(apps) {
|
|
75
76
|
apps.forEach((app) => ux.stdout(regionizeAppName(app)));
|
|
76
77
|
}
|
|
77
|
-
function print(apps, user, space, team) {
|
|
78
|
+
function print(apps, user, space, team, _) {
|
|
78
79
|
if (apps.length === 0) {
|
|
79
80
|
if (space)
|
|
80
81
|
ux.stdout(`There are no apps in space ${color.space(space)}.`);
|
|
@@ -1,88 +1,10 @@
|
|
|
1
|
-
import { color, hux } from '@heroku/heroku-cli-util';
|
|
2
1
|
import { Command, flags } from '@heroku-cli/command';
|
|
2
|
+
import { color, hux } from '@heroku/heroku-cli-util';
|
|
3
3
|
import { Args, ux } from '@oclif/core';
|
|
4
4
|
import { filesize } from 'filesize';
|
|
5
|
-
import _ from 'lodash';
|
|
6
5
|
import * as util from 'util';
|
|
7
6
|
import { getGeneration } from '../../lib/apps/generation.js';
|
|
8
|
-
|
|
9
|
-
function formatDate(date) {
|
|
10
|
-
return date.toISOString();
|
|
11
|
-
}
|
|
12
|
-
async function getInfo(app, client, extended) {
|
|
13
|
-
const promises = [
|
|
14
|
-
client.heroku.get(`/apps/${app}/addons`),
|
|
15
|
-
client.heroku.request(`/apps/${app}`),
|
|
16
|
-
client.heroku.get(`/apps/${app}/dynos`).catch(() => ({ body: [] })),
|
|
17
|
-
client.heroku.get(`/apps/${app}/collaborators`).catch(() => ({ body: [] })),
|
|
18
|
-
client.heroku.get(`/apps/${app}/pipeline-couplings`).catch(() => ({ body: null })),
|
|
19
|
-
];
|
|
20
|
-
if (extended) {
|
|
21
|
-
promises.push(client.heroku.get(`/apps/${app}?extended=true`));
|
|
22
|
-
}
|
|
23
|
-
const [{ body: addons }, { body: appWithMoreInfo }, { body: dynos }, { body: collaborators }, { body: pipelineCouplings }, appExtendedResponse,] = await Promise.all(promises);
|
|
24
|
-
const data = {
|
|
25
|
-
addons,
|
|
26
|
-
app: appWithMoreInfo,
|
|
27
|
-
collaborators,
|
|
28
|
-
dynos,
|
|
29
|
-
pipeline_coupling: pipelineCouplings,
|
|
30
|
-
};
|
|
31
|
-
if (appExtendedResponse) {
|
|
32
|
-
data.appExtended = appExtendedResponse.body;
|
|
33
|
-
}
|
|
34
|
-
if (extended) {
|
|
35
|
-
data.appExtended.acm = data.app.acm;
|
|
36
|
-
data.app = data.appExtended;
|
|
37
|
-
delete data.appExtended;
|
|
38
|
-
}
|
|
39
|
-
return data;
|
|
40
|
-
}
|
|
41
|
-
function print(info, addons, collaborators, extended) {
|
|
42
|
-
const data = {};
|
|
43
|
-
data.Addons = addons;
|
|
44
|
-
data.Collaborators = collaborators;
|
|
45
|
-
if (info.app.archived_at)
|
|
46
|
-
data['Archived At'] = formatDate(new Date(info.app.archived_at));
|
|
47
|
-
if (info.app.cron_finished_at)
|
|
48
|
-
data['Cron Finished At'] = formatDate(new Date(info.app.cron_finished_at));
|
|
49
|
-
if (info.app.cron_next_run)
|
|
50
|
-
data['Cron Next Run'] = formatDate(new Date(info.app.cron_next_run));
|
|
51
|
-
if (info.app.database_size)
|
|
52
|
-
data['Database Size'] = filesize(info.app.database_size, { round: 0, standard: 'jedec' });
|
|
53
|
-
if (info.app.create_status !== 'complete')
|
|
54
|
-
data['Create Status'] = info.app.create_status;
|
|
55
|
-
if (info.app.space)
|
|
56
|
-
data.Space = color.space(info.app.space.name);
|
|
57
|
-
if (info.app.space && info.app.internal_routing)
|
|
58
|
-
data['Internal Routing'] = info.app.internal_routing;
|
|
59
|
-
if (info.pipeline_coupling)
|
|
60
|
-
data.Pipeline = `${color.pipeline(info.pipeline_coupling.pipeline.name)} - ${info.pipeline_coupling.stage}`;
|
|
61
|
-
data['Auto Cert Mgmt'] = info.app.acm;
|
|
62
|
-
data['Git URL'] = info.app.git_url;
|
|
63
|
-
data['Web URL'] = color.info(info.app.web_url);
|
|
64
|
-
data['Repo Size'] = filesize(info.app.repo_size, { round: 0, standard: 'jedec' });
|
|
65
|
-
if (getGeneration(info.app) !== 'fir')
|
|
66
|
-
data['Slug Size'] = filesize(info.app.slug_size, { round: 0, standard: 'jedec' });
|
|
67
|
-
data.Owner = color.user(info.app.owner.email);
|
|
68
|
-
data.Region = info.app.region.name;
|
|
69
|
-
data.Dynos = countBy(info.dynos, 'type');
|
|
70
|
-
data.Stack = (function (app) {
|
|
71
|
-
let stack = info.app.stack.name;
|
|
72
|
-
if (app.stack.name !== app.build_stack.name) {
|
|
73
|
-
stack += ` (next build will use ${app.build_stack.name})`;
|
|
74
|
-
}
|
|
75
|
-
return stack;
|
|
76
|
-
})(info.app);
|
|
77
|
-
hux.styledHeader(color.app(info.app.name));
|
|
78
|
-
hux.styledObject(data);
|
|
79
|
-
if (extended) {
|
|
80
|
-
ux.stdout('\n\n--- Extended Information ---\n\n');
|
|
81
|
-
if (info.app.extended) {
|
|
82
|
-
ux.stdout(util.inspect(info.app.extended));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
7
|
+
import { lazyModuleLoader } from '../../lib/lazy-module-loader.js';
|
|
86
8
|
export default class AppsInfo extends Command {
|
|
87
9
|
static args = {
|
|
88
10
|
app: Args.string({ hidden: true }),
|
|
@@ -112,6 +34,7 @@ repo_size=5000000
|
|
|
112
34
|
static hiddenAliases = ['info'];
|
|
113
35
|
static topic = 'apps';
|
|
114
36
|
async run() {
|
|
37
|
+
const _ = await lazyModuleLoader.loadLodash();
|
|
115
38
|
const { args, flags } = await this.parse(AppsInfo);
|
|
116
39
|
const app = args.app || flags.app;
|
|
117
40
|
if (!app)
|
|
@@ -123,7 +46,7 @@ repo_size=5000000
|
|
|
123
46
|
.sort();
|
|
124
47
|
function shell() {
|
|
125
48
|
function print(k, v) {
|
|
126
|
-
ux.stdout(`${snakeCase(k)}=${v}`);
|
|
49
|
+
ux.stdout(`${_.snakeCase(k)}=${v}`);
|
|
127
50
|
}
|
|
128
51
|
print('auto_cert_mgmt', info.app.acm);
|
|
129
52
|
print('addons', addons);
|
|
@@ -147,7 +70,7 @@ repo_size=5000000
|
|
|
147
70
|
print('slug_size', filesize(info.app.slug_size, { round: 0, standard: 'jedec' }));
|
|
148
71
|
print('owner', info.app.owner.email);
|
|
149
72
|
print('region', info.app.region.name);
|
|
150
|
-
print('dynos', util.inspect(countBy(info.dynos, 'type')));
|
|
73
|
+
print('dynos', util.inspect(_.countBy(info.dynos, 'type')));
|
|
151
74
|
print('stack', info.app.stack.name);
|
|
152
75
|
}
|
|
153
76
|
if (flags.shell) {
|
|
@@ -157,7 +80,84 @@ repo_size=5000000
|
|
|
157
80
|
hux.styledJSON(info);
|
|
158
81
|
}
|
|
159
82
|
else {
|
|
160
|
-
print(info, addons, collaborators, flags.extended);
|
|
83
|
+
print(info, addons, collaborators, flags.extended, _);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function formatDate(date) {
|
|
88
|
+
return date.toISOString();
|
|
89
|
+
}
|
|
90
|
+
async function getInfo(app, client, extended) {
|
|
91
|
+
const promises = [
|
|
92
|
+
client.heroku.get(`/apps/${app}/addons`),
|
|
93
|
+
client.heroku.request(`/apps/${app}`),
|
|
94
|
+
client.heroku.get(`/apps/${app}/dynos`).catch(() => ({ body: [] })),
|
|
95
|
+
client.heroku.get(`/apps/${app}/collaborators`).catch(() => ({ body: [] })),
|
|
96
|
+
client.heroku.get(`/apps/${app}/pipeline-couplings`).catch(() => ({ body: null })),
|
|
97
|
+
];
|
|
98
|
+
if (extended) {
|
|
99
|
+
promises.push(client.heroku.get(`/apps/${app}?extended=true`));
|
|
100
|
+
}
|
|
101
|
+
const [{ body: addons }, { body: appWithMoreInfo }, { body: dynos }, { body: collaborators }, { body: pipelineCouplings }, appExtendedResponse,] = await Promise.all(promises);
|
|
102
|
+
const data = {
|
|
103
|
+
addons,
|
|
104
|
+
app: appWithMoreInfo,
|
|
105
|
+
collaborators,
|
|
106
|
+
dynos,
|
|
107
|
+
pipeline_coupling: pipelineCouplings,
|
|
108
|
+
};
|
|
109
|
+
if (appExtendedResponse) {
|
|
110
|
+
data.appExtended = appExtendedResponse.body;
|
|
111
|
+
}
|
|
112
|
+
if (extended) {
|
|
113
|
+
data.appExtended.acm = data.app.acm;
|
|
114
|
+
data.app = data.appExtended;
|
|
115
|
+
delete data.appExtended;
|
|
116
|
+
}
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
119
|
+
function print(info, addons, collaborators, extended, _) {
|
|
120
|
+
const data = {};
|
|
121
|
+
data.Addons = addons;
|
|
122
|
+
data.Collaborators = collaborators;
|
|
123
|
+
if (info.app.archived_at)
|
|
124
|
+
data['Archived At'] = formatDate(new Date(info.app.archived_at));
|
|
125
|
+
if (info.app.cron_finished_at)
|
|
126
|
+
data['Cron Finished At'] = formatDate(new Date(info.app.cron_finished_at));
|
|
127
|
+
if (info.app.cron_next_run)
|
|
128
|
+
data['Cron Next Run'] = formatDate(new Date(info.app.cron_next_run));
|
|
129
|
+
if (info.app.database_size)
|
|
130
|
+
data['Database Size'] = filesize(info.app.database_size, { round: 0, standard: 'jedec' });
|
|
131
|
+
if (info.app.create_status !== 'complete')
|
|
132
|
+
data['Create Status'] = info.app.create_status;
|
|
133
|
+
if (info.app.space)
|
|
134
|
+
data.Space = color.space(info.app.space.name);
|
|
135
|
+
if (info.app.space && info.app.internal_routing)
|
|
136
|
+
data['Internal Routing'] = info.app.internal_routing;
|
|
137
|
+
if (info.pipeline_coupling)
|
|
138
|
+
data.Pipeline = `${color.pipeline(info.pipeline_coupling.pipeline.name)} - ${info.pipeline_coupling.stage}`;
|
|
139
|
+
data['Auto Cert Mgmt'] = info.app.acm;
|
|
140
|
+
data['Git URL'] = info.app.git_url;
|
|
141
|
+
data['Web URL'] = color.info(info.app.web_url);
|
|
142
|
+
data['Repo Size'] = filesize(info.app.repo_size, { round: 0, standard: 'jedec' });
|
|
143
|
+
if (getGeneration(info.app) !== 'fir')
|
|
144
|
+
data['Slug Size'] = filesize(info.app.slug_size, { round: 0, standard: 'jedec' });
|
|
145
|
+
data.Owner = color.user(info.app.owner.email);
|
|
146
|
+
data.Region = info.app.region.name;
|
|
147
|
+
data.Dynos = _.countBy(info.dynos, 'type');
|
|
148
|
+
data.Stack = (function (app) {
|
|
149
|
+
let stack = info.app.stack.name;
|
|
150
|
+
if (app.stack.name !== app.build_stack.name) {
|
|
151
|
+
stack += ` (next build will use ${app.build_stack.name})`;
|
|
152
|
+
}
|
|
153
|
+
return stack;
|
|
154
|
+
})(info.app);
|
|
155
|
+
hux.styledHeader(color.app(info.app.name));
|
|
156
|
+
hux.styledObject(data);
|
|
157
|
+
if (extended) {
|
|
158
|
+
ux.stdout('\n\n--- Extended Information ---\n\n');
|
|
159
|
+
if (info.app.extended) {
|
|
160
|
+
ux.stdout(util.inspect(info.app.extended));
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from '@heroku-cli/command';
|
|
2
2
|
import * as Heroku from '@heroku-cli/schema';
|
|
3
|
-
import inquirer from 'inquirer';
|
|
4
3
|
export default class AppsTransfer extends Command {
|
|
5
4
|
static args: {
|
|
6
5
|
recipient: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
@@ -15,8 +14,6 @@ export default class AppsTransfer extends Command {
|
|
|
15
14
|
remote: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
15
|
};
|
|
17
16
|
static topic: string;
|
|
18
|
-
getAppsToTransfer(apps: Heroku.App[]):
|
|
19
|
-
ui: inquirer.ui.Prompt<any>;
|
|
20
|
-
};
|
|
17
|
+
getAppsToTransfer(apps: Heroku.App[], inquirer: any): any;
|
|
21
18
|
run(): Promise<void>;
|
|
22
19
|
}
|