@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { waitForReady } from '../domain/wait-for-ready.js';
|
|
2
|
+
const DEFAULT_WAIT_INTERVAL_MS = 1000;
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
4
|
+
/**
|
|
5
|
+
* Create an SNI endpoint (SSL certificate) on an app and associate it with the
|
|
6
|
+
* app's matching domains.
|
|
7
|
+
*
|
|
8
|
+
* Ports the CLI `certs:add` business logic into the SDK.
|
|
9
|
+
*
|
|
10
|
+
* Steps:
|
|
11
|
+
* 1. Create the SNI endpoint from the given certificate chain and private key.
|
|
12
|
+
* 2. Wait for the app's domains to reach ready status (replaces the old CNAME poll).
|
|
13
|
+
* The SDK waits on domain STATUS (`succeeded`/`none`) via `domain.waitForReady`
|
|
14
|
+
* rather than the CLI's old `cname`-defined readiness check, polling at a default
|
|
15
|
+
* 1000ms interval to mirror the CLI's `waitForDomains` cadence. `timeoutMs`
|
|
16
|
+
* defaults to 30s and is applied per pending domain (not a single global budget),
|
|
17
|
+
* so an app with several pending domains can wait longer than 30s overall.
|
|
18
|
+
* 3. List the (now stable) app domains and wildcard-match them against the
|
|
19
|
+
* certificate's `cert_domains`.
|
|
20
|
+
* 4. If any domains matched, invoke `resolveDomains` — the interactive selection
|
|
21
|
+
* hook the consumer supplies — to choose which matched hostnames to associate.
|
|
22
|
+
* 5. Associate the selected hostnames with the endpoint in parallel via
|
|
23
|
+
* `PATCH /apps/{app}/domains/{hostname}` (`domain.update`).
|
|
24
|
+
*
|
|
25
|
+
* If no domains match the certificate, neither `resolveDomains` nor any
|
|
26
|
+
* association call is made.
|
|
27
|
+
*
|
|
28
|
+
* @throws if the domain readiness wait times out or a domain fails, or if any
|
|
29
|
+
* association call rejects.
|
|
30
|
+
* @returns the created SNI endpoint.
|
|
31
|
+
*/
|
|
32
|
+
// Signature mirrors the CLI `certs:add` / plan-locked public API:
|
|
33
|
+
// createAndAssociate(app, certChain, privateKey, {resolveDomains}).
|
|
34
|
+
// eslint-disable-next-line max-params
|
|
35
|
+
export async function createAndAssociate(ctx, appIdentity, certificateChain, privateKey, options) {
|
|
36
|
+
const { intervalMs = DEFAULT_WAIT_INTERVAL_MS, resolveDomains, signal, timeoutMs = DEFAULT_TIMEOUT_MS, } = options;
|
|
37
|
+
signal?.throwIfAborted();
|
|
38
|
+
const platform = signal ? ctx.platform.withOptions({ signal }) : ctx.platform;
|
|
39
|
+
const sniEndpoint = await platform.sniEndpoint.create(appIdentity, {
|
|
40
|
+
certificate_chain: certificateChain,
|
|
41
|
+
private_key: privateKey,
|
|
42
|
+
});
|
|
43
|
+
await waitForReady(ctx, appIdentity, { signal, timeoutMs, waitIntervalMs: intervalMs });
|
|
44
|
+
const apiDomains = await platform.domain.list(appIdentity);
|
|
45
|
+
const appDomains = apiDomains.map(d => d.hostname);
|
|
46
|
+
const certDomains = (sniEndpoint.ssl_cert.cert_domains ?? []);
|
|
47
|
+
const matched = matchDomains(certDomains, appDomains);
|
|
48
|
+
if (matched.length > 0) {
|
|
49
|
+
const selected = await resolveDomains(matched);
|
|
50
|
+
await Promise.all(selected.map(hostname => platform.domain.update(appIdentity, hostname, { sni_endpoint: sniEndpoint.name })));
|
|
51
|
+
}
|
|
52
|
+
return sniEndpoint;
|
|
53
|
+
}
|
|
54
|
+
function splitDomains(domains) {
|
|
55
|
+
return domains.map(domain => [domain.slice(0, 1), domain.slice(1)]);
|
|
56
|
+
}
|
|
57
|
+
// Adapted from the CLI `certs/add.ts` matcher, but ADDS `^`/`$` anchors to fix a
|
|
58
|
+
// prefix-match bug: without them a wildcard cert like `*.example.com` would match
|
|
59
|
+
// hostnames that merely share a prefix (e.g. `www.example.com.evil.org`). Wildcard
|
|
60
|
+
// certs must not match such hostnames, so both ends of the pattern are anchored.
|
|
61
|
+
function createMatcherFromSplitDomain([firstChar, rest]) {
|
|
62
|
+
const matcherContents = [];
|
|
63
|
+
if (firstChar === '*') {
|
|
64
|
+
matcherContents.push(String.raw `^[\w\-]+`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
matcherContents.push(`^${firstChar}`);
|
|
68
|
+
}
|
|
69
|
+
const escapedRest = rest.replaceAll('.', String.raw `\.`);
|
|
70
|
+
matcherContents.push(escapedRest);
|
|
71
|
+
return new RegExp(`${matcherContents.join('')}$`);
|
|
72
|
+
}
|
|
73
|
+
function matchDomains(certDomains, appDomains) {
|
|
74
|
+
const splitCertDomains = splitDomains(certDomains);
|
|
75
|
+
const matchers = splitCertDomains.map(splitDomain => createMatcherFromSplitDomain(splitDomain));
|
|
76
|
+
if (splitCertDomains.some(domain => (domain[0] === '*'))) {
|
|
77
|
+
const matchedDomains = [];
|
|
78
|
+
for (const appDomain of appDomains) {
|
|
79
|
+
if (matchers.some(matcher => matcher.test(appDomain))) {
|
|
80
|
+
matchedDomains.push(appDomain);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return matchedDomains;
|
|
84
|
+
}
|
|
85
|
+
return certDomains.filter(domain => appDomains.includes(domain));
|
|
86
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateAndAssociateOptions } from './create-and-associate.js';
|
|
2
|
+
export { createAndAssociate, type CreateAndAssociateOptions } from './create-and-associate.js';
|
|
3
|
+
export declare const sniEndpointExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "sniEndpoint", {
|
|
4
|
+
createAndAssociate: (appIdentity: string, certificateChain: string, privateKey: string, options: CreateAndAssociateOptions) => Promise<import("@heroku/types/3.sdk").SniEndpoint>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { createAndAssociate } from './create-and-associate.js';
|
|
3
|
+
export { createAndAssociate } from './create-and-associate.js';
|
|
4
|
+
export const sniEndpointExtensions = extendResource('platform', 'sniEndpoint', ctx => ({
|
|
5
|
+
createAndAssociate: (appIdentity, certificateChain, privateKey, options) => createAndAssociate(ctx, appIdentity, certificateChain, privateKey, options),
|
|
6
|
+
}));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TelemetryDrainOptions, TelemetryDrainTarget } from './types.js';
|
|
2
|
+
export { removeDrainsForTarget } from './remove-drains-for-target.js';
|
|
3
|
+
export type { TelemetryDrainOptions, TelemetryDrainTarget } from './types.js';
|
|
4
|
+
export declare const telemetryDrainExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "telemetryDrain", {
|
|
5
|
+
removeDrains: (target: TelemetryDrainTarget, options?: TelemetryDrainOptions) => Promise<import("@heroku/types/3.sdk").TelemetryDrain[]>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { removeDrainsForTarget } from './remove-drains-for-target.js';
|
|
3
|
+
export { removeDrainsForTarget } from './remove-drains-for-target.js';
|
|
4
|
+
export const telemetryDrainExtensions = extendResource('platform', 'telemetryDrain', ctx => ({
|
|
5
|
+
removeDrains: (target, options) => removeDrainsForTarget(ctx, target, options),
|
|
6
|
+
}));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TelemetryDrain } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
import type { TelemetryDrainOptions, TelemetryDrainTarget } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Remove all telemetry drains for a given app or space.
|
|
6
|
+
*
|
|
7
|
+
* Behavior:
|
|
8
|
+
* - Dispatches to listByApp or listBySpace based on target discriminant
|
|
9
|
+
* - Deletes all drains in parallel via Promise.all
|
|
10
|
+
* - Returns empty array if no drains exist
|
|
11
|
+
* - Rejects if any individual delete fails
|
|
12
|
+
*/
|
|
13
|
+
export declare function removeDrainsForTarget(ctx: Pick<ResourceCtx, 'platform'>, target: TelemetryDrainTarget, options?: TelemetryDrainOptions): Promise<TelemetryDrain[]>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove all telemetry drains for a given app or space.
|
|
3
|
+
*
|
|
4
|
+
* Behavior:
|
|
5
|
+
* - Dispatches to listByApp or listBySpace based on target discriminant
|
|
6
|
+
* - Deletes all drains in parallel via Promise.all
|
|
7
|
+
* - Returns empty array if no drains exist
|
|
8
|
+
* - Rejects if any individual delete fails
|
|
9
|
+
*/
|
|
10
|
+
export async function removeDrainsForTarget(ctx, target, options = {}) {
|
|
11
|
+
const { signal } = options;
|
|
12
|
+
const { app, space } = target;
|
|
13
|
+
signal?.throwIfAborted();
|
|
14
|
+
const platform = signal ? ctx.platform.withOptions({ signal }) : ctx.platform;
|
|
15
|
+
let drains = [];
|
|
16
|
+
if (app) {
|
|
17
|
+
drains = await platform.telemetryDrain.listByApp(app);
|
|
18
|
+
}
|
|
19
|
+
else if (space) {
|
|
20
|
+
drains = await platform.telemetryDrain.listBySpace(space);
|
|
21
|
+
}
|
|
22
|
+
signal?.throwIfAborted();
|
|
23
|
+
const deletedDrains = await Promise.all(drains.map(drain => platform.telemetryDrain.delete(drain.id)));
|
|
24
|
+
return deletedDrains;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type TestRunStatus, type WaitForStateOptions } from './wait-for-state.js';
|
|
2
|
+
export { TestRunNotReadyError, type TestRunStatus, waitForState, type WaitForStateOptions, } from './wait-for-state.js';
|
|
3
|
+
export declare const testRunExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "testRun", {
|
|
4
|
+
waitForState: (pipelineId: string, testRunNumber: number, targetStates: ReadonlyArray<string | TestRunStatus>, options?: WaitForStateOptions) => Promise<import("@heroku/types/3.sdk").TestRun>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { waitForState } from './wait-for-state.js';
|
|
3
|
+
export { TestRunNotReadyError, waitForState, } from './wait-for-state.js';
|
|
4
|
+
// Resource key MUST be the camelCase `'testRun'` — it matches the generated
|
|
5
|
+
// route-group export name that `mergeExtensions` looks up on the platform
|
|
6
|
+
// client. The directory stays kebab (`test-run/`).
|
|
7
|
+
export const testRunExtensions = extendResource('platform', 'testRun', ctx => ({
|
|
8
|
+
waitForState: (pipelineId, testRunNumber, targetStates, options) => waitForState(ctx, pipelineId, testRunNumber, targetStates, options),
|
|
9
|
+
}));
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { TestRun } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
/**
|
|
4
|
+
* The documented `TestRun.status` values from the Platform API. Provided
|
|
5
|
+
* for autocomplete in {@link waitForState}'s `targetStates`; the runtime
|
|
6
|
+
* comparison is plain string membership (`targetStates.includes(run.status)`),
|
|
7
|
+
* so any string is accepted.
|
|
8
|
+
*/
|
|
9
|
+
export type TestRunStatus = TestRun['status'];
|
|
10
|
+
/**
|
|
11
|
+
* Thrown by {@link waitForState} when the test run did not reach one of
|
|
12
|
+
* `targetStates` within `timeoutMs`. Carries the last observed run so
|
|
13
|
+
* callers can branch on its terminal `status` (e.g. distinguish a
|
|
14
|
+
* `failed` run from one still `building`). Mirrors `DynoNotReadyError`.
|
|
15
|
+
*/
|
|
16
|
+
export declare class TestRunNotReadyError extends Error {
|
|
17
|
+
readonly testRun: TestRun;
|
|
18
|
+
readonly expectedStates: ReadonlyArray<string>;
|
|
19
|
+
readonly timeoutMs: number;
|
|
20
|
+
readonly id = "test_run_not_ready";
|
|
21
|
+
constructor(testRun: TestRun, expectedStates: ReadonlyArray<string>, timeoutMs: number);
|
|
22
|
+
}
|
|
23
|
+
export type WaitForStateOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Delay between polls in milliseconds. Defaults to 1000.
|
|
26
|
+
*/
|
|
27
|
+
intervalMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Fires after every successful `testRun.infoByPipeline` call, including
|
|
30
|
+
* runs whose `status` does not yet match `targetStates` (and trigger
|
|
31
|
+
* another poll) AND the final matching run before it is returned. Use
|
|
32
|
+
* this to surface live state transitions in a UI. Exceptions thrown from
|
|
33
|
+
* this callback propagate and end the wait — there is no implicit catch.
|
|
34
|
+
*/
|
|
35
|
+
onPoll?: (run: TestRun) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Cancels the wait early. If already aborted, {@link waitForState}
|
|
38
|
+
* rejects before the first poll; if aborted mid-wait, the pending delay
|
|
39
|
+
* rejects with `signal.reason`.
|
|
40
|
+
*/
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
/**
|
|
43
|
+
* Wall-clock budget in milliseconds before {@link TestRunNotReadyError}
|
|
44
|
+
* is thrown. Defaults to 600000 (10 minutes) — a finite default so a
|
|
45
|
+
* stuck run can't hang forever. Pass a larger value for long CI runs.
|
|
46
|
+
*/
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Poll `testRun.infoByPipeline` until the run's `status` is one of
|
|
51
|
+
* `targetStates`, then return that run. Reuses the SDK's cancellable
|
|
52
|
+
* `wait()` helper to space polls — no bare `setTimeout` loop.
|
|
53
|
+
*
|
|
54
|
+
* Transient-error handling: errors from `infoByPipeline` **propagate**;
|
|
55
|
+
* there is no 404/429/503 retry. This matches the closest precedent,
|
|
56
|
+
* `promotePipeline`, and the original CLI `waitForStates`, which had no
|
|
57
|
+
* retry either — adopting `waitForInfo`'s transient retry would be behavior
|
|
58
|
+
* the eventual CLI caller never had.
|
|
59
|
+
*
|
|
60
|
+
* On exceeding `timeoutMs` without a match, throws {@link TestRunNotReadyError}
|
|
61
|
+
* carrying the last observed run.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const run = await platform.testRun.waitForState(pipelineId, 42, ['debugging', 'errored'])
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export declare function waitForState(ctx: Pick<ResourceCtx, 'platform'>, pipelineId: string, testRunNumber: number, targetStates: ReadonlyArray<string | TestRunStatus>, options?: WaitForStateOptions): Promise<TestRun>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { wait } from '../../../utils/wait.js';
|
|
2
|
+
const DEFAULT_INTERVAL_MS = 1000;
|
|
3
|
+
// Finite default so a stuck CI test run can't hang a caller forever.
|
|
4
|
+
// Diverges deliberately from `promotePipeline`, which leaves `timeoutMs`
|
|
5
|
+
// unbounded; callers that need longer can pass an explicit `timeoutMs`.
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 600_000;
|
|
7
|
+
/**
|
|
8
|
+
* Thrown by {@link waitForState} when the test run did not reach one of
|
|
9
|
+
* `targetStates` within `timeoutMs`. Carries the last observed run so
|
|
10
|
+
* callers can branch on its terminal `status` (e.g. distinguish a
|
|
11
|
+
* `failed` run from one still `building`). Mirrors `DynoNotReadyError`.
|
|
12
|
+
*/
|
|
13
|
+
export class TestRunNotReadyError extends Error {
|
|
14
|
+
testRun;
|
|
15
|
+
expectedStates;
|
|
16
|
+
timeoutMs;
|
|
17
|
+
id = 'test_run_not_ready';
|
|
18
|
+
constructor(testRun, expectedStates, timeoutMs) {
|
|
19
|
+
super(`Test run #${testRun.number} did not reach status ${expectedStates.join('/')} `
|
|
20
|
+
+ `within ${timeoutMs}ms (last status: ${testRun.status}).`);
|
|
21
|
+
this.testRun = testRun;
|
|
22
|
+
this.expectedStates = expectedStates;
|
|
23
|
+
this.timeoutMs = timeoutMs;
|
|
24
|
+
this.name = 'TestRunNotReadyError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Poll `testRun.infoByPipeline` until the run's `status` is one of
|
|
29
|
+
* `targetStates`, then return that run. Reuses the SDK's cancellable
|
|
30
|
+
* `wait()` helper to space polls — no bare `setTimeout` loop.
|
|
31
|
+
*
|
|
32
|
+
* Transient-error handling: errors from `infoByPipeline` **propagate**;
|
|
33
|
+
* there is no 404/429/503 retry. This matches the closest precedent,
|
|
34
|
+
* `promotePipeline`, and the original CLI `waitForStates`, which had no
|
|
35
|
+
* retry either — adopting `waitForInfo`'s transient retry would be behavior
|
|
36
|
+
* the eventual CLI caller never had.
|
|
37
|
+
*
|
|
38
|
+
* On exceeding `timeoutMs` without a match, throws {@link TestRunNotReadyError}
|
|
39
|
+
* carrying the last observed run.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const run = await platform.testRun.waitForState(pipelineId, 42, ['debugging', 'errored'])
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export async function waitForState(ctx, pipelineId, testRunNumber, targetStates, options = {}) {
|
|
47
|
+
const { intervalMs = DEFAULT_INTERVAL_MS, onPoll, signal, timeoutMs = DEFAULT_TIMEOUT_MS, } = options;
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
// Scope the in-flight info call to the caller's signal so an abort takes
|
|
50
|
+
// effect during the API call, not just during the backoff.
|
|
51
|
+
const platform = signal ? ctx.platform.withOptions({ signal }) : ctx.platform;
|
|
52
|
+
const deadline = Date.now() + timeoutMs;
|
|
53
|
+
while (true) {
|
|
54
|
+
signal?.throwIfAborted();
|
|
55
|
+
const run = await platform.testRun.infoByPipeline(pipelineId, testRunNumber);
|
|
56
|
+
onPoll?.(run);
|
|
57
|
+
if (targetStates.includes(run.status)) {
|
|
58
|
+
return run;
|
|
59
|
+
}
|
|
60
|
+
if (Date.now() >= deadline) {
|
|
61
|
+
throw new TestRunNotReadyError(run, targetStates, timeoutMs);
|
|
62
|
+
}
|
|
63
|
+
await wait(intervalMs, signal);
|
|
64
|
+
}
|
|
65
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroku/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Heroku SDK",
|
|
5
5
|
"author": "Heroku",
|
|
6
6
|
"type": "module",
|
|
@@ -50,6 +50,14 @@
|
|
|
50
50
|
"types": "./dist/resources/platform/pipeline/*.d.ts",
|
|
51
51
|
"default": "./dist/resources/platform/pipeline/*.js"
|
|
52
52
|
},
|
|
53
|
+
"./resources/platform/test-run": {
|
|
54
|
+
"types": "./dist/resources/platform/test-run/index.d.ts",
|
|
55
|
+
"default": "./dist/resources/platform/test-run/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./resources/data/redis": {
|
|
58
|
+
"types": "./dist/resources/data/redis/index.d.ts",
|
|
59
|
+
"default": "./dist/resources/data/redis/index.js"
|
|
60
|
+
},
|
|
53
61
|
"./resources/*": {
|
|
54
62
|
"types": "./dist/resources/*.d.ts",
|
|
55
63
|
"default": "./dist/resources/*.js"
|
|
@@ -91,8 +99,8 @@
|
|
|
91
99
|
"vitest": "^4.1.4"
|
|
92
100
|
},
|
|
93
101
|
"dependencies": {
|
|
94
|
-
"@heroku/heroku-fetch": "^0.1.
|
|
95
|
-
"@heroku/types": "^
|
|
102
|
+
"@heroku/heroku-fetch": "^0.1.3",
|
|
103
|
+
"@heroku/types": "^4.0.1",
|
|
96
104
|
"debug": "^4.4.0"
|
|
97
105
|
}
|
|
98
106
|
}
|