@heroku/sdk 0.11.0 → 0.11.2
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/dist/core/extensions-proxy.js +51 -1
- package/dist/core/with-signal.d.ts +19 -0
- package/dist/core/with-signal.js +20 -0
- package/dist/resources/data/database.js +19 -6
- package/dist/resources/data/internal/postgres-utils.d.ts +10 -6
- package/dist/resources/data/internal/postgres-utils.js +11 -7
- package/dist/resources/data/internal/resolve-pg-database.d.ts +24 -21
- package/dist/resources/data/internal/resolve-pg-database.js +148 -85
- package/dist/resources/data/maintenance.js +4 -1
- package/dist/resources/data/postgres-database.d.ts +1 -0
- package/dist/resources/data/postgres-database.js +5 -1
- package/dist/resources/data/redis/resolve-by-app.js +2 -1
- package/dist/resources/data/redis/wait-for-ready.d.ts +1 -1
- package/dist/resources/data/redis/wait-for-ready.js +3 -2
- package/dist/resources/data/transfer/capture-and-wait.js +4 -1
- package/dist/resources/data/transfer/restore-and-wait.js +4 -1
- package/dist/resources/data/transfer/wait-for-transfer.js +8 -1
- package/dist/resources/data/transfer-schedule.js +12 -5
- package/dist/resources/extensions/platform.d.ts +3 -0
- package/dist/resources/extensions/platform.js +3 -0
- package/dist/resources/platform/account.d.ts +22 -0
- package/dist/resources/platform/account.js +34 -0
- package/dist/resources/platform/add-on/create-and-wait.js +5 -1
- package/dist/resources/platform/add-on/describe.js +5 -2
- package/dist/resources/platform/add-on/destroy-and-wait.js +2 -3
- package/dist/resources/platform/add-on/list-plans.js +8 -2
- package/dist/resources/platform/add-on/resolve.js +5 -1
- package/dist/resources/platform/add-on/upgrade.js +5 -2
- package/dist/resources/platform/add-on/wait-for-provisioning.js +4 -1
- package/dist/resources/platform/add-on-attachment/resolve.js +2 -3
- package/dist/resources/platform/app/create-and-setup.d.ts +3 -4
- package/dist/resources/platform/app/create-and-setup.js +5 -5
- package/dist/resources/platform/app/diff.js +2 -3
- package/dist/resources/platform/app/index.js +9 -3
- package/dist/resources/platform/app/info.d.ts +4 -0
- package/dist/resources/platform/app/info.js +21 -7
- package/dist/resources/platform/app/transfer.js +2 -1
- package/dist/resources/platform/app/wait-for-acm-certificates.js +2 -1
- package/dist/resources/platform/container/ensure-container-stack.js +2 -1
- package/dist/resources/platform/container/release-images.js +2 -1
- package/dist/resources/platform/container/remove-process-types.js +2 -1
- package/dist/resources/platform/delinquency.d.ts +23 -0
- package/dist/resources/platform/delinquency.js +41 -0
- package/dist/resources/platform/domain/clear-custom-domains.js +2 -1
- package/dist/resources/platform/domain/create-and-wait.js +2 -1
- package/dist/resources/platform/domain/wait-for-ready.js +2 -1
- package/dist/resources/platform/dyno/exec.js +4 -3
- package/dist/resources/platform/dyno/index.js +6 -3
- package/dist/resources/platform/dyno/run.js +2 -1
- package/dist/resources/platform/dyno/wait-for-info.js +2 -1
- package/dist/resources/platform/dyno/wait-for-release.js +2 -1
- package/dist/resources/platform/log-session/index.js +6 -1
- package/dist/resources/platform/pipeline/coupling.js +5 -2
- package/dist/resources/platform/pipeline/index.js +4 -1
- package/dist/resources/platform/pipeline/promotion.js +15 -7
- package/dist/resources/platform/review-app-config/resolve-repo-name.js +4 -3
- package/dist/resources/platform/sni-endpoint/create-and-associate.js +2 -1
- package/dist/resources/platform/space/wait-for-allocated.js +2 -4
- package/dist/resources/platform/team.d.ts +22 -0
- package/dist/resources/platform/team.js +34 -0
- package/dist/resources/platform/telemetry-drain/remove-drains-for-target.js +2 -1
- package/dist/resources/platform/test-run/wait-for-state.js +2 -1
- package/dist/resources/platform/vpn-connection/wait-for-active.js +2 -1
- package/dist/utils/is-abort-error.d.ts +11 -0
- package/dist/utils/is-abort-error.js +14 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import createDebug from 'debug';
|
|
2
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
3
|
import { RedisAddonAmbiguousError, RedisAddonNotFoundError } from './errors.js';
|
|
3
4
|
const debug = createDebug('heroku:sdk:resources:redis');
|
|
4
5
|
const DEFAULT_REDIS_ADDON_SERVICE_PREFIX = 'heroku-redis';
|
|
@@ -19,7 +20,7 @@ const DEFAULT_REDIS_ADDON_SERVICE_PREFIX = 'heroku-redis';
|
|
|
19
20
|
export async function resolveRedisByApp(ctx, appIdentity, options = {}) {
|
|
20
21
|
options.signal?.throwIfAborted();
|
|
21
22
|
const { addonServiceName = DEFAULT_REDIS_ADDON_SERVICE_PREFIX, database } = options;
|
|
22
|
-
const platform =
|
|
23
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
23
24
|
debug('resolveByApp app=%s service-prefix=%s database=%s', appIdentity, addonServiceName, database ?? '<any>');
|
|
24
25
|
const addons = await platform.addOn.listByApp(appIdentity);
|
|
25
26
|
const filter = database?.toUpperCase();
|
|
@@ -26,7 +26,7 @@ export type WaitForRedisReadyOptions = {
|
|
|
26
26
|
* provision call resolves.
|
|
27
27
|
*
|
|
28
28
|
* Cancellation: the caller's `signal` is threaded into both the
|
|
29
|
-
* in-flight request (via `
|
|
29
|
+
* in-flight request (via `withSignal`) and the poll-delay `wait()`,
|
|
30
30
|
* so aborting takes effect during either phase.
|
|
31
31
|
*/
|
|
32
32
|
export declare function waitForRedisReady(ctx: Pick<ResourceCtx, 'data'>, nameOrId: string, options?: WaitForRedisReadyOptions): Promise<RedisWaitResult>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NotFoundError } from '@heroku/heroku-fetch';
|
|
2
2
|
import createDebug from 'debug';
|
|
3
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
3
4
|
import { wait } from '../../../utils/wait.js';
|
|
4
5
|
const debug = createDebug('heroku:sdk:resources:redis');
|
|
5
6
|
const DEFAULT_INTERVAL_MS = 5000;
|
|
@@ -13,13 +14,13 @@ const DEFAULT_INTERVAL_MS = 5000;
|
|
|
13
14
|
* provision call resolves.
|
|
14
15
|
*
|
|
15
16
|
* Cancellation: the caller's `signal` is threaded into both the
|
|
16
|
-
* in-flight request (via `
|
|
17
|
+
* in-flight request (via `withSignal`) and the poll-delay `wait()`,
|
|
17
18
|
* so aborting takes effect during either phase.
|
|
18
19
|
*/
|
|
19
20
|
export async function waitForRedisReady(ctx, nameOrId, options = {}) {
|
|
20
21
|
const { intervalMs = DEFAULT_INTERVAL_MS, signal, timeoutMs } = options;
|
|
21
22
|
signal?.throwIfAborted();
|
|
22
|
-
const data =
|
|
23
|
+
const data = withSignal(ctx.data, signal);
|
|
23
24
|
const deadline = timeoutMs === undefined ? undefined : Date.now() + timeoutMs;
|
|
24
25
|
debug('waitForReady name=%s intervalMs=%d timeoutMs=%s', nameOrId, intervalMs, timeoutMs ?? '<none>');
|
|
25
26
|
while (true) {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { resolvePgDatabase } from '../internal/resolve-pg-database.js';
|
|
2
3
|
import { waitForTransfer } from './wait-for-transfer.js';
|
|
3
4
|
export async function captureAndWait(ctx, appIdentity, addonIdentity, options = {}) {
|
|
4
5
|
const { capturePoller, signal, waitPoller } = options;
|
|
5
6
|
signal?.throwIfAborted();
|
|
6
7
|
const addon = await resolvePgDatabase(ctx, appIdentity, { input: addonIdentity, ...options });
|
|
8
|
+
// Bind the signal so the create request aborts in flight, not just the wait.
|
|
9
|
+
const data = withSignal(ctx.data, signal);
|
|
7
10
|
capturePoller?.onStart?.(addon);
|
|
8
|
-
const backup = await
|
|
11
|
+
const backup = await data.backup.create(addon.id);
|
|
9
12
|
capturePoller?.onStop?.(addon);
|
|
10
13
|
waitPoller?.onStart?.(backup);
|
|
11
14
|
const transfer = await waitForTransfer(ctx, addon.app.name, backup.uuid, options);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { resolvePgDatabase } from '../internal/resolve-pg-database.js';
|
|
2
3
|
import { waitForTransfer } from './wait-for-transfer.js';
|
|
3
4
|
export async function restoreAndWait(ctx, appIdentity, addonIdentity, backupUrl, options = {}) {
|
|
4
5
|
const { extensions, restorePoller, signal, waitPoller } = options;
|
|
5
6
|
signal?.throwIfAborted();
|
|
6
7
|
const addon = await resolvePgDatabase(ctx, appIdentity, { input: addonIdentity, ...options });
|
|
8
|
+
// Bind the signal so the create request aborts in flight, not just the wait.
|
|
9
|
+
const data = withSignal(ctx.data, signal);
|
|
7
10
|
restorePoller?.onStart?.(addon);
|
|
8
11
|
// eslint-disable-next-line camelcase
|
|
9
|
-
const restore = await
|
|
12
|
+
const restore = await data.restore.create(addon.id, { backup_url: backupUrl, extensions });
|
|
10
13
|
restorePoller?.onStop?.(addon);
|
|
11
14
|
waitPoller?.onStart?.(restore);
|
|
12
15
|
const transfer = await waitForTransfer(ctx, addon.app.name, restore.uuid, options);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
3
|
+
import { isAbortError } from '../../../utils/is-abort-error.js';
|
|
2
4
|
import { wait } from '../../../utils/wait.js';
|
|
3
5
|
const DEFAULT_INTERVAL_MS = 3000;
|
|
4
6
|
const MAX_FAILURES = 20;
|
|
@@ -46,7 +48,7 @@ export class TransferTimeoutError extends Error {
|
|
|
46
48
|
export async function waitForTransfer(ctx, appIdentity, transferId, options = {}) {
|
|
47
49
|
const { intervalMs = DEFAULT_INTERVAL_MS, onPoll, signal, timeoutMs, verbose, } = options;
|
|
48
50
|
signal?.throwIfAborted();
|
|
49
|
-
const data =
|
|
51
|
+
const data = withSignal(ctx.data, signal);
|
|
50
52
|
const deadline = timeoutMs === undefined ? undefined : Date.now() + timeoutMs;
|
|
51
53
|
let failures = 0;
|
|
52
54
|
let transfer;
|
|
@@ -56,6 +58,11 @@ export async function waitForTransfer(ctx, appIdentity, transferId, options = {}
|
|
|
56
58
|
transfer = await data.transfer.infoByApp(appIdentity, transferId, { verbose });
|
|
57
59
|
}
|
|
58
60
|
catch (error) {
|
|
61
|
+
// Never retry a cancellation: a client- or caller-level abort must stop
|
|
62
|
+
// the poll immediately rather than burn up to MAX_FAILURES attempts.
|
|
63
|
+
if (signal?.aborted || isAbortError(error)) {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
59
66
|
failures++;
|
|
60
67
|
if (failures > MAX_FAILURES) {
|
|
61
68
|
throw error;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { extendResource } from '../../core/extend-resource.js';
|
|
2
|
-
import {
|
|
2
|
+
import { withSignal } from '../../core/with-signal.js';
|
|
3
|
+
import { findLegacyDatabase } from './internal/postgres-utils.js';
|
|
3
4
|
import { resolvePgDatabase } from './internal/resolve-pg-database.js';
|
|
4
5
|
export async function list(ctx, appIdentity, options = {}) {
|
|
5
6
|
options.signal?.throwIfAborted();
|
|
6
7
|
// Schedules are app-scoped, so any legacy DB ID unlocks the app's list.
|
|
7
|
-
const addon = await
|
|
8
|
-
|
|
8
|
+
const addon = await findLegacyDatabase(ctx, appIdentity);
|
|
9
|
+
// Bind the signal so the request aborts in flight, not just during resolution.
|
|
10
|
+
const data = withSignal(ctx.data, options.signal);
|
|
11
|
+
return data.transferSchedule.list(addon.id);
|
|
9
12
|
}
|
|
10
13
|
export async function create(ctx, appIdentity, addonIdentity, body, options = {}) {
|
|
11
14
|
options.signal?.throwIfAborted();
|
|
@@ -17,12 +20,16 @@ export async function create(ctx, appIdentity, addonIdentity, body, options = {}
|
|
|
17
20
|
// eslint-disable-next-line camelcase
|
|
18
21
|
requestBody = { ...body, schedule_name: `${attachment.name}_URL` };
|
|
19
22
|
}
|
|
20
|
-
|
|
23
|
+
// Bind the signal so the request aborts in flight, not just during resolution.
|
|
24
|
+
const data = withSignal(ctx.data, options.signal);
|
|
25
|
+
return data.transferSchedule.create(addon.id, requestBody);
|
|
21
26
|
}
|
|
22
27
|
export async function del(ctx, appIdentity, addonIdentity, scheduleId, options = {}) {
|
|
23
28
|
options.signal?.throwIfAborted();
|
|
24
29
|
const addon = await resolvePgDatabase(ctx, appIdentity, { input: addonIdentity, ...options });
|
|
25
|
-
|
|
30
|
+
// Bind the signal so the request aborts in flight, not just during resolution.
|
|
31
|
+
const data = withSignal(ctx.data, options.signal);
|
|
32
|
+
return data.transferSchedule.delete(addon.id, scheduleId);
|
|
26
33
|
}
|
|
27
34
|
export const transferScheduleExtensions = extendResource('data', 'transferSchedule', ctx => ({
|
|
28
35
|
create: (appIdentity, addonIdentity, body, options) => create(ctx, appIdentity, addonIdentity, body, options),
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export { accountExtensions, type AccountWithDelinquency, type GetAccountWithDelinquencyOptions } from '../platform/account.js';
|
|
1
2
|
export { addOnAttachmentExtensions } from '../platform/add-on-attachment/index.js';
|
|
2
3
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
3
4
|
export { appExtensions, type AppInfo, type DiffAppsOptions, type DiffRow, type PipelineCouplingDetail, } from '../platform/app/index.js';
|
|
4
5
|
export { containerExtensions, type DockerReleasesBatchUpdateOpts, NotAContainerAppError, type ReleaseDockerImagesResult, type RemoveProcessTypesOpts, } from '../platform/container/index.js';
|
|
6
|
+
export { type DelinquencyResource, type DelinquencySchedule, formatDelinquencyWarning, } from '../platform/delinquency.js';
|
|
5
7
|
export { type CreateAndWaitOptions, domainExtensions, type DomainOptions, type WaitForReadyOptions, } from '../platform/domain/index.js';
|
|
6
8
|
export { DynoCrashedError, type DynoExtended, type DynoExtendedFields, dynoExtensions, DynoNotReadyError, type DynoState, enableExec, exchangeExecCredentials, type ExchangeExecCredentialsOptions, type ExecCredentials, type ExecOptions, type ExecPrereqs, execPrereqs, type ExecReservation, execStatus, type ExecStatusOptions, listExtended, type ListExtendedOptions, restartForExec, type RestartForExecOptions, runDyno, type RunDynoOptions, waitForInfo, type WaitForInfoOptions, waitForRelease, type WaitForReleaseOptions, type WaitForReleaseProgress, type WaitForReleaseResult, } from '../platform/dyno/index.js';
|
|
7
9
|
export { type AttachmentAttachedEvent, type AttachmentDetachedEvent, type AttachmentUpdatedEvent, type HerokuLogEvent, logSessionExtensions, parseHerokuLogLine, type ProvisioningCompletedEvent, type ScaledToEntry, type ScaledToEvent, type StartingProcessEvent, type StateChangedEvent, } from '../platform/log-session/index.js';
|
|
@@ -12,6 +14,7 @@ export { type ResolveRepoNameOptions, reviewAppConfigExtensions } from '../platf
|
|
|
12
14
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
13
15
|
export { createAndAssociate, type CreateAndAssociateOptions, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
14
16
|
export { spaceExtensions, SpaceNotAllocatedError, SpaceNotReadyError, type SpaceOptions, type SpaceWithNat, waitForAllocated, type WaitForAllocatedOptions, } from '../platform/space/index.js';
|
|
17
|
+
export { type GetTeamWithDelinquencyOptions, teamExtensions, type TeamWithDelinquency } from '../platform/team.js';
|
|
15
18
|
export { telemetryDrainExtensions, type TelemetryDrainOptions, type TelemetryDrainTarget } from '../platform/telemetry-drain/index.js';
|
|
16
19
|
export { testRunExtensions, TestRunNotReadyError, type TestRunStatus, waitForState, type WaitForStateOptions, } from '../platform/test-run/index.js';
|
|
17
20
|
export { vpnConnectionExtensions, VpnConnectionFailedError, VpnConnectionNotReadyError, type VpnConnectionOptions, waitForActive, type WaitForActiveOptions, } from '../platform/vpn-connection/index.js';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export { accountExtensions } from '../platform/account.js';
|
|
1
2
|
export { addOnAttachmentExtensions } from '../platform/add-on-attachment/index.js';
|
|
2
3
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
3
4
|
export { appExtensions, } from '../platform/app/index.js';
|
|
4
5
|
export { containerExtensions, NotAContainerAppError, } from '../platform/container/index.js';
|
|
6
|
+
export { formatDelinquencyWarning, } from '../platform/delinquency.js';
|
|
5
7
|
export { domainExtensions, } from '../platform/domain/index.js';
|
|
6
8
|
export { DynoCrashedError, dynoExtensions, DynoNotReadyError, enableExec, exchangeExecCredentials, execPrereqs, execStatus, listExtended, restartForExec, runDyno, waitForInfo, waitForRelease, } from '../platform/dyno/index.js';
|
|
7
9
|
export { logSessionExtensions, parseHerokuLogLine, } from '../platform/log-session/index.js';
|
|
@@ -12,6 +14,7 @@ export { reviewAppConfigExtensions } from '../platform/review-app-config/index.j
|
|
|
12
14
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
13
15
|
export { createAndAssociate, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
14
16
|
export { spaceExtensions, SpaceNotAllocatedError, SpaceNotReadyError, waitForAllocated, } from '../platform/space/index.js';
|
|
17
|
+
export { teamExtensions } from '../platform/team.js';
|
|
15
18
|
export { telemetryDrainExtensions } from '../platform/telemetry-drain/index.js';
|
|
16
19
|
export { testRunExtensions, TestRunNotReadyError, waitForState, } from '../platform/test-run/index.js';
|
|
17
20
|
export { vpnConnectionExtensions, VpnConnectionFailedError, VpnConnectionNotReadyError, waitForActive, } from '../platform/vpn-connection/index.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Account, AccountDelinquency } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../core/extend-resource.js';
|
|
3
|
+
export type GetAccountWithDelinquencyOptions = {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
};
|
|
6
|
+
export type AccountWithDelinquency = {
|
|
7
|
+
account: Account;
|
|
8
|
+
delinquency: AccountDelinquency | null;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Fetch the authenticated account together with its delinquency status.
|
|
12
|
+
*
|
|
13
|
+
* Runs the two reads concurrently. The account lookup hard-fails — there is no
|
|
14
|
+
* result without it. The delinquency lookup soft-fails to `null`: a hiccup on
|
|
15
|
+
* that secondary call must not sink an otherwise-successful account fetch (it
|
|
16
|
+
* backs commands like `whoami`). Callers decide whether and how to surface a
|
|
17
|
+
* warning from `delinquency` — `formatDelinquencyWarning` renders the sentence.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getAccountWithDelinquency(ctx: Pick<ResourceCtx, 'platform'>, options?: GetAccountWithDelinquencyOptions): Promise<AccountWithDelinquency>;
|
|
20
|
+
export declare const accountExtensions: import("../../core/extend-resource.js").ResourceExtension<"platform", "account", {
|
|
21
|
+
getWithDelinquency: (options?: GetAccountWithDelinquencyOptions) => Promise<AccountWithDelinquency>;
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import createDebug from 'debug';
|
|
2
|
+
import { extendResource } from '../../core/extend-resource.js';
|
|
3
|
+
const debug = createDebug('heroku:sdk:resources:account');
|
|
4
|
+
/**
|
|
5
|
+
* Fetch the authenticated account together with its delinquency status.
|
|
6
|
+
*
|
|
7
|
+
* Runs the two reads concurrently. The account lookup hard-fails — there is no
|
|
8
|
+
* result without it. The delinquency lookup soft-fails to `null`: a hiccup on
|
|
9
|
+
* that secondary call must not sink an otherwise-successful account fetch (it
|
|
10
|
+
* backs commands like `whoami`). Callers decide whether and how to surface a
|
|
11
|
+
* warning from `delinquency` — `formatDelinquencyWarning` renders the sentence.
|
|
12
|
+
*/
|
|
13
|
+
export async function getAccountWithDelinquency(ctx, options = {}) {
|
|
14
|
+
options.signal?.throwIfAborted();
|
|
15
|
+
const platform = options.signal
|
|
16
|
+
? ctx.platform.withOptions({ signal: options.signal })
|
|
17
|
+
: ctx.platform;
|
|
18
|
+
const [account, delinquency] = await Promise.all([
|
|
19
|
+
platform.account.info(),
|
|
20
|
+
platform.accountDelinquency.info().catch((error) => {
|
|
21
|
+
// Soft-fail a secondary-call hiccup to null, but rethrow a cancellation:
|
|
22
|
+
// if the caller aborted, returning null would misreport a delinquent
|
|
23
|
+
// account as current. Anything else is logged and swallowed.
|
|
24
|
+
if (options.signal?.aborted)
|
|
25
|
+
throw error;
|
|
26
|
+
debug('account delinquency lookup failed; treating as not delinquent: %O', error);
|
|
27
|
+
return null;
|
|
28
|
+
}),
|
|
29
|
+
]);
|
|
30
|
+
return { account, delinquency };
|
|
31
|
+
}
|
|
32
|
+
export const accountExtensions = extendResource('platform', 'account', ctx => ({
|
|
33
|
+
getWithDelinquency: (options) => getAccountWithDelinquency(ctx, options),
|
|
34
|
+
}));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HerokuApiError } from '@heroku/heroku-fetch';
|
|
2
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
3
|
import { wait } from '../../../utils/wait.js';
|
|
3
4
|
import { AddonConfirmationRequiredError, AddonProvisioningFailedError } from './errors.js';
|
|
4
5
|
const DEFAULT_CREATE_WAIT_INTERVAL_MS = 5000;
|
|
@@ -28,7 +29,10 @@ const DEFAULT_CREATE_WAIT_INTERVAL_MS = 5000;
|
|
|
28
29
|
*/
|
|
29
30
|
export async function createAndWait(ctx, appIdentity, body, options = {}) {
|
|
30
31
|
options.signal?.throwIfAborted();
|
|
31
|
-
|
|
32
|
+
// Bind the signal to the client so the create and poll requests abort in
|
|
33
|
+
// flight, not just the delay between polls.
|
|
34
|
+
const platform = withSignal(ctx.platform, options.signal)
|
|
35
|
+
.withHeaders({ 'Accept-Expansion': 'addon_service,plan' });
|
|
32
36
|
let addon;
|
|
33
37
|
try {
|
|
34
38
|
addon = await platform
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { resolveAddonInternal } from './resolve.js';
|
|
2
3
|
/**
|
|
3
4
|
* Resolve, describe, and decorate an add-on for display.
|
|
@@ -13,11 +14,13 @@ import { resolveAddonInternal } from './resolve.js';
|
|
|
13
14
|
*/
|
|
14
15
|
export async function describeAddon(ctx, addonIdentity, options = {}) {
|
|
15
16
|
options.signal?.throwIfAborted();
|
|
17
|
+
// Bind the signal so both requests abort in flight, not just at the checkpoints.
|
|
18
|
+
const base = withSignal(ctx.platform, options.signal);
|
|
16
19
|
// Accept-Expansion is scoped to the resolve call (addOn.resolution
|
|
17
20
|
// serializer accepts addon_service + plan expansions). The
|
|
18
21
|
// /addons/:id/addon-attachments endpoint uses Accept-Inclusion, not
|
|
19
22
|
// Accept-Expansion, and rejects an unknown expansion header.
|
|
20
|
-
const resolvingPlatform =
|
|
23
|
+
const resolvingPlatform = base.withHeaders({
|
|
21
24
|
Accept: 'application/vnd.heroku+json; version=3.sdk',
|
|
22
25
|
'Accept-Expansion': 'addon_service,plan',
|
|
23
26
|
});
|
|
@@ -26,7 +29,7 @@ export async function describeAddon(ctx, addonIdentity, options = {}) {
|
|
|
26
29
|
appIdentity: options.appIdentity,
|
|
27
30
|
});
|
|
28
31
|
options.signal?.throwIfAborted();
|
|
29
|
-
const attachments = await
|
|
32
|
+
const attachments = await base.addOnAttachment.listByAddOn(addon.id);
|
|
30
33
|
const plan = addon.plan;
|
|
31
34
|
return {
|
|
32
35
|
...addon,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { wait } from '../../../utils/wait.js';
|
|
2
3
|
import { AddonProvisioningFailedError } from './errors.js';
|
|
3
4
|
const DEFAULT_DESTROY_WAIT_INTERVAL_MS = 5000;
|
|
@@ -21,9 +22,7 @@ const DEFAULT_DESTROY_WAIT_INTERVAL_MS = 5000;
|
|
|
21
22
|
*/
|
|
22
23
|
export async function destroyAndWait(ctx, appIdentity, addonIdentity, options = {}) {
|
|
23
24
|
options.signal?.throwIfAborted();
|
|
24
|
-
const platform = ctx.platform
|
|
25
|
-
.withHeaders({ 'Accept-Expansion': 'plan' })
|
|
26
|
-
.withOptions({ signal: options.signal });
|
|
25
|
+
const platform = withSignal(ctx.platform.withHeaders({ 'Accept-Expansion': 'plan' }), options.signal);
|
|
27
26
|
// The force body is scoped to the delete call only — a sticky body on the
|
|
28
27
|
// shared client would leak onto the GET poll requests below.
|
|
29
28
|
const deleteClient = options.force
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { AddonNotFoundError } from './errors.js';
|
|
2
3
|
import { resolveAddonInternal } from './resolve.js';
|
|
3
4
|
/**
|
|
@@ -7,7 +8,9 @@ import { resolveAddonInternal } from './resolve.js';
|
|
|
7
8
|
*/
|
|
8
9
|
export async function listPlans(ctx, serviceIdentity, options = {}) {
|
|
9
10
|
options.signal?.throwIfAborted();
|
|
10
|
-
|
|
11
|
+
// Bind the signal so the request aborts in flight.
|
|
12
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
13
|
+
const plans = await platform.plan.listByAddOn(serviceIdentity);
|
|
11
14
|
return [...plans].sort(byPriceCentsAsc);
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
@@ -26,7 +29,10 @@ export async function listPlans(ctx, serviceIdentity, options = {}) {
|
|
|
26
29
|
*/
|
|
27
30
|
export async function listPlansForAddon(ctx, addonIdentity, options = {}) {
|
|
28
31
|
options.signal?.throwIfAborted();
|
|
29
|
-
|
|
32
|
+
// Bind the signal so the resolve request aborts in flight (the helper itself
|
|
33
|
+
// doesn't thread options.signal into the client).
|
|
34
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
35
|
+
const addon = await resolveAddonInternal(platform, addonIdentity, {
|
|
30
36
|
appIdentity: options.appIdentity,
|
|
31
37
|
});
|
|
32
38
|
// The route accepts either name or id; name is stable across
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { debug } from './debug.js';
|
|
2
3
|
import { AddonAmbiguousError, AddonNotFoundError } from './errors.js';
|
|
3
4
|
/**
|
|
@@ -25,7 +26,10 @@ import { AddonAmbiguousError, AddonNotFoundError } from './errors.js';
|
|
|
25
26
|
*/
|
|
26
27
|
export async function resolveAddon(ctx, addonIdentity, options = {}) {
|
|
27
28
|
options.signal?.throwIfAborted();
|
|
28
|
-
|
|
29
|
+
// Bind the signal so the resolve request aborts in flight (the helper itself
|
|
30
|
+
// doesn't thread options.signal into the client).
|
|
31
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
32
|
+
return resolveAddonInternal(platform, addonIdentity, options);
|
|
29
33
|
}
|
|
30
34
|
export async function resolveAddonInternal(platform, addonIdentity, options = {}) {
|
|
31
35
|
const { addonService, appIdentity } = options;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { debug } from './debug.js';
|
|
2
3
|
import { resolveAddonInternal } from './resolve.js';
|
|
3
4
|
/**
|
|
@@ -18,7 +19,9 @@ import { resolveAddonInternal } from './resolve.js';
|
|
|
18
19
|
*/
|
|
19
20
|
export async function upgrade(ctx, addonIdentity, plan, options = {}) {
|
|
20
21
|
options.signal?.throwIfAborted();
|
|
21
|
-
|
|
22
|
+
// Bind the signal so the resolve and update requests abort in flight.
|
|
23
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
24
|
+
const addon = await resolveAddonInternal(platform, addonIdentity, {
|
|
22
25
|
addonService: options.addonService,
|
|
23
26
|
appIdentity: options.appIdentity,
|
|
24
27
|
});
|
|
@@ -31,5 +34,5 @@ export async function upgrade(ctx, addonIdentity, plan, options = {}) {
|
|
|
31
34
|
debug('upgrade plan qualified plan=%s qualified=%s', plan, qualifiedPlan);
|
|
32
35
|
}
|
|
33
36
|
debug('upgrade addon=%s app=%s plan=%s', addon.id, addon.app.id, qualifiedPlan);
|
|
34
|
-
return
|
|
37
|
+
return platform.addOn.update(addon.app.id, addon.id, { plan: qualifiedPlan });
|
|
35
38
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { wait } from '../../../utils/wait.js';
|
|
2
3
|
import { AddonProvisioningFailedError } from './errors.js';
|
|
3
4
|
const DEFAULT_WAIT_INTERVAL_MS = 5000;
|
|
@@ -19,7 +20,9 @@ const DEFAULT_WAIT_INTERVAL_MS = 5000;
|
|
|
19
20
|
export async function waitForProvisioning(ctx, addon, options = {}) {
|
|
20
21
|
options.signal?.throwIfAborted();
|
|
21
22
|
const intervalMs = options.waitIntervalMs ?? DEFAULT_WAIT_INTERVAL_MS;
|
|
22
|
-
|
|
23
|
+
// Bind the signal so each poll request aborts in flight, not just the delay.
|
|
24
|
+
const platform = withSignal(ctx.platform, options.signal)
|
|
25
|
+
.withHeaders({ 'Accept-Expansion': 'addon_service,plan' });
|
|
23
26
|
let current = addon;
|
|
24
27
|
/* eslint-disable no-await-in-loop */
|
|
25
28
|
while (current.state === 'provisioning' || current.state === 'deprovisioning') {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import createDebug from 'debug';
|
|
2
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
3
|
const debug = createDebug('heroku:sdk:resources:add-on-attachment');
|
|
3
4
|
export class AddonAttachmentNotFoundError extends Error {
|
|
4
5
|
resource;
|
|
@@ -47,9 +48,7 @@ export async function resolveAddonAttachment(ctx, appIdentity, attachmentName, o
|
|
|
47
48
|
const { addonService, signal } = options;
|
|
48
49
|
signal?.throwIfAborted();
|
|
49
50
|
debug('resolve app=%s attachment=%s service=%s', appIdentity ?? '<global>', attachmentName, addonService ?? '<any>');
|
|
50
|
-
const platform = ctx.platform
|
|
51
|
-
.withHeaders({ 'Accept-Inclusion': 'addon:plan,config_vars' })
|
|
52
|
-
.withOptions({ signal });
|
|
51
|
+
const platform = withSignal(ctx.platform.withHeaders({ 'Accept-Inclusion': 'addon:plan,config_vars' }), signal);
|
|
53
52
|
const matches = await platform.addOnAttachment.resolution({
|
|
54
53
|
// eslint-disable-next-line camelcase
|
|
55
54
|
addon_attachment: attachmentName,
|
|
@@ -39,10 +39,9 @@ export type CreateAndSetupInput = {
|
|
|
39
39
|
* spinner. Per the poller contract, `onStart` fires BEFORE any request is
|
|
40
40
|
* dispatched, and `onStop` fires ONLY on success — if a step rejects, the
|
|
41
41
|
* error propagates and `onStop` is never called (there is no try/finally, so
|
|
42
|
-
* the caller's error path stops the spinner). Signal threading
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* pre-flight `throwIfAborted()`.
|
|
42
|
+
* the caller's error path stops the spinner). Signal threading binds the
|
|
43
|
+
* signal to a scoped client (via `withSignal`) so an abort cancels in-flight
|
|
44
|
+
* requests, not just the pre-flight `throwIfAborted()`.
|
|
46
45
|
*
|
|
47
46
|
* The CLI parses flags / heroku.yml into `input` and owns git-remote setup
|
|
48
47
|
* and rendering; this method owns the API orchestration only.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
/**
|
|
2
3
|
* Create an app and run its post-create setup in one call.
|
|
3
4
|
*
|
|
@@ -13,17 +14,16 @@
|
|
|
13
14
|
* spinner. Per the poller contract, `onStart` fires BEFORE any request is
|
|
14
15
|
* dispatched, and `onStop` fires ONLY on success — if a step rejects, the
|
|
15
16
|
* error propagates and `onStop` is never called (there is no try/finally, so
|
|
16
|
-
* the caller's error path stops the spinner). Signal threading
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* pre-flight `throwIfAborted()`.
|
|
17
|
+
* the caller's error path stops the spinner). Signal threading binds the
|
|
18
|
+
* signal to a scoped client (via `withSignal`) so an abort cancels in-flight
|
|
19
|
+
* requests, not just the pre-flight `throwIfAborted()`.
|
|
20
20
|
*
|
|
21
21
|
* The CLI parses flags / heroku.yml into `input` and owns git-remote setup
|
|
22
22
|
* and rendering; this method owns the API orchestration only.
|
|
23
23
|
*/
|
|
24
24
|
export async function createAndSetup(ctx, input, options = {}) {
|
|
25
25
|
options.signal?.throwIfAborted();
|
|
26
|
-
const platform =
|
|
26
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
27
27
|
const { addons, buildpack, configVars, ...createParams } = input;
|
|
28
28
|
const app = (createParams.space || createParams.team)
|
|
29
29
|
? (await platform.teamApp.create(createParams))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NotFoundError } from '@heroku/heroku-fetch';
|
|
2
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
3
|
async function checksum(platform, app) {
|
|
3
4
|
try {
|
|
4
5
|
const releases = await platform.withHeaders({ Range: 'version ..; max=1, order=desc' }).release.list(app);
|
|
@@ -95,9 +96,7 @@ async function diffFeatures(platform, app1, app2) {
|
|
|
95
96
|
*/
|
|
96
97
|
export async function diffApps(ctx, app1, app2, options = {}) {
|
|
97
98
|
options.signal?.throwIfAborted();
|
|
98
|
-
const platform = options.signal
|
|
99
|
-
? ctx.platform.withOptions({ signal: options.signal })
|
|
100
|
-
: ctx.platform;
|
|
99
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
101
100
|
const files = await diffFiles(platform, app1, app2);
|
|
102
101
|
const [env, stack, bp, addons, features] = await Promise.all([
|
|
103
102
|
diffEnv(platform, app1, app2),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
3
|
import { createAndSetup } from './create-and-setup.js';
|
|
3
4
|
import { diffApps } from './diff.js';
|
|
4
5
|
import { describeApp } from './info.js';
|
|
@@ -11,11 +12,15 @@ export { transferApp } from './transfer.js';
|
|
|
11
12
|
export { waitForACMCertificates } from './wait-for-acm-certificates.js';
|
|
12
13
|
export async function enableMaintenance(ctx, appIdentity, options = {}) {
|
|
13
14
|
options.signal?.throwIfAborted();
|
|
14
|
-
|
|
15
|
+
// Bind the signal so the request aborts in flight.
|
|
16
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
17
|
+
return platform.app.update(appIdentity, { maintenance: true });
|
|
15
18
|
}
|
|
16
19
|
export async function disableMaintenance(ctx, appIdentity, options = {}) {
|
|
17
20
|
options.signal?.throwIfAborted();
|
|
18
|
-
|
|
21
|
+
// Bind the signal so the request aborts in flight.
|
|
22
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
23
|
+
return platform.app.update(appIdentity, { maintenance: false });
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
21
26
|
* Look up the platform generation (`cedar` or `fir`) of an app.
|
|
@@ -28,7 +33,8 @@ export async function disableMaintenance(ctx, appIdentity, options = {}) {
|
|
|
28
33
|
*/
|
|
29
34
|
export async function getGeneration(ctx, appIdentity, options = {}) {
|
|
30
35
|
options.signal?.throwIfAborted();
|
|
31
|
-
|
|
36
|
+
// Bind the signal so the request aborts in flight.
|
|
37
|
+
const platform = withSignal(ctx.platform, options.signal).withHeaders({
|
|
32
38
|
Accept: 'application/vnd.heroku+json; version=3.sdk',
|
|
33
39
|
});
|
|
34
40
|
const app = await platform.app.info(appIdentity);
|
|
@@ -26,5 +26,9 @@ export type AppInfo = {
|
|
|
26
26
|
* any error collapses to an empty default. The broad .catch is
|
|
27
27
|
* intentional v1 fidelity with the Heroku CLI's apps:info; a future
|
|
28
28
|
* tightening to specific status codes is non-breaking.
|
|
29
|
+
*
|
|
30
|
+
* Cancellation is exempt from the soft-fail: an aborted sub-request
|
|
31
|
+
* rethrows so the whole describe rejects instead of silently returning a
|
|
32
|
+
* partial view when the caller cancelled.
|
|
29
33
|
*/
|
|
30
34
|
export declare function describeApp(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, options?: DescribeAppOptions): Promise<AppInfo>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
2
|
+
import { isAbortError } from '../../../utils/is-abort-error.js';
|
|
1
3
|
/**
|
|
2
4
|
* Composite "describe this app" view: parallel-fetches add-ons, the app
|
|
3
5
|
* itself, dynos, collaborators, and the pipeline coupling.
|
|
@@ -7,19 +9,31 @@
|
|
|
7
9
|
* any error collapses to an empty default. The broad .catch is
|
|
8
10
|
* intentional v1 fidelity with the Heroku CLI's apps:info; a future
|
|
9
11
|
* tightening to specific status codes is non-breaking.
|
|
12
|
+
*
|
|
13
|
+
* Cancellation is exempt from the soft-fail: an aborted sub-request
|
|
14
|
+
* rethrows so the whole describe rejects instead of silently returning a
|
|
15
|
+
* partial view when the caller cancelled.
|
|
10
16
|
*/
|
|
11
17
|
export async function describeApp(ctx, appIdentity, options = {}) {
|
|
12
|
-
options
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
const { signal } = options;
|
|
19
|
+
signal?.throwIfAborted();
|
|
20
|
+
const platform = withSignal(ctx.platform, signal);
|
|
21
|
+
// Collapse an expected soft error to a default, but never swallow an abort.
|
|
22
|
+
// Checks the signal's `aborted` flag as well as the error shape so a
|
|
23
|
+
// custom-reason abort (`controller.abort(reason)`), which `isAbortError`
|
|
24
|
+
// alone can't detect, still rejects instead of returning a partial view.
|
|
25
|
+
const softFail = (fallback) => (error) => {
|
|
26
|
+
if (signal?.aborted || isAbortError(error))
|
|
27
|
+
throw error;
|
|
28
|
+
return fallback;
|
|
29
|
+
};
|
|
16
30
|
const [addons, app, dynos, collaborators, pipelineCoupling] = await Promise.all([
|
|
17
31
|
platform.addOn.listByApp(appIdentity),
|
|
18
32
|
platform.app.info(appIdentity),
|
|
19
|
-
platform.dyno.list(appIdentity).catch((
|
|
20
|
-
platform.collaborator.list(appIdentity).catch((
|
|
33
|
+
platform.dyno.list(appIdentity).catch(softFail([])),
|
|
34
|
+
platform.collaborator.list(appIdentity).catch(softFail([])),
|
|
21
35
|
platform.pipelineCoupling.infoByApp(appIdentity)
|
|
22
|
-
.catch((
|
|
36
|
+
.catch(softFail(null)),
|
|
23
37
|
]);
|
|
24
38
|
return {
|
|
25
39
|
addons, app, collaborators, dynos, pipelineCoupling,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
/**
|
|
2
3
|
* Transfer an app to another user or team.
|
|
3
4
|
*
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
*/
|
|
16
17
|
export async function transferApp(ctx, appIdentity, recipient, options = {}) {
|
|
17
18
|
options.signal?.throwIfAborted();
|
|
18
|
-
const platform =
|
|
19
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
19
20
|
if (options.personalToPersonal) {
|
|
20
21
|
const body = { app: appIdentity, recipient };
|
|
21
22
|
if (options.silent !== undefined)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { wait } from '../../../utils/wait.js';
|
|
2
3
|
const DEFAULT_WAIT_INTERVAL_MS = 15_000;
|
|
3
4
|
/**
|
|
@@ -16,7 +17,7 @@ const DEFAULT_WAIT_INTERVAL_MS = 15_000;
|
|
|
16
17
|
export async function waitForACMCertificates(ctx, appIdentity, options = {}) {
|
|
17
18
|
const { intervalMs = DEFAULT_WAIT_INTERVAL_MS, signal, timeoutMs } = options;
|
|
18
19
|
signal?.throwIfAborted();
|
|
19
|
-
const platform =
|
|
20
|
+
const platform = withSignal(ctx.platform, signal);
|
|
20
21
|
function terminal(domains) {
|
|
21
22
|
return domains
|
|
22
23
|
.filter(domain => domain.kind === 'custom')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
export class NotAContainerAppError extends Error {
|
|
2
3
|
app;
|
|
3
4
|
id = 'not_a_container_app';
|
|
@@ -14,7 +15,7 @@ export class NotAContainerAppError extends Error {
|
|
|
14
15
|
export async function ensureContainerStack(ctx, appIdentity, options = {}) {
|
|
15
16
|
const { signal } = options;
|
|
16
17
|
signal?.throwIfAborted();
|
|
17
|
-
const platform =
|
|
18
|
+
const platform = withSignal(ctx.platform, signal);
|
|
18
19
|
const app = await platform.app.info(appIdentity);
|
|
19
20
|
const allowedStack = 'container';
|
|
20
21
|
if (app.build_stack.name !== allowedStack && app.stack.name !== allowedStack) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { withSignal } from '../../../core/with-signal.js';
|
|
1
2
|
import { ensureContainerStack } from './ensure-container-stack.js';
|
|
2
3
|
/**
|
|
3
4
|
* Release Docker images to a container app.
|
|
4
5
|
*/
|
|
5
6
|
export async function releaseDockerImages(ctx, appIdentity, imagesByProcessType, options = {}) {
|
|
6
7
|
await ensureContainerStack(ctx, appIdentity, options);
|
|
7
|
-
const platform =
|
|
8
|
+
const platform = withSignal(ctx.platform, options.signal);
|
|
8
9
|
const oldReleases = await platform
|
|
9
10
|
.withHeaders({ Range: 'version ..; max=1, order=desc' })
|
|
10
11
|
.release.list(appIdentity);
|