heroku 10.12.0-beta.0 → 10.13.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.
@@ -81,7 +81,7 @@ class Push extends command_1.Command {
81
81
  else {
82
82
  heroku_cli_util_1.hux.styledHeader(`Pushing ${job.name} (${job.dockerfile})`);
83
83
  }
84
- await DockerHelper.pushImage(job.resource);
84
+ await DockerHelper.pushImage(job.resource, this.config.arch);
85
85
  }
86
86
  const plural = jobs.length !== 1;
87
87
  core_1.ux.log(`Your image${plural ? 's have' : ' has'} been successfully pushed. You can now release ${plural ? 'them' : 'it'} with the 'container:release' command.`);
@@ -7,6 +7,7 @@ const util_1 = require("@oclif/core/lib/util");
7
7
  const date_fns_1 = require("date-fns");
8
8
  const http_call_1 = require("@heroku/http-call");
9
9
  const util_2 = require("../lib/status/util");
10
+ const errorMessage = 'Heroku platform status is unavailable at this time. Refer to https://status.salesforce.com/products/Heroku or try again later.';
10
11
  const printStatus = (status) => {
11
12
  const colorize = color_1.default[status];
12
13
  let message = (0, util_1.capitalize)(status);
@@ -15,27 +16,162 @@ const printStatus = (status) => {
15
16
  }
16
17
  return colorize(message);
17
18
  };
19
+ const getTrustStatus = async () => {
20
+ const trustHost = process.env.SF_TRUST_STAGING ? 'https://status-api-stg.test.edgekey.net/v1' : 'https://api.status.salesforce.com/v1';
21
+ const currentDateTime = new Date(Date.now()).toISOString();
22
+ let instances = [];
23
+ let activeIncidents = [];
24
+ let maintenances = [];
25
+ let localizations = [];
26
+ try {
27
+ const [instanceResponse, activeIncidentsResponse, maintenancesResponse, localizationsResponse] = await Promise.all([
28
+ http_call_1.default.get(`${trustHost}/instances?products=Heroku`),
29
+ http_call_1.default.get(`${trustHost}/incidents/active`),
30
+ http_call_1.default.get(`${trustHost}/maintenances?startTime=${currentDateTime}&limit=10&offset=0&product=Heroku&locale=en`),
31
+ http_call_1.default.get(`${trustHost}/localizations?locale=en`),
32
+ ]);
33
+ instances = instanceResponse.body;
34
+ activeIncidents = activeIncidentsResponse.body;
35
+ maintenances = maintenancesResponse.body;
36
+ localizations = localizationsResponse.body;
37
+ }
38
+ catch (_a) {
39
+ core_1.ux.error(errorMessage, { exit: 1 });
40
+ }
41
+ return formatTrustResponse(instances, activeIncidents, maintenances, localizations);
42
+ };
43
+ const determineIncidentSeverity = (incidents) => {
44
+ const severityArray = [];
45
+ incidents.forEach(incident => {
46
+ incident.IncidentImpacts.forEach(impact => {
47
+ if (!impact.endTime && impact.severity) {
48
+ severityArray.push(impact.severity);
49
+ }
50
+ });
51
+ });
52
+ if (severityArray.includes('major'))
53
+ return 'red';
54
+ if (severityArray.includes('minor'))
55
+ return 'yellow';
56
+ return 'green';
57
+ };
58
+ const formatTrustResponse = (instances, activeIncidents, maintenances, localizations) => {
59
+ const systemStatus = [];
60
+ const incidents = [];
61
+ const scheduled = [];
62
+ const instanceKeyArray = new Set(instances.map(instance => instance.key));
63
+ const herokuActiveIncidents = activeIncidents.filter(incident => {
64
+ return incident.instanceKeys.some(key => instanceKeyArray.has(key));
65
+ });
66
+ const toolsIncidents = herokuActiveIncidents.filter(incident => {
67
+ const tools = ['TOOLS', 'Tools', 'CLI', 'Dashboard', 'Platform API'];
68
+ return tools.some(tool => incident.serviceKeys.includes(tool));
69
+ });
70
+ const appsIncidents = herokuActiveIncidents.filter(incident => {
71
+ return incident.serviceKeys.includes('HerokuApps') || incident.serviceKeys.includes('Apps');
72
+ });
73
+ const dataIncidents = herokuActiveIncidents.filter(incident => {
74
+ return incident.serviceKeys.includes('HerokuData') || incident.serviceKeys.includes('Data');
75
+ });
76
+ if (appsIncidents.length > 0) {
77
+ const severity = determineIncidentSeverity(appsIncidents);
78
+ systemStatus.push({ system: 'Apps', status: severity });
79
+ incidents.push(...appsIncidents);
80
+ }
81
+ else {
82
+ systemStatus.push({ system: 'Apps', status: 'green' });
83
+ }
84
+ if (dataIncidents.length > 0) {
85
+ const severity = determineIncidentSeverity(dataIncidents);
86
+ systemStatus.push({ system: 'Data', status: severity });
87
+ incidents.push(...dataIncidents);
88
+ }
89
+ else {
90
+ systemStatus.push({ system: 'Data', status: 'green' });
91
+ }
92
+ if (toolsIncidents.length > 0) {
93
+ const severity = determineIncidentSeverity(toolsIncidents);
94
+ systemStatus.push({ system: 'Tools', status: severity });
95
+ incidents.push(...toolsIncidents);
96
+ }
97
+ else {
98
+ systemStatus.push({ system: 'Tools', status: 'green' });
99
+ }
100
+ if (maintenances.length > 0)
101
+ scheduled.push(...maintenances);
102
+ if (incidents.length > 0) {
103
+ incidents.forEach(incident => {
104
+ incident.IncidentEvents.forEach(event => {
105
+ var _a;
106
+ event.localizedType = (_a = localizations.find((l) => l.modelKey === event.type)) === null || _a === void 0 ? void 0 : _a.text;
107
+ });
108
+ });
109
+ }
110
+ return {
111
+ status: systemStatus,
112
+ incidents,
113
+ scheduled,
114
+ };
115
+ };
18
116
  class Status extends core_1.Command {
19
117
  async run() {
118
+ var _a;
20
119
  const { flags } = await this.parse(Status);
21
- const apiPath = '/api/v4/current-status';
22
- const host = process.env.HEROKU_STATUS_HOST || 'https://status.heroku.com';
23
- const { body } = await http_call_1.default.get(host + apiPath);
120
+ const herokuApiPath = '/api/v4/current-status';
121
+ let herokuStatus;
122
+ let formattedTrustStatus;
123
+ if (process.env.TRUST_ONLY) {
124
+ formattedTrustStatus = await getTrustStatus();
125
+ }
126
+ else {
127
+ try {
128
+ // Try calling the Heroku status API first
129
+ const herokuHost = process.env.HEROKU_STATUS_HOST || 'https://status.heroku.com';
130
+ const herokuStatusResponse = await http_call_1.default.get(herokuHost + herokuApiPath);
131
+ herokuStatus = herokuStatusResponse.body;
132
+ }
133
+ catch (_b) {
134
+ // If the Heroku status API call fails, call the SF Trust API
135
+ formattedTrustStatus = await getTrustStatus();
136
+ }
137
+ }
138
+ if (!herokuStatus && !formattedTrustStatus)
139
+ core_1.ux.error(errorMessage, { exit: 1 });
24
140
  if (flags.json) {
25
- heroku_cli_util_1.hux.styledJSON(body);
141
+ heroku_cli_util_1.hux.styledJSON(herokuStatus !== null && herokuStatus !== void 0 ? herokuStatus : formattedTrustStatus);
26
142
  return;
27
143
  }
28
- for (const item of body.status) {
29
- const message = printStatus(item.status);
30
- this.log(`${(item.system + ':').padEnd(11)}${message}`);
144
+ const systemStatus = herokuStatus ? herokuStatus.status : formattedTrustStatus === null || formattedTrustStatus === void 0 ? void 0 : formattedTrustStatus.status;
145
+ if (systemStatus) {
146
+ for (const item of systemStatus) {
147
+ const message = printStatus(item.status);
148
+ this.log(`${(item.system + ':').padEnd(11)}${message}`);
149
+ }
150
+ }
151
+ else {
152
+ core_1.ux.error(errorMessage, { exit: 1 });
153
+ }
154
+ if (herokuStatus) {
155
+ for (const incident of herokuStatus.incidents) {
156
+ core_1.ux.log();
157
+ heroku_cli_util_1.hux.styledHeader(`${incident.title} ${color_1.default.yellow(incident.created_at)} ${color_1.default.cyan(incident.full_url)}`);
158
+ const padding = (0, util_2.getMaxUpdateTypeLength)(incident.updates.map(update => update.update_type));
159
+ for (const u of incident.updates) {
160
+ core_1.ux.log(`${color_1.default.yellow(u.update_type.padEnd(padding))} ${new Date(u.updated_at).toISOString()} (${(0, date_fns_1.formatDistanceToNow)(new Date(u.updated_at))} ago)`);
161
+ core_1.ux.log(`${u.contents}\n`);
162
+ }
163
+ }
31
164
  }
32
- for (const incident of body.incidents) {
33
- core_1.ux.log();
34
- heroku_cli_util_1.hux.styledHeader(`${incident.title} ${color_1.default.yellow(incident.created_at)} ${color_1.default.cyan(incident.full_url)}`);
35
- const padding = (0, util_2.maxBy)(incident.updates, (i) => i.update_type.length).update_type.length + 0;
36
- for (const u of incident.updates) {
37
- core_1.ux.log(`${color_1.default.yellow(u.update_type.padEnd(padding))} ${new Date(u.updated_at).toISOString()} (${(0, date_fns_1.formatDistanceToNow)(new Date(u.updated_at))} ago)`);
38
- core_1.ux.log(`${u.contents}\n`);
165
+ else if (formattedTrustStatus) {
166
+ for (const incident of formattedTrustStatus.incidents) {
167
+ core_1.ux.log();
168
+ heroku_cli_util_1.hux.styledHeader(`${incident.id} ${color_1.default.yellow(incident.createdAt)} ${color_1.default.cyan(`https://status.salesforce.com/incidents/${incident.id}`)}`);
169
+ const padding = (0, util_2.getMaxUpdateTypeLength)(incident.IncidentEvents.map(event => { var _a; return (_a = event.localizedType) !== null && _a !== void 0 ? _a : event.type; }));
170
+ for (const event of incident.IncidentEvents) {
171
+ const eventType = (_a = event.localizedType) !== null && _a !== void 0 ? _a : event.type;
172
+ core_1.ux.log(`${color_1.default.yellow(eventType.padEnd(padding))} ${new Date(event.createdAt).toISOString()} (${(0, date_fns_1.formatDistanceToNow)(new Date(event.createdAt))} ago)`);
173
+ core_1.ux.log(`${event.message}\n`);
174
+ }
39
175
  }
40
176
  }
41
177
  }
@@ -27,6 +27,6 @@ declare type BuildImageParams = {
27
27
  arch?: string;
28
28
  };
29
29
  export declare const buildImage: ({ dockerfile, resource, buildArgs, path, arch }: BuildImageParams) => Promise<string>;
30
- export declare const pushImage: (resource: string) => Promise<string>;
30
+ export declare const pushImage: (resource: string, arch: string) => Promise<string>;
31
31
  export declare const runImage: (resource: string, command: string, port: number) => Promise<string>;
32
32
  export {};
@@ -145,8 +145,14 @@ const buildImage = async function ({ dockerfile, resource, buildArgs, path, arch
145
145
  const args = ['build', '-f', dockerfile, '-t', resource];
146
146
  // Older Docker versions don't allow for this flag, but we are
147
147
  // adding it here when necessary to allow for pushing a docker build from m1/m2 Macs.
148
- if (arch === 'arm64')
148
+ if (arch === 'arm64' || arch === 'aarch64')
149
149
  args.push('--platform', 'linux/amd64');
150
+ // newer docker versions support attestations and software bill of materials, so we want to disable them to save time/space
151
+ // Heroku's container registry doesn't support pushing them right now
152
+ if (await (0, exports.version)() >= [24, 0, 0]) {
153
+ args.push('--provenance', 'false');
154
+ args.push('--sbom', 'false');
155
+ }
150
156
  for (const element of buildArgs) {
151
157
  if (element.length > 0) {
152
158
  args.push('--build-arg', element);
@@ -156,8 +162,13 @@ const buildImage = async function ({ dockerfile, resource, buildArgs, path, arch
156
162
  return (0, exports.cmd)('docker', args);
157
163
  };
158
164
  exports.buildImage = buildImage;
159
- const pushImage = async function (resource) {
165
+ const pushImage = async function (resource, arch) {
160
166
  const args = ['push', resource];
167
+ // Older Docker versions don't allow for this flag, but we are
168
+ // adding it here when necessary to allow for pushing a docker build from m1/m2 Macs.
169
+ // Heroku's container registry doesn't support pushing multi-arch images so we need to push the expected arch
170
+ if (arch === 'arm64' || arch === 'aarch64')
171
+ args.push('--platform', 'linux/amd64');
161
172
  return (0, exports.cmd)('docker', args);
162
173
  };
163
174
  exports.pushImage = pushImage;
@@ -1 +1 @@
1
- export declare function maxBy<T>(arr: T[], fn: (i: T) => number): T | undefined;
1
+ export declare function getMaxUpdateTypeLength(updateTypes: string[]): number;
@@ -1,14 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.maxBy = void 0;
4
- function maxBy(arr, fn) {
5
- let max;
6
- for (const cur of arr) {
7
- const i = fn(cur);
8
- if (!max || i > max.i) {
9
- max = { i, element: cur };
3
+ exports.getMaxUpdateTypeLength = void 0;
4
+ function getMaxUpdateTypeLength(updateTypes) {
5
+ let max = 0;
6
+ for (const update of updateTypes) {
7
+ if (!max || update.length > max) {
8
+ max = update.length;
10
9
  }
11
10
  }
12
- return max && max.element;
11
+ return max;
13
12
  }
14
- exports.maxBy = maxBy;
13
+ exports.getMaxUpdateTypeLength = getMaxUpdateTypeLength;