@heroku/sdk 0.5.7 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -8
- package/dist/core/extend-resource.d.ts +11 -2
- package/dist/core/heroku-sdk.d.ts +13 -1
- package/dist/core/heroku-sdk.js +59 -3
- package/dist/resources/extensions/platform.d.ts +4 -1
- package/dist/resources/extensions/platform.js +4 -1
- package/dist/resources/platform/app/diff.d.ts +20 -0
- package/dist/resources/platform/app/diff.js +110 -0
- package/dist/resources/platform/app/index.d.ts +3 -0
- package/dist/resources/platform/app/index.js +3 -0
- package/dist/resources/platform/container/ensure-container-stack.d.ts +2 -7
- package/dist/resources/platform/container/ensure-container-stack.js +1 -10
- package/dist/resources/platform/domain/test-utils.js +2 -0
- package/dist/resources/platform/review-app-config/debug.d.ts +2 -0
- package/dist/resources/platform/review-app-config/debug.js +2 -0
- package/dist/resources/platform/review-app-config/index.d.ts +5 -0
- package/dist/resources/platform/review-app-config/index.js +17 -0
- package/dist/resources/platform/review-app-config/resolve-repo-name.d.ts +16 -0
- package/dist/resources/platform/review-app-config/resolve-repo-name.js +54 -0
- package/dist/resources/platform/space/errors.d.ts +28 -0
- package/dist/resources/platform/space/errors.js +34 -0
- package/dist/resources/platform/space/index.d.ts +7 -0
- package/dist/resources/platform/space/index.js +7 -0
- package/dist/resources/platform/space/types.d.ts +25 -0
- package/dist/resources/platform/space/types.js +1 -0
- package/dist/resources/platform/space/wait-for-allocated.d.ts +29 -0
- package/dist/resources/platform/space/wait-for-allocated.js +56 -0
- package/dist/resources/platform/vpn-connection/index.d.ts +8 -0
- package/dist/resources/platform/vpn-connection/index.js +6 -0
- package/dist/resources/platform/vpn-connection/wait-for-active.d.ts +39 -0
- package/dist/resources/platform/vpn-connection/wait-for-active.js +48 -0
- package/dist/services/dashboard-backend.d.ts +5 -0
- package/dist/services/dashboard-backend.js +10 -0
- package/dist/services/repositories-api.d.ts +5 -0
- package/dist/services/repositories-api.js +10 -0
- package/dist/services/repositories.d.ts +5 -0
- package/dist/services/repositories.js +11 -0
- package/package.json +40 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Heroku SDK
|
|
2
2
|
|
|
3
|
-
A TypeScript SDK for the [Heroku Platform API](https://devcenter.heroku.com/articles/platform-api-reference), [Heroku Data API](https://devcenter.heroku.com/articles/heroku-postgres-api-reference),
|
|
3
|
+
A TypeScript SDK for the [Heroku Platform API](https://devcenter.heroku.com/articles/platform-api-reference), [Heroku Data API](https://devcenter.heroku.com/articles/heroku-postgres-api-reference), the Heroku Dashboard Backend, the Heroku Metrics API, and the Heroku Repositories API. It generates fully-typed clients at runtime from route definitions — no hand-written method per endpoint.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -22,31 +22,69 @@ const sdk = new HerokuSDK({ extensions: [appExtensions] })
|
|
|
22
22
|
// Upstream route (auto-generated from the API spec)
|
|
23
23
|
const apps = await sdk.platform.app.list()
|
|
24
24
|
const app = await sdk.platform.app.info('my-app')
|
|
25
|
+
const favorites = await sdk.dashboardBackend.favorite.list({ type: 'app' })
|
|
25
26
|
|
|
26
27
|
// Hand-written extension method (provided by appExtensions)
|
|
27
28
|
await sdk.platform.app.enableMaintenance('my-app')
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
Extensions can also coordinate multiple services. For example, resolve a
|
|
32
|
+
pipeline first, then resolve its GitHub repository through Repositories API
|
|
33
|
+
with the Kolkrabbi-backed repositories service as a fallback:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { HerokuSDK } from '@heroku/sdk'
|
|
37
|
+
import { reviewAppConfigExtensions } from '@heroku/sdk/extensions/platform'
|
|
38
|
+
|
|
39
|
+
const sdk = new HerokuSDK({ extensions: [reviewAppConfigExtensions] })
|
|
40
|
+
const pipeline = await sdk.platform.pipeline.info('my-pipeline')
|
|
41
|
+
|
|
42
|
+
if (!pipeline.id) throw new Error('Pipeline response did not include an id')
|
|
43
|
+
const repo = await sdk.platform.reviewAppConfig.resolveRepoName(pipeline.id)
|
|
44
|
+
```
|
|
45
|
+
|
|
30
46
|
You can also pass options directly:
|
|
31
47
|
|
|
32
48
|
```ts
|
|
33
49
|
const sdk = new HerokuSDK({ clientOptions: { token: 'your-api-token' } })
|
|
34
50
|
```
|
|
35
51
|
|
|
52
|
+
Common client options apply to every service. In particular, a common `baseUrl`
|
|
53
|
+
overrides every service's default host, including Metrics and Repositories. Use
|
|
54
|
+
shallow per-service overrides when only specific services should use a host or
|
|
55
|
+
other client setting:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const sdk = new HerokuSDK({
|
|
59
|
+
clientOptions: { token: 'your-api-token' },
|
|
60
|
+
clientOptionsByService: {
|
|
61
|
+
platform: { baseUrl: 'https://api.heroku.com' },
|
|
62
|
+
repositoriesApi: { baseUrl: 'https://api.heroku.com' },
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
36
67
|
### Per-service clients
|
|
37
68
|
|
|
38
69
|
If you only need one service (and want the smallest possible bundle), import the factory directly. These return the same typed client the SDK class wraps internally.
|
|
39
70
|
|
|
40
71
|
```ts
|
|
41
|
-
import { createPlatformClient }
|
|
42
|
-
import { createDataClient }
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
import { createPlatformClient } from '@heroku/sdk/platform'
|
|
73
|
+
import { createDataClient } from '@heroku/sdk/data'
|
|
74
|
+
import { createDashboardBackendClient } from '@heroku/sdk/dashboard-backend'
|
|
75
|
+
import { createMetricsClient } from '@heroku/sdk/metrics'
|
|
76
|
+
import { createRepositoriesClient } from '@heroku/sdk/repositories'
|
|
77
|
+
import { createRepositoriesApiClient } from '@heroku/sdk/repositories-api'
|
|
78
|
+
|
|
79
|
+
const platform = createPlatformClient()
|
|
80
|
+
const data = createDataClient()
|
|
81
|
+
const dashboardBackend = createDashboardBackendClient()
|
|
82
|
+
const metrics = createMetricsClient()
|
|
83
|
+
const repositories = createRepositoriesClient()
|
|
84
|
+
const repositoriesApi = createRepositoriesApiClient()
|
|
48
85
|
|
|
49
86
|
const apps = await platform.app.list()
|
|
87
|
+
const favorites = await dashboardBackend.favorite.list({ type: 'app' })
|
|
50
88
|
```
|
|
51
89
|
|
|
52
90
|
### Imports
|
|
@@ -56,7 +94,10 @@ const apps = await platform.app.list()
|
|
|
56
94
|
| `HerokuSDK`, `extendResource`, types | `@heroku/sdk` |
|
|
57
95
|
| `createPlatformClient`, `PlatformClient` | `@heroku/sdk/platform` |
|
|
58
96
|
| `createDataClient`, `DataClient` | `@heroku/sdk/data` |
|
|
97
|
+
| `createDashboardBackendClient`, `DashboardBackendClient` | `@heroku/sdk/dashboard-backend` |
|
|
59
98
|
| `createMetricsClient`, `MetricsClient` | `@heroku/sdk/metrics` |
|
|
99
|
+
| `createRepositoriesClient`, `RepositoriesClient` | `@heroku/sdk/repositories` |
|
|
100
|
+
| `createRepositoriesApiClient`, `RepositoriesApiClient` | `@heroku/sdk/repositories-api` |
|
|
60
101
|
| `appExtensions`, `dynoExtensions`, … | `@heroku/sdk/extensions/platform` |
|
|
61
102
|
| `databaseExtensions`, … | `@heroku/sdk/extensions/data` |
|
|
62
103
|
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import type { DashboardBackendClient } from '../services/dashboard-backend.js';
|
|
1
2
|
import type { DataClient } from '../services/data.js';
|
|
2
3
|
import type { MetricsClient } from '../services/metrics.js';
|
|
3
4
|
import type { PlatformClient } from '../services/platform.js';
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
import type { RepositoriesApiClient } from '../services/repositories-api.js';
|
|
6
|
+
import type { RepositoriesClient } from '../services/repositories.js';
|
|
7
|
+
export type ServiceName = 'dashboardBackend' | 'data' | 'metrics' | 'platform' | 'repositories' | 'repositoriesApi';
|
|
8
|
+
type BaseResourceCtx = {
|
|
9
|
+
dashboardBackend: DashboardBackendClient;
|
|
6
10
|
data: DataClient;
|
|
7
11
|
metrics: MetricsClient;
|
|
8
12
|
platform: PlatformClient;
|
|
13
|
+
repositories: RepositoriesClient;
|
|
14
|
+
};
|
|
15
|
+
export type ResourceCtx = BaseResourceCtx & {
|
|
16
|
+
/** Available to SDK-provided extensions; optional for compatibility with existing extension contexts. */
|
|
17
|
+
repositoriesApi?: RepositoriesApiClient;
|
|
9
18
|
};
|
|
10
19
|
export type ResourceMethods = Record<string, (...args: any[]) => any>;
|
|
11
20
|
export type ResourceExtension<S extends ServiceName = ServiceName, R extends string = string, M extends ResourceMethods = ResourceMethods> = {
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import type { HerokuApiClientOptions } from '@heroku/heroku-fetch';
|
|
2
|
+
import type { DashboardBackendClient } from '../services/dashboard-backend.js';
|
|
2
3
|
import type { DataClient } from '../services/data.js';
|
|
3
4
|
import type { MetricsClient } from '../services/metrics.js';
|
|
4
5
|
import type { PlatformClient } from '../services/platform.js';
|
|
5
|
-
import type {
|
|
6
|
+
import type { RepositoriesApiClient } from '../services/repositories-api.js';
|
|
7
|
+
import type { RepositoriesClient } from '../services/repositories.js';
|
|
8
|
+
import type { ApplyExtensions, ExtensionsFor, ResourceExtension, ServiceName } from './extend-resource.js';
|
|
9
|
+
type ServiceClientOptions = Omit<HerokuApiClientOptions, 'service'> & {
|
|
10
|
+
service?: never;
|
|
11
|
+
};
|
|
6
12
|
export type HerokuSDKOptions<Exts extends readonly ResourceExtension[]> = {
|
|
7
13
|
clientOptions?: HerokuApiClientOptions;
|
|
14
|
+
/** Shallow per-service overrides applied after common clientOptions. */
|
|
15
|
+
clientOptionsByService?: Partial<Record<ServiceName, ServiceClientOptions>>;
|
|
8
16
|
extensions?: Exts;
|
|
9
17
|
};
|
|
10
18
|
export declare class HerokuSDK<const Exts extends readonly ResourceExtension[] = readonly ResourceExtension[]> {
|
|
11
19
|
#private;
|
|
12
20
|
constructor(options?: HerokuSDKOptions<Exts>);
|
|
21
|
+
get dashboardBackend(): ApplyExtensions<DashboardBackendClient, ExtensionsFor<Exts, 'dashboardBackend'>>;
|
|
13
22
|
get data(): ApplyExtensions<DataClient, ExtensionsFor<Exts, 'data'>>;
|
|
14
23
|
get metrics(): ApplyExtensions<MetricsClient, ExtensionsFor<Exts, 'metrics'>>;
|
|
15
24
|
get platform(): ApplyExtensions<PlatformClient, ExtensionsFor<Exts, 'platform'>>;
|
|
25
|
+
get repositories(): ApplyExtensions<RepositoriesClient, ExtensionsFor<Exts, 'repositories'>>;
|
|
26
|
+
get repositoriesApi(): ApplyExtensions<RepositoriesApiClient, ExtensionsFor<Exts, 'repositoriesApi'>>;
|
|
16
27
|
}
|
|
28
|
+
export {};
|
package/dist/core/heroku-sdk.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { createDashboardBackendClient } from '../services/dashboard-backend.js';
|
|
1
2
|
import { createDataClient } from '../services/data.js';
|
|
2
3
|
import { createMetricsClient } from '../services/metrics.js';
|
|
3
4
|
import { createPlatformClient } from '../services/platform.js';
|
|
5
|
+
import { createRepositoriesApiClient } from '../services/repositories-api.js';
|
|
6
|
+
import { createRepositoriesClient } from '../services/repositories.js';
|
|
4
7
|
import { mergeExtensions } from './extensions-proxy.js';
|
|
5
8
|
function partitionByService(extensions) {
|
|
6
9
|
const map = new Map();
|
|
@@ -13,18 +16,30 @@ function partitionByService(extensions) {
|
|
|
13
16
|
}
|
|
14
17
|
export class HerokuSDK {
|
|
15
18
|
#clientOptions;
|
|
19
|
+
#clientOptionsByService;
|
|
16
20
|
#ctx;
|
|
21
|
+
#dashboardBackend;
|
|
17
22
|
#data;
|
|
18
23
|
#extensionsByService;
|
|
19
24
|
#metrics;
|
|
20
25
|
#platform;
|
|
26
|
+
#rawDashboardBackend;
|
|
21
27
|
#rawData;
|
|
22
28
|
#rawMetrics;
|
|
23
29
|
#rawPlatform;
|
|
30
|
+
#rawRepositories;
|
|
31
|
+
#rawRepositoriesApi;
|
|
32
|
+
#repositories;
|
|
33
|
+
#repositoriesApi;
|
|
24
34
|
constructor(options = {}) {
|
|
25
35
|
this.#clientOptions = options.clientOptions ?? {};
|
|
36
|
+
this.#clientOptionsByService = options.clientOptionsByService ?? {};
|
|
26
37
|
this.#extensionsByService = partitionByService(options.extensions ?? []);
|
|
27
38
|
}
|
|
39
|
+
get dashboardBackend() {
|
|
40
|
+
this.#dashboardBackend ??= mergeExtensions(this.#getRawDashboardBackend(), this.#extensionsByService.get('dashboardBackend') ?? [], this.#getCtx());
|
|
41
|
+
return this.#dashboardBackend;
|
|
42
|
+
}
|
|
28
43
|
get data() {
|
|
29
44
|
this.#data ??= mergeExtensions(this.#getRawData(), this.#extensionsByService.get('data') ?? [], this.#getCtx());
|
|
30
45
|
return this.#data;
|
|
@@ -37,8 +52,29 @@ export class HerokuSDK {
|
|
|
37
52
|
this.#platform ??= mergeExtensions(this.#getRawPlatform(), this.#extensionsByService.get('platform') ?? [], this.#getCtx());
|
|
38
53
|
return this.#platform;
|
|
39
54
|
}
|
|
55
|
+
get repositories() {
|
|
56
|
+
this.#repositories ??= mergeExtensions(this.#getRawRepositories(), this.#extensionsByService.get('repositories') ?? [], this.#getCtx());
|
|
57
|
+
return this.#repositories;
|
|
58
|
+
}
|
|
59
|
+
get repositoriesApi() {
|
|
60
|
+
this.#repositoriesApi ??= mergeExtensions(this.#getRawRepositoriesApi(), this.#extensionsByService.get('repositoriesApi') ?? [], this.#getCtx());
|
|
61
|
+
return this.#repositoriesApi;
|
|
62
|
+
}
|
|
63
|
+
#getClientOptions(service) {
|
|
64
|
+
const serviceOverride = this.#clientOptionsByService[service];
|
|
65
|
+
const serviceOptions = { ...serviceOverride };
|
|
66
|
+
delete serviceOptions.service;
|
|
67
|
+
return {
|
|
68
|
+
...this.#clientOptions,
|
|
69
|
+
...serviceOptions,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
40
72
|
#getCtx() {
|
|
41
73
|
this.#ctx ??= Object.defineProperties({}, {
|
|
74
|
+
dashboardBackend: {
|
|
75
|
+
enumerable: true,
|
|
76
|
+
get: () => this.#getRawDashboardBackend(),
|
|
77
|
+
},
|
|
42
78
|
data: {
|
|
43
79
|
enumerable: true,
|
|
44
80
|
get: () => this.#getRawData(),
|
|
@@ -51,19 +87,39 @@ export class HerokuSDK {
|
|
|
51
87
|
enumerable: true,
|
|
52
88
|
get: () => this.#getRawPlatform(),
|
|
53
89
|
},
|
|
90
|
+
repositories: {
|
|
91
|
+
enumerable: true,
|
|
92
|
+
get: () => this.#getRawRepositories(),
|
|
93
|
+
},
|
|
94
|
+
repositoriesApi: {
|
|
95
|
+
enumerable: true,
|
|
96
|
+
get: () => this.#getRawRepositoriesApi(),
|
|
97
|
+
},
|
|
54
98
|
});
|
|
55
99
|
return this.#ctx;
|
|
56
100
|
}
|
|
101
|
+
#getRawDashboardBackend() {
|
|
102
|
+
this.#rawDashboardBackend ??= createDashboardBackendClient(this.#getClientOptions('dashboardBackend'));
|
|
103
|
+
return this.#rawDashboardBackend;
|
|
104
|
+
}
|
|
57
105
|
#getRawData() {
|
|
58
|
-
this.#rawData ??= createDataClient(this.#
|
|
106
|
+
this.#rawData ??= createDataClient(this.#getClientOptions('data'));
|
|
59
107
|
return this.#rawData;
|
|
60
108
|
}
|
|
61
109
|
#getRawMetrics() {
|
|
62
|
-
this.#rawMetrics ??= createMetricsClient(this.#
|
|
110
|
+
this.#rawMetrics ??= createMetricsClient(this.#getClientOptions('metrics'));
|
|
63
111
|
return this.#rawMetrics;
|
|
64
112
|
}
|
|
65
113
|
#getRawPlatform() {
|
|
66
|
-
this.#rawPlatform ??= createPlatformClient(this.#
|
|
114
|
+
this.#rawPlatform ??= createPlatformClient(this.#getClientOptions('platform'));
|
|
67
115
|
return this.#rawPlatform;
|
|
68
116
|
}
|
|
117
|
+
#getRawRepositories() {
|
|
118
|
+
this.#rawRepositories ??= createRepositoriesClient(this.#getClientOptions('repositories'));
|
|
119
|
+
return this.#rawRepositories;
|
|
120
|
+
}
|
|
121
|
+
#getRawRepositoriesApi() {
|
|
122
|
+
this.#rawRepositoriesApi ??= createRepositoriesApiClient(this.#getClientOptions('repositoriesApi'));
|
|
123
|
+
return this.#rawRepositoriesApi;
|
|
124
|
+
}
|
|
69
125
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
2
|
-
export { appExtensions, type AppInfo, type PipelineCouplingDetail } from '../platform/app/index.js';
|
|
2
|
+
export { appExtensions, type AppInfo, type DiffAppsOptions, type DiffRow, type PipelineCouplingDetail, } from '../platform/app/index.js';
|
|
3
3
|
export { containerExtensions, type DockerReleasesBatchUpdateOpts, NotAContainerAppError, type ReleaseDockerImagesResult, type RemoveProcessTypesOpts, } from '../platform/container/index.js';
|
|
4
4
|
export { type CreateAndWaitOptions, domainExtensions, type DomainOptions, type WaitForReadyOptions, } from '../platform/domain/index.js';
|
|
5
5
|
export { dynoExtensions, DynoNotReadyError, type DynoState, runDyno, type RunDynoOptions, waitForInfo, type WaitForInfoOptions, } from '../platform/dyno/index.js';
|
|
@@ -7,7 +7,10 @@ export { type AttachmentAttachedEvent, type AttachmentDetachedEvent, type Attach
|
|
|
7
7
|
export { pipelineCouplingExtensions } from '../platform/pipeline/coupling.js';
|
|
8
8
|
export { pipelineExtensions } from '../platform/pipeline/index.js';
|
|
9
9
|
export { pipelinePromotionExtensions } from '../platform/pipeline/promotion.js';
|
|
10
|
+
export { type ResolveRepoNameOptions, reviewAppConfigExtensions } from '../platform/review-app-config/index.js';
|
|
10
11
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
11
12
|
export { createAndAssociate, type CreateAndAssociateOptions, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
13
|
+
export { spaceExtensions, SpaceNotAllocatedError, SpaceNotReadyError, type SpaceOptions, type SpaceWithNat, waitForAllocated, type WaitForAllocatedOptions, } from '../platform/space/index.js';
|
|
12
14
|
export { telemetryDrainExtensions, type TelemetryDrainOptions, type TelemetryDrainTarget } from '../platform/telemetry-drain/index.js';
|
|
13
15
|
export { testRunExtensions, TestRunNotReadyError, type TestRunStatus, waitForState, type WaitForStateOptions, } from '../platform/test-run/index.js';
|
|
16
|
+
export { vpnConnectionExtensions, VpnConnectionFailedError, VpnConnectionNotReadyError, type VpnConnectionOptions, waitForActive, type WaitForActiveOptions, } from '../platform/vpn-connection/index.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { addOnExtensions } from '../platform/add-on/index.js';
|
|
2
|
-
export { appExtensions } from '../platform/app/index.js';
|
|
2
|
+
export { appExtensions, } from '../platform/app/index.js';
|
|
3
3
|
export { containerExtensions, NotAContainerAppError, } from '../platform/container/index.js';
|
|
4
4
|
export { domainExtensions, } from '../platform/domain/index.js';
|
|
5
5
|
export { dynoExtensions, DynoNotReadyError, runDyno, waitForInfo, } from '../platform/dyno/index.js';
|
|
@@ -7,7 +7,10 @@ export { logSessionExtensions, parseHerokuLogLine, } from '../platform/log-sessi
|
|
|
7
7
|
export { pipelineCouplingExtensions } from '../platform/pipeline/coupling.js';
|
|
8
8
|
export { pipelineExtensions } from '../platform/pipeline/index.js';
|
|
9
9
|
export { pipelinePromotionExtensions } from '../platform/pipeline/promotion.js';
|
|
10
|
+
export { reviewAppConfigExtensions } from '../platform/review-app-config/index.js';
|
|
10
11
|
export { privateToShield, shieldToPrivate } from '../platform/shield.js';
|
|
11
12
|
export { createAndAssociate, sniEndpointExtensions } from '../platform/sni-endpoint/index.js';
|
|
13
|
+
export { spaceExtensions, SpaceNotAllocatedError, SpaceNotReadyError, waitForAllocated, } from '../platform/space/index.js';
|
|
12
14
|
export { telemetryDrainExtensions } from '../platform/telemetry-drain/index.js';
|
|
13
15
|
export { testRunExtensions, TestRunNotReadyError, waitForState, } from '../platform/test-run/index.js';
|
|
16
|
+
export { vpnConnectionExtensions, VpnConnectionFailedError, VpnConnectionNotReadyError, waitForActive, } from '../platform/vpn-connection/index.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
2
|
+
export type DiffAppsOptions = {
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
};
|
|
5
|
+
export interface DiffRow {
|
|
6
|
+
app1: string | undefined;
|
|
7
|
+
app2: string | undefined;
|
|
8
|
+
prop: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Composite diff of two apps: compares slug checksum, config vars, stack,
|
|
12
|
+
* buildpacks, add-ons, and features, returning one `DiffRow` per differing
|
|
13
|
+
* property.
|
|
14
|
+
*
|
|
15
|
+
* Faithful port of the Heroku CLI's `apps:diff`: `diffFiles` runs first (so a
|
|
16
|
+
* missing app surfaces the `App not found: <app>` error before the rest), then
|
|
17
|
+
* the remaining five diffs run in parallel and their rows are concatenated in
|
|
18
|
+
* the CLI's order. Rendering (truncation, table output) stays with the caller.
|
|
19
|
+
*/
|
|
20
|
+
export declare function diffApps(ctx: Pick<ResourceCtx, 'platform'>, app1: string, app2: string, options?: DiffAppsOptions): Promise<DiffRow[]>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { NotFoundError } from '@heroku/heroku-fetch';
|
|
2
|
+
async function checksum(platform, app) {
|
|
3
|
+
try {
|
|
4
|
+
const releases = await platform.withHeaders({ Range: 'version ..; max=1, order=desc' }).release.list(app);
|
|
5
|
+
if (releases?.[0]?.slug) {
|
|
6
|
+
const slugId = releases[0].slug.id;
|
|
7
|
+
const slug = await platform.slug.info(app, slugId);
|
|
8
|
+
return slug?.checksum ?? null;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (isNotFound(error)) {
|
|
14
|
+
throw new Error(`App not found: ${app}`, { cause: error });
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function isNotFound(error) {
|
|
20
|
+
return error instanceof NotFoundError;
|
|
21
|
+
}
|
|
22
|
+
async function diffFiles(platform, app1, app2) {
|
|
23
|
+
const sums = await Promise.all([checksum(platform, app1), checksum(platform, app2)]);
|
|
24
|
+
return sums[0] === sums[1] ? [] : [{ app1: sums[0] ?? undefined, app2: sums[1] ?? undefined, prop: 'slug (checksum)' }];
|
|
25
|
+
}
|
|
26
|
+
async function diffEnv(platform, app1, app2) {
|
|
27
|
+
const [vars1, vars2] = await Promise.all([
|
|
28
|
+
platform.configVar.infoForApp(app1),
|
|
29
|
+
platform.configVar.infoForApp(app2),
|
|
30
|
+
]);
|
|
31
|
+
const cfg1 = vars1 ?? {};
|
|
32
|
+
const cfg2 = vars2 ?? {};
|
|
33
|
+
const keys = new Set([...Object.keys(cfg1), ...Object.keys(cfg2)]);
|
|
34
|
+
return [...keys]
|
|
35
|
+
.filter(k => cfg1[k] !== cfg2[k])
|
|
36
|
+
.map(k => ({ app1: cfg1[k], app2: cfg2[k], prop: `config (${k})` }));
|
|
37
|
+
}
|
|
38
|
+
async function diffStack(platform, app1, app2) {
|
|
39
|
+
const [res1, res2] = await Promise.all([
|
|
40
|
+
platform.app.info(app1),
|
|
41
|
+
platform.app.info(app2),
|
|
42
|
+
]);
|
|
43
|
+
const a = res1?.stack?.name;
|
|
44
|
+
const b = res2?.stack?.name;
|
|
45
|
+
return a === b ? [] : [{ app1: a, app2: b, prop: 'stack' }];
|
|
46
|
+
}
|
|
47
|
+
async function diffBuildpacks(platform, app1, app2) {
|
|
48
|
+
const [res1, res2] = await Promise.all([
|
|
49
|
+
platform.buildpackInstallation.list(app1),
|
|
50
|
+
platform.buildpackInstallation.list(app2),
|
|
51
|
+
]);
|
|
52
|
+
const bps1 = res1 ?? [];
|
|
53
|
+
const bps2 = res2 ?? [];
|
|
54
|
+
const urls1 = bps1.map(obj => obj.buildpack?.url ?? '');
|
|
55
|
+
const urls2 = bps2.map(obj => obj.buildpack?.url ?? '');
|
|
56
|
+
const longest = urls1.length >= urls2.length ? urls1 : urls2;
|
|
57
|
+
const pairs = longest.map((_, k) => ({
|
|
58
|
+
app1: urls1[k],
|
|
59
|
+
app2: urls2[k],
|
|
60
|
+
prop: `buildpack (${k})`,
|
|
61
|
+
}));
|
|
62
|
+
return pairs.filter(pair => pair.app1 !== pair.app2);
|
|
63
|
+
}
|
|
64
|
+
async function diffAddons(platform, app1, app2) {
|
|
65
|
+
const [addons1, addons2] = await Promise.all([
|
|
66
|
+
platform.addOn.listByApp(app1),
|
|
67
|
+
platform.addOn.listByApp(app2),
|
|
68
|
+
]);
|
|
69
|
+
const names1 = new Set((addons1 ?? []).map(addon => addon.addon_service?.name ?? '').filter(Boolean));
|
|
70
|
+
const names2 = new Set((addons2 ?? []).map(addon => addon.addon_service?.name ?? '').filter(Boolean));
|
|
71
|
+
const only1 = [...names1].filter(name => !names2.has(name)).map(name => ({ app1: 'true', app2: 'false', prop: `add-on (${name})` }));
|
|
72
|
+
const only2 = [...names2].filter(name => !names1.has(name)).map(name => ({ app1: 'false', app2: 'true', prop: `add-on (${name})` }));
|
|
73
|
+
return [...only1, ...only2];
|
|
74
|
+
}
|
|
75
|
+
async function diffFeatures(platform, app1, app2) {
|
|
76
|
+
const [features1, features2] = await Promise.all([
|
|
77
|
+
platform.appFeature.list(app1),
|
|
78
|
+
platform.appFeature.list(app2),
|
|
79
|
+
]);
|
|
80
|
+
const names1 = new Set((features1 ?? []).map(f => (f.enabled ? f.name : null)).filter(Boolean));
|
|
81
|
+
const names2 = new Set((features2 ?? []).map(f => (f.enabled ? f.name : null)).filter(Boolean));
|
|
82
|
+
const only1 = [...names1].filter(name => !names2.has(name)).map(name => ({ app1: 'enabled', app2: 'disabled', prop: `feature (${name})` }));
|
|
83
|
+
const only2 = [...names2].filter(name => !names1.has(name)).map(name => ({ app1: 'disabled', app2: 'enabled', prop: `feature (${name})` }));
|
|
84
|
+
return [...only1, ...only2];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Composite diff of two apps: compares slug checksum, config vars, stack,
|
|
88
|
+
* buildpacks, add-ons, and features, returning one `DiffRow` per differing
|
|
89
|
+
* property.
|
|
90
|
+
*
|
|
91
|
+
* Faithful port of the Heroku CLI's `apps:diff`: `diffFiles` runs first (so a
|
|
92
|
+
* missing app surfaces the `App not found: <app>` error before the rest), then
|
|
93
|
+
* the remaining five diffs run in parallel and their rows are concatenated in
|
|
94
|
+
* the CLI's order. Rendering (truncation, table output) stays with the caller.
|
|
95
|
+
*/
|
|
96
|
+
export async function diffApps(ctx, app1, app2, options = {}) {
|
|
97
|
+
options.signal?.throwIfAborted();
|
|
98
|
+
const platform = options.signal
|
|
99
|
+
? ctx.platform.withOptions({ signal: options.signal })
|
|
100
|
+
: ctx.platform;
|
|
101
|
+
const files = await diffFiles(platform, app1, app2);
|
|
102
|
+
const [env, stack, bp, addons, features] = await Promise.all([
|
|
103
|
+
diffEnv(platform, app1, app2),
|
|
104
|
+
diffStack(platform, app1, app2),
|
|
105
|
+
diffBuildpacks(platform, app1, app2),
|
|
106
|
+
diffAddons(platform, app1, app2),
|
|
107
|
+
diffFeatures(platform, app1, app2),
|
|
108
|
+
]);
|
|
109
|
+
return [...files, ...env, ...stack, ...bp, ...addons, ...features];
|
|
110
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { App } from '@heroku/types/3.sdk';
|
|
2
2
|
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
3
|
import type { CreateAndSetupInput, CreateAndSetupOptions } from './create-and-setup.js';
|
|
4
|
+
import type { DiffAppsOptions } from './diff.js';
|
|
4
5
|
import type { DescribeAppOptions } from './info.js';
|
|
5
6
|
import type { TransferOptions } from './transfer.js';
|
|
6
7
|
import type { WaitForACMCertificatesOptions } from './wait-for-acm-certificates.js';
|
|
7
8
|
export { createAndSetup, type CreateAndSetupInput, type CreateAndSetupOptions, type SetupStep, } from './create-and-setup.js';
|
|
9
|
+
export { diffApps, type DiffAppsOptions, type DiffRow } from './diff.js';
|
|
8
10
|
export { type AppInfo, describeApp, type DescribeAppOptions, type PipelineCouplingDetail, } from './info.js';
|
|
9
11
|
export { transferApp, type TransferOptions } from './transfer.js';
|
|
10
12
|
export { waitForACMCertificates, type WaitForACMCertificatesOptions } from './wait-for-acm-certificates.js';
|
|
@@ -57,6 +59,7 @@ export declare function getProcessTier(ctx: {
|
|
|
57
59
|
export declare const appExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "app", {
|
|
58
60
|
createAndSetup: (input: CreateAndSetupInput, options?: CreateAndSetupOptions) => Promise<App>;
|
|
59
61
|
describe: (appIdentity: string, options?: DescribeAppOptions) => Promise<import("./info.js").AppInfo>;
|
|
62
|
+
diff: (app1: string, app2: string, options?: DiffAppsOptions) => Promise<import("./diff.js").DiffRow[]>;
|
|
60
63
|
disableMaintenance: (appIdentity: string, options?: AppMaintenanceOptions) => Promise<App>;
|
|
61
64
|
enableMaintenance: (appIdentity: string, options?: AppMaintenanceOptions) => Promise<App>;
|
|
62
65
|
getGeneration: (appIdentity: string, options?: GetGenerationOptions) => Promise<GenerationKind | undefined>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
2
|
import { createAndSetup } from './create-and-setup.js';
|
|
3
|
+
import { diffApps } from './diff.js';
|
|
3
4
|
import { describeApp } from './info.js';
|
|
4
5
|
import { transferApp } from './transfer.js';
|
|
5
6
|
import { waitForACMCertificates } from './wait-for-acm-certificates.js';
|
|
6
7
|
export { createAndSetup, } from './create-and-setup.js';
|
|
8
|
+
export { diffApps } from './diff.js';
|
|
7
9
|
export { describeApp, } from './info.js';
|
|
8
10
|
export { transferApp } from './transfer.js';
|
|
9
11
|
export { waitForACMCertificates } from './wait-for-acm-certificates.js';
|
|
@@ -52,6 +54,7 @@ export async function getProcessTier(ctx, appIdentity, options = {}) {
|
|
|
52
54
|
export const appExtensions = extendResource('platform', 'app', ctx => ({
|
|
53
55
|
createAndSetup: (input, options) => createAndSetup(ctx, input, options),
|
|
54
56
|
describe: (appIdentity, options) => describeApp(ctx, appIdentity, options),
|
|
57
|
+
diff: (app1, app2, options) => diffApps(ctx, app1, app2, options),
|
|
55
58
|
disableMaintenance: (appIdentity, options) => disableMaintenance(ctx, appIdentity, options),
|
|
56
59
|
enableMaintenance: (appIdentity, options) => enableMaintenance(ctx, appIdentity, options),
|
|
57
60
|
getGeneration: (appIdentity, options) => getGeneration(ctx, appIdentity, options),
|
|
@@ -2,14 +2,9 @@ import type { App } from '@heroku/types/3.sdk';
|
|
|
2
2
|
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
3
|
import type { ContainerOptions } from './index.js';
|
|
4
4
|
export declare class NotAContainerAppError extends Error {
|
|
5
|
-
readonly app:
|
|
5
|
+
readonly app: App;
|
|
6
6
|
readonly id = "not_a_container_app";
|
|
7
|
-
constructor(app:
|
|
8
|
-
get body(): {
|
|
9
|
-
app: Pick<App, "stack" | "id" | "name" | "build_stack">;
|
|
10
|
-
id: string;
|
|
11
|
-
message: string;
|
|
12
|
-
};
|
|
7
|
+
constructor(app: App);
|
|
13
8
|
}
|
|
14
9
|
/**
|
|
15
10
|
* Ensure that the given app is a container app.
|
|
@@ -6,9 +6,6 @@ export class NotAContainerAppError extends Error {
|
|
|
6
6
|
this.app = app;
|
|
7
7
|
this.name = 'NotAContainerAppError';
|
|
8
8
|
}
|
|
9
|
-
get body() {
|
|
10
|
-
return { app: this.app, id: this.id, message: this.message };
|
|
11
|
-
}
|
|
12
9
|
}
|
|
13
10
|
/**
|
|
14
11
|
* Ensure that the given app is a container app.
|
|
@@ -21,12 +18,6 @@ export async function ensureContainerStack(ctx, appIdentity, options = {}) {
|
|
|
21
18
|
const app = await platform.app.info(appIdentity);
|
|
22
19
|
const allowedStack = 'container';
|
|
23
20
|
if (app.build_stack.name !== allowedStack && app.stack.name !== allowedStack) {
|
|
24
|
-
throw new NotAContainerAppError(
|
|
25
|
-
// eslint-disable-next-line camelcase
|
|
26
|
-
build_stack: app.build_stack,
|
|
27
|
-
id: app.id,
|
|
28
|
-
name: app.name,
|
|
29
|
-
stack: app.stack,
|
|
30
|
-
});
|
|
21
|
+
throw new NotAContainerAppError(app);
|
|
31
22
|
}
|
|
32
23
|
}
|
|
@@ -16,9 +16,11 @@ export function buildCtx(stubs = {}) {
|
|
|
16
16
|
};
|
|
17
17
|
platform.withOptions.mockReturnValue(platform);
|
|
18
18
|
return {
|
|
19
|
+
dashboardBackend: {},
|
|
19
20
|
data: {},
|
|
20
21
|
metrics: {},
|
|
21
22
|
platform: platform,
|
|
23
|
+
repositories: {},
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
export function buildDomain(overrides = {}) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ResolveRepoNameOptions } from './resolve-repo-name.js';
|
|
2
|
+
export { resolveRepoName, type ResolveRepoNameOptions } from './resolve-repo-name.js';
|
|
3
|
+
export declare const reviewAppConfigExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "reviewAppConfig", {
|
|
4
|
+
resolveRepoName: (pipelineId: string, options?: ResolveRepoNameOptions) => Promise<string>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { resolveRepoName } from './resolve-repo-name.js';
|
|
3
|
+
export { resolveRepoName } from './resolve-repo-name.js';
|
|
4
|
+
export const reviewAppConfigExtensions = extendResource('platform', 'reviewAppConfig', ctx => {
|
|
5
|
+
const { repositoriesApi } = ctx;
|
|
6
|
+
if (!repositoriesApi) {
|
|
7
|
+
throw new Error('reviewAppConfigExtensions requires ResourceCtx.repositoriesApi');
|
|
8
|
+
}
|
|
9
|
+
const resolveRepoNameCtx = {
|
|
10
|
+
platform: ctx.platform,
|
|
11
|
+
repositories: ctx.repositories,
|
|
12
|
+
repositoriesApi,
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
resolveRepoName: (pipelineId, options) => resolveRepoName(resolveRepoNameCtx, pipelineId, options),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
2
|
+
type ResolveRepoNameCtx = Pick<ResourceCtx, 'platform' | 'repositories'> & {
|
|
3
|
+
repositoriesApi: NonNullable<ResourceCtx['repositoriesApi']>;
|
|
4
|
+
};
|
|
5
|
+
export type ResolveRepoNameOptions = {
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Resolves the GitHub repository name for a canonical pipeline ID.
|
|
10
|
+
* Resolve user-supplied pipeline names before calling this helper.
|
|
11
|
+
* Uses Repositories API when the account feature is enabled, and falls back
|
|
12
|
+
* to the Kolkrabbi-backed repositories service when the feature is disabled,
|
|
13
|
+
* the feature lookup fails, or Repositories API cannot return a usable name.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveRepoName(ctx: ResolveRepoNameCtx, pipelineId: string, options?: ResolveRepoNameOptions): Promise<string>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { debug } from './debug.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the GitHub repository name for a canonical pipeline ID.
|
|
4
|
+
* Resolve user-supplied pipeline names before calling this helper.
|
|
5
|
+
* Uses Repositories API when the account feature is enabled, and falls back
|
|
6
|
+
* to the Kolkrabbi-backed repositories service when the feature is disabled,
|
|
7
|
+
* the feature lookup fails, or Repositories API cannot return a usable name.
|
|
8
|
+
*/
|
|
9
|
+
export async function resolveRepoName(ctx, pipelineId, options = {}) {
|
|
10
|
+
const { signal } = options;
|
|
11
|
+
signal?.throwIfAborted();
|
|
12
|
+
const platform = signal ? ctx.platform.withOptions({ signal }) : ctx.platform;
|
|
13
|
+
let useRepositoriesApi;
|
|
14
|
+
try {
|
|
15
|
+
const feature = await platform.accountFeature.info('dashboard-repositories-api');
|
|
16
|
+
signal?.throwIfAborted();
|
|
17
|
+
useRepositoriesApi = feature.enabled;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
signal?.throwIfAborted();
|
|
21
|
+
debug('feature lookup failed error=%s; falling back', describeError(error));
|
|
22
|
+
}
|
|
23
|
+
if (useRepositoriesApi) {
|
|
24
|
+
try {
|
|
25
|
+
const repositoriesApi = signal ? ctx.repositoriesApi.withOptions({ signal }) : ctx.repositoriesApi;
|
|
26
|
+
const repository = await repositoriesApi.githubRepository.info(pipelineId);
|
|
27
|
+
signal?.throwIfAborted();
|
|
28
|
+
const repositoryName = repository.full_name?.trim();
|
|
29
|
+
if (repositoryName) {
|
|
30
|
+
return repositoryName;
|
|
31
|
+
}
|
|
32
|
+
debug('repositories API returned no full name; falling back');
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
signal?.throwIfAborted();
|
|
36
|
+
debug('repositories API lookup failed error=%s; falling back', describeError(error));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else if (useRepositoriesApi === false) {
|
|
40
|
+
signal?.throwIfAborted();
|
|
41
|
+
debug('repositories API disabled; falling back');
|
|
42
|
+
}
|
|
43
|
+
const repositories = signal ? ctx.repositories.withOptions({ signal }) : ctx.repositories;
|
|
44
|
+
const pipelineRepository = await repositories.pipelineRepository.info(pipelineId);
|
|
45
|
+
signal?.throwIfAborted();
|
|
46
|
+
const repositoryName = pipelineRepository.repository.name.trim();
|
|
47
|
+
if (!repositoryName) {
|
|
48
|
+
throw new Error('Repositories service returned no repository name');
|
|
49
|
+
}
|
|
50
|
+
return repositoryName;
|
|
51
|
+
}
|
|
52
|
+
function describeError(error) {
|
|
53
|
+
return error instanceof Error ? error.name : typeof error;
|
|
54
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Space } from '@heroku/types/3.sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by `waitForAllocated` when the space settles into a terminal
|
|
4
|
+
* state other than `allocated` (e.g. `deleting`).
|
|
5
|
+
*/
|
|
6
|
+
export declare class SpaceNotAllocatedError extends Error {
|
|
7
|
+
readonly space: Space;
|
|
8
|
+
readonly id = "space_not_allocated";
|
|
9
|
+
constructor(space: Space);
|
|
10
|
+
get body(): {
|
|
11
|
+
id: string;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown by `waitForAllocated` when `timeoutMs` elapses while the space
|
|
17
|
+
* is still `allocating`.
|
|
18
|
+
*/
|
|
19
|
+
export declare class SpaceNotReadyError extends Error {
|
|
20
|
+
readonly space: Space;
|
|
21
|
+
readonly timeoutMs: number;
|
|
22
|
+
readonly id = "space_not_ready";
|
|
23
|
+
constructor(space: Space, timeoutMs: number);
|
|
24
|
+
get body(): {
|
|
25
|
+
id: string;
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by `waitForAllocated` when the space settles into a terminal
|
|
3
|
+
* state other than `allocated` (e.g. `deleting`).
|
|
4
|
+
*/
|
|
5
|
+
export class SpaceNotAllocatedError extends Error {
|
|
6
|
+
space;
|
|
7
|
+
id = 'space_not_allocated';
|
|
8
|
+
constructor(space) {
|
|
9
|
+
super(`The space ${space.name} did not become allocated, with state ${space.state}.`);
|
|
10
|
+
this.space = space;
|
|
11
|
+
this.name = 'SpaceNotAllocatedError';
|
|
12
|
+
}
|
|
13
|
+
get body() {
|
|
14
|
+
return { id: this.id, message: this.message };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown by `waitForAllocated` when `timeoutMs` elapses while the space
|
|
19
|
+
* is still `allocating`.
|
|
20
|
+
*/
|
|
21
|
+
export class SpaceNotReadyError extends Error {
|
|
22
|
+
space;
|
|
23
|
+
timeoutMs;
|
|
24
|
+
id = 'space_not_ready';
|
|
25
|
+
constructor(space, timeoutMs) {
|
|
26
|
+
super(`Timeout waiting for space ${space.name} to become allocated.`);
|
|
27
|
+
this.space = space;
|
|
28
|
+
this.timeoutMs = timeoutMs;
|
|
29
|
+
this.name = 'SpaceNotReadyError';
|
|
30
|
+
}
|
|
31
|
+
get body() {
|
|
32
|
+
return { id: this.id, message: this.message };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WaitForAllocatedOptions } from './types.js';
|
|
2
|
+
export { SpaceNotAllocatedError, SpaceNotReadyError } from './errors.js';
|
|
3
|
+
export type { SpaceOptions, WaitForAllocatedOptions } from './types.js';
|
|
4
|
+
export { type SpaceWithNat, waitForAllocated } from './wait-for-allocated.js';
|
|
5
|
+
export declare const spaceExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "space", {
|
|
6
|
+
waitForAllocated: (name: string, options?: WaitForAllocatedOptions) => Promise<import("./wait-for-allocated.js").SpaceWithNat>;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { waitForAllocated } from './wait-for-allocated.js';
|
|
3
|
+
export { SpaceNotAllocatedError, SpaceNotReadyError } from './errors.js';
|
|
4
|
+
export { waitForAllocated } from './wait-for-allocated.js';
|
|
5
|
+
export const spaceExtensions = extendResource('platform', 'space', ctx => ({
|
|
6
|
+
waitForAllocated: (name, options) => waitForAllocated(ctx, name, options),
|
|
7
|
+
}));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SpaceOptions = {
|
|
2
|
+
signal?: AbortSignal;
|
|
3
|
+
};
|
|
4
|
+
export type WaitForAllocatedOptions = SpaceOptions & {
|
|
5
|
+
/**
|
|
6
|
+
* When true, once the space reaches a terminal state, also fetches
|
|
7
|
+
* the space's NAT info (`spaceNat.info`) and attaches it as `nat` on
|
|
8
|
+
* the result.
|
|
9
|
+
*
|
|
10
|
+
* Fetching NAT info is soft-failure: if it rejects, `nat` is left
|
|
11
|
+
* `undefined` on the returned space rather than failing the whole
|
|
12
|
+
* wait.
|
|
13
|
+
*/
|
|
14
|
+
includeNat?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Delay between polls in milliseconds.
|
|
17
|
+
* Defaults to 5000 (5s)
|
|
18
|
+
*/
|
|
19
|
+
intervalMs?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Wall-clock budget in milliseconds before `SpaceNotReadyError`
|
|
22
|
+
* is thrown. Defaults to 1200000 (20 minutes)
|
|
23
|
+
*/
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Space, SpaceNat } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
import type { WaitForAllocatedOptions } from './types.js';
|
|
4
|
+
export type SpaceWithNat = Space & {
|
|
5
|
+
/**
|
|
6
|
+
* The space's NAT info, attached when `options.includeNat` is set and
|
|
7
|
+
* the fetch succeeds. Left `undefined` if `includeNat` was not
|
|
8
|
+
* requested, or if the NAT fetch failed (soft-failure).
|
|
9
|
+
*/
|
|
10
|
+
nat?: SpaceNat;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Poll `space.info` until the space's `state` is no longer `allocating`,
|
|
14
|
+
* then return it.
|
|
15
|
+
*
|
|
16
|
+
* If the terminal state is not `allocated` (e.g. `deleting`), throws
|
|
17
|
+
* `SpaceNotAllocatedError`. On exceeding `timeoutMs` without reaching a
|
|
18
|
+
* terminal state, throws `SpaceNotReadyError`.
|
|
19
|
+
*
|
|
20
|
+
* Each poll uses the SDK default `Accept: version=3.sdk` with
|
|
21
|
+
* `Accept-Expansion: region` to match the existing CLI behaviour.
|
|
22
|
+
*
|
|
23
|
+
* When `options.includeNat` is true and the space becomes allocated,
|
|
24
|
+
* also fetches `spaceNat.info` and attaches it as `nat` on the result.
|
|
25
|
+
* That fetch is soft-failure: if it rejects, `nat` is left `undefined`
|
|
26
|
+
* rather than failing the wait. A caller abort during the fetch is the
|
|
27
|
+
* exception — it is re-thrown rather than swallowed.
|
|
28
|
+
*/
|
|
29
|
+
export declare function waitForAllocated(ctx: Pick<ResourceCtx, 'platform'>, name: string, options?: WaitForAllocatedOptions): Promise<SpaceWithNat>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { wait } from '../../../utils/wait.js';
|
|
2
|
+
import { SpaceNotAllocatedError, SpaceNotReadyError } from './errors.js';
|
|
3
|
+
const DEFAULT_INTERVAL_MS = 5000;
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
|
|
5
|
+
/**
|
|
6
|
+
* Poll `space.info` until the space's `state` is no longer `allocating`,
|
|
7
|
+
* then return it.
|
|
8
|
+
*
|
|
9
|
+
* If the terminal state is not `allocated` (e.g. `deleting`), throws
|
|
10
|
+
* `SpaceNotAllocatedError`. On exceeding `timeoutMs` without reaching a
|
|
11
|
+
* terminal state, throws `SpaceNotReadyError`.
|
|
12
|
+
*
|
|
13
|
+
* Each poll uses the SDK default `Accept: version=3.sdk` with
|
|
14
|
+
* `Accept-Expansion: region` to match the existing CLI behaviour.
|
|
15
|
+
*
|
|
16
|
+
* When `options.includeNat` is true and the space becomes allocated,
|
|
17
|
+
* also fetches `spaceNat.info` and attaches it as `nat` on the result.
|
|
18
|
+
* That fetch is soft-failure: if it rejects, `nat` is left `undefined`
|
|
19
|
+
* rather than failing the wait. A caller abort during the fetch is the
|
|
20
|
+
* exception — it is re-thrown rather than swallowed.
|
|
21
|
+
*/
|
|
22
|
+
export async function waitForAllocated(ctx, name, options = {}) {
|
|
23
|
+
const { includeNat, intervalMs = DEFAULT_INTERVAL_MS, signal, timeoutMs = DEFAULT_TIMEOUT_MS, } = options;
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
let platform = ctx.platform.withHeaders({ 'Accept-Expansion': 'region' });
|
|
26
|
+
if (signal) {
|
|
27
|
+
platform = platform.withOptions({ signal });
|
|
28
|
+
}
|
|
29
|
+
const deadline = Date.now() + timeoutMs;
|
|
30
|
+
let space = await platform.space.info(name);
|
|
31
|
+
while (space.state === 'allocating') {
|
|
32
|
+
signal?.throwIfAborted();
|
|
33
|
+
if (Date.now() >= deadline) {
|
|
34
|
+
throw new SpaceNotReadyError(space, timeoutMs);
|
|
35
|
+
}
|
|
36
|
+
await wait(intervalMs, signal);
|
|
37
|
+
space = await platform.space.info(name);
|
|
38
|
+
}
|
|
39
|
+
if (space.state !== 'allocated') {
|
|
40
|
+
throw new SpaceNotAllocatedError(space);
|
|
41
|
+
}
|
|
42
|
+
if (!includeNat) {
|
|
43
|
+
return space;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const nat = await platform.spaceNat.info(name);
|
|
47
|
+
return { ...space, nat };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Soft-failure: a rejected NAT fetch leaves `nat` undefined rather than
|
|
51
|
+
// failing the wait — but a caller abort must not be swallowed here, so
|
|
52
|
+
// re-throw it if the signal aborted during the fetch.
|
|
53
|
+
signal?.throwIfAborted();
|
|
54
|
+
return space;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type WaitForActiveOptions } from './wait-for-active.js';
|
|
2
|
+
export { VpnConnectionFailedError, VpnConnectionNotReadyError, waitForActive, type WaitForActiveOptions, } from './wait-for-active.js';
|
|
3
|
+
export type VpnConnectionOptions = {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
};
|
|
6
|
+
export declare const vpnConnectionExtensions: import("../../../core/extend-resource.js").ResourceExtension<"platform", "vpnConnection", {
|
|
7
|
+
waitForActive: (spaceIdentity: string, vpnConnectionIdentity: string, options?: WaitForActiveOptions) => Promise<import("@heroku/types/3.sdk").VpnConnection>;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { extendResource } from '../../../core/extend-resource.js';
|
|
2
|
+
import { waitForActive } from './wait-for-active.js';
|
|
3
|
+
export { VpnConnectionFailedError, VpnConnectionNotReadyError, waitForActive, } from './wait-for-active.js';
|
|
4
|
+
export const vpnConnectionExtensions = extendResource('platform', 'vpnConnection', ctx => ({
|
|
5
|
+
waitForActive: (spaceIdentity, vpnConnectionIdentity, options) => waitForActive(ctx, spaceIdentity, vpnConnectionIdentity, options),
|
|
6
|
+
}));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { VpnConnection } from '@heroku/types/3.sdk';
|
|
2
|
+
import type { ResourceCtx } from '../../../core/extend-resource.js';
|
|
3
|
+
import type { Poller } from '../../../utils/poller.js';
|
|
4
|
+
import type { VpnConnectionOptions } from './index.js';
|
|
5
|
+
export declare class VpnConnectionFailedError extends Error {
|
|
6
|
+
readonly id = "vpn_connection_failed";
|
|
7
|
+
constructor(vpnConnection: VpnConnection);
|
|
8
|
+
}
|
|
9
|
+
export declare class VpnConnectionNotReadyError extends Error {
|
|
10
|
+
readonly timeoutMs: number;
|
|
11
|
+
readonly id = "vpn_connection_not_ready";
|
|
12
|
+
constructor(vpnConnection: VpnConnection, timeoutMs: number);
|
|
13
|
+
}
|
|
14
|
+
export type WaitForActiveOptions = VpnConnectionOptions & {
|
|
15
|
+
/**
|
|
16
|
+
* Delay between polls in milliseconds.
|
|
17
|
+
* Defaults to 10000 (10s)
|
|
18
|
+
*/
|
|
19
|
+
intervalMs?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Fires `poller.onStart` immediately before polling begins
|
|
22
|
+
* and `poller.onStop` once the connection becomes active.
|
|
23
|
+
*/
|
|
24
|
+
poller?: Poller<VpnConnection>;
|
|
25
|
+
/**
|
|
26
|
+
* Wall-clock budget in milliseconds before {@link VpnConnectionNotReadyError}
|
|
27
|
+
* is thrown. Defaults to 1200000 (20 minutes)
|
|
28
|
+
*/
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Poll `vpnConnection.info` until the connection's `status` is `active`,
|
|
33
|
+
* then return it.
|
|
34
|
+
*
|
|
35
|
+
* If `status` becomes `failed`, throws {@link VpnConnectionFailedError}.
|
|
36
|
+
* On exceeding `timeoutMs` without reaching `active` or `failed`, throws
|
|
37
|
+
* {@link VpnConnectionNotReadyError}.
|
|
38
|
+
*/
|
|
39
|
+
export declare function waitForActive(ctx: Pick<ResourceCtx, 'platform'>, spaceIdentity: string, vpnConnectionIdentity: string, options?: WaitForActiveOptions): Promise<VpnConnection>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { wait } from '../../../utils/wait.js';
|
|
2
|
+
const DEFAULT_INTERVAL_MS = 10 * 1000;
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 20 * 60 * 1000;
|
|
4
|
+
export class VpnConnectionFailedError extends Error {
|
|
5
|
+
id = 'vpn_connection_failed';
|
|
6
|
+
constructor(vpnConnection) {
|
|
7
|
+
super(vpnConnection.status_message);
|
|
8
|
+
this.name = 'VpnConnectionFailedError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class VpnConnectionNotReadyError extends Error {
|
|
12
|
+
timeoutMs;
|
|
13
|
+
id = 'vpn_connection_not_ready';
|
|
14
|
+
constructor(vpnConnection, timeoutMs) {
|
|
15
|
+
super(`Timeout waiting for VPN connection ${vpnConnection.name} to become allocated.`);
|
|
16
|
+
this.timeoutMs = timeoutMs;
|
|
17
|
+
this.name = 'VpnConnectionNotReadyError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Poll `vpnConnection.info` until the connection's `status` is `active`,
|
|
22
|
+
* then return it.
|
|
23
|
+
*
|
|
24
|
+
* If `status` becomes `failed`, throws {@link VpnConnectionFailedError}.
|
|
25
|
+
* On exceeding `timeoutMs` without reaching `active` or `failed`, throws
|
|
26
|
+
* {@link VpnConnectionNotReadyError}.
|
|
27
|
+
*/
|
|
28
|
+
export async function waitForActive(ctx, spaceIdentity, vpnConnectionIdentity, options = {}) {
|
|
29
|
+
const { intervalMs = DEFAULT_INTERVAL_MS, poller, signal, timeoutMs = DEFAULT_TIMEOUT_MS, } = options;
|
|
30
|
+
signal?.throwIfAborted();
|
|
31
|
+
const platform = signal ? ctx.platform.withOptions({ signal }) : ctx.platform;
|
|
32
|
+
const deadline = Date.now() + timeoutMs;
|
|
33
|
+
let vpnConnection = await platform.vpnConnection.info(spaceIdentity, vpnConnectionIdentity);
|
|
34
|
+
poller?.onStart?.(vpnConnection);
|
|
35
|
+
while (vpnConnection.status !== 'active') {
|
|
36
|
+
signal?.throwIfAborted();
|
|
37
|
+
if (vpnConnection.status === 'failed') {
|
|
38
|
+
throw new VpnConnectionFailedError(vpnConnection);
|
|
39
|
+
}
|
|
40
|
+
if (Date.now() >= deadline) {
|
|
41
|
+
throw new VpnConnectionNotReadyError(vpnConnection, timeoutMs);
|
|
42
|
+
}
|
|
43
|
+
await wait(intervalMs, signal);
|
|
44
|
+
vpnConnection = await platform.vpnConnection.info(spaceIdentity, vpnConnectionIdentity);
|
|
45
|
+
}
|
|
46
|
+
poller?.onStop?.(vpnConnection);
|
|
47
|
+
return vpnConnection;
|
|
48
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HerokuApiClientOptions } from '@heroku/heroku-fetch';
|
|
2
|
+
import type { HerokuClient } from '@heroku/types/dashboard-backend';
|
|
3
|
+
import type { RoutesClientExtras } from '../core/create-client.js';
|
|
4
|
+
export type DashboardBackendClient = HerokuClient & RoutesClientExtras<HerokuClient>;
|
|
5
|
+
export declare function createDashboardBackendClient(options?: HerokuApiClientOptions): DashboardBackendClient;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as routes from '@heroku/types/dashboard-backend/routes';
|
|
2
|
+
import { createClient } from '../core/create-client.js';
|
|
3
|
+
export function createDashboardBackendClient(options = {}) {
|
|
4
|
+
const baseUrl = process.env.HEROKU_PARTICLEBOARD_URL;
|
|
5
|
+
return createClient(routes, {
|
|
6
|
+
service: 'particleboard',
|
|
7
|
+
...(baseUrl && { baseUrl }),
|
|
8
|
+
...options,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HerokuApiClientOptions } from '@heroku/heroku-fetch';
|
|
2
|
+
import type { HerokuClient } from '@heroku/types/repositories-api';
|
|
3
|
+
import type { RoutesClientExtras } from '../core/create-client.js';
|
|
4
|
+
export type RepositoriesApiClient = HerokuClient & RoutesClientExtras<HerokuClient>;
|
|
5
|
+
export declare function createRepositoriesApiClient(options?: HerokuApiClientOptions): RepositoriesApiClient;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as routes from '@heroku/types/repositories-api/routes';
|
|
2
|
+
import { createClient } from '../core/create-client.js';
|
|
3
|
+
const REPOSITORIES_API_ACCEPT = 'application/vnd.heroku+json; version=3.repositories-api';
|
|
4
|
+
export function createRepositoriesApiClient(options = {}) {
|
|
5
|
+
return createClient(routes, {
|
|
6
|
+
defaultAccept: REPOSITORIES_API_ACCEPT,
|
|
7
|
+
service: 'platform',
|
|
8
|
+
...options,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HerokuApiClientOptions } from '@heroku/heroku-fetch';
|
|
2
|
+
import type { HerokuClient } from '@heroku/types/repositories';
|
|
3
|
+
import type { RoutesClientExtras } from '../core/create-client.js';
|
|
4
|
+
export type RepositoriesClient = HerokuClient & RoutesClientExtras<HerokuClient>;
|
|
5
|
+
export declare function createRepositoriesClient(options?: HerokuApiClientOptions): RepositoriesClient;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as routes from '@heroku/types/repositories/routes';
|
|
2
|
+
import { createClient } from '../core/create-client.js';
|
|
3
|
+
const DEFAULT_REPOSITORIES_BASE_URL = 'https://kolkrabbi.heroku.com';
|
|
4
|
+
export function createRepositoriesClient(options = {}) {
|
|
5
|
+
const baseUrl = process.env.HEROKU_REPOSITORIES_HOST ?? DEFAULT_REPOSITORIES_BASE_URL;
|
|
6
|
+
return createClient(routes, {
|
|
7
|
+
baseUrl,
|
|
8
|
+
service: 'custom',
|
|
9
|
+
...options,
|
|
10
|
+
});
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroku/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Heroku SDK",
|
|
5
5
|
"author": "Heroku",
|
|
6
6
|
"type": "module",
|
|
@@ -18,10 +18,22 @@
|
|
|
18
18
|
"types": "./dist/services/data.d.ts",
|
|
19
19
|
"default": "./dist/services/data.js"
|
|
20
20
|
},
|
|
21
|
+
"./dashboard-backend": {
|
|
22
|
+
"types": "./dist/services/dashboard-backend.d.ts",
|
|
23
|
+
"default": "./dist/services/dashboard-backend.js"
|
|
24
|
+
},
|
|
21
25
|
"./metrics": {
|
|
22
26
|
"types": "./dist/services/metrics.d.ts",
|
|
23
27
|
"default": "./dist/services/metrics.js"
|
|
24
28
|
},
|
|
29
|
+
"./repositories": {
|
|
30
|
+
"types": "./dist/services/repositories.d.ts",
|
|
31
|
+
"default": "./dist/services/repositories.js"
|
|
32
|
+
},
|
|
33
|
+
"./repositories-api": {
|
|
34
|
+
"types": "./dist/services/repositories-api.d.ts",
|
|
35
|
+
"default": "./dist/services/repositories-api.js"
|
|
36
|
+
},
|
|
25
37
|
"./extensions/platform": {
|
|
26
38
|
"types": "./dist/resources/extensions/platform.d.ts",
|
|
27
39
|
"default": "./dist/resources/extensions/platform.js"
|
|
@@ -38,6 +50,14 @@
|
|
|
38
50
|
"types": "./dist/resources/platform/app/index.d.ts",
|
|
39
51
|
"default": "./dist/resources/platform/app/index.js"
|
|
40
52
|
},
|
|
53
|
+
"./resources/platform/container": {
|
|
54
|
+
"types": "./dist/resources/platform/container/index.d.ts",
|
|
55
|
+
"default": "./dist/resources/platform/container/index.js"
|
|
56
|
+
},
|
|
57
|
+
"./resources/platform/domain": {
|
|
58
|
+
"types": "./dist/resources/platform/domain/index.d.ts",
|
|
59
|
+
"default": "./dist/resources/platform/domain/index.js"
|
|
60
|
+
},
|
|
41
61
|
"./resources/platform/dyno": {
|
|
42
62
|
"types": "./dist/resources/platform/dyno/index.d.ts",
|
|
43
63
|
"default": "./dist/resources/platform/dyno/index.js"
|
|
@@ -54,10 +74,26 @@
|
|
|
54
74
|
"types": "./dist/resources/platform/pipeline/*.d.ts",
|
|
55
75
|
"default": "./dist/resources/platform/pipeline/*.js"
|
|
56
76
|
},
|
|
77
|
+
"./resources/platform/review-app-config": {
|
|
78
|
+
"types": "./dist/resources/platform/review-app-config/index.d.ts",
|
|
79
|
+
"default": "./dist/resources/platform/review-app-config/index.js"
|
|
80
|
+
},
|
|
81
|
+
"./resources/platform/sni-endpoint": {
|
|
82
|
+
"types": "./dist/resources/platform/sni-endpoint/index.d.ts",
|
|
83
|
+
"default": "./dist/resources/platform/sni-endpoint/index.js"
|
|
84
|
+
},
|
|
85
|
+
"./resources/platform/telemetry-drain": {
|
|
86
|
+
"types": "./dist/resources/platform/telemetry-drain/index.d.ts",
|
|
87
|
+
"default": "./dist/resources/platform/telemetry-drain/index.js"
|
|
88
|
+
},
|
|
57
89
|
"./resources/platform/test-run": {
|
|
58
90
|
"types": "./dist/resources/platform/test-run/index.d.ts",
|
|
59
91
|
"default": "./dist/resources/platform/test-run/index.js"
|
|
60
92
|
},
|
|
93
|
+
"./resources/platform/vpn-connection": {
|
|
94
|
+
"types": "./dist/resources/platform/vpn-connection/index.d.ts",
|
|
95
|
+
"default": "./dist/resources/platform/vpn-connection/index.js"
|
|
96
|
+
},
|
|
61
97
|
"./resources/data/redis": {
|
|
62
98
|
"types": "./dist/resources/data/redis/index.d.ts",
|
|
63
99
|
"default": "./dist/resources/data/redis/index.js"
|
|
@@ -80,7 +116,8 @@
|
|
|
80
116
|
"lint:fix": "eslint --fix",
|
|
81
117
|
"prebuild": "rimraf dist",
|
|
82
118
|
"prepare": "npm run build",
|
|
83
|
-
"test": "vitest --run"
|
|
119
|
+
"test": "vitest --run",
|
|
120
|
+
"test:types": "tsc --noEmit -p tsconfig.type-tests.json"
|
|
84
121
|
},
|
|
85
122
|
"license": "Apache-2.0",
|
|
86
123
|
"engines": {
|
|
@@ -104,7 +141,7 @@
|
|
|
104
141
|
},
|
|
105
142
|
"dependencies": {
|
|
106
143
|
"@heroku/heroku-fetch": "^0.1.4",
|
|
107
|
-
"@heroku/types": "^4.
|
|
144
|
+
"@heroku/types": "^4.7.0",
|
|
108
145
|
"debug": "^4.4.0"
|
|
109
146
|
}
|
|
110
147
|
}
|