@ankhorage/contracts 19.0.0 → 19.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/deployProvider.d.ts +5 -0
- package/dist/deployProvider.d.ts.map +1 -0
- package/dist/deployProvider.js +5 -0
- package/dist/deployProvider.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/infra/infraFields.d.ts +1 -0
- package/dist/infra/infraFields.d.ts.map +1 -1
- package/dist/infra/infraFields.js +2 -0
- package/dist/infra/infraFields.js.map +1 -1
- package/dist/infra/isInfraEnvironmentSpec.d.ts.map +1 -1
- package/dist/infra/isInfraEnvironmentSpec.js +6 -1
- package/dist/infra/isInfraEnvironmentSpec.js.map +1 -1
- package/dist/infra/isInfraWorkloadSpec.d.ts.map +1 -1
- package/dist/infra/isInfraWorkloadSpec.js +22 -2
- package/dist/infra/isInfraWorkloadSpec.js.map +1 -1
- package/dist/types/deployMonetization.d.ts +78 -0
- package/dist/types/deployMonetization.d.ts.map +1 -0
- package/dist/types/deployMonetization.js +2 -0
- package/dist/types/deployMonetization.js.map +1 -0
- package/dist/types/deployProvider.d.ts +266 -0
- package/dist/types/deployProvider.d.ts.map +1 -0
- package/dist/types/deployProvider.js +21 -0
- package/dist/types/deployProvider.js.map +1 -0
- package/dist/types/deployRelease.d.ts +128 -0
- package/dist/types/deployRelease.d.ts.map +1 -0
- package/dist/types/deployRelease.js +2 -0
- package/dist/types/deployRelease.js.map +1 -0
- package/dist/types/deployStoreListing.d.ts +91 -0
- package/dist/types/deployStoreListing.d.ts.map +1 -0
- package/dist/types/deployStoreListing.js +2 -0
- package/dist/types/deployStoreListing.js.map +1 -0
- package/dist/types/infraWorkload.d.ts +2 -0
- package/dist/types/infraWorkload.d.ts.map +1 -1
- package/dist/types/infraWorkload.js.map +1 -1
- package/package.json +5 -1
- package/src/deployProvider.test.ts +82 -0
- package/src/deployProvider.ts +4 -0
- package/src/index.ts +1 -0
- package/src/infra/infraFields.ts +3 -0
- package/src/infra/isInfraEnvironmentSpec.ts +13 -1
- package/src/infra/isInfraWorkloadSpec.ts +56 -33
- package/src/infra-published-port.test.ts +43 -0
- package/src/infra-workload.test.ts +1 -1
- package/src/types/deployMonetization.ts +106 -0
- package/src/types/deployProvider.ts +338 -0
- package/src/types/deployRelease.ts +153 -0
- package/src/types/deployStoreListing.ts +125 -0
- package/src/types/infraWorkload.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"infraWorkload.js","sourceRoot":"","sources":["../../src/types/infraWorkload.ts"],"names":[],"mappings":"","sourcesContent":["import type { InfraControlPlaneCredentialRef, InfraSecretReference } from './infraSecrets';\n\nexport interface InfraWorkloadArtifact {\n readonly kind: 'image';\n /** Prebuilt image, optionally pinned by digest. No registry/build provider requirement. */\n readonly image: string;\n}\n\nexport type InfraWorkloadScalarValue =\n | { readonly kind: 'literal'; readonly value: string }\n | { readonly kind: 'secret'; readonly reference: InfraSecretReference }\n | {\n readonly kind: 'credential';\n readonly reference: InfraControlPlaneCredentialRef;\n readonly key: string;\n }\n | { readonly kind: 'output'; readonly resourceId: string; readonly output: string };\n\nexport type InfraWorkloadValue =\n | InfraWorkloadScalarValue\n | {\n readonly kind: 'template';\n /** Ordered scalar segments materialized into one value only at the runtime boundary. */\n readonly segments: readonly InfraWorkloadScalarValue[];\n };\n\nexport interface InfraWorkloadPort {\n readonly name: string;\n readonly port: number;\n readonly protocol?: 'tcp' | 'udp';\n}\n\nexport type InfraWorkloadHealthSpec = {\n readonly intervalSeconds?: number;\n readonly timeoutSeconds?: number;\n readonly failureThreshold?: number;\n} & (\n | { readonly kind: 'http'; readonly port: number; readonly path: string }\n | { readonly kind: 'tcp'; readonly port: number }\n | { readonly kind: 'command'; readonly command: readonly string[] }\n);\n\nexport interface InfraWorkloadResourceSpec {\n readonly cpuMillis?: number;\n readonly memoryMiB?: number;\n}\n\nexport interface InfraWorkloadVolumeSpec {\n readonly id: string;\n readonly mountPath: string;\n readonly sizeGiB: number;\n /** Persistence survives down and is retained by default on destroy. */\n readonly retention: 'retain' | 'delete-on-destroy';\n}\n\n/** Portable config/policy files; secret values are materialized by the runtime. */\nexport interface InfraWorkloadFileSpec {\n readonly path: string;\n readonly content: InfraWorkloadValue;\n}\n\nexport interface InfraWorkloadSpec {\n readonly id: string;\n readonly artifact: InfraWorkloadArtifact;\n readonly command?: readonly string[];\n readonly args?: readonly string[];\n readonly ports?: readonly InfraWorkloadPort[];\n readonly environment?: Readonly<Record<string, InfraWorkloadValue>>;\n readonly files?: readonly InfraWorkloadFileSpec[];\n readonly health?: InfraWorkloadHealthSpec;\n readonly resources?: InfraWorkloadResourceSpec;\n readonly persistence?: readonly InfraWorkloadVolumeSpec[];\n readonly exposure?: 'internal' | 'public';\n readonly replicas?: number;\n /** IDs in the composed desired-state graph; the orchestrator validates missing/cyclic edges. */\n readonly dependsOn?: readonly string[];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"infraWorkload.js","sourceRoot":"","sources":["../../src/types/infraWorkload.ts"],"names":[],"mappings":"","sourcesContent":["import type { InfraControlPlaneCredentialRef, InfraSecretReference } from './infraSecrets';\n\nexport interface InfraWorkloadArtifact {\n readonly kind: 'image';\n /** Prebuilt image, optionally pinned by digest. No registry/build provider requirement. */\n readonly image: string;\n}\n\nexport type InfraWorkloadScalarValue =\n | { readonly kind: 'literal'; readonly value: string }\n | { readonly kind: 'secret'; readonly reference: InfraSecretReference }\n | {\n readonly kind: 'credential';\n readonly reference: InfraControlPlaneCredentialRef;\n readonly key: string;\n }\n | { readonly kind: 'output'; readonly resourceId: string; readonly output: string };\n\nexport type InfraWorkloadValue =\n | InfraWorkloadScalarValue\n | {\n readonly kind: 'template';\n /** Ordered scalar segments materialized into one value only at the runtime boundary. */\n readonly segments: readonly InfraWorkloadScalarValue[];\n };\n\nexport interface InfraWorkloadPort {\n readonly name: string;\n readonly port: number;\n readonly protocol?: 'tcp' | 'udp';\n /** Exact external listener port requested for a public single-replica workload. */\n readonly publishedPort?: number;\n}\n\nexport type InfraWorkloadHealthSpec = {\n readonly intervalSeconds?: number;\n readonly timeoutSeconds?: number;\n readonly failureThreshold?: number;\n} & (\n | { readonly kind: 'http'; readonly port: number; readonly path: string }\n | { readonly kind: 'tcp'; readonly port: number }\n | { readonly kind: 'command'; readonly command: readonly string[] }\n);\n\nexport interface InfraWorkloadResourceSpec {\n readonly cpuMillis?: number;\n readonly memoryMiB?: number;\n}\n\nexport interface InfraWorkloadVolumeSpec {\n readonly id: string;\n readonly mountPath: string;\n readonly sizeGiB: number;\n /** Persistence survives down and is retained by default on destroy. */\n readonly retention: 'retain' | 'delete-on-destroy';\n}\n\n/** Portable config/policy files; secret values are materialized by the runtime. */\nexport interface InfraWorkloadFileSpec {\n readonly path: string;\n readonly content: InfraWorkloadValue;\n}\n\nexport interface InfraWorkloadSpec {\n readonly id: string;\n readonly artifact: InfraWorkloadArtifact;\n readonly command?: readonly string[];\n readonly args?: readonly string[];\n readonly ports?: readonly InfraWorkloadPort[];\n readonly environment?: Readonly<Record<string, InfraWorkloadValue>>;\n readonly files?: readonly InfraWorkloadFileSpec[];\n readonly health?: InfraWorkloadHealthSpec;\n readonly resources?: InfraWorkloadResourceSpec;\n readonly persistence?: readonly InfraWorkloadVolumeSpec[];\n readonly exposure?: 'internal' | 'public';\n readonly replicas?: number;\n /** IDs in the composed desired-state graph; the orchestrator validates missing/cyclic edges. */\n readonly dependsOn?: readonly string[];\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/contracts",
|
|
3
|
-
"version": "19.
|
|
3
|
+
"version": "19.2.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"ankh": {
|
|
6
6
|
"category": "contracts",
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
"types": "./dist/deploy.d.ts",
|
|
59
59
|
"default": "./dist/deploy.js"
|
|
60
60
|
},
|
|
61
|
+
"./deploy-provider": {
|
|
62
|
+
"types": "./dist/deployProvider.d.ts",
|
|
63
|
+
"default": "./dist/deployProvider.js"
|
|
64
|
+
},
|
|
61
65
|
"./runtime": {
|
|
62
66
|
"types": "./dist/runtimeCallbacks.d.ts",
|
|
63
67
|
"default": "./dist/runtimeCallbacks.js"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from 'bun:test';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
DEPLOYMENT_PROVIDER_CAPABILITY_IDS,
|
|
8
|
+
type DeploymentProviderRegistration,
|
|
9
|
+
} from './deployProvider';
|
|
10
|
+
|
|
11
|
+
const fakeProvider: DeploymentProviderRegistration = {
|
|
12
|
+
descriptor: {
|
|
13
|
+
id: 'fake',
|
|
14
|
+
packageName: '@ankhorage/deploy-provider-fake',
|
|
15
|
+
displayName: 'Fake provider',
|
|
16
|
+
capabilities: [{ id: 'web-publish', targets: ['web'] }],
|
|
17
|
+
},
|
|
18
|
+
webPublisher: {
|
|
19
|
+
publishAsync: (request) =>
|
|
20
|
+
Promise.resolve({
|
|
21
|
+
status: 'completed',
|
|
22
|
+
value: {
|
|
23
|
+
target: 'web',
|
|
24
|
+
revision: request.revision,
|
|
25
|
+
provider: 'fake',
|
|
26
|
+
deploymentId: 'fake-deployment',
|
|
27
|
+
url: 'https://example.test',
|
|
28
|
+
production: request.intent.mode === 'production',
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe('deployment provider contracts', () => {
|
|
35
|
+
it('defines the portable provider capability vocabulary', () => {
|
|
36
|
+
expect(DEPLOYMENT_PROVIDER_CAPABILITY_IDS).toEqual([
|
|
37
|
+
'setup',
|
|
38
|
+
'web-publish',
|
|
39
|
+
'android-build',
|
|
40
|
+
'android-publish',
|
|
41
|
+
'ios-build',
|
|
42
|
+
'ios-publish',
|
|
43
|
+
'store-listing',
|
|
44
|
+
'monetization',
|
|
45
|
+
'release',
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('supports an external provider registration without Deploy implementation types', async () => {
|
|
50
|
+
const result = await fakeProvider.webPublisher?.publishAsync({
|
|
51
|
+
projectRoot: '/project',
|
|
52
|
+
exportDirectory: '/tmp/export',
|
|
53
|
+
revision: 'revision-1',
|
|
54
|
+
intent: { mode: 'production' },
|
|
55
|
+
credentials: [],
|
|
56
|
+
resolveSecret: () => Promise.resolve(null),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
status: 'completed',
|
|
61
|
+
value: {
|
|
62
|
+
target: 'web',
|
|
63
|
+
revision: 'revision-1',
|
|
64
|
+
provider: 'fake',
|
|
65
|
+
deploymentId: 'fake-deployment',
|
|
66
|
+
url: 'https://example.test',
|
|
67
|
+
production: true,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('publishes the focused deployment provider contract subpath', async () => {
|
|
73
|
+
const packageJson = JSON.parse(await readFile(join(process.cwd(), 'package.json'), 'utf8')) as {
|
|
74
|
+
exports?: Record<string, { default?: string; types?: string }>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
expect(packageJson.exports?.['./deploy-provider']).toEqual({
|
|
78
|
+
types: './dist/deployProvider.d.ts',
|
|
79
|
+
default: './dist/deployProvider.js',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
package/src/index.ts
CHANGED
package/src/infra/infraFields.ts
CHANGED
|
@@ -17,4 +17,7 @@ export const infraFields = {
|
|
|
17
17
|
value === undefined || (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0),
|
|
18
18
|
port: (value: unknown): boolean =>
|
|
19
19
|
typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535,
|
|
20
|
+
optionalPort: (value: unknown): boolean =>
|
|
21
|
+
value === undefined ||
|
|
22
|
+
(typeof value === 'number' && Number.isInteger(value) && value > 0 && value <= 65535),
|
|
20
23
|
};
|
|
@@ -13,7 +13,9 @@ import { isInfraWorkloadSpec } from './isInfraWorkloadSpec';
|
|
|
13
13
|
|
|
14
14
|
/*** Validate sibling capabilities and their required relationships within one environment. */
|
|
15
15
|
export function isInfraEnvironmentSpec(value: unknown): value is InfraEnvironmentSpec {
|
|
16
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
isEnvironmentShape(value) && hasServiceDependencies(value) && hasUniquePublishedPorts(value)
|
|
18
|
+
);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/*** Check all environment field shapes before inspecting canonical dependency metadata. */
|
|
@@ -104,6 +106,16 @@ function isWorkloads(value: unknown): boolean {
|
|
|
104
106
|
);
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
/*** Prevent two workloads in one environment from claiming the same external listener. */
|
|
110
|
+
function hasUniquePublishedPorts(value: InfraEnvironmentSpec): boolean {
|
|
111
|
+
const ports = (value.workloads ?? []).flatMap((workload) =>
|
|
112
|
+
(workload.ports ?? []).flatMap(({ publishedPort }) =>
|
|
113
|
+
publishedPort === undefined ? [] : [publishedPort],
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
return new Set(ports).size === ports.length;
|
|
117
|
+
}
|
|
118
|
+
|
|
107
119
|
/*** Check catalog dependencies against selected sibling capabilities and runtime capabilities. */
|
|
108
120
|
function hasServiceDependencies(value: InfraEnvironmentSpec): boolean {
|
|
109
121
|
const selections = [
|
|
@@ -11,39 +11,44 @@ import { isInfraWorkloadValue } from './isInfraWorkloadValue';
|
|
|
11
11
|
|
|
12
12
|
/*** Validate the portable desired workload; runtime-specific fields and plaintext secret objects fail. */
|
|
13
13
|
export function isInfraWorkloadSpec(value: unknown): value is InfraWorkloadSpec {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
ports
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
environment
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
files
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
resources
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
volumes
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
exposure
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
14
|
+
if (
|
|
15
|
+
!isInfraShape(value, {
|
|
16
|
+
id: isNonEmptyString,
|
|
17
|
+
artifact: (artifact) =>
|
|
18
|
+
isInfraShape(artifact, { kind: (kind) => kind === 'image', image: isNonEmptyString }),
|
|
19
|
+
command: infraFields.optionalStrings,
|
|
20
|
+
args: (args) => args === undefined || isStringArray(args),
|
|
21
|
+
ports: (ports) =>
|
|
22
|
+
ports === undefined ||
|
|
23
|
+
(Array.isArray(ports) && ports.every(isPort) && hasUniqueField(ports, 'name')),
|
|
24
|
+
environment: (environment) =>
|
|
25
|
+
environment === undefined ||
|
|
26
|
+
(isRecord(environment) && Object.values(environment).every(isInfraWorkloadValue)),
|
|
27
|
+
files: (files) =>
|
|
28
|
+
files === undefined ||
|
|
29
|
+
(Array.isArray(files) && files.every(isFile) && hasUniqueField(files, 'path')),
|
|
30
|
+
health: (health) => health === undefined || isInfraWorkloadHealth(health),
|
|
31
|
+
resources: (resources) =>
|
|
32
|
+
resources === undefined ||
|
|
33
|
+
isInfraShape(resources, {
|
|
34
|
+
cpuMillis: infraFields.optionalPositiveInteger,
|
|
35
|
+
memoryMiB: infraFields.optionalPositiveInteger,
|
|
36
|
+
}),
|
|
37
|
+
persistence: (volumes) =>
|
|
38
|
+
volumes === undefined ||
|
|
39
|
+
(Array.isArray(volumes) &&
|
|
40
|
+
volumes.every(isVolume) &&
|
|
41
|
+
hasUniqueField(volumes, 'id') &&
|
|
42
|
+
hasUniqueField(volumes, 'mountPath')),
|
|
43
|
+
exposure: (exposure) =>
|
|
44
|
+
exposure === undefined || exposure === 'internal' || exposure === 'public',
|
|
45
|
+
replicas: infraFields.optionalNonnegativeInteger,
|
|
46
|
+
dependsOn: infraFields.optionalStrings,
|
|
47
|
+
} satisfies InfraShape<InfraWorkloadSpec>)
|
|
48
|
+
) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return hasValidPublishedPorts(value);
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/*** Named transport ports stay independent from runtime resource types. */
|
|
@@ -52,9 +57,27 @@ function isPort(value: unknown): boolean {
|
|
|
52
57
|
name: isNonEmptyString,
|
|
53
58
|
port: infraFields.port,
|
|
54
59
|
protocol: (protocol) => protocol === undefined || protocol === 'tcp' || protocol === 'udp',
|
|
60
|
+
publishedPort: infraFields.optionalPort,
|
|
55
61
|
});
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
/*** Require fixed external listeners to belong to one public workload replica. */
|
|
65
|
+
function hasValidPublishedPorts(workload: unknown): boolean {
|
|
66
|
+
if (!isRecord(workload)) return false;
|
|
67
|
+
const ports = Array.isArray(workload.ports) ? workload.ports : [];
|
|
68
|
+
const published = ports.flatMap((port) => {
|
|
69
|
+
if (!isRecord(port) || typeof port.publishedPort !== 'number') return [];
|
|
70
|
+
return [port.publishedPort];
|
|
71
|
+
});
|
|
72
|
+
return (
|
|
73
|
+
new Set(published).size === published.length &&
|
|
74
|
+
(published.length === 0 ||
|
|
75
|
+
(workload.exposure === 'public' &&
|
|
76
|
+
(workload.replicas === undefined ||
|
|
77
|
+
(typeof workload.replicas === 'number' && workload.replicas <= 1))))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
/*** Portable files model policy/config materialization without host filesystem access. */
|
|
59
82
|
function isFile(value: unknown): boolean {
|
|
60
83
|
return isInfraShape(value, { path: isAbsoluteWorkloadPath, content: isInfraWorkloadValue });
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { isInfraEnvironmentSpec, isInfraWorkloadSpec } from './infra';
|
|
4
|
+
|
|
5
|
+
const publicWorkload = {
|
|
6
|
+
id: 'api',
|
|
7
|
+
artifact: { kind: 'image', image: 'example/api:1' },
|
|
8
|
+
ports: [{ name: 'http', port: 8080, publishedPort: 18_080 }],
|
|
9
|
+
exposure: 'public',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
describe('published workload ports', () => {
|
|
13
|
+
it('accepts one exact external listener for a public workload', () => {
|
|
14
|
+
expect(isInfraWorkloadSpec(publicWorkload)).toBe(true);
|
|
15
|
+
expect(isInfraWorkloadSpec({ ...publicWorkload, replicas: 0 })).toBe(true);
|
|
16
|
+
expect(isInfraWorkloadSpec({ ...publicWorkload, replicas: 1 })).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it.each([
|
|
20
|
+
{ ...publicWorkload, ports: [{ name: 'http', port: 80, publishedPort: 0 }] },
|
|
21
|
+
{ ...publicWorkload, ports: [{ name: 'http', port: 80, publishedPort: 65_536 }] },
|
|
22
|
+
{ ...publicWorkload, exposure: 'internal' },
|
|
23
|
+
{ ...publicWorkload, replicas: 2 },
|
|
24
|
+
{
|
|
25
|
+
...publicWorkload,
|
|
26
|
+
ports: [
|
|
27
|
+
{ name: 'http', port: 80, publishedPort: 8080 },
|
|
28
|
+
{ name: 'admin', port: 81, publishedPort: 8080 },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
])('rejects an invalid fixed external listener: %j', (workload) => {
|
|
32
|
+
expect(isInfraWorkloadSpec(workload)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('rejects external listener collisions across workloads', () => {
|
|
36
|
+
expect(
|
|
37
|
+
isInfraEnvironmentSpec({
|
|
38
|
+
deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
|
|
39
|
+
workloads: [publicWorkload, { ...publicWorkload, id: 'admin' }],
|
|
40
|
+
}),
|
|
41
|
+
).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -22,7 +22,7 @@ const workload = {
|
|
|
22
22
|
artifact: { kind: 'image', image: 'example/backend@sha256:abc' },
|
|
23
23
|
command: ['server'],
|
|
24
24
|
args: ['--verbose'],
|
|
25
|
-
ports: [{ name: 'http', port: 8080 }],
|
|
25
|
+
ports: [{ name: 'http', port: 8080, publishedPort: 18_080 }],
|
|
26
26
|
environment: {
|
|
27
27
|
NODE_ENV: { kind: 'literal', value: 'production' },
|
|
28
28
|
DB_PASSWORD: { kind: 'secret', reference },
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DeploymentCredentialReference,
|
|
3
|
+
DeploymentProviderResult,
|
|
4
|
+
DeploymentSecretResolver,
|
|
5
|
+
DeploymentStoreIdentity,
|
|
6
|
+
} from './deployProvider';
|
|
7
|
+
|
|
8
|
+
export type MonetizationProductKind = 'consumable' | 'non-consumable' | 'subscription';
|
|
9
|
+
|
|
10
|
+
export interface MonetizationLocalization {
|
|
11
|
+
readonly locale: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly description: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface MonetizationBasePrice {
|
|
17
|
+
readonly country: string;
|
|
18
|
+
readonly currency: string;
|
|
19
|
+
readonly amount: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type MonetizationSubscriptionPeriod = 'P1W' | 'P1M' | 'P2M' | 'P3M' | 'P6M' | 'P1Y';
|
|
23
|
+
|
|
24
|
+
export interface MonetizationSubscription {
|
|
25
|
+
readonly family: string;
|
|
26
|
+
readonly period: MonetizationSubscriptionPeriod;
|
|
27
|
+
readonly level?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MonetizationProduct {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly kind: MonetizationProductKind;
|
|
33
|
+
readonly localizations: readonly MonetizationLocalization[];
|
|
34
|
+
readonly basePrice: MonetizationBasePrice;
|
|
35
|
+
readonly subscription?: MonetizationSubscription;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MonetizationObservedProduct {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly kind: MonetizationProductKind | 'one-time';
|
|
41
|
+
readonly localizations: readonly MonetizationLocalization[];
|
|
42
|
+
readonly basePrice?: MonetizationBasePrice;
|
|
43
|
+
readonly subscription?: MonetizationSubscription;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface MonetizationDesiredState {
|
|
47
|
+
readonly revision: string;
|
|
48
|
+
readonly products: readonly MonetizationProduct[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface MonetizationDiagnostic {
|
|
52
|
+
readonly severity: 'warning' | 'error';
|
|
53
|
+
readonly code: string;
|
|
54
|
+
readonly message: string;
|
|
55
|
+
readonly target?: 'android' | 'ios';
|
|
56
|
+
readonly productId?: string;
|
|
57
|
+
readonly locale?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MonetizationTargetState {
|
|
61
|
+
readonly target: 'android' | 'ios';
|
|
62
|
+
readonly products: readonly MonetizationObservedProduct[];
|
|
63
|
+
readonly subscriptionFamilies: readonly string[];
|
|
64
|
+
readonly diagnostics: readonly MonetizationDiagnostic[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface MonetizationPlanStep {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly target: 'android' | 'ios';
|
|
70
|
+
readonly productId: string;
|
|
71
|
+
readonly operation:
|
|
72
|
+
| 'ensure-subscription-family'
|
|
73
|
+
| 'create-product'
|
|
74
|
+
| 'update-metadata'
|
|
75
|
+
| 'update-price'
|
|
76
|
+
| 'update-subscription';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface MonetizationPlan {
|
|
80
|
+
readonly status: 'no-change' | 'changes' | 'blocked';
|
|
81
|
+
readonly desiredRevision: string;
|
|
82
|
+
readonly currentRevision: string;
|
|
83
|
+
readonly steps: readonly MonetizationPlanStep[];
|
|
84
|
+
readonly diagnostics: readonly MonetizationDiagnostic[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MonetizationAdapterContext {
|
|
88
|
+
readonly identity: DeploymentStoreIdentity;
|
|
89
|
+
readonly credentials: readonly DeploymentCredentialReference[];
|
|
90
|
+
readonly resolveSecret: DeploymentSecretResolver;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface MonetizationSyncRequest extends MonetizationAdapterContext {
|
|
94
|
+
readonly desired: MonetizationDesiredState;
|
|
95
|
+
readonly plan: MonetizationPlan;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface DeploymentMonetizationAdapter {
|
|
99
|
+
readonly target: 'android' | 'ios';
|
|
100
|
+
inspectAsync(
|
|
101
|
+
context: MonetizationAdapterContext,
|
|
102
|
+
): Promise<DeploymentProviderResult<MonetizationTargetState>>;
|
|
103
|
+
syncAsync(
|
|
104
|
+
request: MonetizationSyncRequest,
|
|
105
|
+
): Promise<DeploymentProviderResult<MonetizationTargetState>>;
|
|
106
|
+
}
|