@ankhorage/contracts 14.0.0 → 15.0.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 +31 -0
- package/README.md +10 -2
- package/dist/appManifest/apis.d.ts +1 -0
- package/dist/appManifest/apis.d.ts.map +1 -1
- package/dist/appManifest/apis.js +17 -14
- package/dist/appManifest/apis.js.map +1 -1
- package/dist/appManifest/bindings.d.ts +3 -0
- package/dist/appManifest/bindings.d.ts.map +1 -1
- package/dist/appManifest/bindings.js +17 -1
- package/dist/appManifest/bindings.js.map +1 -1
- package/dist/appManifest/constants.d.ts +18 -0
- package/dist/appManifest/constants.d.ts.map +1 -0
- package/dist/appManifest/constants.js +18 -0
- package/dist/appManifest/constants.js.map +1 -0
- package/dist/appManifest/data.d.ts +4 -0
- package/dist/appManifest/data.d.ts.map +1 -1
- package/dist/appManifest/data.js +22 -3
- package/dist/appManifest/data.js.map +1 -1
- package/dist/appManifest/dataSources.d.ts +1 -0
- package/dist/appManifest/dataSources.d.ts.map +1 -1
- package/dist/appManifest/dataSources.js +7 -6
- package/dist/appManifest/dataSources.js.map +1 -1
- package/dist/appManifest/deploy.d.ts +1 -0
- package/dist/appManifest/deploy.d.ts.map +1 -1
- package/dist/appManifest/deploy.js +16 -13
- package/dist/appManifest/deploy.js.map +1 -1
- package/dist/appManifest/icon.d.ts.map +1 -1
- package/dist/appManifest/icon.js +2 -1
- package/dist/appManifest/icon.js.map +1 -1
- package/dist/appManifest/isAppManifest.d.ts +4 -0
- package/dist/appManifest/isAppManifest.d.ts.map +1 -0
- package/dist/appManifest/isAppManifest.js +69 -0
- package/dist/appManifest/isAppManifest.js.map +1 -0
- package/dist/appManifest/isAppStateSpec.d.ts +4 -0
- package/dist/appManifest/isAppStateSpec.d.ts.map +1 -0
- package/dist/appManifest/isAppStateSpec.js +9 -0
- package/dist/appManifest/isAppStateSpec.js.map +1 -0
- package/dist/appManifest/isManifestValue.d.ts +3 -0
- package/dist/appManifest/isManifestValue.d.ts.map +1 -0
- package/dist/appManifest/isManifestValue.js +15 -0
- package/dist/appManifest/isManifestValue.js.map +1 -0
- package/dist/appManifest/media.d.ts +1 -0
- package/dist/appManifest/media.d.ts.map +1 -1
- package/dist/appManifest/media.js +10 -8
- package/dist/appManifest/media.js.map +1 -1
- package/dist/appManifest/navigator.d.ts.map +1 -1
- package/dist/appManifest/navigator.js +7 -6
- package/dist/appManifest/navigator.js.map +1 -1
- package/dist/appManifest/navigatorOptions.d.ts +0 -2
- package/dist/appManifest/navigatorOptions.d.ts.map +1 -1
- package/dist/appManifest/navigatorOptions.js +8 -12
- package/dist/appManifest/navigatorOptions.js.map +1 -1
- package/dist/appManifest/parseAppManifest.d.ts +10 -0
- package/dist/appManifest/parseAppManifest.d.ts.map +1 -0
- package/dist/appManifest/parseAppManifest.js +14 -0
- package/dist/appManifest/parseAppManifest.js.map +1 -0
- package/dist/appManifest/screens.d.ts +4 -0
- package/dist/appManifest/screens.d.ts.map +1 -1
- package/dist/appManifest/screens.js +14 -2
- package/dist/appManifest/screens.js.map +1 -1
- package/dist/appManifest.d.ts +3 -18
- package/dist/appManifest.d.ts.map +1 -1
- package/dist/appManifest.js +2 -80
- package/dist/appManifest.js.map +1 -1
- package/dist/auth.d.ts +3 -2
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/deploy.d.ts +0 -9
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +0 -8
- package/dist/deploy.js.map +1 -1
- package/dist/environments.d.ts +4 -0
- package/dist/environments.d.ts.map +1 -0
- package/dist/environments.js +3 -0
- package/dist/environments.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/constants.d.ts +91 -0
- package/dist/infra/constants.d.ts.map +1 -0
- package/dist/infra/constants.js +91 -0
- package/dist/infra/constants.js.map +1 -0
- package/dist/infra/infraFields.d.ts +12 -0
- package/dist/infra/infraFields.d.ts.map +1 -0
- package/dist/infra/infraFields.js +14 -0
- package/dist/infra/infraFields.js.map +1 -0
- package/dist/infra/isInfraAdapterDescriptor.d.ts +4 -0
- package/dist/infra/isInfraAdapterDescriptor.d.ts.map +1 -0
- package/dist/infra/isInfraAdapterDescriptor.js +31 -0
- package/dist/infra/isInfraAdapterDescriptor.js.map +1 -0
- package/dist/infra/isInfraAuthSpec.d.ts +4 -0
- package/dist/infra/isInfraAuthSpec.d.ts.map +1 -0
- package/dist/infra/isInfraAuthSpec.js +75 -0
- package/dist/infra/isInfraAuthSpec.js.map +1 -0
- package/dist/infra/isInfraCredentialRef.d.ts +3 -0
- package/dist/infra/isInfraCredentialRef.d.ts.map +1 -0
- package/dist/infra/isInfraCredentialRef.js +10 -0
- package/dist/infra/isInfraCredentialRef.js.map +1 -0
- package/dist/infra/isInfraDeploymentSpec.d.ts +4 -0
- package/dist/infra/isInfraDeploymentSpec.d.ts.map +1 -0
- package/dist/infra/isInfraDeploymentSpec.js +66 -0
- package/dist/infra/isInfraDeploymentSpec.js.map +1 -0
- package/dist/infra/isInfraEnvironmentSpec.d.ts +4 -0
- package/dist/infra/isInfraEnvironmentSpec.d.ts.map +1 -0
- package/dist/infra/isInfraEnvironmentSpec.js +82 -0
- package/dist/infra/isInfraEnvironmentSpec.js.map +1 -0
- package/dist/infra/isInfraManifest.d.ts +4 -0
- package/dist/infra/isInfraManifest.d.ts.map +1 -0
- package/dist/infra/isInfraManifest.js +23 -0
- package/dist/infra/isInfraManifest.js.map +1 -0
- package/dist/infra/isInfraShape.d.ts +3 -0
- package/dist/infra/isInfraShape.d.ts.map +1 -0
- package/dist/infra/isInfraShape.js +10 -0
- package/dist/infra/isInfraShape.js.map +1 -0
- package/dist/infra/isInfraWorkloadHealth.d.ts +3 -0
- package/dist/infra/isInfraWorkloadHealth.d.ts.map +1 -0
- package/dist/infra/isInfraWorkloadHealth.js +37 -0
- package/dist/infra/isInfraWorkloadHealth.js.map +1 -0
- package/dist/infra/isInfraWorkloadSpec.d.ts +4 -0
- package/dist/infra/isInfraWorkloadSpec.d.ts.map +1 -0
- package/dist/infra/isInfraWorkloadSpec.js +70 -0
- package/dist/infra/isInfraWorkloadSpec.js.map +1 -0
- package/dist/infra/isInfraWorkloadValue.d.ts +3 -0
- package/dist/infra/isInfraWorkloadValue.d.ts.map +1 -0
- package/dist/infra/isInfraWorkloadValue.js +40 -0
- package/dist/infra/isInfraWorkloadValue.js.map +1 -0
- package/dist/infra/parseInfraManifest.d.ts +5 -0
- package/dist/infra/parseInfraManifest.d.ts.map +1 -0
- package/dist/infra/parseInfraManifest.js +17 -0
- package/dist/infra/parseInfraManifest.js.map +1 -0
- package/dist/infra/validateInfraAdapterSelection.d.ts +6 -0
- package/dist/infra/validateInfraAdapterSelection.d.ts.map +1 -0
- package/dist/infra/validateInfraAdapterSelection.js +36 -0
- package/dist/infra/validateInfraAdapterSelection.js.map +1 -0
- package/dist/infra.d.ts +16 -0
- package/dist/infra.d.ts.map +1 -0
- package/dist/infra.js +11 -0
- package/dist/infra.js.map +1 -0
- package/dist/media.d.ts +7 -6
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +5 -5
- package/dist/media.js.map +1 -1
- package/dist/state.d.ts +5 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js.map +1 -1
- package/dist/types/appManifest.d.ts +9 -0
- package/dist/types/appManifest.d.ts.map +1 -0
- package/dist/types/appManifest.js +2 -0
- package/dist/types/appManifest.js.map +1 -0
- package/dist/types/infraAdapters.d.ts +85 -0
- package/dist/types/infraAdapters.d.ts.map +1 -0
- package/dist/types/infraAdapters.js +2 -0
- package/dist/types/infraAdapters.js.map +1 -0
- package/dist/types/infraLifecycle.d.ts +107 -0
- package/dist/types/infraLifecycle.d.ts.map +1 -0
- package/dist/types/infraLifecycle.js +2 -0
- package/dist/types/infraLifecycle.js.map +1 -0
- package/dist/types/infraManifest.d.ts +142 -0
- package/dist/types/infraManifest.d.ts.map +1 -0
- package/dist/types/infraManifest.js +2 -0
- package/dist/types/infraManifest.js.map +1 -0
- package/dist/types/infraSecrets.d.ts +15 -0
- package/dist/types/infraSecrets.d.ts.map +1 -0
- package/dist/types/infraSecrets.js +2 -0
- package/dist/types/infraSecrets.js.map +1 -0
- package/dist/types/infraValidation.d.ts +5 -0
- package/dist/types/infraValidation.d.ts.map +1 -0
- package/dist/types/infraValidation.js +2 -0
- package/dist/types/infraValidation.js.map +1 -0
- package/dist/types/infraWorkload.d.ts +70 -0
- package/dist/types/infraWorkload.d.ts.map +1 -0
- package/dist/types/infraWorkload.js +2 -0
- package/dist/types/infraWorkload.js.map +1 -0
- package/dist/types.d.ts +7 -70
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -9
- package/dist/types.js.map +1 -1
- package/package.json +11 -2
- package/src/appManifest/apis.ts +18 -16
- package/src/appManifest/bindings.ts +18 -1
- package/src/appManifest/constants.ts +19 -0
- package/src/appManifest/data.ts +23 -9
- package/src/appManifest/dataSources.ts +8 -7
- package/src/appManifest/deploy.ts +17 -15
- package/src/appManifest/icon.ts +3 -1
- package/src/appManifest/isAppManifest.ts +95 -0
- package/src/appManifest/isAppStateSpec.ts +13 -0
- package/src/appManifest/isManifestValue.ts +19 -0
- package/src/appManifest/media.ts +11 -10
- package/src/appManifest/navigator.ts +7 -6
- package/src/appManifest/navigatorOptions.ts +9 -16
- package/src/appManifest/parseAppManifest.ts +15 -0
- package/src/appManifest/screens.ts +14 -2
- package/src/appManifest.test.ts +9 -5
- package/src/appManifest.ts +3 -107
- package/src/auth-flow-contract.test.ts +6 -10
- package/src/auth-oauth.test.ts +2 -2
- package/src/auth.ts +3 -2
- package/src/bindings.test.ts +5 -0
- package/src/contracts.test.ts +21 -19
- package/src/deploy.test.ts +13 -5
- package/src/deploy.ts +0 -11
- package/src/environments.ts +4 -0
- package/src/index.ts +2 -1
- package/src/infra/constants.ts +91 -0
- package/src/infra/infraFields.ts +20 -0
- package/src/infra/isInfraAdapterDescriptor.ts +37 -0
- package/src/infra/isInfraAuthSpec.ts +98 -0
- package/src/infra/isInfraCredentialRef.ts +11 -0
- package/src/infra/isInfraDeploymentSpec.ts +75 -0
- package/src/infra/isInfraEnvironmentSpec.ts +95 -0
- package/src/infra/isInfraManifest.ts +29 -0
- package/src/infra/isInfraShape.ts +16 -0
- package/src/infra/isInfraWorkloadHealth.ts +38 -0
- package/src/infra/isInfraWorkloadSpec.ts +87 -0
- package/src/infra/isInfraWorkloadValue.ts +41 -0
- package/src/infra/parseInfraManifest.ts +20 -0
- package/src/infra/validateInfraAdapterSelection.ts +42 -0
- package/src/infra-types.test.ts +78 -0
- package/src/infra-workload.test.ts +186 -0
- package/src/infra.test.ts +268 -0
- package/src/infra.ts +15 -0
- package/src/media.ts +12 -11
- package/src/profile-contract.test.ts +2 -3
- package/src/secrets.test.ts +19 -13
- package/src/state.ts +6 -0
- package/src/types/appManifest.ts +5 -0
- package/src/types/infraAdapters.ts +148 -0
- package/src/types/infraLifecycle.ts +105 -0
- package/src/types/infraManifest.ts +130 -0
- package/src/types/infraSecrets.ts +16 -0
- package/src/types/infraValidation.ts +2 -0
- package/src/types/infraWorkload.ts +64 -0
- package/src/types.ts +7 -87
- package/dist/appManifest/infra.d.ts +0 -2
- package/dist/appManifest/infra.d.ts.map +0 -1
- package/dist/appManifest/infra.js +0 -130
- package/dist/appManifest/infra.js.map +0 -1
- package/dist/appManifest/shared.d.ts +0 -8
- package/dist/appManifest/shared.d.ts.map +0 -1
- package/dist/appManifest/shared.js +0 -31
- package/dist/appManifest/shared.js.map +0 -1
- package/dist/secretManifest.d.ts +0 -11
- package/dist/secretManifest.d.ts.map +0 -1
- package/dist/secretManifest.js +0 -2
- package/dist/secretManifest.js.map +0 -1
- package/src/appManifest/infra.ts +0 -190
- package/src/appManifest/shared.ts +0 -40
- package/src/secretManifest.ts +0 -12
package/src/appManifest/media.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { hasOnlyKeys, isRecord } from '@ankhorage/utility/object';
|
|
2
|
+
import { isNonEmptyString, isOptionalString } from '@ankhorage/utility/string';
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
MEDIA_ASSET_KINDS,
|
|
3
6
|
type MediaAsset,
|
|
@@ -5,10 +8,10 @@ import {
|
|
|
5
8
|
type MediaAssetSource,
|
|
6
9
|
type MediaManifest,
|
|
7
10
|
} from '../media';
|
|
8
|
-
import { isOptionalString, isRecord } from './shared';
|
|
9
11
|
|
|
10
12
|
const MEDIA_ASSET_KIND_SET = new Set<string>(MEDIA_ASSET_KINDS);
|
|
11
13
|
|
|
14
|
+
/*** Validate authored media assets and their registry identity. */
|
|
12
15
|
export function isMediaManifest(value: unknown): value is MediaManifest {
|
|
13
16
|
if (!isRecord(value) || !hasOnlyKeys(value, ['assets']) || !isRecord(value.assets)) return false;
|
|
14
17
|
|
|
@@ -17,6 +20,7 @@ export function isMediaManifest(value: unknown): value is MediaManifest {
|
|
|
17
20
|
);
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
/*** Validate media identity, kind, source and optional metadata. */
|
|
20
24
|
function isMediaAsset(value: unknown): value is MediaAsset {
|
|
21
25
|
return (
|
|
22
26
|
isRecord(value) &&
|
|
@@ -31,6 +35,7 @@ function isMediaAsset(value: unknown): value is MediaAsset {
|
|
|
31
35
|
);
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
/*** Validate the selected bundled, URL or storage-backed media source. */
|
|
34
39
|
function isMediaAssetSource(value: unknown): value is MediaAssetSource {
|
|
35
40
|
if (!isRecord(value) || typeof value.kind !== 'string') return false;
|
|
36
41
|
|
|
@@ -52,6 +57,7 @@ function isMediaAssetSource(value: unknown): value is MediaAssetSource {
|
|
|
52
57
|
);
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
/*** Validate optional descriptive and numeric media metadata. */
|
|
55
61
|
function isMediaAssetMetadata(value: unknown): value is MediaAssetMetadata {
|
|
56
62
|
return (
|
|
57
63
|
isRecord(value) &&
|
|
@@ -72,10 +78,12 @@ function isMediaAssetMetadata(value: unknown): value is MediaAssetMetadata {
|
|
|
72
78
|
);
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
/*** Validate an authored HTTP or HTTPS media URL. */
|
|
75
82
|
function isStableRemoteUrl(value: unknown): boolean {
|
|
76
83
|
return typeof value === 'string' && /^https?:\/\//iu.test(value.trim());
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
/*** Validate a portable bundled-media path. */
|
|
79
87
|
function isBundledPath(value: unknown): boolean {
|
|
80
88
|
if (!isNonEmptyString(value)) return false;
|
|
81
89
|
const path = value.trim();
|
|
@@ -83,19 +91,12 @@ function isBundledPath(value: unknown): boolean {
|
|
|
83
91
|
return !path.split('/').includes('..');
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
return typeof value === 'string' && value.trim().length > 0;
|
|
88
|
-
}
|
|
89
|
-
|
|
94
|
+
/*** Accept an omitted media metric or a finite non-negative number. */
|
|
90
95
|
function isOptionalFiniteNonNegativeNumber(value: unknown): boolean {
|
|
91
96
|
return value === undefined || (typeof value === 'number' && Number.isFinite(value) && value >= 0);
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
/*** Accept an omitted media metric or a finite positive number. */
|
|
94
100
|
function isOptionalFinitePositiveNumber(value: unknown): boolean {
|
|
95
101
|
return value === undefined || (typeof value === 'number' && Number.isFinite(value) && value > 0);
|
|
96
102
|
}
|
|
97
|
-
|
|
98
|
-
function hasOnlyKeys(value: Record<string, unknown>, allowedKeys: readonly string[]): boolean {
|
|
99
|
-
const allowed = new Set(allowedKeys);
|
|
100
|
-
return Object.keys(value).every((key) => allowed.has(key));
|
|
101
|
-
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import { isStringArray } from '@ankhorage/utility/array';
|
|
2
|
+
import { hasOnlyKeys, isRecord } from '@ankhorage/utility/object';
|
|
3
|
+
import { isOptionalString } from '@ankhorage/utility/string';
|
|
4
|
+
|
|
1
5
|
import { type AppNavigatorManifest, NAVIGATOR_PRESETS, NAVIGATOR_TYPES } from '../navigator';
|
|
2
6
|
import { isIconSpec } from './icon';
|
|
3
7
|
import {
|
|
4
|
-
hasOnlyKeys,
|
|
5
8
|
isDrawerNavigatorOptions,
|
|
6
9
|
isNavigatorScreenReference,
|
|
7
10
|
isStackImplementationConfig,
|
|
8
11
|
isStackScreenOptions,
|
|
9
12
|
isTabsImplementationConfig,
|
|
10
13
|
} from './navigatorOptions';
|
|
11
|
-
import { isOptionalBoolean, isOptionalString, isRecord, isStringArray } from './shared';
|
|
12
14
|
|
|
13
15
|
/*** Validate the complete serialized `AppManifest.navigator` slice. */
|
|
14
16
|
export function isAppNavigatorManifest(value: unknown): value is AppNavigatorManifest {
|
|
@@ -72,12 +74,11 @@ function isRouteDefinition(value: unknown): boolean {
|
|
|
72
74
|
return (
|
|
73
75
|
isRecord(value) &&
|
|
74
76
|
typeof value.name === 'string' &&
|
|
75
|
-
|
|
76
|
-
isOptionalString(value.label) &&
|
|
77
|
+
[value.path, value.label, value.screenId].every(isOptionalString) &&
|
|
77
78
|
(value.icon === undefined || isIconSpec(value.icon)) &&
|
|
78
|
-
|
|
79
|
+
(value.showInPrimaryNavigation === undefined ||
|
|
80
|
+
typeof value.showInPrimaryNavigation === 'boolean') &&
|
|
79
81
|
(value.guards === undefined || isStringArray(value.guards)) &&
|
|
80
|
-
isOptionalString(value.screenId) &&
|
|
81
82
|
(value.stackOptions === undefined || isStackScreenOptions(value.stackOptions)) &&
|
|
82
83
|
(value.navigator === undefined || isNavigatorNode(value.navigator))
|
|
83
84
|
);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { hasOnlyKeys, isRecord } from '@ankhorage/utility/object';
|
|
2
|
+
import { isOptionalString } from '@ankhorage/utility/string';
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
DRAWER_POSITIONS,
|
|
3
6
|
DRAWER_TYPES,
|
|
@@ -8,7 +11,6 @@ import {
|
|
|
8
11
|
NATIVE_TABS_MINIMIZE_BEHAVIORS,
|
|
9
12
|
STACK_PRESENTATIONS,
|
|
10
13
|
} from '../navigator';
|
|
11
|
-
import { isOptionalBoolean, isOptionalString, isRecord } from './shared';
|
|
12
14
|
|
|
13
15
|
/*** Validate stable native, JavaScript, or alpha Experimental Stack desired state. */
|
|
14
16
|
export function isStackImplementationConfig(value: Record<string, unknown>): boolean {
|
|
@@ -63,7 +65,7 @@ export function isStackScreenOptions(value: unknown): boolean {
|
|
|
63
65
|
}
|
|
64
66
|
return (
|
|
65
67
|
(value.sheetAllowedDetents === undefined || isSheetAllowedDetents(value.sheetAllowedDetents)) &&
|
|
66
|
-
|
|
68
|
+
(value.sheetGrabberVisible === undefined || typeof value.sheetGrabberVisible === 'boolean')
|
|
67
69
|
);
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -77,8 +79,8 @@ export function isDrawerNavigatorOptions(value: unknown): boolean {
|
|
|
77
79
|
contains(DRAWER_POSITIONS, value.drawerPosition))) &&
|
|
78
80
|
(value.drawerType === undefined ||
|
|
79
81
|
(typeof value.drawerType === 'string' && contains(DRAWER_TYPES, value.drawerType))) &&
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
(value.swipeEnabled === undefined || typeof value.swipeEnabled === 'boolean') &&
|
|
83
|
+
(value.headerShown === undefined || typeof value.headerShown === 'boolean')
|
|
82
84
|
);
|
|
83
85
|
}
|
|
84
86
|
|
|
@@ -101,15 +103,6 @@ export function isNavigatorScreenReference(value: unknown): boolean {
|
|
|
101
103
|
return isRecord(value) && hasOnlyKeys(value, ['screenId']) && typeof value.screenId === 'string';
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
/*** Check that a finite configuration object contains no unsupported keys. */
|
|
105
|
-
export function hasOnlyKeys(
|
|
106
|
-
value: Record<string, unknown>,
|
|
107
|
-
allowedKeys: readonly string[],
|
|
108
|
-
): boolean {
|
|
109
|
-
const allowed = new Set(allowedKeys);
|
|
110
|
-
return Object.keys(value).every((key) => allowed.has(key));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
106
|
/*** Validate JavaScript Stack options without accepting native-only presentations. */
|
|
114
107
|
function isJavaScriptStackScreenOptions(value: unknown): boolean {
|
|
115
108
|
return (
|
|
@@ -141,9 +134,9 @@ function isStackHeaderOptions(value: unknown): boolean {
|
|
|
141
134
|
function isStackHeaderOptionsShape(value: Record<string, unknown>): boolean {
|
|
142
135
|
return (
|
|
143
136
|
isOptionalString(value.title) &&
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
137
|
+
(value.headerShown === undefined || typeof value.headerShown === 'boolean') &&
|
|
138
|
+
(value.headerTransparent === undefined || typeof value.headerTransparent === 'boolean') &&
|
|
139
|
+
(value.headerBackVisible === undefined || typeof value.headerBackVisible === 'boolean')
|
|
147
140
|
);
|
|
148
141
|
}
|
|
149
142
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AppManifestParseResult } from '../types/appManifest';
|
|
2
|
+
import { isAppManifest } from './isAppManifest';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parse unknown JSON-compatible input at the canonical AppManifest boundary.
|
|
6
|
+
*
|
|
7
|
+
* Contracts owns structural manifest validation. Consumers may add semantic
|
|
8
|
+
* diagnostics after this parser succeeds, but should not reconstruct the
|
|
9
|
+
* AppManifest shape in their own packages.
|
|
10
|
+
*/
|
|
11
|
+
export function parseAppManifest(value: unknown): AppManifestParseResult {
|
|
12
|
+
return isAppManifest(value)
|
|
13
|
+
? { ok: true, manifest: value }
|
|
14
|
+
: { ok: false, message: 'Value is not a canonical AppManifest.' };
|
|
15
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { COLOR_HARMONIES } from '@ankhorage/color-theory';
|
|
2
|
+
import { isRecord } from '@ankhorage/utility/object';
|
|
3
|
+
import { isOptionalString } from '@ankhorage/utility/string';
|
|
2
4
|
|
|
3
5
|
import { isMediaAssetReference } from '../media';
|
|
4
6
|
import { APP_CATEGORIES } from '../types';
|
|
5
7
|
import { isBindingValueSource, isScreenDataLoaderDefinition } from './bindings';
|
|
6
|
-
import { isOptionalNumber, isOptionalString, isRecord } from './shared';
|
|
7
8
|
|
|
8
9
|
export { isAppNavigatorManifest } from './navigator';
|
|
9
10
|
|
|
@@ -11,6 +12,7 @@ const APP_CATEGORY_SET = new Set<string>(APP_CATEGORIES);
|
|
|
11
12
|
const COLOR_HARMONY_SET = new Set<string>(COLOR_HARMONIES);
|
|
12
13
|
const SPLASH_SCREEN_RESIZE_MODE_SET = new Set<string>(['contain', 'cover', 'native']);
|
|
13
14
|
|
|
15
|
+
/*** Validate application identity and optional authored metadata. */
|
|
14
16
|
export function isManifestMetadata(value: unknown): boolean {
|
|
15
17
|
return (
|
|
16
18
|
isRecord(value) &&
|
|
@@ -25,6 +27,7 @@ export function isManifestMetadata(value: unknown): boolean {
|
|
|
25
27
|
);
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/*** Validate theme identity and its required light and dark modes. */
|
|
28
31
|
export function isThemeConfig(value: unknown): boolean {
|
|
29
32
|
return (
|
|
30
33
|
isRecord(value) &&
|
|
@@ -35,6 +38,7 @@ export function isThemeConfig(value: unknown): boolean {
|
|
|
35
38
|
);
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
/*** Validate screens in the authored screen registry. */
|
|
38
42
|
export function isScreenRegistry(value: unknown): boolean {
|
|
39
43
|
return (
|
|
40
44
|
isRecord(value) &&
|
|
@@ -45,6 +49,7 @@ export function isScreenRegistry(value: unknown): boolean {
|
|
|
45
49
|
);
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
/*** Validate optional light and dark splash-screen configuration. */
|
|
48
53
|
export function isSplashScreenSpec(value: unknown): boolean {
|
|
49
54
|
return (
|
|
50
55
|
isSplashScreenModeSpec(value) &&
|
|
@@ -53,6 +58,7 @@ export function isSplashScreenSpec(value: unknown): boolean {
|
|
|
53
58
|
);
|
|
54
59
|
}
|
|
55
60
|
|
|
61
|
+
/*** Validate the primary color and supported color harmony of a theme mode. */
|
|
56
62
|
function isThemeModeConfig(value: unknown): boolean {
|
|
57
63
|
return (
|
|
58
64
|
isRecord(value) &&
|
|
@@ -62,6 +68,7 @@ function isThemeModeConfig(value: unknown): boolean {
|
|
|
62
68
|
);
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
/*** Validate an authored component node and its nested children. */
|
|
65
72
|
function isUiNode(value: unknown): boolean {
|
|
66
73
|
return (
|
|
67
74
|
isRecord(value) &&
|
|
@@ -76,6 +83,7 @@ function isUiNode(value: unknown): boolean {
|
|
|
76
83
|
);
|
|
77
84
|
}
|
|
78
85
|
|
|
86
|
+
/*** Validate repeat source and item binding configuration. */
|
|
79
87
|
function isUiNodeRepeatSpec(value: unknown): boolean {
|
|
80
88
|
return (
|
|
81
89
|
isRecord(value) &&
|
|
@@ -86,6 +94,7 @@ function isUiNodeRepeatSpec(value: unknown): boolean {
|
|
|
86
94
|
);
|
|
87
95
|
}
|
|
88
96
|
|
|
97
|
+
/*** Validate screen identity, root component and optional requirements. */
|
|
89
98
|
function isScreenSpec(value: unknown): boolean {
|
|
90
99
|
return (
|
|
91
100
|
isRecord(value) &&
|
|
@@ -101,11 +110,12 @@ function isScreenSpec(value: unknown): boolean {
|
|
|
101
110
|
);
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
/*** Validate one splash-screen appearance configuration. */
|
|
104
114
|
function isSplashScreenModeSpec(value: unknown): boolean {
|
|
105
115
|
return (
|
|
106
116
|
isRecord(value) &&
|
|
107
117
|
(value.image === undefined || isMediaAssetReference(value.image)) &&
|
|
108
|
-
|
|
118
|
+
(value.imageWidth === undefined || typeof value.imageWidth === 'number') &&
|
|
109
119
|
(value.resizeMode === undefined ||
|
|
110
120
|
(typeof value.resizeMode === 'string' &&
|
|
111
121
|
SPLASH_SCREEN_RESIZE_MODE_SET.has(value.resizeMode))) &&
|
|
@@ -113,6 +123,7 @@ function isSplashScreenModeSpec(value: unknown): boolean {
|
|
|
113
123
|
);
|
|
114
124
|
}
|
|
115
125
|
|
|
126
|
+
/*** Validate screen capability and permission requirements. */
|
|
116
127
|
function isScreenRequirements(value: unknown): boolean {
|
|
117
128
|
return (
|
|
118
129
|
isRecord(value) &&
|
|
@@ -121,6 +132,7 @@ function isScreenRequirements(value: unknown): boolean {
|
|
|
121
132
|
);
|
|
122
133
|
}
|
|
123
134
|
|
|
135
|
+
/*** Validate the entries of a capability or permission requirement list. */
|
|
124
136
|
function isRequirementArray(value: unknown, key: 'capability' | 'permission'): boolean {
|
|
125
137
|
return (
|
|
126
138
|
Array.isArray(value) &&
|
package/src/appManifest.test.ts
CHANGED
|
@@ -37,12 +37,16 @@ function createManifest(): Record<string, unknown> {
|
|
|
37
37
|
},
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
|
+
state: { provider: 'legend', persistence: false },
|
|
40
41
|
infra: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
environments: {
|
|
43
|
+
local: {
|
|
44
|
+
deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
|
|
45
|
+
database: { provider: 'supabase', tier: 'dev' },
|
|
46
|
+
objectStorage: { provider: 'supabase', buckets: ['media'] },
|
|
47
|
+
networking: { domain: 'example.test' },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
46
50
|
apis: [
|
|
47
51
|
{
|
|
48
52
|
id: 'nutrition',
|
package/src/appManifest.ts
CHANGED
|
@@ -1,112 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { isAppDeployManifest } from './appManifest/deploy';
|
|
4
|
-
import { isInfraManifest } from './appManifest/infra';
|
|
5
|
-
import { isMediaManifest } from './appManifest/media';
|
|
6
|
-
import {
|
|
7
|
-
isAppNavigatorManifest,
|
|
8
|
-
isManifestMetadata,
|
|
9
|
-
isScreenRegistry,
|
|
10
|
-
isSplashScreenSpec,
|
|
11
|
-
isThemeConfig,
|
|
12
|
-
} from './appManifest/screens';
|
|
13
|
-
import { isRecord, isStringArray } from './appManifest/shared';
|
|
14
|
-
import type { AppManifest } from './types';
|
|
15
|
-
|
|
1
|
+
export { isAppManifest } from './appManifest/isAppManifest';
|
|
2
|
+
export { parseAppManifest } from './appManifest/parseAppManifest';
|
|
16
3
|
export type {
|
|
17
4
|
SplashScreenModeSpec,
|
|
18
5
|
SplashScreenResizeMode,
|
|
19
6
|
SplashScreenSpec,
|
|
20
7
|
} from './appManifest/splashScreen';
|
|
21
|
-
|
|
22
|
-
export type AppManifestParseResult =
|
|
23
|
-
| { readonly ok: true; readonly manifest: AppManifest }
|
|
24
|
-
| { readonly ok: false; readonly message: string };
|
|
25
|
-
|
|
26
|
-
const APP_MANIFEST_KEY_POLICY = {
|
|
27
|
-
metadata: 'required',
|
|
28
|
-
themes: 'required',
|
|
29
|
-
activeThemeId: 'required',
|
|
30
|
-
activeThemeMode: 'optional',
|
|
31
|
-
splashScreen: 'optional',
|
|
32
|
-
media: 'optional',
|
|
33
|
-
deploy: 'optional',
|
|
34
|
-
infra: 'required',
|
|
35
|
-
navigator: 'required',
|
|
36
|
-
screens: 'required',
|
|
37
|
-
dataSources: 'optional',
|
|
38
|
-
dataBindings: 'optional',
|
|
39
|
-
repository: 'optional',
|
|
40
|
-
settings: 'required',
|
|
41
|
-
} as const satisfies Record<keyof AppManifest, 'optional' | 'required'>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Parse unknown JSON-compatible input at the canonical AppManifest boundary.
|
|
45
|
-
*
|
|
46
|
-
* Contracts owns structural manifest validation. Consumers may add semantic
|
|
47
|
-
* diagnostics after this parser succeeds, but should not reconstruct the
|
|
48
|
-
* AppManifest shape in their own packages.
|
|
49
|
-
*/
|
|
50
|
-
export function parseAppManifest(value: unknown): AppManifestParseResult {
|
|
51
|
-
return isAppManifest(value)
|
|
52
|
-
? { ok: true, manifest: value }
|
|
53
|
-
: { ok: false, message: 'Value is not a canonical AppManifest.' };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Return whether an unknown value satisfies the canonical AppManifest shape. */
|
|
57
|
-
export function isAppManifest(value: unknown): value is AppManifest {
|
|
58
|
-
return (
|
|
59
|
-
isRecord(value) &&
|
|
60
|
-
hasRequiredManifestKeys(value) &&
|
|
61
|
-
!('generatedApis' in value) &&
|
|
62
|
-
isManifestMetadata(value.metadata) &&
|
|
63
|
-
Array.isArray(value.themes) &&
|
|
64
|
-
value.themes.every(isThemeConfig) &&
|
|
65
|
-
typeof value.activeThemeId === 'string' &&
|
|
66
|
-
isActiveThemeMode(value.activeThemeMode) &&
|
|
67
|
-
(value.splashScreen === undefined || isSplashScreenSpec(value.splashScreen)) &&
|
|
68
|
-
(value.media === undefined || isMediaManifest(value.media)) &&
|
|
69
|
-
(value.deploy === undefined || isAppDeployManifest(value.deploy)) &&
|
|
70
|
-
isInfraManifest(value.infra) &&
|
|
71
|
-
isAppNavigatorManifest(value.navigator) &&
|
|
72
|
-
isScreenRegistry(value.screens) &&
|
|
73
|
-
(value.dataSources === undefined || isDataSourceRegistry(value.dataSources)) &&
|
|
74
|
-
(value.dataBindings === undefined || isComponentDataBindingRegistry(value.dataBindings)) &&
|
|
75
|
-
(value.repository === undefined || isRepositoryManifest(value.repository)) &&
|
|
76
|
-
isAppSettings(value.settings)
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function hasRequiredManifestKeys(value: Record<string, unknown>): boolean {
|
|
81
|
-
return Object.entries(APP_MANIFEST_KEY_POLICY).every(
|
|
82
|
-
([key, policy]) => policy === 'optional' || key in value,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function isActiveThemeMode(value: unknown): boolean {
|
|
87
|
-
return value === undefined || value === 'dark' || value === 'light';
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isRepositoryManifest(value: unknown): boolean {
|
|
91
|
-
return (
|
|
92
|
-
isRecord(value) &&
|
|
93
|
-
value.provider === 'github' &&
|
|
94
|
-
typeof value.owner === 'string' &&
|
|
95
|
-
value.owner.length > 0 &&
|
|
96
|
-
typeof value.name === 'string' &&
|
|
97
|
-
value.name.length > 0 &&
|
|
98
|
-
typeof value.url === 'string' &&
|
|
99
|
-
value.url.length > 0 &&
|
|
100
|
-
value.defaultBranch === 'main'
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function isAppSettings(value: unknown): boolean {
|
|
105
|
-
return (
|
|
106
|
-
isRecord(value) &&
|
|
107
|
-
!('apiBaseUrl' in value) &&
|
|
108
|
-
isRecord(value.localization) &&
|
|
109
|
-
typeof value.localization.defaultLocale === 'string' &&
|
|
110
|
-
isStringArray(value.localization.locales)
|
|
111
|
-
);
|
|
112
|
-
}
|
|
8
|
+
export type { AppManifestParseResult } from './types/appManifest';
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
type AppSettings,
|
|
8
8
|
type AuthFlowConfig,
|
|
9
9
|
type AuthProviderConfig,
|
|
10
|
-
type AuthSpec,
|
|
11
10
|
DEFAULT_AUTH_FLOW,
|
|
11
|
+
type InfraAuthSpec,
|
|
12
12
|
resolveAuthFlow,
|
|
13
13
|
} from './index';
|
|
14
14
|
|
|
@@ -52,13 +52,13 @@ describe('canonical authentication flow contract', () => {
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it('allows authentication without an authorization model', () => {
|
|
55
|
-
const auth:
|
|
55
|
+
const auth: InfraAuthSpec = {
|
|
56
56
|
scope: 'global',
|
|
57
57
|
provider: 'supabase',
|
|
58
58
|
flow: DEFAULT_AUTH_FLOW,
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
expect(auth
|
|
61
|
+
expect(Object.hasOwn(auth, 'authorization')).toBe(false);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
it('removes auth flow from application settings at compile time', () => {
|
|
@@ -91,13 +91,9 @@ describe('canonical authentication flow contract', () => {
|
|
|
91
91
|
expect(removedProperty.provider).toBe('supabase');
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
it('
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
return auth.authorization.kind;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
expect(typeof assumesAuthorization).toBe('function');
|
|
94
|
+
it('removes nested authorization from the authentication contract', () => {
|
|
95
|
+
const hasAuthorization: 'authorization' extends keyof InfraAuthSpec ? true : false = false;
|
|
96
|
+
expect(hasAuthorization).toBe(false);
|
|
101
97
|
});
|
|
102
98
|
|
|
103
99
|
it('prevents the removed settings auth-flow path from returning to source', async () => {
|
package/src/auth-oauth.test.ts
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type AuthAdapter,
|
|
12
12
|
type AuthOAuthConfig,
|
|
13
13
|
type AuthSession,
|
|
14
|
-
type
|
|
14
|
+
type InfraAuthSpec,
|
|
15
15
|
} from './index';
|
|
16
16
|
|
|
17
17
|
const session: AuthSession = {
|
|
@@ -80,7 +80,7 @@ describe('OAuth auth contracts', () => {
|
|
|
80
80
|
],
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
const auth:
|
|
83
|
+
const auth: InfraAuthSpec = {
|
|
84
84
|
scope: 'global',
|
|
85
85
|
provider: 'supabase',
|
|
86
86
|
oauth,
|
package/src/auth.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AppDeployTargetId } from './deploy';
|
|
2
|
+
import type { AppEnvironmentId } from './environments';
|
|
2
3
|
import type { SecretRef } from './secrets';
|
|
3
4
|
import type { IconSpec } from './types';
|
|
4
5
|
|
|
@@ -90,7 +91,7 @@ export type AuthOAuthSetupRequirement =
|
|
|
90
91
|
export interface AuthOAuthSetupPlan {
|
|
91
92
|
readonly provider: AuthOAuthProviderId;
|
|
92
93
|
readonly transport: AuthOAuthTransportId;
|
|
93
|
-
readonly environment:
|
|
94
|
+
readonly environment: AppEnvironmentId;
|
|
94
95
|
/** Enabled application targets considered while deriving this plan. */
|
|
95
96
|
readonly targets: readonly AppDeployTargetId[];
|
|
96
97
|
readonly requirements: readonly AuthOAuthSetupRequirement[];
|
package/src/bindings.test.ts
CHANGED
|
@@ -490,6 +490,11 @@ describe('component data-binding contracts', () => {
|
|
|
490
490
|
],
|
|
491
491
|
activeThemeId: 'default',
|
|
492
492
|
infra: {
|
|
493
|
+
environments: {
|
|
494
|
+
local: {
|
|
495
|
+
deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
|
|
496
|
+
},
|
|
497
|
+
},
|
|
493
498
|
apis: [
|
|
494
499
|
{
|
|
495
500
|
id: 'cms',
|
package/src/contracts.test.ts
CHANGED
|
@@ -9,11 +9,10 @@ import {
|
|
|
9
9
|
APP_CATEGORIES,
|
|
10
10
|
type AppCategory,
|
|
11
11
|
type AppManifest,
|
|
12
|
-
|
|
12
|
+
type AppStateSpec,
|
|
13
13
|
AUTH_SIGN_IN_IDENTIFIERS,
|
|
14
14
|
AUTH_SIGN_UP_POLICIES,
|
|
15
15
|
type AuthFlowConfig,
|
|
16
|
-
type AuthSpec,
|
|
17
16
|
type ButtonPressEventDto,
|
|
18
17
|
type CollectionItemPressEventDto,
|
|
19
18
|
type ComponentEventDto,
|
|
@@ -22,15 +21,12 @@ import {
|
|
|
22
21
|
type DbAdminAdapter,
|
|
23
22
|
type DbChangeEvent,
|
|
24
23
|
type DbRealtimeAdapter,
|
|
25
|
-
DEPLOYMENT_TARGETS,
|
|
26
24
|
type FormSubmitEventDto,
|
|
27
25
|
type ImageAssetSource,
|
|
26
|
+
type InfraAuthSpec,
|
|
28
27
|
type InfraManifest,
|
|
29
28
|
NAVIGATOR_TYPES,
|
|
30
29
|
type RouteDefinition,
|
|
31
|
-
STATE_PERSISTENCE_MODES,
|
|
32
|
-
STATE_PROVIDERS,
|
|
33
|
-
type StateSpec,
|
|
34
30
|
type StoragePublicUrlResult,
|
|
35
31
|
type StorageResult,
|
|
36
32
|
type StorageUploadResult,
|
|
@@ -105,10 +101,6 @@ describe('contracts', () => {
|
|
|
105
101
|
'utilities_tools',
|
|
106
102
|
'weather',
|
|
107
103
|
]);
|
|
108
|
-
expect(DEPLOYMENT_TARGETS).toEqual(['minikube']);
|
|
109
|
-
expect(AUTH_PROVIDERS).toEqual(['supabase']);
|
|
110
|
-
expect(STATE_PROVIDERS).toEqual(['legend']);
|
|
111
|
-
expect(STATE_PERSISTENCE_MODES).toEqual(['none', 'local', 'secure', 'database']);
|
|
112
104
|
});
|
|
113
105
|
|
|
114
106
|
it('exports the app category union for template packages', () => {
|
|
@@ -255,14 +247,19 @@ describe('contracts', () => {
|
|
|
255
247
|
expect(manifest.screens.products?.root.repeat?.empty?.[0]?.type).toBe('Notice');
|
|
256
248
|
});
|
|
257
249
|
|
|
258
|
-
it('
|
|
259
|
-
const state:
|
|
250
|
+
it('selects in-memory application state independently from infrastructure', () => {
|
|
251
|
+
const state: AppStateSpec = {
|
|
260
252
|
provider: 'legend',
|
|
261
|
-
persistence:
|
|
253
|
+
persistence: false,
|
|
262
254
|
};
|
|
263
|
-
const manifest: Pick<AppManifest, 'infra'> = {
|
|
255
|
+
const manifest: Pick<AppManifest, 'infra' | 'state'> = {
|
|
256
|
+
state,
|
|
264
257
|
infra: {
|
|
265
|
-
|
|
258
|
+
environments: {
|
|
259
|
+
local: {
|
|
260
|
+
deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
266
263
|
modules: [],
|
|
267
264
|
modulesConfig: {
|
|
268
265
|
localization: {
|
|
@@ -273,8 +270,9 @@ describe('contracts', () => {
|
|
|
273
270
|
};
|
|
274
271
|
|
|
275
272
|
expect(JSON.parse(JSON.stringify(manifest))).toEqual({
|
|
273
|
+
state,
|
|
276
274
|
infra: {
|
|
277
|
-
|
|
275
|
+
environments: manifest.infra.environments,
|
|
278
276
|
modules: [],
|
|
279
277
|
modulesConfig: {
|
|
280
278
|
localization: {
|
|
@@ -292,6 +290,11 @@ describe('contracts', () => {
|
|
|
292
290
|
const hasLegacyModules: HasLegacyModules = false;
|
|
293
291
|
const hasLegacyModulesConfig: HasLegacyModulesConfig = false;
|
|
294
292
|
const infra: InfraManifest = {
|
|
293
|
+
environments: {
|
|
294
|
+
local: {
|
|
295
|
+
deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
|
|
296
|
+
},
|
|
297
|
+
},
|
|
295
298
|
modules: ['expo-localization'],
|
|
296
299
|
modulesConfig: {
|
|
297
300
|
'expo-localization': {
|
|
@@ -308,7 +311,7 @@ describe('contracts', () => {
|
|
|
308
311
|
|
|
309
312
|
expect(hasLegacyModules).toBe(false);
|
|
310
313
|
expect(hasLegacyModulesConfig).toBe(false);
|
|
311
|
-
expect(Object.keys(infra).sort()).toEqual(['modules', 'modulesConfig']);
|
|
314
|
+
expect(Object.keys(infra).sort()).toEqual(['environments', 'modules', 'modulesConfig']);
|
|
312
315
|
expect(expoConfig.plugins).toEqual(['expo-router']);
|
|
313
316
|
});
|
|
314
317
|
|
|
@@ -367,10 +370,9 @@ describe('contracts', () => {
|
|
|
367
370
|
unauthorizedRoute: '/sign-in',
|
|
368
371
|
};
|
|
369
372
|
|
|
370
|
-
const auth:
|
|
373
|
+
const auth: InfraAuthSpec = {
|
|
371
374
|
scope: 'global',
|
|
372
375
|
provider: 'supabase',
|
|
373
|
-
authorization: { kind: 'RBAC', engine: 'cerbos' },
|
|
374
376
|
flow: authFlow,
|
|
375
377
|
signIn: { identifiers: ['email'] },
|
|
376
378
|
signUp: {
|
package/src/deploy.test.ts
CHANGED
|
@@ -4,8 +4,9 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { describe, expect, it } from 'bun:test';
|
|
5
5
|
|
|
6
6
|
import { isAppManifest } from './appManifest';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { APP_DEPLOY_TARGET_IDS, isAppDeployManifest } from './deploy';
|
|
8
|
+
import { APP_ENVIRONMENT_IDS } from './environments';
|
|
9
|
+
import { INFRA_RUNTIME_COMPATIBILITY } from './infra';
|
|
9
10
|
|
|
10
11
|
function createManifest(deploy?: unknown): Record<string, unknown> {
|
|
11
12
|
return {
|
|
@@ -26,7 +27,14 @@ function createManifest(deploy?: unknown): Record<string, unknown> {
|
|
|
26
27
|
],
|
|
27
28
|
activeThemeId: 'default',
|
|
28
29
|
...(deploy === undefined ? {} : { deploy }),
|
|
29
|
-
infra: {
|
|
30
|
+
infra: {
|
|
31
|
+
environments: {
|
|
32
|
+
local: {
|
|
33
|
+
deployment: { compute: { provider: 'local' }, runtime: { provider: 'minikube' } },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
modules: [],
|
|
37
|
+
},
|
|
30
38
|
navigator: { type: 'stack', routes: [] },
|
|
31
39
|
screens: {},
|
|
32
40
|
settings: { localization: { defaultLocale: 'en', locales: ['en'] } },
|
|
@@ -36,11 +44,11 @@ function createManifest(deploy?: unknown): Record<string, unknown> {
|
|
|
36
44
|
describe('app deployment contracts', () => {
|
|
37
45
|
it('keeps app distribution targets distinct from Infra deployment targets', () => {
|
|
38
46
|
expect(APP_DEPLOY_TARGET_IDS).toEqual(['web', 'android', 'ios']);
|
|
39
|
-
expect(
|
|
47
|
+
expect(Object.keys(INFRA_RUNTIME_COMPATIBILITY)).toEqual(['minikube', 'k3s', 'docker-compose']);
|
|
40
48
|
});
|
|
41
49
|
|
|
42
50
|
it('defines one logical environment vocabulary across platform-specific execution', () => {
|
|
43
|
-
expect(
|
|
51
|
+
expect(APP_ENVIRONMENT_IDS).toEqual(['local', 'preview', 'production']);
|
|
44
52
|
});
|
|
45
53
|
|
|
46
54
|
it('accepts canonical web, Android, and iOS desired state with stable native schemes', () => {
|