@heroku/sdk 0.5.2 → 0.5.4
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/create-client.d.ts +9 -1
- package/dist/core/create-client.js +4 -2
- package/dist/resources/data/redis/errors.d.ts +8 -0
- package/dist/resources/data/redis/errors.js +15 -0
- package/dist/resources/data/redis/index.d.ts +9 -0
- package/dist/resources/data/redis/index.js +10 -0
- package/dist/resources/data/redis/resolve-by-app.d.ts +35 -0
- package/dist/resources/data/redis/resolve-by-app.js +56 -0
- package/dist/resources/data/redis/wait-for-ready.d.ts +32 -0
- package/dist/resources/data/redis/wait-for-ready.js +46 -0
- package/dist/resources/extensions/data.d.ts +1 -0
- package/dist/resources/extensions/data.js +1 -0
- package/dist/resources/extensions/platform.d.ts +5 -1
- package/dist/resources/extensions/platform.js +5 -1
- package/dist/resources/platform/add-on/destroy-and-wait.d.ts +22 -0
- package/dist/resources/platform/add-on/destroy-and-wait.js +62 -0
- package/dist/resources/platform/add-on/index.d.ts +9 -5
- package/dist/resources/platform/add-on/index.js +6 -0
- package/dist/resources/platform/add-on/types.d.ts +35 -0
- package/dist/resources/platform/add-on/wait-for-provisioning.d.ts +19 -0
- package/dist/resources/platform/add-on/wait-for-provisioning.js +37 -0
- package/dist/resources/platform/app/index.d.ts +3 -0
- package/dist/resources/platform/app/index.js +3 -0
- package/dist/resources/platform/app/wait-for-acm-certificates.d.ts +34 -0
- package/dist/resources/platform/app/wait-for-acm-certificates.js +51 -0
- package/dist/resources/platform/container/ensure-container-stack.d.ts +18 -0
- package/dist/resources/platform/container/ensure-container-stack.js +32 -0
- package/dist/resources/platform/container/index.d.ts +13 -0
- package/dist/resources/platform/container/index.js +12 -0
- package/dist/resources/platform/container/release-images.d.ts +21 -0
- package/dist/resources/platform/container/release-images.js +23 -0
- package/dist/resources/platform/container/remove-process-types.d.ts +22 -0
- package/dist/resources/platform/container/remove-process-types.js +20 -0
- package/dist/resources/platform/dyno/index.d.ts +3 -0
- package/dist/resources/platform/dyno/index.js +3 -0
- package/dist/resources/platform/dyno/run.d.ts +76 -0
- package/dist/resources/platform/dyno/run.js +74 -0
- package/dist/resources/platform/log-session/index.js +109 -12
- package/dist/resources/platform/sni-endpoint/create-and-associate.d.ts +38 -0
- package/dist/resources/platform/sni-endpoint/create-and-associate.js +86 -0
- package/dist/resources/platform/sni-endpoint/index.d.ts +5 -0
- package/dist/resources/platform/sni-endpoint/index.js +6 -0
- package/dist/resources/platform/telemetry-drain/index.d.ts +6 -0
- package/dist/resources/platform/telemetry-drain/index.js +6 -0
- package/dist/resources/platform/telemetry-drain/remove-drains-for-target.d.ts +13 -0
- package/dist/resources/platform/telemetry-drain/remove-drains-for-target.js +25 -0
- package/dist/resources/platform/telemetry-drain/types.d.ts +13 -0
- package/dist/resources/platform/telemetry-drain/types.js +1 -0
- package/dist/resources/platform/test-run/index.d.ts +5 -0
- package/dist/resources/platform/test-run/index.js +9 -0
- package/dist/resources/platform/test-run/wait-for-state.d.ts +68 -0
- package/dist/resources/platform/test-run/wait-for-state.js +65 -0
- package/package.json +11 -3
|
@@ -7,7 +7,15 @@ type RoutesModule = Record<string, Record<string, RouteDefinition>>;
|
|
|
7
7
|
* and `stream` are deliberately excluded — those belong on the
|
|
8
8
|
* individual route call, not on the client wrapper.
|
|
9
9
|
*/
|
|
10
|
-
export type StickyRouteOptions = Pick<RequestOptions, 'headers' | 'signal' | 'timeout'
|
|
10
|
+
export type StickyRouteOptions = Pick<RequestOptions, 'headers' | 'signal' | 'timeout'> & {
|
|
11
|
+
/**
|
|
12
|
+
* Request body applied to write calls on the derived client. Primarily
|
|
13
|
+
* for methods whose route metadata lacks `hasRequestBody` (e.g. DELETE
|
|
14
|
+
* with a `force` payload) where the body cannot be passed positionally.
|
|
15
|
+
* The underlying client forwards this as the JSON payload.
|
|
16
|
+
*/
|
|
17
|
+
body?: unknown;
|
|
18
|
+
};
|
|
11
19
|
/**
|
|
12
20
|
* Methods available on every routes-generated client in addition to
|
|
13
21
|
* the resources/methods generated from the routes registry.
|
|
@@ -11,12 +11,14 @@ function mergeStickyOptions(base, next) {
|
|
|
11
11
|
if (next.headers || base?.headers) {
|
|
12
12
|
merged.headers = { ...base?.headers, ...next.headers };
|
|
13
13
|
}
|
|
14
|
-
// signal and
|
|
15
|
-
// caller actually set them. Otherwise the previous value persists.
|
|
14
|
+
// signal, timeout, and body are not additive — last wins, but only if
|
|
15
|
+
// the caller actually set them. Otherwise the previous value persists.
|
|
16
16
|
if (next.signal !== undefined)
|
|
17
17
|
merged.signal = next.signal;
|
|
18
18
|
if (next.timeout !== undefined)
|
|
19
19
|
merged.timeout = next.timeout;
|
|
20
|
+
if (next.body !== undefined)
|
|
21
|
+
merged.body = next.body;
|
|
20
22
|
return merged;
|
|
21
23
|
}
|
|
22
24
|
function buildClientProxy(httpClient, routes, inheritedOptions) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AddOn } from '@heroku/types/3.sdk';
|
|
2
|
+
import { AddonAmbiguousError, AddonNotFoundError } from '../../platform/add-on/index.js';
|
|
3
|
+
export declare class RedisAddonNotFoundError extends AddonNotFoundError {
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
export declare class RedisAddonAmbiguousError extends AddonAmbiguousError {
|
|
7
|
+
constructor(matches: AddOn[]);
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AddonAmbiguousError, AddonNotFoundError } from '../../platform/add-on/index.js';
|
|
2
|
+
export class RedisAddonNotFoundError extends AddonNotFoundError {
|
|
3
|
+
constructor() {
|
|
4
|
+
super('redis instance');
|
|
5
|
+
this.message = 'No Redis instances found.';
|
|
6
|
+
this.name = 'RedisAddonNotFoundError';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class RedisAddonAmbiguousError extends AddonAmbiguousError {
|
|
10
|
+
constructor(matches) {
|
|
11
|
+
super(matches);
|
|
12
|
+
this.message = `Please specify a single instance. Found: ${matches.map(m => m.name).join(', ')}`;
|
|
13
|
+
this.name = 'RedisAddonAmbiguousError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ResolveRedisByAppOptions } from './resolve-by-app.js';
|
|
2
|
+
import type { WaitForRedisReadyOptions } from './wait-for-ready.js';
|
|
3
|
+
export { RedisAddonAmbiguousError, RedisAddonNotFoundError } from './errors.js';
|
|
4
|
+
export { resolveRedisByApp, type ResolveRedisByAppOptions } from './resolve-by-app.js';
|
|
5
|
+
export { waitForRedisReady, type WaitForRedisReadyOptions } from './wait-for-ready.js';
|
|
6
|
+
export declare const redisExtensions: import("../../../core/extend-resource.js").ResourceExtension<"data", "redis", {
|
|
7
|
+
resolveByApp: (appIdentity: string, options?: ResolveRedisByAppOptions) => Promise<import("../../platform/add-on/types.js").ResolvedAddOn>;
|
|
8
|
+
waitForReady: (nameOrId: string, options?: WaitForRedisReadyOptions) => Promise<import("@heroku/types/data").RedisWaitResult>;
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { resolveRedisByApp } from './resolve-by-app.js';
|
|
3
|
+
import { waitForRedisReady } from './wait-for-ready.js';
|
|
4
|
+
export { RedisAddonAmbiguousError, RedisAddonNotFoundError } from './errors.js';
|
|
5
|
+
export { resolveRedisByApp } from './resolve-by-app.js';
|
|
6
|
+
export { waitForRedisReady } from './wait-for-ready.js';
|
|
7
|
+
export const redisExtensions = extendResource('data', 'redis', ctx => ({
|
|
8
|
+
resolveByApp: (appIdentity, options) => resolveRedisByApp(ctx, appIdentity, options),
|
|
9
|
+
waitForReady: (nameOrId, options) => waitForRedisReady(ctx, nameOrId, options),
|
|
10
|
+
}));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
2
|
+
import type { ResolvedAddOn } from '../../platform/add-on/index.js';
|
|
3
|
+
export type ResolveRedisByAppOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Override the add-on service prefix used to identify a redis add-on
|
|
6
|
+
* on the app. Defaults to `heroku-redis`. The match is a prefix
|
|
7
|
+
* (`startsWith`) on `addon_service.name`, matching the CLI's existing
|
|
8
|
+
* `makeAddonsFilter` semantics.
|
|
9
|
+
*/
|
|
10
|
+
addonServiceName?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional filter narrowing the app's redis add-ons to a single
|
|
13
|
+
* database. Matched case-insensitively against the add-on's `name`
|
|
14
|
+
* and each of its `config_vars`, using substring containment. This
|
|
15
|
+
* mirrors the CLI's `makeAddonsFilter` behavior so migrating callers
|
|
16
|
+
* observe identical resolution semantics.
|
|
17
|
+
*/
|
|
18
|
+
database?: string;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a redis add-on attached to `appIdentity`.
|
|
23
|
+
*
|
|
24
|
+
* Lists the app's add-ons, filters them to those whose
|
|
25
|
+
* `addon_service.name` starts with `heroku-redis` (or the caller's
|
|
26
|
+
* `addonServiceName`), and further narrows by the optional `database`
|
|
27
|
+
* filter using the same case-insensitive substring match the CLI has
|
|
28
|
+
* always used.
|
|
29
|
+
*
|
|
30
|
+
* Throws `RedisAddonNotFoundError` if no add-ons remain after filtering,
|
|
31
|
+
* `RedisAddonAmbiguousError` if more than one remains. Both subclass the
|
|
32
|
+
* generic add-on errors so callers doing `instanceof AddonNotFoundError`
|
|
33
|
+
* still match.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveRedisByApp(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, options?: ResolveRedisByAppOptions): Promise<ResolvedAddOn>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import createDebug from 'debug';
|
|
2
|
+
import { RedisAddonAmbiguousError, RedisAddonNotFoundError } from './errors.js';
|
|
3
|
+
const debug = createDebug('heroku:sdk:resources:redis');
|
|
4
|
+
const DEFAULT_REDIS_ADDON_SERVICE_PREFIX = 'heroku-redis';
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a redis add-on attached to `appIdentity`.
|
|
7
|
+
*
|
|
8
|
+
* Lists the app's add-ons, filters them to those whose
|
|
9
|
+
* `addon_service.name` starts with `heroku-redis` (or the caller's
|
|
10
|
+
* `addonServiceName`), and further narrows by the optional `database`
|
|
11
|
+
* filter using the same case-insensitive substring match the CLI has
|
|
12
|
+
* always used.
|
|
13
|
+
*
|
|
14
|
+
* Throws `RedisAddonNotFoundError` if no add-ons remain after filtering,
|
|
15
|
+
* `RedisAddonAmbiguousError` if more than one remains. Both subclass the
|
|
16
|
+
* generic add-on errors so callers doing `instanceof AddonNotFoundError`
|
|
17
|
+
* still match.
|
|
18
|
+
*/
|
|
19
|
+
export async function resolveRedisByApp(ctx, appIdentity, options = {}) {
|
|
20
|
+
options.signal?.throwIfAborted();
|
|
21
|
+
const { addonServiceName = DEFAULT_REDIS_ADDON_SERVICE_PREFIX, database } = options;
|
|
22
|
+
const platform = options.signal ? ctx.platform.withOptions({ signal: options.signal }) : ctx.platform;
|
|
23
|
+
debug('resolveByApp app=%s service-prefix=%s database=%s', appIdentity, addonServiceName, database ?? '<any>');
|
|
24
|
+
const addons = await platform.addOn.listByApp(appIdentity);
|
|
25
|
+
const filter = database?.toUpperCase();
|
|
26
|
+
const matches = addons.filter(addon => {
|
|
27
|
+
const service = addon.addon_service?.name;
|
|
28
|
+
if (!service || !service.startsWith(addonServiceName))
|
|
29
|
+
return false;
|
|
30
|
+
if (!filter)
|
|
31
|
+
return true;
|
|
32
|
+
return matchesDatabaseFilter(addon, filter);
|
|
33
|
+
});
|
|
34
|
+
debug('resolveByApp app=%s candidates=%d matches=%d', appIdentity, addons.length, matches.length);
|
|
35
|
+
if (matches.length === 0) {
|
|
36
|
+
throw new RedisAddonNotFoundError();
|
|
37
|
+
}
|
|
38
|
+
if (matches.length > 1) {
|
|
39
|
+
throw new RedisAddonAmbiguousError(matches);
|
|
40
|
+
}
|
|
41
|
+
const match = matches[0];
|
|
42
|
+
if (!match.id || !match.app?.id) {
|
|
43
|
+
throw new Error(`Resolved redis add-on is missing required fields (id=${match.id}, app.id=${match.app?.id})`);
|
|
44
|
+
}
|
|
45
|
+
return match;
|
|
46
|
+
}
|
|
47
|
+
function matchesDatabaseFilter(addon, filter) {
|
|
48
|
+
const configVars = addon.config_vars ?? [];
|
|
49
|
+
for (const configVar of configVars) {
|
|
50
|
+
if (configVar.toUpperCase().includes(filter))
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (addon.name && addon.name.toUpperCase().includes(filter))
|
|
54
|
+
return true;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { RedisWaitResult } from '@heroku/types/data';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
export type WaitForRedisReadyOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Polling interval in milliseconds. Defaults to 5000 (5s), matching
|
|
6
|
+
* the CLI's historical `--wait-interval` default.
|
|
7
|
+
*/
|
|
8
|
+
intervalMs?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Aborts the in-flight request and any pending poll delay.
|
|
11
|
+
*/
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
/**
|
|
14
|
+
* Maximum total time to wait before throwing. If omitted, polls until
|
|
15
|
+
* the database reports ready or `signal` aborts.
|
|
16
|
+
*/
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Poll `data.redis.wait` until the redis database reports ready
|
|
21
|
+
* (`waiting?: false`).
|
|
22
|
+
*
|
|
23
|
+
* A 404 from the wait endpoint during the initial provisioning window
|
|
24
|
+
* is treated as "still waiting" — the platform briefly returns 404 for
|
|
25
|
+
* a database whose row has not yet been created after the addon
|
|
26
|
+
* provision call resolves.
|
|
27
|
+
*
|
|
28
|
+
* Cancellation: the caller's `signal` is threaded into both the
|
|
29
|
+
* in-flight request (via `withOptions`) and the poll-delay `wait()`,
|
|
30
|
+
* so aborting takes effect during either phase.
|
|
31
|
+
*/
|
|
32
|
+
export declare function waitForRedisReady(ctx: Pick<ResourceCtx, 'data'>, nameOrId: string, options?: WaitForRedisReadyOptions): Promise<RedisWaitResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NotFoundError } from '@heroku/heroku-fetch';
|
|
2
|
+
import createDebug from 'debug';
|
|
3
|
+
import { wait } from '../../../utils/wait.js';
|
|
4
|
+
const debug = createDebug('heroku:sdk:resources:redis');
|
|
5
|
+
const DEFAULT_INTERVAL_MS = 5000;
|
|
6
|
+
/**
|
|
7
|
+
* Poll `data.redis.wait` until the redis database reports ready
|
|
8
|
+
* (`waiting?: false`).
|
|
9
|
+
*
|
|
10
|
+
* A 404 from the wait endpoint during the initial provisioning window
|
|
11
|
+
* is treated as "still waiting" — the platform briefly returns 404 for
|
|
12
|
+
* a database whose row has not yet been created after the addon
|
|
13
|
+
* provision call resolves.
|
|
14
|
+
*
|
|
15
|
+
* Cancellation: the caller's `signal` is threaded into both the
|
|
16
|
+
* in-flight request (via `withOptions`) and the poll-delay `wait()`,
|
|
17
|
+
* so aborting takes effect during either phase.
|
|
18
|
+
*/
|
|
19
|
+
export async function waitForRedisReady(ctx, nameOrId, options = {}) {
|
|
20
|
+
const { intervalMs = DEFAULT_INTERVAL_MS, signal, timeoutMs } = options;
|
|
21
|
+
signal?.throwIfAborted();
|
|
22
|
+
const data = signal ? ctx.data.withOptions({ signal }) : ctx.data;
|
|
23
|
+
const deadline = timeoutMs === undefined ? undefined : Date.now() + timeoutMs;
|
|
24
|
+
debug('waitForReady name=%s intervalMs=%d timeoutMs=%s', nameOrId, intervalMs, timeoutMs ?? '<none>');
|
|
25
|
+
while (true) {
|
|
26
|
+
signal?.throwIfAborted();
|
|
27
|
+
let status;
|
|
28
|
+
try {
|
|
29
|
+
status = await data.redis.wait(nameOrId);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (!(error instanceof NotFoundError))
|
|
33
|
+
throw error;
|
|
34
|
+
debug('waitForReady name=%s 404 during provisioning window', nameOrId);
|
|
35
|
+
status = { message: 'not found', 'waiting?': true };
|
|
36
|
+
}
|
|
37
|
+
if (!status['waiting?']) {
|
|
38
|
+
debug('waitForReady name=%s ready', nameOrId);
|
|
39
|
+
return status;
|
|
40
|
+
}
|
|
41
|
+
if (deadline !== undefined && Date.now() >= deadline) {
|
|
42
|
+
throw new Error(`Redis database '${nameOrId}' did not become ready within ${timeoutMs}ms`);
|
|
43
|
+
}
|
|
44
|
+
await wait(intervalMs, signal);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { databaseExtensions } from '../data/database.js';
|
|
2
2
|
export { maintenanceExtensions } from '../data/maintenance.js';
|
|
3
3
|
export { postgresDatabaseExtensions } from '../data/postgres-database.js';
|
|
4
|
+
export { redisExtensions } from '../data/redis/index.js';
|
|
4
5
|
export { transferExtensions } from '../data/transfer.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { databaseExtensions } from '../data/database.js';
|
|
2
2
|
export { maintenanceExtensions } from '../data/maintenance.js';
|
|
3
3
|
export { postgresDatabaseExtensions } from '../data/postgres-database.js';
|
|
4
|
+
export { redisExtensions } from '../data/redis/index.js';
|
|
4
5
|
export { transferExtensions } from '../data/transfer.js';
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
2
2
|
export { appExtensions, type AppInfo, type PipelineCouplingDetail } from '../platform/app/index.js';
|
|
3
|
+
export { containerExtensions, type DockerReleasesBatchUpdateOpts, NotAContainerAppError, type ReleaseDockerImagesResult, type RemoveProcessTypesOpts, } from '../platform/container/index.js';
|
|
3
4
|
export { type CreateAndWaitOptions, domainExtensions, type DomainOptions, type WaitForReadyOptions, } from '../platform/domain/index.js';
|
|
4
|
-
export { dynoExtensions, DynoNotReadyError, type DynoState, waitForInfo, type WaitForInfoOptions, } from '../platform/dyno/index.js';
|
|
5
|
+
export { dynoExtensions, DynoNotReadyError, type DynoState, runDyno, type RunDynoOptions, waitForInfo, type WaitForInfoOptions, } from '../platform/dyno/index.js';
|
|
5
6
|
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';
|
|
6
7
|
export { pipelineCouplingExtensions } from '../platform/pipeline/coupling.js';
|
|
7
8
|
export { pipelineExtensions } from '../platform/pipeline/index.js';
|
|
8
9
|
export { pipelinePromotionExtensions } from '../platform/pipeline/promotion.js';
|
|
9
10
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
11
|
+
export { createAndAssociate, type CreateAndAssociateOptions, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
12
|
+
export { telemetryDrainExtensions, type TelemetryDrainOptions, type TelemetryDrainTarget } from '../platform/telemetry-drain/index.js';
|
|
13
|
+
export { testRunExtensions, TestRunNotReadyError, type TestRunStatus, waitForState, type WaitForStateOptions, } from '../platform/test-run/index.js';
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
2
2
|
export { appExtensions } from '../platform/app/index.js';
|
|
3
|
+
export { containerExtensions, NotAContainerAppError, } from '../platform/container/index.js';
|
|
3
4
|
export { domainExtensions, } from '../platform/domain/index.js';
|
|
4
|
-
export { dynoExtensions, DynoNotReadyError, waitForInfo, } from '../platform/dyno/index.js';
|
|
5
|
+
export { dynoExtensions, DynoNotReadyError, runDyno, waitForInfo, } from '../platform/dyno/index.js';
|
|
5
6
|
export { logSessionExtensions, parseHerokuLogLine, } from '../platform/log-session/index.js';
|
|
6
7
|
export { pipelineCouplingExtensions } from '../platform/pipeline/coupling.js';
|
|
7
8
|
export { pipelineExtensions } from '../platform/pipeline/index.js';
|
|
8
9
|
export { pipelinePromotionExtensions } from '../platform/pipeline/promotion.js';
|
|
9
10
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
11
|
+
export { createAndAssociate, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
12
|
+
export { telemetryDrainExtensions } from '../platform/telemetry-drain/index.js';
|
|
13
|
+
export { testRunExtensions, TestRunNotReadyError, waitForState, } from '../platform/test-run/index.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AddOn } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
import type { DestroyAndWaitOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Delete an add-on and optionally wait for deprovisioning to complete.
|
|
6
|
+
*
|
|
7
|
+
* Wraps `addOn.delete` with two pieces of orchestration:
|
|
8
|
+
*
|
|
9
|
+
* - When `wait: true` is set, polls `addOn.infoByApp` on a
|
|
10
|
+
* `waitIntervalMs` cadence until the add-on's `state` is no longer
|
|
11
|
+
* `deprovisioning`. A 404 response during polling is treated as
|
|
12
|
+
* successful deprovisioning (the platform deletes the record).
|
|
13
|
+
* - `options.onDeprovisioning` fires once after the delete response
|
|
14
|
+
* when polling is about to begin, letting callers surface a
|
|
15
|
+
* two-phase status display.
|
|
16
|
+
*
|
|
17
|
+
* When `options.force` is set, the delete request sends `{force: true}`
|
|
18
|
+
* as its body (scoped to the delete call only, not the poll requests).
|
|
19
|
+
*
|
|
20
|
+
* Both the delete and the poll requests send `Accept-Expansion: plan`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function destroyAndWait(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, addonIdentity: string, options?: DestroyAndWaitOptions): Promise<AddOn>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { wait } from '../../../utils/wait.js';
|
|
2
|
+
import { AddonProvisioningFailedError } from './errors.js';
|
|
3
|
+
const DEFAULT_DESTROY_WAIT_INTERVAL_MS = 5000;
|
|
4
|
+
/**
|
|
5
|
+
* Delete an add-on and optionally wait for deprovisioning to complete.
|
|
6
|
+
*
|
|
7
|
+
* Wraps `addOn.delete` with two pieces of orchestration:
|
|
8
|
+
*
|
|
9
|
+
* - When `wait: true` is set, polls `addOn.infoByApp` on a
|
|
10
|
+
* `waitIntervalMs` cadence until the add-on's `state` is no longer
|
|
11
|
+
* `deprovisioning`. A 404 response during polling is treated as
|
|
12
|
+
* successful deprovisioning (the platform deletes the record).
|
|
13
|
+
* - `options.onDeprovisioning` fires once after the delete response
|
|
14
|
+
* when polling is about to begin, letting callers surface a
|
|
15
|
+
* two-phase status display.
|
|
16
|
+
*
|
|
17
|
+
* When `options.force` is set, the delete request sends `{force: true}`
|
|
18
|
+
* as its body (scoped to the delete call only, not the poll requests).
|
|
19
|
+
*
|
|
20
|
+
* Both the delete and the poll requests send `Accept-Expansion: plan`.
|
|
21
|
+
*/
|
|
22
|
+
export async function destroyAndWait(ctx, appIdentity, addonIdentity, options = {}) {
|
|
23
|
+
options.signal?.throwIfAborted();
|
|
24
|
+
const platform = ctx.platform
|
|
25
|
+
.withHeaders({ 'Accept-Expansion': 'plan' })
|
|
26
|
+
.withOptions({ signal: options.signal });
|
|
27
|
+
// The force body is scoped to the delete call only — a sticky body on the
|
|
28
|
+
// shared client would leak onto the GET poll requests below.
|
|
29
|
+
const deleteClient = options.force
|
|
30
|
+
? platform.withOptions({ body: { force: true } })
|
|
31
|
+
: platform;
|
|
32
|
+
const deleted = await deleteClient.addOn.delete(appIdentity, addonIdentity);
|
|
33
|
+
let addon = deleted ?? {};
|
|
34
|
+
if (!options.wait || (addon.state !== 'deprovisioning' && addon.state !== 'provisioning')) {
|
|
35
|
+
return addon;
|
|
36
|
+
}
|
|
37
|
+
await options.onDeprovisioning?.(addon);
|
|
38
|
+
const intervalMs = options.waitIntervalMs ?? DEFAULT_DESTROY_WAIT_INTERVAL_MS;
|
|
39
|
+
/* eslint-disable no-await-in-loop */
|
|
40
|
+
while (addon.state === 'deprovisioning' || addon.state === 'provisioning') {
|
|
41
|
+
options.signal?.throwIfAborted();
|
|
42
|
+
await wait(intervalMs, options.signal);
|
|
43
|
+
addon = await platform.addOn.infoByApp(appIdentity, addonIdentity).catch((error) => {
|
|
44
|
+
// Platform deletes the record on successful deprovisioning — a 404 is success.
|
|
45
|
+
if (isNotFound(error)) {
|
|
46
|
+
return { ...addon, state: 'deprovisioned' };
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/* eslint-enable no-await-in-loop */
|
|
52
|
+
if (addon.state !== 'deprovisioned') {
|
|
53
|
+
throw new AddonProvisioningFailedError(addon);
|
|
54
|
+
}
|
|
55
|
+
return addon;
|
|
56
|
+
}
|
|
57
|
+
function isNotFound(error) {
|
|
58
|
+
return (typeof error === 'object'
|
|
59
|
+
&& error !== null
|
|
60
|
+
&& 'statusCode' in error
|
|
61
|
+
&& error.statusCode === 404);
|
|
62
|
+
}
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import type { AddOnCreateOpts } from '@heroku/types/3.sdk';
|
|
1
|
+
import type { AddOn, AddOnCreateOpts } from '@heroku/types/3.sdk';
|
|
2
2
|
import type { ListPlansForAddonOptions } from './list-plans.js';
|
|
3
|
-
import type { AddOnOptions, CreateAndWaitOptions, ResolveAddonOptions, UpgradeAddOnOptions } from './types.js';
|
|
3
|
+
import type { AddOnOptions, CreateAndWaitOptions, DestroyAndWaitOptions, ResolveAddonOptions, UpgradeAddOnOptions, WaitForProvisioningOptions } from './types.js';
|
|
4
4
|
import { formatPlanPriceLabel, priceForPlan } from './pricing.js';
|
|
5
5
|
export { createAndWait } from './create-and-wait.js';
|
|
6
6
|
export { describeAddon } from './describe.js';
|
|
7
|
+
export { destroyAndWait } from './destroy-and-wait.js';
|
|
7
8
|
export { AddonAmbiguousError, AddonConfirmationRequiredError, AddonNotFoundError, AddonProvisioningFailedError, } from './errors.js';
|
|
8
9
|
export { listPlans, listPlansForAddon, type ListPlansForAddonOptions } from './list-plans.js';
|
|
9
10
|
export { formatPlanPriceLabel, type FormatPlanPriceLabelOptions, type PlanPriceBreakdown, priceForPlan, } from './pricing.js';
|
|
10
11
|
export { resolveAddon, resolveAddonByAttachment } from './resolve.js';
|
|
11
|
-
export type { AddOnOptions, CreateAndWaitOptions, DescribedAddOn, ResolveAddonOptions, ResolvedAddOn, UpgradeAddOnOptions, } from './types.js';
|
|
12
|
+
export type { AddOnOptions, CreateAndWaitOptions, DescribedAddOn, DestroyAndWaitOptions, ResolveAddonOptions, ResolvedAddOn, UpgradeAddOnOptions, WaitForProvisioningOptions, } from './types.js';
|
|
12
13
|
export { upgrade } from './upgrade.js';
|
|
14
|
+
export { waitForProvisioning } from './wait-for-provisioning.js';
|
|
13
15
|
export declare const addOnExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "addOn", {
|
|
14
|
-
createAndWait: (appIdentity: string, body: AddOnCreateOpts, options?: CreateAndWaitOptions) => Promise<
|
|
16
|
+
createAndWait: (appIdentity: string, body: AddOnCreateOpts, options?: CreateAndWaitOptions) => Promise<AddOn>;
|
|
15
17
|
describe: (addonIdentity: string, options?: ResolveAddonOptions) => Promise<import("./types.js").DescribedAddOn>;
|
|
18
|
+
destroyAndWait: (appIdentity: string, addonIdentity: string, options?: DestroyAndWaitOptions) => Promise<AddOn>;
|
|
16
19
|
formatPlanPriceLabel: typeof formatPlanPriceLabel;
|
|
17
20
|
listPlans: (serviceIdentity: string, options?: AddOnOptions) => Promise<import("@heroku/types/3.sdk").Plan[]>;
|
|
18
21
|
listPlansForAddon: (addonIdentity: string, options?: ListPlansForAddonOptions) => Promise<import("@heroku/types/3.sdk").Plan[]>;
|
|
19
22
|
priceForPlan: typeof priceForPlan;
|
|
20
23
|
resolve: (addonIdentity: string, options?: ResolveAddonOptions) => Promise<import("./types.js").ResolvedAddOn>;
|
|
21
24
|
resolveByAttachment: (appIdentity: string, attachmentName: string, options?: AddOnOptions) => Promise<import("./types.js").ResolvedAddOn>;
|
|
22
|
-
upgrade: (addonIdentity: string, plan: string, options?: UpgradeAddOnOptions) => Promise<
|
|
25
|
+
upgrade: (addonIdentity: string, plan: string, options?: UpgradeAddOnOptions) => Promise<AddOn>;
|
|
26
|
+
waitForProvisioning: (addon: AddOn, options?: WaitForProvisioningOptions) => Promise<AddOn>;
|
|
23
27
|
}>;
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
2
|
import { createAndWait } from './create-and-wait.js';
|
|
3
3
|
import { describeAddon } from './describe.js';
|
|
4
|
+
import { destroyAndWait } from './destroy-and-wait.js';
|
|
4
5
|
import { listPlans, listPlansForAddon } from './list-plans.js';
|
|
5
6
|
import { formatPlanPriceLabel, priceForPlan } from './pricing.js';
|
|
6
7
|
import { resolveAddon, resolveAddonByAttachment } from './resolve.js';
|
|
7
8
|
import { upgrade } from './upgrade.js';
|
|
9
|
+
import { waitForProvisioning } from './wait-for-provisioning.js';
|
|
8
10
|
export { createAndWait } from './create-and-wait.js';
|
|
9
11
|
export { describeAddon } from './describe.js';
|
|
12
|
+
export { destroyAndWait } from './destroy-and-wait.js';
|
|
10
13
|
export { AddonAmbiguousError, AddonConfirmationRequiredError, AddonNotFoundError, AddonProvisioningFailedError, } from './errors.js';
|
|
11
14
|
export { listPlans, listPlansForAddon } from './list-plans.js';
|
|
12
15
|
export { formatPlanPriceLabel, priceForPlan, } from './pricing.js';
|
|
13
16
|
export { resolveAddon, resolveAddonByAttachment } from './resolve.js';
|
|
14
17
|
export { upgrade } from './upgrade.js';
|
|
18
|
+
export { waitForProvisioning } from './wait-for-provisioning.js';
|
|
15
19
|
export const addOnExtensions = extendResource('platform', 'addOn', ctx => ({
|
|
16
20
|
createAndWait: (appIdentity, body, options) => createAndWait(ctx, appIdentity, body, options),
|
|
17
21
|
describe: (addonIdentity, options) => describeAddon(ctx, addonIdentity, options),
|
|
22
|
+
destroyAndWait: (appIdentity, addonIdentity, options) => destroyAndWait(ctx, appIdentity, addonIdentity, options),
|
|
18
23
|
formatPlanPriceLabel,
|
|
19
24
|
listPlans: (serviceIdentity, options) => listPlans(ctx, serviceIdentity, options),
|
|
20
25
|
listPlansForAddon: (addonIdentity, options) => listPlansForAddon(ctx, addonIdentity, options),
|
|
@@ -22,4 +27,5 @@ export const addOnExtensions = extendResource('platform', 'addOn', ctx => ({
|
|
|
22
27
|
resolve: (addonIdentity, options) => resolveAddon(ctx, addonIdentity, options),
|
|
23
28
|
resolveByAttachment: (appIdentity, attachmentName, options) => resolveAddonByAttachment(ctx, appIdentity, attachmentName, options),
|
|
24
29
|
upgrade: (addonIdentity, plan, options) => upgrade(ctx, addonIdentity, plan, options),
|
|
30
|
+
waitForProvisioning: (addon, options) => waitForProvisioning(ctx, addon, options),
|
|
25
31
|
}));
|
|
@@ -24,6 +24,41 @@ export type DescribedAddOn = ResolvedAddOn & {
|
|
|
24
24
|
export type UpgradeAddOnOptions = ResolveAddonOptions & {
|
|
25
25
|
onResolved?: (addon: ResolvedAddOn) => Promise<void> | void;
|
|
26
26
|
};
|
|
27
|
+
export type DestroyAndWaitOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* When true, sends `{force: true}` as the delete body — allows
|
|
30
|
+
* destruction even if the add-on is attached to other apps. Mirrors
|
|
31
|
+
* the CLI's `--force` flag. Defaults to false.
|
|
32
|
+
*/
|
|
33
|
+
force?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Fires once after the initial delete returns and the add-on is still
|
|
36
|
+
* deprovisioning, before the first poll. Useful for two-phase UX:
|
|
37
|
+
* e.g. "Destroying…" followed by "Waiting for deprovisioning…".
|
|
38
|
+
*
|
|
39
|
+
* Only invoked when `wait: true` and the delete response state is
|
|
40
|
+
* `deprovisioning`.
|
|
41
|
+
*/
|
|
42
|
+
onDeprovisioning?: (addon: AddOn) => Promise<void> | void;
|
|
43
|
+
/**
|
|
44
|
+
* If true, poll until the add-on leaves `deprovisioning`. If the
|
|
45
|
+
* terminal state is not `deprovisioned`, throws `AddonProvisioningFailedError`.
|
|
46
|
+
*
|
|
47
|
+
* If false (the default), returns immediately after the delete call.
|
|
48
|
+
*/
|
|
49
|
+
wait?: boolean;
|
|
50
|
+
/** Polling interval in milliseconds. Defaults to 5000. */
|
|
51
|
+
waitIntervalMs?: number;
|
|
52
|
+
} & AddOnOptions;
|
|
53
|
+
export type WaitForProvisioningOptions = AddOnOptions & {
|
|
54
|
+
/**
|
|
55
|
+
* When provided, polls `GET /apps/:app/addons/:addon` (app-scoped).
|
|
56
|
+
* Without it, polls `GET /addons/:addon` (global).
|
|
57
|
+
*/
|
|
58
|
+
appIdentity?: string;
|
|
59
|
+
/** Polling interval in milliseconds. Defaults to 5000. */
|
|
60
|
+
waitIntervalMs?: number;
|
|
61
|
+
};
|
|
27
62
|
export type CreateAndWaitOptions = {
|
|
28
63
|
/**
|
|
29
64
|
* Fires once after the initial create returns and the add-on is
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AddOn } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
import type { WaitForProvisioningOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Poll add-on state until it leaves a non-terminal state.
|
|
6
|
+
*
|
|
7
|
+
* When `appIdentity` is provided, polls `GET /apps/:app/addons/:addon`
|
|
8
|
+
* (app-scoped). Without it, polls `GET /addons/:addon` (global).
|
|
9
|
+
*
|
|
10
|
+
* Terminal states: `provisioned`, `deprovisioned`. Polling continues
|
|
11
|
+
* while the add-on is in `provisioning` or `deprovisioning`.
|
|
12
|
+
*
|
|
13
|
+
* Throws `AddonProvisioningFailedError` if the terminal state is
|
|
14
|
+
* `deprovisioned`.
|
|
15
|
+
*
|
|
16
|
+
* The `Accept-Expansion: addon_service,plan` header is included on
|
|
17
|
+
* each poll to match the existing CLI behaviour.
|
|
18
|
+
*/
|
|
19
|
+
export declare function waitForProvisioning(ctx: Pick<ResourceCtx, 'platform'>, addon: AddOn, options?: WaitForProvisioningOptions): Promise<AddOn>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { wait } from '../../../utils/wait.js';
|
|
2
|
+
import { AddonProvisioningFailedError } from './errors.js';
|
|
3
|
+
const DEFAULT_WAIT_INTERVAL_MS = 5000;
|
|
4
|
+
/**
|
|
5
|
+
* Poll add-on state until it leaves a non-terminal state.
|
|
6
|
+
*
|
|
7
|
+
* When `appIdentity` is provided, polls `GET /apps/:app/addons/:addon`
|
|
8
|
+
* (app-scoped). Without it, polls `GET /addons/:addon` (global).
|
|
9
|
+
*
|
|
10
|
+
* Terminal states: `provisioned`, `deprovisioned`. Polling continues
|
|
11
|
+
* while the add-on is in `provisioning` or `deprovisioning`.
|
|
12
|
+
*
|
|
13
|
+
* Throws `AddonProvisioningFailedError` if the terminal state is
|
|
14
|
+
* `deprovisioned`.
|
|
15
|
+
*
|
|
16
|
+
* The `Accept-Expansion: addon_service,plan` header is included on
|
|
17
|
+
* each poll to match the existing CLI behaviour.
|
|
18
|
+
*/
|
|
19
|
+
export async function waitForProvisioning(ctx, addon, options = {}) {
|
|
20
|
+
options.signal?.throwIfAborted();
|
|
21
|
+
const intervalMs = options.waitIntervalMs ?? DEFAULT_WAIT_INTERVAL_MS;
|
|
22
|
+
const platform = ctx.platform.withHeaders({ 'Accept-Expansion': 'addon_service,plan' });
|
|
23
|
+
let current = addon;
|
|
24
|
+
/* eslint-disable no-await-in-loop */
|
|
25
|
+
while (current.state === 'provisioning' || current.state === 'deprovisioning') {
|
|
26
|
+
options.signal?.throwIfAborted();
|
|
27
|
+
await wait(intervalMs, options.signal);
|
|
28
|
+
current = options.appIdentity
|
|
29
|
+
? await platform.addOn.infoByApp(options.appIdentity, current.name)
|
|
30
|
+
: await platform.addOn.info(current.name);
|
|
31
|
+
}
|
|
32
|
+
/* eslint-enable no-await-in-loop */
|
|
33
|
+
if (current.state === 'deprovisioned') {
|
|
34
|
+
throw new AddonProvisioningFailedError(current);
|
|
35
|
+
}
|
|
36
|
+
return current;
|
|
37
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { App } from '@heroku/types/3.sdk';
|
|
2
2
|
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
3
|
import type { DescribeAppOptions } from './info.js';
|
|
4
|
+
import type { WaitForACMCertificatesOptions } from './wait-for-acm-certificates.js';
|
|
4
5
|
export { type AppInfo, describeApp, type DescribeAppOptions, type PipelineCouplingDetail, } from './info.js';
|
|
6
|
+
export { waitForACMCertificates, type WaitForACMCertificatesOptions } from './wait-for-acm-certificates.js';
|
|
5
7
|
export type AppMaintenanceOptions = {
|
|
6
8
|
signal?: AbortSignal;
|
|
7
9
|
};
|
|
@@ -55,4 +57,5 @@ export declare const appExtensions: import("../../../core/extend-resource.js").R
|
|
|
55
57
|
getGeneration: (appIdentity: string, options?: GetGenerationOptions) => Promise<GenerationKind | undefined>;
|
|
56
58
|
getProcessTier: (appIdentity: string, options?: GetProcessTierOptions) => Promise<ProcessTier | undefined>;
|
|
57
59
|
isShielded: (appIdentity: string, options?: IsShieldedOptions) => Promise<boolean>;
|
|
60
|
+
waitForACMCertificates: (appIdentity: string, options?: WaitForACMCertificatesOptions) => Promise<import("@heroku/types/3.sdk").Domain[]>;
|
|
58
61
|
}>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
2
|
import { describeApp } from './info.js';
|
|
3
|
+
import { waitForACMCertificates } from './wait-for-acm-certificates.js';
|
|
3
4
|
export { describeApp, } from './info.js';
|
|
5
|
+
export { waitForACMCertificates } from './wait-for-acm-certificates.js';
|
|
4
6
|
export async function enableMaintenance(ctx, appIdentity, options = {}) {
|
|
5
7
|
options.signal?.throwIfAborted();
|
|
6
8
|
return ctx.platform.app.update(appIdentity, { maintenance: true });
|
|
@@ -50,4 +52,5 @@ export const appExtensions = extendResource('platform', 'app', ctx => ({
|
|
|
50
52
|
getGeneration: (appIdentity, options) => getGeneration(ctx, appIdentity, options),
|
|
51
53
|
getProcessTier: (appIdentity, options) => getProcessTier(ctx, appIdentity, options),
|
|
52
54
|
isShielded: (appIdentity, options) => isShielded(ctx, appIdentity, options),
|
|
55
|
+
waitForACMCertificates: (appIdentity, options) => waitForACMCertificates(ctx, appIdentity, options),
|
|
53
56
|
}));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Domain } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
export type WaitForACMCertificatesOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Base polling interval in milliseconds. Defaults to 15000 (the
|
|
6
|
+
* CLI's 15s base). The effective delay grows with the CLI's backoff
|
|
7
|
+
* formula, scaled off this value.
|
|
8
|
+
*/
|
|
9
|
+
intervalMs?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Abort signal to cancel the operation.
|
|
12
|
+
*/
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
/**
|
|
15
|
+
* Maximum time in milliseconds to wait before giving up.
|
|
16
|
+
* If undefined, waits indefinitely (preserving the CLI's
|
|
17
|
+
* poll-forever behavior) until the signal aborts.
|
|
18
|
+
*/
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Waits for ACM (Automated Certificate Management) certificates to be
|
|
23
|
+
* issued on all custom domains of an app.
|
|
24
|
+
*
|
|
25
|
+
* Polls the app's domains until every `custom` domain reaches a
|
|
26
|
+
* terminal `acm_status` of `'cert issued'` or `'failed'`, then returns
|
|
27
|
+
* the custom domains. `heroku` domains are ignored and never block
|
|
28
|
+
* termination.
|
|
29
|
+
*
|
|
30
|
+
* @throws if any custom domain finishes with `acm_status: 'failed'`
|
|
31
|
+
* (`'ACM not enabled for some domains'`), or if `timeoutMs` elapses
|
|
32
|
+
* before all custom domains reach a terminal status.
|
|
33
|
+
*/
|
|
34
|
+
export declare function waitForACMCertificates(ctx: Pick<ResourceCtx, 'platform'>, appIdentity: string, options?: WaitForACMCertificatesOptions): Promise<Domain[]>;
|