@ankhorage/contracts 13.0.1 → 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 +37 -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/splashScreen.d.ts +15 -0
- package/dist/appManifest/splashScreen.d.ts.map +1 -0
- package/dist/appManifest/splashScreen.js +2 -0
- package/dist/appManifest/splashScreen.js.map +1 -0
- package/dist/appManifest.d.ts +4 -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 +8 -82
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -9
- package/dist/types.js.map +1 -1
- package/package.json +13 -3
- 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/splashScreen.test.ts +40 -0
- package/src/appManifest/splashScreen.ts +24 -0
- package/src/appManifest.test.ts +9 -5
- package/src/appManifest.ts +8 -106
- 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 -39
- 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 +8 -103
- 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) &&
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import type { AppManifest, SplashScreenSpec } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('AppManifest splash screen contract', () => {
|
|
6
|
+
it('accepts portable media references with Expo-owned option types', () => {
|
|
7
|
+
const splashScreen: SplashScreenSpec = {
|
|
8
|
+
backgroundColor: '#ffffff',
|
|
9
|
+
image: { mediaId: 'splash-logo' },
|
|
10
|
+
imageWidth: 160,
|
|
11
|
+
resizeMode: 'contain',
|
|
12
|
+
dark: {
|
|
13
|
+
backgroundColor: '#000000',
|
|
14
|
+
image: { mediaId: 'splash-logo-dark' },
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const manifest: Pick<AppManifest, 'splashScreen'> = { splashScreen };
|
|
19
|
+
|
|
20
|
+
expect(JSON.parse(JSON.stringify(manifest))).toEqual({ splashScreen });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('keeps root-only Expo options out of dark-mode overrides', () => {
|
|
24
|
+
const splashWithDarkImageWidth: SplashScreenSpec = {
|
|
25
|
+
dark: {
|
|
26
|
+
// @ts-expect-error Expo dark-mode options do not include image sizing.
|
|
27
|
+
imageWidth: 160,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const splashWithDarkResizeMode: SplashScreenSpec = {
|
|
31
|
+
dark: {
|
|
32
|
+
// @ts-expect-error Expo dark-mode options do not include resize mode.
|
|
33
|
+
resizeMode: 'contain',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
expect(splashWithDarkImageWidth.dark).toEqual({ imageWidth: 160 });
|
|
38
|
+
expect(splashWithDarkResizeMode.dark).toEqual({ resizeMode: 'contain' });
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Props as ExpoSplashScreenPluginProps } from 'expo-splash-screen/plugin';
|
|
2
|
+
|
|
3
|
+
import type { MediaAssetReference } from '../media';
|
|
4
|
+
|
|
5
|
+
type ExpoSplashScreenRootProps = Pick<
|
|
6
|
+
ExpoSplashScreenPluginProps,
|
|
7
|
+
'backgroundColor' | 'image' | 'imageWidth' | 'resizeMode'
|
|
8
|
+
>;
|
|
9
|
+
|
|
10
|
+
type PortableSplashScreenProps<T extends { readonly image?: string }> = Readonly<
|
|
11
|
+
Omit<T, 'image'>
|
|
12
|
+
> & {
|
|
13
|
+
readonly image?: MediaAssetReference;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type SplashScreenResizeMode = NonNullable<ExpoSplashScreenPluginProps['resizeMode']>;
|
|
17
|
+
|
|
18
|
+
export type SplashScreenModeSpec = PortableSplashScreenProps<
|
|
19
|
+
NonNullable<ExpoSplashScreenPluginProps['dark']>
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
export type SplashScreenSpec = PortableSplashScreenProps<ExpoSplashScreenRootProps> & {
|
|
23
|
+
readonly dark?: SplashScreenModeSpec;
|
|
24
|
+
};
|
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,106 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
isScreenRegistry,
|
|
10
|
-
isSplashScreenSpec,
|
|
11
|
-
isThemeConfig,
|
|
12
|
-
} from './appManifest/screens';
|
|
13
|
-
import { isRecord, isStringArray } from './appManifest/shared';
|
|
14
|
-
import type { AppManifest } from './types';
|
|
15
|
-
|
|
16
|
-
export type AppManifestParseResult =
|
|
17
|
-
| { readonly ok: true; readonly manifest: AppManifest }
|
|
18
|
-
| { readonly ok: false; readonly message: string };
|
|
19
|
-
|
|
20
|
-
const APP_MANIFEST_KEY_POLICY = {
|
|
21
|
-
metadata: 'required',
|
|
22
|
-
themes: 'required',
|
|
23
|
-
activeThemeId: 'required',
|
|
24
|
-
activeThemeMode: 'optional',
|
|
25
|
-
splashScreen: 'optional',
|
|
26
|
-
media: 'optional',
|
|
27
|
-
deploy: 'optional',
|
|
28
|
-
infra: 'required',
|
|
29
|
-
navigator: 'required',
|
|
30
|
-
screens: 'required',
|
|
31
|
-
dataSources: 'optional',
|
|
32
|
-
dataBindings: 'optional',
|
|
33
|
-
repository: 'optional',
|
|
34
|
-
settings: 'required',
|
|
35
|
-
} as const satisfies Record<keyof AppManifest, 'optional' | 'required'>;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Parse unknown JSON-compatible input at the canonical AppManifest boundary.
|
|
39
|
-
*
|
|
40
|
-
* Contracts owns structural manifest validation. Consumers may add semantic
|
|
41
|
-
* diagnostics after this parser succeeds, but should not reconstruct the
|
|
42
|
-
* AppManifest shape in their own packages.
|
|
43
|
-
*/
|
|
44
|
-
export function parseAppManifest(value: unknown): AppManifestParseResult {
|
|
45
|
-
return isAppManifest(value)
|
|
46
|
-
? { ok: true, manifest: value }
|
|
47
|
-
: { ok: false, message: 'Value is not a canonical AppManifest.' };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Return whether an unknown value satisfies the canonical AppManifest shape. */
|
|
51
|
-
export function isAppManifest(value: unknown): value is AppManifest {
|
|
52
|
-
return (
|
|
53
|
-
isRecord(value) &&
|
|
54
|
-
hasRequiredManifestKeys(value) &&
|
|
55
|
-
!('generatedApis' in value) &&
|
|
56
|
-
isManifestMetadata(value.metadata) &&
|
|
57
|
-
Array.isArray(value.themes) &&
|
|
58
|
-
value.themes.every(isThemeConfig) &&
|
|
59
|
-
typeof value.activeThemeId === 'string' &&
|
|
60
|
-
isActiveThemeMode(value.activeThemeMode) &&
|
|
61
|
-
(value.splashScreen === undefined || isSplashScreenSpec(value.splashScreen)) &&
|
|
62
|
-
(value.media === undefined || isMediaManifest(value.media)) &&
|
|
63
|
-
(value.deploy === undefined || isAppDeployManifest(value.deploy)) &&
|
|
64
|
-
isInfraManifest(value.infra) &&
|
|
65
|
-
isAppNavigatorManifest(value.navigator) &&
|
|
66
|
-
isScreenRegistry(value.screens) &&
|
|
67
|
-
(value.dataSources === undefined || isDataSourceRegistry(value.dataSources)) &&
|
|
68
|
-
(value.dataBindings === undefined || isComponentDataBindingRegistry(value.dataBindings)) &&
|
|
69
|
-
(value.repository === undefined || isRepositoryManifest(value.repository)) &&
|
|
70
|
-
isAppSettings(value.settings)
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function hasRequiredManifestKeys(value: Record<string, unknown>): boolean {
|
|
75
|
-
return Object.entries(APP_MANIFEST_KEY_POLICY).every(
|
|
76
|
-
([key, policy]) => policy === 'optional' || key in value,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isActiveThemeMode(value: unknown): boolean {
|
|
81
|
-
return value === undefined || value === 'dark' || value === 'light';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function isRepositoryManifest(value: unknown): boolean {
|
|
85
|
-
return (
|
|
86
|
-
isRecord(value) &&
|
|
87
|
-
value.provider === 'github' &&
|
|
88
|
-
typeof value.owner === 'string' &&
|
|
89
|
-
value.owner.length > 0 &&
|
|
90
|
-
typeof value.name === 'string' &&
|
|
91
|
-
value.name.length > 0 &&
|
|
92
|
-
typeof value.url === 'string' &&
|
|
93
|
-
value.url.length > 0 &&
|
|
94
|
-
value.defaultBranch === 'main'
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function isAppSettings(value: unknown): boolean {
|
|
99
|
-
return (
|
|
100
|
-
isRecord(value) &&
|
|
101
|
-
!('apiBaseUrl' in value) &&
|
|
102
|
-
isRecord(value.localization) &&
|
|
103
|
-
typeof value.localization.defaultLocale === 'string' &&
|
|
104
|
-
isStringArray(value.localization.locales)
|
|
105
|
-
);
|
|
106
|
-
}
|
|
1
|
+
export { isAppManifest } from './appManifest/isAppManifest';
|
|
2
|
+
export { parseAppManifest } from './appManifest/parseAppManifest';
|
|
3
|
+
export type {
|
|
4
|
+
SplashScreenModeSpec,
|
|
5
|
+
SplashScreenResizeMode,
|
|
6
|
+
SplashScreenSpec,
|
|
7
|
+
} from './appManifest/splashScreen';
|
|
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',
|