@ankhorage/contracts 7.6.0 → 7.7.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ankhorage/contracts
2
2
 
3
+ ## 7.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 00d608f: Add canonical application environment IDs and stable native scheme identity to app deployment target contracts.
8
+
3
9
  ## 7.6.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # CONTRACTS
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v7.5.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v7.6.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Serializable app, action, theme, auth, and secret-store contracts for Ankhorage.
9
9
 
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/appManifest/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,iBAAiB,EAIlB,MAAM,WAAW,CAAC;AAUnB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAE9E"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/appManifest/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,iBAAiB,EAIlB,MAAM,WAAW,CAAC;AAWnB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAE9E"}
@@ -3,8 +3,9 @@ const DEPLOY_KEYS = new Set(['targets']);
3
3
  const TARGET_KEYS = new Set(['web', 'android', 'ios']);
4
4
  const PROVIDER_KEYS = new Set(['build', 'publish']);
5
5
  const WEB_KEYS = new Set(['enabled', 'providers']);
6
- const ANDROID_KEYS = new Set(['enabled', 'package', 'providers']);
7
- const IOS_KEYS = new Set(['enabled', 'bundleIdentifier', 'providers']);
6
+ const ANDROID_KEYS = new Set(['enabled', 'package', 'scheme', 'providers']);
7
+ const IOS_KEYS = new Set(['enabled', 'bundleIdentifier', 'scheme', 'providers']);
8
+ const URI_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
8
9
  export function isAppDeployManifest(value) {
9
10
  return isRecord(value) && hasOnlyKeys(value, DEPLOY_KEYS) && isAppDeployTargets(value.targets);
10
11
  }
@@ -26,6 +27,7 @@ function isAndroidTarget(value) {
26
27
  hasOnlyKeys(value, ANDROID_KEYS) &&
27
28
  typeof value.enabled === 'boolean' &&
28
29
  isNonEmptyString(value.package) &&
30
+ isOptionalScheme(value.scheme) &&
29
31
  isOptionalProviders(value.providers));
30
32
  }
31
33
  function isIosTarget(value) {
@@ -33,8 +35,12 @@ function isIosTarget(value) {
33
35
  hasOnlyKeys(value, IOS_KEYS) &&
34
36
  typeof value.enabled === 'boolean' &&
35
37
  isNonEmptyString(value.bundleIdentifier) &&
38
+ isOptionalScheme(value.scheme) &&
36
39
  isOptionalProviders(value.providers));
37
40
  }
41
+ function isOptionalScheme(value) {
42
+ return value === undefined || (typeof value === 'string' && URI_SCHEME_PATTERN.test(value));
43
+ }
38
44
  function isOptionalProviders(value) {
39
45
  return value === undefined || isProviderSelection(value);
40
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/appManifest/deploy.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AppDeployAndroidTargetConfig,\n AppDeployIosTargetConfig,\n AppDeployManifest,\n AppDeployProviderSelection,\n AppDeployTargets,\n AppDeployWebTargetConfig,\n} from '../deploy';\nimport { isRecord } from './shared';\n\nconst DEPLOY_KEYS = new Set(['targets']);\nconst TARGET_KEYS = new Set(['web', 'android', 'ios']);\nconst PROVIDER_KEYS = new Set(['build', 'publish']);\nconst WEB_KEYS = new Set(['enabled', 'providers']);\nconst ANDROID_KEYS = new Set(['enabled', 'package', 'providers']);\nconst IOS_KEYS = new Set(['enabled', 'bundleIdentifier', 'providers']);\n\nexport function isAppDeployManifest(value: unknown): value is AppDeployManifest {\n return isRecord(value) && hasOnlyKeys(value, DEPLOY_KEYS) && isAppDeployTargets(value.targets);\n}\n\nfunction isAppDeployTargets(value: unknown): value is AppDeployTargets {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, TARGET_KEYS) &&\n (value.web === undefined || isWebTarget(value.web)) &&\n (value.android === undefined || isAndroidTarget(value.android)) &&\n (value.ios === undefined || isIosTarget(value.ios))\n );\n}\n\nfunction isWebTarget(value: unknown): value is AppDeployWebTargetConfig {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, WEB_KEYS) &&\n typeof value.enabled === 'boolean' &&\n isOptionalProviders(value.providers)\n );\n}\n\nfunction isAndroidTarget(value: unknown): value is AppDeployAndroidTargetConfig {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, ANDROID_KEYS) &&\n typeof value.enabled === 'boolean' &&\n isNonEmptyString(value.package) &&\n isOptionalProviders(value.providers)\n );\n}\n\nfunction isIosTarget(value: unknown): value is AppDeployIosTargetConfig {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, IOS_KEYS) &&\n typeof value.enabled === 'boolean' &&\n isNonEmptyString(value.bundleIdentifier) &&\n isOptionalProviders(value.providers)\n );\n}\n\nfunction isOptionalProviders(value: unknown): value is AppDeployProviderSelection | undefined {\n return value === undefined || isProviderSelection(value);\n}\n\nfunction isProviderSelection(value: unknown): value is AppDeployProviderSelection {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, PROVIDER_KEYS) &&\n (value.build === undefined || isNonEmptyString(value.build)) &&\n (value.publish === undefined || isNonEmptyString(value.publish))\n );\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, allowed: ReadonlySet<string>): boolean {\n return Object.keys(value).every((key) => allowed.has(key));\n}\n"]}
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/appManifest/deploy.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AppDeployAndroidTargetConfig,\n AppDeployIosTargetConfig,\n AppDeployManifest,\n AppDeployProviderSelection,\n AppDeployTargets,\n AppDeployWebTargetConfig,\n} from '../deploy';\nimport { isRecord } from './shared';\n\nconst DEPLOY_KEYS = new Set(['targets']);\nconst TARGET_KEYS = new Set(['web', 'android', 'ios']);\nconst PROVIDER_KEYS = new Set(['build', 'publish']);\nconst WEB_KEYS = new Set(['enabled', 'providers']);\nconst ANDROID_KEYS = new Set(['enabled', 'package', 'scheme', 'providers']);\nconst IOS_KEYS = new Set(['enabled', 'bundleIdentifier', 'scheme', 'providers']);\nconst URI_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*$/u;\n\nexport function isAppDeployManifest(value: unknown): value is AppDeployManifest {\n return isRecord(value) && hasOnlyKeys(value, DEPLOY_KEYS) && isAppDeployTargets(value.targets);\n}\n\nfunction isAppDeployTargets(value: unknown): value is AppDeployTargets {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, TARGET_KEYS) &&\n (value.web === undefined || isWebTarget(value.web)) &&\n (value.android === undefined || isAndroidTarget(value.android)) &&\n (value.ios === undefined || isIosTarget(value.ios))\n );\n}\n\nfunction isWebTarget(value: unknown): value is AppDeployWebTargetConfig {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, WEB_KEYS) &&\n typeof value.enabled === 'boolean' &&\n isOptionalProviders(value.providers)\n );\n}\n\nfunction isAndroidTarget(value: unknown): value is AppDeployAndroidTargetConfig {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, ANDROID_KEYS) &&\n typeof value.enabled === 'boolean' &&\n isNonEmptyString(value.package) &&\n isOptionalScheme(value.scheme) &&\n isOptionalProviders(value.providers)\n );\n}\n\nfunction isIosTarget(value: unknown): value is AppDeployIosTargetConfig {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, IOS_KEYS) &&\n typeof value.enabled === 'boolean' &&\n isNonEmptyString(value.bundleIdentifier) &&\n isOptionalScheme(value.scheme) &&\n isOptionalProviders(value.providers)\n );\n}\n\nfunction isOptionalScheme(value: unknown): value is string | undefined {\n return value === undefined || (typeof value === 'string' && URI_SCHEME_PATTERN.test(value));\n}\n\nfunction isOptionalProviders(value: unknown): value is AppDeployProviderSelection | undefined {\n return value === undefined || isProviderSelection(value);\n}\n\nfunction isProviderSelection(value: unknown): value is AppDeployProviderSelection {\n return (\n isRecord(value) &&\n hasOnlyKeys(value, PROVIDER_KEYS) &&\n (value.build === undefined || isNonEmptyString(value.build)) &&\n (value.publish === undefined || isNonEmptyString(value.publish))\n );\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim().length > 0;\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, allowed: ReadonlySet<string>): boolean {\n return Object.keys(value).every((key) => allowed.has(key));\n}\n"]}
package/dist/deploy.d.ts CHANGED
@@ -1,5 +1,14 @@
1
1
  export declare const APP_DEPLOY_TARGET_IDS: readonly ["web", "android", "ios"];
2
2
  export type AppDeployTargetId = (typeof APP_DEPLOY_TARGET_IDS)[number];
3
+ /**
4
+ * Logical application environments shared across platform-specific planning.
5
+ *
6
+ * `local` maps to local Web, iOS development, and Android debug execution.
7
+ * Environment-specific endpoints, signing material, and credentials are owned
8
+ * by their dedicated deployment/infra configuration rather than this identity contract.
9
+ */
10
+ export declare const APP_DEPLOY_ENVIRONMENT_IDS: readonly ["local", "preview", "production"];
11
+ export type AppDeployEnvironmentId = (typeof APP_DEPLOY_ENVIRONMENT_IDS)[number];
3
12
  export interface AppDeployProviderSelection {
4
13
  readonly build?: string;
5
14
  readonly publish?: string;
@@ -11,11 +20,15 @@ export interface AppDeployWebTargetConfig {
11
20
  export interface AppDeployAndroidTargetConfig {
12
21
  readonly enabled: boolean;
13
22
  readonly package: string;
23
+ /** Stable application URI scheme used for target-aware deep links such as OAuth callbacks. */
24
+ readonly scheme?: string;
14
25
  readonly providers?: AppDeployProviderSelection;
15
26
  }
16
27
  export interface AppDeployIosTargetConfig {
17
28
  readonly enabled: boolean;
18
29
  readonly bundleIdentifier: string;
30
+ /** Stable application URI scheme used for target-aware deep links such as OAuth callbacks. */
31
+ readonly scheme?: string;
19
32
  readonly providers?: AppDeployProviderSelection;
20
33
  }
21
34
  export interface AppDeployTargets {
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../src/deploy.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,oCAAqC,CAAC;AAExE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,4BAA4B,CAAC;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,wBAAwB,CAAC;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;CACpC;AAED,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../src/deploy.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,oCAAqC,CAAC;AAExE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvE;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,6CAA8C,CAAC;AAEtF,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,OAAO,CAAC,EAAE,4BAA4B,CAAC;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,wBAAwB,CAAC;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;CACpC;AAED,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/deploy.js CHANGED
@@ -1,3 +1,11 @@
1
1
  export const APP_DEPLOY_TARGET_IDS = ['web', 'android', 'ios'];
2
+ /**
3
+ * Logical application environments shared across platform-specific planning.
4
+ *
5
+ * `local` maps to local Web, iOS development, and Android debug execution.
6
+ * Environment-specific endpoints, signing material, and credentials are owned
7
+ * by their dedicated deployment/infra configuration rather than this identity contract.
8
+ */
9
+ export const APP_DEPLOY_ENVIRONMENT_IDS = ['local', 'preview', 'production'];
2
10
  export { isAppDeployManifest } from './appManifest/deploy';
3
11
  //# sourceMappingURL=deploy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../src/deploy.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAU,CAAC;AAoCxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["export const APP_DEPLOY_TARGET_IDS = ['web', 'android', 'ios'] as const;\n\nexport type AppDeployTargetId = (typeof APP_DEPLOY_TARGET_IDS)[number];\n\nexport interface AppDeployProviderSelection {\n readonly build?: string;\n readonly publish?: string;\n}\n\nexport interface AppDeployWebTargetConfig {\n readonly enabled: boolean;\n readonly providers?: AppDeployProviderSelection;\n}\n\nexport interface AppDeployAndroidTargetConfig {\n readonly enabled: boolean;\n readonly package: string;\n readonly providers?: AppDeployProviderSelection;\n}\n\nexport interface AppDeployIosTargetConfig {\n readonly enabled: boolean;\n readonly bundleIdentifier: string;\n readonly providers?: AppDeployProviderSelection;\n}\n\nexport interface AppDeployTargets {\n readonly web?: AppDeployWebTargetConfig;\n readonly android?: AppDeployAndroidTargetConfig;\n readonly ios?: AppDeployIosTargetConfig;\n}\n\nexport interface AppDeployManifest {\n readonly targets: AppDeployTargets;\n}\n\nexport { isAppDeployManifest } from './appManifest/deploy';\n"]}
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../src/deploy.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAU,CAAC;AAIxE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,CAAU,CAAC;AAwCtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["export const APP_DEPLOY_TARGET_IDS = ['web', 'android', 'ios'] as const;\n\nexport type AppDeployTargetId = (typeof APP_DEPLOY_TARGET_IDS)[number];\n\n/**\n * Logical application environments shared across platform-specific planning.\n *\n * `local` maps to local Web, iOS development, and Android debug execution.\n * Environment-specific endpoints, signing material, and credentials are owned\n * by their dedicated deployment/infra configuration rather than this identity contract.\n */\nexport const APP_DEPLOY_ENVIRONMENT_IDS = ['local', 'preview', 'production'] as const;\n\nexport type AppDeployEnvironmentId = (typeof APP_DEPLOY_ENVIRONMENT_IDS)[number];\n\nexport interface AppDeployProviderSelection {\n readonly build?: string;\n readonly publish?: string;\n}\n\nexport interface AppDeployWebTargetConfig {\n readonly enabled: boolean;\n readonly providers?: AppDeployProviderSelection;\n}\n\nexport interface AppDeployAndroidTargetConfig {\n readonly enabled: boolean;\n readonly package: string;\n /** Stable application URI scheme used for target-aware deep links such as OAuth callbacks. */\n readonly scheme?: string;\n readonly providers?: AppDeployProviderSelection;\n}\n\nexport interface AppDeployIosTargetConfig {\n readonly enabled: boolean;\n readonly bundleIdentifier: string;\n /** Stable application URI scheme used for target-aware deep links such as OAuth callbacks. */\n readonly scheme?: string;\n readonly providers?: AppDeployProviderSelection;\n}\n\nexport interface AppDeployTargets {\n readonly web?: AppDeployWebTargetConfig;\n readonly android?: AppDeployAndroidTargetConfig;\n readonly ios?: AppDeployIosTargetConfig;\n}\n\nexport interface AppDeployManifest {\n readonly targets: AppDeployTargets;\n}\n\nexport { isAppDeployManifest } from './appManifest/deploy';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/contracts",
3
- "version": "7.6.0",
3
+ "version": "7.7.0",
4
4
  "main": "./dist/index.js",
5
5
  "ankh": {
6
6
  "category": "contracts",
@@ -12,8 +12,9 @@ const DEPLOY_KEYS = new Set(['targets']);
12
12
  const TARGET_KEYS = new Set(['web', 'android', 'ios']);
13
13
  const PROVIDER_KEYS = new Set(['build', 'publish']);
14
14
  const WEB_KEYS = new Set(['enabled', 'providers']);
15
- const ANDROID_KEYS = new Set(['enabled', 'package', 'providers']);
16
- const IOS_KEYS = new Set(['enabled', 'bundleIdentifier', 'providers']);
15
+ const ANDROID_KEYS = new Set(['enabled', 'package', 'scheme', 'providers']);
16
+ const IOS_KEYS = new Set(['enabled', 'bundleIdentifier', 'scheme', 'providers']);
17
+ const URI_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
17
18
 
18
19
  export function isAppDeployManifest(value: unknown): value is AppDeployManifest {
19
20
  return isRecord(value) && hasOnlyKeys(value, DEPLOY_KEYS) && isAppDeployTargets(value.targets);
@@ -44,6 +45,7 @@ function isAndroidTarget(value: unknown): value is AppDeployAndroidTargetConfig
44
45
  hasOnlyKeys(value, ANDROID_KEYS) &&
45
46
  typeof value.enabled === 'boolean' &&
46
47
  isNonEmptyString(value.package) &&
48
+ isOptionalScheme(value.scheme) &&
47
49
  isOptionalProviders(value.providers)
48
50
  );
49
51
  }
@@ -54,10 +56,15 @@ function isIosTarget(value: unknown): value is AppDeployIosTargetConfig {
54
56
  hasOnlyKeys(value, IOS_KEYS) &&
55
57
  typeof value.enabled === 'boolean' &&
56
58
  isNonEmptyString(value.bundleIdentifier) &&
59
+ isOptionalScheme(value.scheme) &&
57
60
  isOptionalProviders(value.providers)
58
61
  );
59
62
  }
60
63
 
64
+ function isOptionalScheme(value: unknown): value is string | undefined {
65
+ return value === undefined || (typeof value === 'string' && URI_SCHEME_PATTERN.test(value));
66
+ }
67
+
61
68
  function isOptionalProviders(value: unknown): value is AppDeployProviderSelection | undefined {
62
69
  return value === undefined || isProviderSelection(value);
63
70
  }
@@ -4,7 +4,7 @@ import { join } from 'node:path';
4
4
  import { describe, expect, it } from 'bun:test';
5
5
 
6
6
  import { isAppManifest } from './appManifest';
7
- import { APP_DEPLOY_TARGET_IDS, isAppDeployManifest } from './deploy';
7
+ import { APP_DEPLOY_ENVIRONMENT_IDS, APP_DEPLOY_TARGET_IDS, isAppDeployManifest } from './deploy';
8
8
  import { DEPLOYMENT_TARGETS } from './types';
9
9
 
10
10
  function createManifest(deploy?: unknown): Record<string, unknown> {
@@ -39,18 +39,24 @@ describe('app deployment contracts', () => {
39
39
  expect(DEPLOYMENT_TARGETS).toEqual(['minikube']);
40
40
  });
41
41
 
42
- it('accepts canonical web, Android, and iOS desired state', () => {
42
+ it('defines one logical environment vocabulary across platform-specific execution', () => {
43
+ expect(APP_DEPLOY_ENVIRONMENT_IDS).toEqual(['local', 'preview', 'production']);
44
+ });
45
+
46
+ it('accepts canonical web, Android, and iOS desired state with stable native schemes', () => {
43
47
  const deploy = {
44
48
  targets: {
45
49
  web: { enabled: true, providers: { publish: 'eas' } },
46
50
  android: {
47
51
  enabled: true,
48
52
  package: 'com.example.app',
53
+ scheme: 'example-app',
49
54
  providers: { build: 'eas', publish: 'google-play' },
50
55
  },
51
56
  ios: {
52
57
  enabled: false,
53
58
  bundleIdentifier: 'com.example.app',
59
+ scheme: 'example-app',
54
60
  providers: { build: 'eas', publish: 'app-store-connect' },
55
61
  },
56
62
  },
@@ -60,6 +66,40 @@ describe('app deployment contracts', () => {
60
66
  expect(isAppManifest(createManifest(deploy))).toBe(true);
61
67
  });
62
68
 
69
+ it('keeps native schemes optional for existing deployment manifests', () => {
70
+ expect(
71
+ isAppDeployManifest({
72
+ targets: {
73
+ android: { enabled: true, package: 'com.example.app' },
74
+ ios: { enabled: true, bundleIdentifier: 'com.example.app' },
75
+ },
76
+ }),
77
+ ).toBe(true);
78
+ });
79
+
80
+ it('accepts every supported platform combination', () => {
81
+ const web = { enabled: true };
82
+ const android = { enabled: true, package: 'com.example.app', scheme: 'example-app' };
83
+ const ios = {
84
+ enabled: true,
85
+ bundleIdentifier: 'com.example.app',
86
+ scheme: 'example-app',
87
+ };
88
+ const combinations = [
89
+ { web },
90
+ { android },
91
+ { ios },
92
+ { web, android },
93
+ { web, ios },
94
+ { android, ios },
95
+ { web, android, ios },
96
+ ];
97
+
98
+ for (const targets of combinations) {
99
+ expect(isAppDeployManifest({ targets })).toBe(true);
100
+ }
101
+ });
102
+
63
103
  it('keeps deployment optional and accepts an empty target registry structurally', () => {
64
104
  expect(isAppManifest(createManifest())).toBe(true);
65
105
  expect(isAppDeployManifest({ targets: {} })).toBe(true);
@@ -86,7 +126,14 @@ describe('app deployment contracts', () => {
86
126
  { targets: { desktop: { enabled: true } } },
87
127
  { targets: { web: { enabled: 'yes' } } },
88
128
  { targets: { android: { enabled: true, package: '' } } },
129
+ { targets: { android: { enabled: true, package: 'com.example.app', scheme: '' } } },
130
+ { targets: { android: { enabled: true, package: 'com.example.app', scheme: '9app' } } },
89
131
  { targets: { ios: { enabled: true, bundleIdentifier: ' ' } } },
132
+ {
133
+ targets: {
134
+ ios: { enabled: true, bundleIdentifier: 'com.example.app', scheme: 'bad scheme' },
135
+ },
136
+ },
90
137
  { targets: { web: { enabled: true, providers: { publish: '' } } } },
91
138
  { targets: { web: { enabled: true, providerConfig: { region: 'eu' } } } },
92
139
  { targets: { web: { enabled: true, credentials: { token: 'secret' } } } },
@@ -102,7 +149,13 @@ describe('app deployment contracts', () => {
102
149
  expect(
103
150
  isAppManifest(
104
151
  createManifest({
105
- targets: { android: { enabled: true, package: 'com.example.app', credentials: {} } },
152
+ targets: {
153
+ android: {
154
+ enabled: true,
155
+ package: 'com.example.app',
156
+ credentials: {},
157
+ },
158
+ },
106
159
  }),
107
160
  ),
108
161
  ).toBe(false);
package/src/deploy.ts CHANGED
@@ -2,6 +2,17 @@ export const APP_DEPLOY_TARGET_IDS = ['web', 'android', 'ios'] as const;
2
2
 
3
3
  export type AppDeployTargetId = (typeof APP_DEPLOY_TARGET_IDS)[number];
4
4
 
5
+ /**
6
+ * Logical application environments shared across platform-specific planning.
7
+ *
8
+ * `local` maps to local Web, iOS development, and Android debug execution.
9
+ * Environment-specific endpoints, signing material, and credentials are owned
10
+ * by their dedicated deployment/infra configuration rather than this identity contract.
11
+ */
12
+ export const APP_DEPLOY_ENVIRONMENT_IDS = ['local', 'preview', 'production'] as const;
13
+
14
+ export type AppDeployEnvironmentId = (typeof APP_DEPLOY_ENVIRONMENT_IDS)[number];
15
+
5
16
  export interface AppDeployProviderSelection {
6
17
  readonly build?: string;
7
18
  readonly publish?: string;
@@ -15,12 +26,16 @@ export interface AppDeployWebTargetConfig {
15
26
  export interface AppDeployAndroidTargetConfig {
16
27
  readonly enabled: boolean;
17
28
  readonly package: string;
29
+ /** Stable application URI scheme used for target-aware deep links such as OAuth callbacks. */
30
+ readonly scheme?: string;
18
31
  readonly providers?: AppDeployProviderSelection;
19
32
  }
20
33
 
21
34
  export interface AppDeployIosTargetConfig {
22
35
  readonly enabled: boolean;
23
36
  readonly bundleIdentifier: string;
37
+ /** Stable application URI scheme used for target-aware deep links such as OAuth callbacks. */
38
+ readonly scheme?: string;
24
39
  readonly providers?: AppDeployProviderSelection;
25
40
  }
26
41