@better-update/cli 0.3.0 → 0.3.2

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.
Files changed (152) hide show
  1. package/dist/index.js +5319 -0
  2. package/dist/index.js.map +1 -0
  3. package/package.json +12 -9
  4. package/CHANGELOG.md +0 -58
  5. package/oxlint.config.ts +0 -6
  6. package/src/app-layer.ts +0 -29
  7. package/src/application/build-workflow.ts +0 -222
  8. package/src/application/command-exit.ts +0 -13
  9. package/src/application/login.ts +0 -87
  10. package/src/application/update-promote.ts +0 -88
  11. package/src/application/update-publish.ts +0 -402
  12. package/src/application/update-rollback.ts +0 -275
  13. package/src/commands/analytics/adoption.ts +0 -40
  14. package/src/commands/analytics/channels.ts +0 -35
  15. package/src/commands/analytics/helpers.ts +0 -3
  16. package/src/commands/analytics/index.ts +0 -13
  17. package/src/commands/analytics/platforms.ts +0 -39
  18. package/src/commands/analytics/updates.ts +0 -35
  19. package/src/commands/audit-logs/helpers.ts +0 -3
  20. package/src/commands/audit-logs/index.ts +0 -8
  21. package/src/commands/audit-logs/list.ts +0 -66
  22. package/src/commands/branches.ts +0 -70
  23. package/src/commands/build/android.ts +0 -129
  24. package/src/commands/build/index.ts +0 -63
  25. package/src/commands/build/ios.ts +0 -199
  26. package/src/commands/build/reserve-and-upload.test.ts +0 -263
  27. package/src/commands/build/reserve-and-upload.ts +0 -160
  28. package/src/commands/build/run-step.ts +0 -131
  29. package/src/commands/builds/compatibility-matrix.ts +0 -48
  30. package/src/commands/builds/delete.ts +0 -15
  31. package/src/commands/builds/get.ts +0 -34
  32. package/src/commands/builds/helpers.ts +0 -3
  33. package/src/commands/builds/index.ts +0 -20
  34. package/src/commands/builds/install-link.ts +0 -20
  35. package/src/commands/builds/list.ts +0 -38
  36. package/src/commands/channels/create.ts +0 -37
  37. package/src/commands/channels/delete.ts +0 -15
  38. package/src/commands/channels/helpers.ts +0 -18
  39. package/src/commands/channels/index.ts +0 -24
  40. package/src/commands/channels/list.ts +0 -38
  41. package/src/commands/channels/pause.ts +0 -15
  42. package/src/commands/channels/resume.ts +0 -15
  43. package/src/commands/channels/rollout/complete.ts +0 -17
  44. package/src/commands/channels/rollout/create.ts +0 -36
  45. package/src/commands/channels/rollout/index.ts +0 -11
  46. package/src/commands/channels/rollout/revert.ts +0 -17
  47. package/src/commands/channels/rollout/update.ts +0 -23
  48. package/src/commands/channels/update.ts +0 -32
  49. package/src/commands/credentials/delete.ts +0 -24
  50. package/src/commands/credentials/index.ts +0 -10
  51. package/src/commands/credentials/list.ts +0 -33
  52. package/src/commands/credentials/upload.ts +0 -91
  53. package/src/commands/env/delete.ts +0 -35
  54. package/src/commands/env/export.ts +0 -27
  55. package/src/commands/env/get.ts +0 -25
  56. package/src/commands/env/helpers.ts +0 -13
  57. package/src/commands/env/import.ts +0 -31
  58. package/src/commands/env/index.ts +0 -24
  59. package/src/commands/env/list.ts +0 -44
  60. package/src/commands/env/pull.ts +0 -27
  61. package/src/commands/env/set.ts +0 -42
  62. package/src/commands/fingerprint/compare.ts +0 -25
  63. package/src/commands/fingerprint/generate.ts +0 -18
  64. package/src/commands/fingerprint/index.ts +0 -9
  65. package/src/commands/init.ts +0 -35
  66. package/src/commands/login.ts +0 -13
  67. package/src/commands/logout.ts +0 -12
  68. package/src/commands/projects.ts +0 -84
  69. package/src/commands/status.ts +0 -48
  70. package/src/commands/update/delete.ts +0 -15
  71. package/src/commands/update/helpers.ts +0 -22
  72. package/src/commands/update/index.ts +0 -22
  73. package/src/commands/update/list.ts +0 -60
  74. package/src/commands/update/promote.ts +0 -30
  75. package/src/commands/update/publish.ts +0 -94
  76. package/src/commands/update/rollback.ts +0 -42
  77. package/src/commands/update/rollout/complete.ts +0 -17
  78. package/src/commands/update/rollout/index.ts +0 -10
  79. package/src/commands/update/rollout/revert.ts +0 -17
  80. package/src/commands/update/rollout/set.ts +0 -23
  81. package/src/index.ts +0 -53
  82. package/src/lib/android-keystore.test.ts +0 -114
  83. package/src/lib/android-keystore.ts +0 -76
  84. package/src/lib/android-signing-gradle.test.ts +0 -95
  85. package/src/lib/android-signing-gradle.ts +0 -52
  86. package/src/lib/app-json.ts +0 -81
  87. package/src/lib/apple-auth.test.ts +0 -402
  88. package/src/lib/apple-auth.ts +0 -132
  89. package/src/lib/artifact-finder.test.ts +0 -195
  90. package/src/lib/artifact-finder.ts +0 -122
  91. package/src/lib/browser-login.test.ts +0 -88
  92. package/src/lib/browser-login.ts +0 -193
  93. package/src/lib/build-profile.test.ts +0 -290
  94. package/src/lib/build-profile.ts +0 -234
  95. package/src/lib/cli-schemas.ts +0 -39
  96. package/src/lib/command-errors.ts +0 -60
  97. package/src/lib/credentials-downloader.ts +0 -181
  98. package/src/lib/credentials-manager.ts +0 -354
  99. package/src/lib/env-exporter.test.ts +0 -96
  100. package/src/lib/env-exporter.ts +0 -28
  101. package/src/lib/exit-codes.ts +0 -82
  102. package/src/lib/expo-config.ts +0 -130
  103. package/src/lib/expo-export.test.ts +0 -94
  104. package/src/lib/expo-export.ts +0 -281
  105. package/src/lib/fingerprint.ts +0 -67
  106. package/src/lib/format-error.ts +0 -22
  107. package/src/lib/git-context.ts +0 -56
  108. package/src/lib/gradle-config.ts +0 -126
  109. package/src/lib/ios-export-options.test.ts +0 -98
  110. package/src/lib/ios-export-options.ts +0 -62
  111. package/src/lib/ios-keychain.ts +0 -181
  112. package/src/lib/ios-provisioning.test.ts +0 -115
  113. package/src/lib/ios-provisioning.ts +0 -179
  114. package/src/lib/output.ts +0 -32
  115. package/src/lib/pkcs12.ts +0 -73
  116. package/src/lib/plist.ts +0 -39
  117. package/src/lib/post-build-validation.ts +0 -146
  118. package/src/lib/presigned-upload.test.ts +0 -140
  119. package/src/lib/presigned-upload.ts +0 -35
  120. package/src/lib/record.ts +0 -5
  121. package/src/lib/resolve-named-resource.ts +0 -24
  122. package/src/lib/runtime-version.test.ts +0 -119
  123. package/src/lib/runtime-version.ts +0 -62
  124. package/src/lib/sha256.test.ts +0 -108
  125. package/src/lib/sha256.ts +0 -80
  126. package/src/lib/signed-payloads.test.ts +0 -181
  127. package/src/lib/signed-payloads.ts +0 -164
  128. package/src/lib/string-utils.ts +0 -4
  129. package/src/lib/temp-dir.ts +0 -14
  130. package/src/lib/test-utils.ts +0 -13
  131. package/src/lib/update-platforms.test.ts +0 -45
  132. package/src/lib/update-platforms.ts +0 -19
  133. package/src/lib/xcpretty-formatter.ts +0 -21
  134. package/src/services/api-client.ts +0 -42
  135. package/src/services/apple-session-store.ts +0 -100
  136. package/src/services/auth-store.ts +0 -85
  137. package/src/services/cli-runtime.ts +0 -46
  138. package/src/services/config-store.ts +0 -108
  139. package/src/services/presigned-upload.ts +0 -84
  140. package/src/services/update-asset-uploader.ts +0 -72
  141. package/src/types/keychain.d.ts +0 -22
  142. package/tests/e2e/build.test.ts +0 -270
  143. package/tests/e2e/commands.test.ts +0 -694
  144. package/tests/e2e/ota-lifecycle.test.ts +0 -275
  145. package/tests/e2e/publish.test.ts +0 -150
  146. package/tests/helpers/cli-e2e.ts +0 -426
  147. package/tests/helpers/pty-driver.ts +0 -142
  148. package/tests/interactive/harness/provider-prompt.ts +0 -54
  149. package/tests/interactive/login.test.ts +0 -47
  150. package/tests/interactive/provider-select.test.ts +0 -59
  151. package/tsconfig.json +0 -7
  152. package/vitest.config.ts +0 -38
package/package.json CHANGED
@@ -1,24 +1,24 @@
1
1
  {
2
2
  "name": "@better-update/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "bin": {
5
- "better-update": "./src/index.ts"
5
+ "better-update": "./dist/index.js"
6
6
  },
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "type": "module",
11
+ "main": "./dist/index.js",
8
12
  "exports": {
9
- ".": "./src/index.ts"
13
+ ".": "./dist/index.js"
10
14
  },
11
15
  "publishConfig": {
12
16
  "access": "public"
13
17
  },
14
18
  "dependencies": {
15
- "@better-update/api": "0.1.0",
16
- "@better-update/encoding": "0.0.1",
17
- "@better-update/expo-protocol": "0.0.1",
18
- "@better-update/safe-json": "0.0.1",
19
19
  "@effect/cli": "^0.75.1",
20
20
  "@effect/platform": "^0.96.0",
21
- "@effect/platform-bun": "^0.89.0",
21
+ "@effect/platform-node": "^0.106.0",
22
22
  "@expo/apple-utils": "^2.1.21",
23
23
  "@expo/config": "^55.0.15",
24
24
  "@expo/pkcs12": "^0.4.2",
@@ -32,5 +32,8 @@
32
32
  "optionalDependencies": {
33
33
  "keychain": "^1.5.0"
34
34
  },
35
- "gitHead": "64a0b8c3b55a6d8aecbfa2d3bee2716695936d03"
35
+ "engines": {
36
+ "node": ">=22"
37
+ },
38
+ "gitHead": "aa2a611f489b0e8790768012d935ba0139e391f5"
36
39
  }
package/CHANGELOG.md DELETED
@@ -1,58 +0,0 @@
1
- # Change Log
2
-
3
- All notable changes to this project will be documented in this file.
4
- See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
-
6
- ## 0.3.0 (2026-04-21)
7
-
8
- ### Features
9
-
10
- * add CLI build command for local native iOS/Android builds ([97a3e73](https://github.com/better-update/better-update/commit/97a3e73501f120118e44087f2359696c5a913c9d)) - by @
11
- * add CLI package for project, credential, and env management ([07c0ff7](https://github.com/better-update/better-update/commit/07c0ff75d55f88bae94e4760625013b070896f36)) - by @
12
- * add CLI update publish command ([5f4268a](https://github.com/better-update/better-update/commit/5f4268a90bacb8a231bbc98ca92bd70452bcf02c)) - by @
13
- * **cli,server,dashboard:** close EAS Update feature gaps ([#4](https://github.com/better-update/better-update/issues/4), [#6](https://github.com/better-update/better-update/issues/6), [#7](https://github.com/better-update/better-update/issues/7), [#2](https://github.com/better-update/better-update/issues/2)) ([ecdc224](https://github.com/better-update/better-update/commit/ecdc22423f4117ab27ddac15e175329bf50c1031)) - by @
14
- * **cli,server:** integrate EAS CLI libraries, Apple auto-provisioning, and content-type-namespaced asset hashing ([ff3d881](https://github.com/better-update/better-update/commit/ff3d8812a27387e2331cc9e01741f4c01195e31a)) - by @
15
- * **cli:** add --rollout-percentage to update publish + schema-validated args ([e656e75](https://github.com/better-update/better-update/commit/e656e75685e4fe8f872d7495325f1bd8d6534cc8)) - by @
16
- * **cli:** add Android keystore certificate expiry validation ([78f3e46](https://github.com/better-update/better-update/commit/78f3e460e394632a0a39a6e3b5d7779b2dcd174d)) - by @
17
- * **cli:** add Apple App Store Connect provider selection ([0cd1894](https://github.com/better-update/better-update/commit/0cd189454eeed653826867411102b993d1fc1c9f)) - by @
18
- * **cli:** add full CLI coverage for all API groups ([9e05b10](https://github.com/better-update/better-update/commit/9e05b10152d27c9617288a0c182678aaec9d4864)) - by @
19
- * **repo:** add rollback-to-embedded flows ([f5fe1c4](https://github.com/better-update/better-update/commit/f5fe1c439ad7424dc671d84a7b81721e9b6976ad)) - by @
20
- * **repo:** add update lifecycle and credential flows ([f55cf5d](https://github.com/better-update/better-update/commit/f55cf5dea7874540fef6c9620ab8ced7c78e872f)) - by @
21
- * **repo:** expand build management coverage and dashboard details ([96d8d11](https://github.com/better-update/better-update/commit/96d8d11c41a171d67a3dad82d936c924a3e614c7)) - by @
22
- * **repo:** switch uploads to direct r2 flow ([736e7f1](https://github.com/better-update/better-update/commit/736e7f17d887a773b35038119981b31195b4f510)) - by @
23
- * **server,cli:** add build-credentials resolver with roster-hash gated profile regen ([e3a42f8](https://github.com/better-update/better-update/commit/e3a42f86eb7dfd6ec20edeafae6e1aae9c5f76ad)) - by @
24
-
25
- ### Bug Fixes
26
-
27
- * **cli:** defer api client auth and repair e2e harness ([77de9db](https://github.com/better-update/better-update/commit/77de9db2a93facbfbdb859fe65b5307d1f56a29e)) - by @
28
- * **dashboard:** remove build upload UI and restrict uploads to CLI only ([278b011](https://github.com/better-update/better-update/commit/278b0117945a6b766171df0a74d1443b0710ab10)) - by @
29
- * **repo:** add knip dead code detection, fix timing-safe HMAC verification ([ef3e3b6](https://github.com/better-update/better-update/commit/ef3e3b6c6d936e85346ed2b0f53941e92cc8d226)) - by @
30
- * **repo:** harden update publish and republish flows ([5b2a7b7](https://github.com/better-update/better-update/commit/5b2a7b7dc164baf29bbe54db83174f3a1f09b503)) - by @
31
- * **repo:** stream update artifacts and enable signed promote flows ([4ed09f1](https://github.com/better-update/better-update/commit/4ed09f15909e44a68e597a28d00c7b0825448211)) - by @
32
-
33
- ## 0.2.0 (2026-04-21)
34
-
35
- ### Features
36
-
37
- * add CLI build command for local native iOS/Android builds ([97a3e73](https://github.com/better-update/better-update/commit/97a3e73501f120118e44087f2359696c5a913c9d)) - by @trancong12102
38
- * add CLI package for project, credential, and env management ([07c0ff7](https://github.com/better-update/better-update/commit/07c0ff75d55f88bae94e4760625013b070896f36)) - by @trancong12102
39
- * add CLI update publish command ([5f4268a](https://github.com/better-update/better-update/commit/5f4268a90bacb8a231bbc98ca92bd70452bcf02c)) - by @trancong12102
40
- * **cli,server,dashboard:** close EAS Update feature gaps ([#4](https://github.com/better-update/better-update/issues/4), [#6](https://github.com/better-update/better-update/issues/6), [#7](https://github.com/better-update/better-update/issues/7), [#2](https://github.com/better-update/better-update/issues/2)) ([ecdc224](https://github.com/better-update/better-update/commit/ecdc22423f4117ab27ddac15e175329bf50c1031)) - by @trancong12102
41
- * **cli,server:** integrate EAS CLI libraries, Apple auto-provisioning, and content-type-namespaced asset hashing ([ff3d881](https://github.com/better-update/better-update/commit/ff3d8812a27387e2331cc9e01741f4c01195e31a)) - by @trancong12102
42
- * **cli:** add --rollout-percentage to update publish + schema-validated args ([e656e75](https://github.com/better-update/better-update/commit/e656e75685e4fe8f872d7495325f1bd8d6534cc8)) - by @trancong12102
43
- * **cli:** add Android keystore certificate expiry validation ([78f3e46](https://github.com/better-update/better-update/commit/78f3e460e394632a0a39a6e3b5d7779b2dcd174d)) - by @trancong12102
44
- * **cli:** add Apple App Store Connect provider selection ([0cd1894](https://github.com/better-update/better-update/commit/0cd189454eeed653826867411102b993d1fc1c9f)) - by @trancong12102
45
- * **cli:** add full CLI coverage for all API groups ([9e05b10](https://github.com/better-update/better-update/commit/9e05b10152d27c9617288a0c182678aaec9d4864)) - by @trancong12102
46
- * **repo:** add rollback-to-embedded flows ([f5fe1c4](https://github.com/better-update/better-update/commit/f5fe1c439ad7424dc671d84a7b81721e9b6976ad)) - by @trancong12102
47
- * **repo:** add update lifecycle and credential flows ([f55cf5d](https://github.com/better-update/better-update/commit/f55cf5dea7874540fef6c9620ab8ced7c78e872f)) - by @trancong12102
48
- * **repo:** expand build management coverage and dashboard details ([96d8d11](https://github.com/better-update/better-update/commit/96d8d11c41a171d67a3dad82d936c924a3e614c7)) - by @trancong12102
49
- * **repo:** switch uploads to direct r2 flow ([736e7f1](https://github.com/better-update/better-update/commit/736e7f17d887a773b35038119981b31195b4f510)) - by @trancong12102
50
- * **server,cli:** add build-credentials resolver with roster-hash gated profile regen ([e3a42f8](https://github.com/better-update/better-update/commit/e3a42f86eb7dfd6ec20edeafae6e1aae9c5f76ad)) - by @trancong12102
51
-
52
- ### Bug Fixes
53
-
54
- * **cli:** defer api client auth and repair e2e harness ([77de9db](https://github.com/better-update/better-update/commit/77de9db2a93facbfbdb859fe65b5307d1f56a29e)) - by @trancong12102
55
- * **dashboard:** remove build upload UI and restrict uploads to CLI only ([278b011](https://github.com/better-update/better-update/commit/278b0117945a6b766171df0a74d1443b0710ab10)) - by @trancong12102
56
- * **repo:** add knip dead code detection, fix timing-safe HMAC verification ([ef3e3b6](https://github.com/better-update/better-update/commit/ef3e3b6c6d936e85346ed2b0f53941e92cc8d226)) - by @trancong12102
57
- * **repo:** harden update publish and republish flows ([5b2a7b7](https://github.com/better-update/better-update/commit/5b2a7b7dc164baf29bbe54db83174f3a1f09b503)) - by @trancong12102
58
- * **repo:** stream update artifacts and enable signed promote flows ([4ed09f1](https://github.com/better-update/better-update/commit/4ed09f15909e44a68e597a28d00c7b0825448211)) - by @trancong12102
package/oxlint.config.ts DELETED
@@ -1,6 +0,0 @@
1
- import cli from "@better-update/oxlint-config/cli";
2
- import { defineConfig } from "oxlint";
3
-
4
- export default defineConfig({
5
- extends: [cli],
6
- });
package/src/app-layer.ts DELETED
@@ -1,29 +0,0 @@
1
- import { FetchHttpClient } from "@effect/platform";
2
- import { BunContext } from "@effect/platform-bun";
3
- import { Layer } from "effect";
4
-
5
- import { ApiClientLive } from "./services/api-client";
6
- import { AppleSessionStoreLive } from "./services/apple-session-store";
7
- import { AuthStoreLive } from "./services/auth-store";
8
- import { CliRuntimeLive } from "./services/cli-runtime";
9
- import { ConfigStoreLive } from "./services/config-store";
10
- import { PresignedUploadClientLive } from "./services/presigned-upload";
11
- import { UpdateAssetUploaderLive } from "./services/update-asset-uploader";
12
-
13
- const CliPlatformLayer = Layer.mergeAll(CliRuntimeLive, BunContext.layer, FetchHttpClient.layer);
14
- const CliStoreLayer = Layer.mergeAll(AuthStoreLive, ConfigStoreLive, AppleSessionStoreLive).pipe(
15
- Layer.provide(CliPlatformLayer),
16
- );
17
- const CliAdapterDependencies = Layer.mergeAll(CliPlatformLayer, CliStoreLayer);
18
- const ApiClientLayer = ApiClientLive.pipe(Layer.provide(CliAdapterDependencies));
19
- const PresignedUploadLayer = PresignedUploadClientLive.pipe(Layer.provide(CliPlatformLayer));
20
- const UpdateAssetUploaderLayer = UpdateAssetUploaderLive.pipe(
21
- Layer.provide(Layer.mergeAll(ApiClientLayer, PresignedUploadLayer)),
22
- );
23
-
24
- export const CliLive = Layer.mergeAll(
25
- CliAdapterDependencies,
26
- ApiClientLayer,
27
- PresignedUploadLayer,
28
- UpdateAssetUploaderLayer,
29
- );
@@ -1,222 +0,0 @@
1
- import { Console, Effect } from "effect";
2
-
3
- import { runAndroidBuild } from "../commands/build/android";
4
- import { runIosBuild } from "../commands/build/ios";
5
- import { reserveAndUpload } from "../commands/build/reserve-and-upload";
6
- import { readAppJson, readProjectId } from "../lib/app-json";
7
- import { readAppMeta, readBuildProfile } from "../lib/build-profile";
8
- import { pullEnvVars } from "../lib/env-exporter";
9
- import { BuildProfileError } from "../lib/exit-codes";
10
- import { readAppMetaFromConfig, readExpoConfig } from "../lib/expo-config";
11
- import { readGitContext } from "../lib/git-context";
12
- import { readGradleConfig, warnOnGradleMismatch } from "../lib/gradle-config";
13
- import { printKeyValue } from "../lib/output";
14
- import { resolveRuntimeVersion } from "../lib/runtime-version";
15
- import { acquireBuildTempDir } from "../lib/temp-dir";
16
- import { apiClient } from "../services/api-client";
17
- import { CliRuntime } from "../services/cli-runtime";
18
-
19
- import type { BuildTarget } from "../commands/build/reserve-and-upload";
20
- import type { Platform } from "../lib/build-profile";
21
-
22
- export interface RunBuildWorkflowOptions {
23
- readonly platform: Platform;
24
- readonly profileName: string;
25
- readonly message: string | undefined;
26
- readonly noUpload: boolean;
27
- readonly rawOutput?: boolean;
28
- }
29
-
30
- type AppMeta = Effect.Effect.Success<ReturnType<typeof readAppMeta>>;
31
- type BuildProfile = Effect.Effect.Success<ReturnType<typeof readBuildProfile>>;
32
- type ApiClient = Effect.Effect.Success<typeof apiClient>;
33
-
34
- interface PlatformBuildInput {
35
- readonly api: ApiClient;
36
- readonly options: RunBuildWorkflowOptions;
37
- readonly profile: BuildProfile;
38
- readonly appMeta: AppMeta;
39
- readonly envVars: Record<string, string>;
40
- readonly projectId: string;
41
- readonly projectRoot: string;
42
- readonly tempDir: string;
43
- }
44
-
45
- const runIosPlatformBuild = (input: PlatformBuildInput) =>
46
- Effect.gen(function* () {
47
- const { api, appMeta, envVars, options, profile, projectId, projectRoot, tempDir } = input;
48
- if (!profile.ios) {
49
- return yield* new BuildProfileError({
50
- message: `Profile "${profile.name}" has no ios section.`,
51
- });
52
- }
53
- const iosProfile = profile.ios;
54
- const iosBundleId = appMeta.bundleId;
55
- if (!iosBundleId) {
56
- return yield* new BuildProfileError({
57
- message: "Missing expo.ios.bundleIdentifier in app.json.",
58
- });
59
- }
60
- const build = yield* runIosBuild({
61
- api,
62
- tempDir,
63
- projectRoot,
64
- iosProfile,
65
- bundleId: iosBundleId,
66
- envVars,
67
- projectId,
68
- rawOutput: options.rawOutput,
69
- });
70
- const target: BuildTarget = {
71
- platform: "ios",
72
- distribution: iosProfile.distribution,
73
- artifactFormat: "ipa",
74
- };
75
- return { build, target, bundleId: iosBundleId };
76
- });
77
-
78
- const runAndroidPlatformBuild = (input: PlatformBuildInput) =>
79
- Effect.gen(function* () {
80
- const { api, appMeta, envVars, profile, projectId, projectRoot, tempDir } = input;
81
- if (!profile.android) {
82
- return yield* new BuildProfileError({
83
- message: `Profile "${profile.name}" has no android section.`,
84
- });
85
- }
86
- const androidProfile = profile.android;
87
- const androidBundleId = appMeta.androidPackage;
88
- if (!androidBundleId) {
89
- return yield* new BuildProfileError({
90
- message: "Missing expo.android.package in app.json.",
91
- });
92
- }
93
- // Cross-validate Gradle config against app.json (Groovy only). When
94
- // Gradle resolves a different applicationId, the built APK/AAB is signed
95
- // Under that id — so the Credential resolver must key off the Gradle value.
96
- const androidDir = `${projectRoot}/android`;
97
- const gradleConfig = yield* readGradleConfig(androidDir);
98
- yield* warnOnGradleMismatch(gradleConfig, androidBundleId);
99
- const applicationIdentifier = gradleConfig?.applicationId ?? androidBundleId;
100
- const build = yield* runAndroidBuild({
101
- api,
102
- tempDir,
103
- projectRoot,
104
- androidProfile,
105
- applicationIdentifier,
106
- envVars,
107
- projectId,
108
- });
109
- const target: BuildTarget =
110
- androidProfile.format === "aab"
111
- ? { platform: "android", distribution: "play-store", artifactFormat: "aab" }
112
- : { platform: "android", distribution: "direct", artifactFormat: "apk" };
113
- return { build, target, bundleId: applicationIdentifier };
114
- });
115
-
116
- const runPlatformBuild = (input: PlatformBuildInput) =>
117
- input.options.platform === "ios" ? runIosPlatformBuild(input) : runAndroidPlatformBuild(input);
118
-
119
- export const runBuildWorkflow = (options: RunBuildWorkflowOptions) =>
120
- Effect.scoped(
121
- Effect.gen(function* () {
122
- const api = yield* apiClient;
123
- const runtime = yield* CliRuntime;
124
- const projectRoot = yield* runtime.cwd;
125
-
126
- const appJson = yield* readAppJson;
127
- const projectId = yield* readProjectId;
128
-
129
- const profile = yield* readBuildProfile(appJson, options.profileName);
130
-
131
- // Load env vars BEFORE resolving dynamic config — app.config.js may read process.env
132
- const envVars = yield* pullEnvVars(api, {
133
- projectId,
134
- environment: profile.environment,
135
- });
136
-
137
- // Try @expo/config for dynamic configs (app.config.js/ts), fall back to static app.json.
138
- // EnvVars are applied as a scoped process.env overlay inside readExpoConfig and restored
139
- // After the call so secrets do not leak to child processes spawned later.
140
- const expoConfig = yield* readExpoConfig(projectRoot, envVars);
141
- const appMeta = expoConfig
142
- ? yield* readAppMetaFromConfig(expoConfig, options.platform).pipe(
143
- Effect.tap(() => Console.log("Resolved app config via @expo/config")),
144
- Effect.catchAll(() => readAppMeta(appJson, options.platform)),
145
- )
146
- : yield* readAppMeta(appJson, options.platform);
147
-
148
- const runtimeVersion = yield* resolveRuntimeVersion({
149
- raw: appMeta.rawRuntimeVersion,
150
- appVersion: appMeta.appVersion,
151
- projectRoot,
152
- });
153
-
154
- const tempDir = yield* acquireBuildTempDir;
155
-
156
- yield* Console.log(
157
- `Building ${options.platform} artifact for profile "${profile.name}" (runtimeVersion=${runtimeVersion})`,
158
- );
159
-
160
- const outcome = yield* runPlatformBuild({
161
- api,
162
- options,
163
- profile,
164
- appMeta,
165
- envVars,
166
- projectId,
167
- projectRoot,
168
- tempDir,
169
- });
170
- const { build, target, bundleId } = outcome;
171
-
172
- yield* Console.log(`Artifact produced: ${build.artifactPath}`);
173
-
174
- if (options.noUpload) {
175
- yield* printKeyValue([
176
- ["Artifact", build.artifactPath],
177
- ["SHA-256", build.sha256],
178
- ["Bytes", String(build.byteSize)],
179
- ["Upload", "skipped (--no-upload)"],
180
- ]);
181
- return;
182
- }
183
-
184
- const rawGitContext = yield* readGitContext(projectRoot);
185
- const gitContext: {
186
- readonly ref?: string;
187
- readonly commit?: string;
188
- readonly dirty: boolean;
189
- } = {
190
- ...(rawGitContext.ref === undefined ? {} : { ref: rawGitContext.ref }),
191
- ...(rawGitContext.commit === undefined ? {} : { commit: rawGitContext.commit }),
192
- dirty: rawGitContext.dirty,
193
- };
194
-
195
- const result = yield* reserveAndUpload(api, {
196
- target,
197
- projectId,
198
- profileName: profile.name,
199
- runtimeVersion,
200
- ...(appMeta.appVersion === undefined ? {} : { appVersion: appMeta.appVersion }),
201
- ...(appMeta.buildNumber === undefined ? {} : { buildNumber: appMeta.buildNumber }),
202
- bundleId,
203
- gitContext,
204
- ...(options.message === undefined ? {} : { message: options.message }),
205
- artifactPath: build.artifactPath,
206
- sha256: build.sha256,
207
- byteSize: build.byteSize,
208
- });
209
-
210
- yield* Console.log("");
211
- yield* printKeyValue([
212
- ["Build ID", result.id],
213
- ["Status", result.status],
214
- ["Platform", options.platform],
215
- ["Profile", profile.name],
216
- ["Runtime version", runtimeVersion],
217
- ["Artifact", build.artifactPath],
218
- ["SHA-256", build.sha256],
219
- ["Bytes", String(build.byteSize)],
220
- ]);
221
- }),
222
- );
@@ -1,13 +0,0 @@
1
- import { Console, Effect } from "effect";
2
-
3
- import { CliRuntime } from "../services/cli-runtime";
4
-
5
- export const exitWith = (code: number, message: string): Effect.Effect<void, never, CliRuntime> =>
6
- Console.error(message).pipe(
7
- Effect.zipRight(
8
- Effect.gen(function* () {
9
- const runtime = yield* CliRuntime;
10
- yield* runtime.setExitCode(code);
11
- }),
12
- ),
13
- );
@@ -1,87 +0,0 @@
1
- import { Prompt } from "@effect/cli";
2
- import { Command } from "@effect/platform";
3
- import { Console, Effect, Redacted } from "effect";
4
-
5
- import type { CommandExecutor } from "@effect/platform";
6
-
7
- import { createBrowserLoginServer } from "../lib/browser-login";
8
- import { AuthStore } from "../services/auth-store";
9
- import { CliRuntime } from "../services/cli-runtime";
10
- import { ConfigStore } from "../services/config-store";
11
-
12
- const tokenPrompt = Prompt.password({
13
- message: "Paste your API key (from dashboard > API Keys):",
14
- });
15
-
16
- const buildOpenBrowserCommand = (platform: NodeJS.Platform, url: string) => {
17
- if (platform === "darwin") {
18
- return Command.make("open", url);
19
- }
20
- if (platform === "win32") {
21
- return Command.make("cmd", "/c", "start", "", url);
22
- }
23
- return Command.make("xdg-open", url);
24
- };
25
-
26
- const openBrowser = (
27
- url: string,
28
- ): Effect.Effect<void, never, CliRuntime | CommandExecutor.CommandExecutor> =>
29
- Effect.gen(function* () {
30
- const runtime = yield* CliRuntime;
31
- const command = buildOpenBrowserCommand(runtime.platform, url);
32
-
33
- const opened = yield* Command.exitCode(command).pipe(
34
- Effect.map((code) => code === 0),
35
- Effect.catchAll(() => Effect.succeed(false)),
36
- );
37
-
38
- if (!opened) {
39
- yield* Console.log(`Open this URL manually:\n${url}`);
40
- }
41
- });
42
-
43
- const browserLogin = Effect.scoped(
44
- Effect.gen(function* () {
45
- const configStore = yield* ConfigStore;
46
- const authStore = yield* AuthStore;
47
- const dashboardUrl = yield* configStore.getDashboardUrl;
48
-
49
- const loginServer = yield* Effect.acquireRelease(
50
- Effect.sync(createBrowserLoginServer),
51
- (server) => Effect.sync(server.stop),
52
- );
53
-
54
- const loginUrl = `${dashboardUrl}/cli-login?callbackUrl=${encodeURIComponent(loginServer.callbackUrl)}`;
55
-
56
- yield* Console.log("Opening browser for better-update login...");
57
- yield* Console.log("");
58
- yield* openBrowser(loginUrl);
59
-
60
- const token = yield* loginServer.waitForToken;
61
- yield* authStore.saveToken(token);
62
- yield* Console.log("");
63
- yield* Console.log("Logged in successfully. Token saved to ~/.better-update/auth.json");
64
- }),
65
- );
66
-
67
- const manualLogin = Effect.gen(function* () {
68
- yield* Console.log("Log in to better-update with an existing API key");
69
- yield* Console.log("Get your API key from the dashboard > API Keys page");
70
- yield* Console.log("");
71
-
72
- const token = Redacted.value(yield* tokenPrompt);
73
- const authStore = yield* AuthStore;
74
- yield* authStore.saveToken(token);
75
- yield* Console.log("");
76
- yield* Console.log("Logged in successfully. Token saved to ~/.better-update/auth.json");
77
- });
78
-
79
- export const runLogin = (options: { readonly manualApiKey: boolean }) =>
80
- Effect.gen(function* () {
81
- if (options.manualApiKey) {
82
- yield* manualLogin;
83
- return;
84
- }
85
-
86
- yield* browserLogin;
87
- });
@@ -1,88 +0,0 @@
1
- import { Effect } from "effect";
2
-
3
- import type { FileSystem } from "@effect/platform";
4
-
5
- import { UpdatePromoteError } from "../lib/exit-codes";
6
- import { formatCause } from "../lib/format-error";
7
- import { loadOptionalSignedPayload } from "../lib/signed-payloads";
8
- import { apiClient } from "../services/api-client";
9
-
10
- import type { AuthRequiredError } from "../lib/exit-codes";
11
- import type { ApiClientService } from "../services/api-client";
12
-
13
- export interface RunUpdatePromoteOptions {
14
- readonly updateId: string;
15
- readonly channel: string;
16
- readonly manifestBodyFile: string | undefined;
17
- readonly signatureFile: string | undefined;
18
- readonly certificateChainFile: string | undefined;
19
- }
20
-
21
- export interface UpdatePromoteResult {
22
- readonly sourceUpdateId: string;
23
- readonly channel: string;
24
- readonly updateId: string;
25
- }
26
-
27
- export const runUpdatePromote = (
28
- options: RunUpdatePromoteOptions,
29
- ): Effect.Effect<
30
- UpdatePromoteResult,
31
- AuthRequiredError | UpdatePromoteError,
32
- ApiClientService | FileSystem.FileSystem
33
- > =>
34
- Effect.gen(function* () {
35
- const api = yield* apiClient;
36
- const signedPayload = yield* loadOptionalSignedPayload({
37
- files: {
38
- manifestBodyFile: options.manifestBodyFile,
39
- signatureFile: options.signatureFile,
40
- certificateChainFile: options.certificateChainFile,
41
- },
42
- label: "Signed promote",
43
- makeError: (message) => new UpdatePromoteError({ message }),
44
- });
45
-
46
- const result = yield* api.updates
47
- .republish({
48
- payload: {
49
- sourceUpdateId: options.updateId,
50
- destinationChannel: options.channel,
51
- ...(signedPayload
52
- ? {
53
- signedUpdates: [
54
- {
55
- sourceUpdateId: options.updateId,
56
- manifestBody: signedPayload.manifestBody,
57
- signature: signedPayload.signature,
58
- certificateChain: signedPayload.certificateChain,
59
- },
60
- ],
61
- }
62
- : {}),
63
- },
64
- })
65
- .pipe(
66
- Effect.catchIf(
67
- (cause): cause is Exclude<typeof cause, AuthRequiredError> =>
68
- (cause as { readonly _tag?: string })._tag !== "AuthRequiredError",
69
- (cause) =>
70
- new UpdatePromoteError({
71
- message: `Failed to promote update: ${formatCause(cause)}`,
72
- }),
73
- ),
74
- );
75
-
76
- const [promotedUpdate] = result.updates;
77
- if (!promotedUpdate) {
78
- return yield* new UpdatePromoteError({
79
- message: "Promote completed without returning a promoted update.",
80
- });
81
- }
82
-
83
- return {
84
- sourceUpdateId: options.updateId,
85
- channel: options.channel,
86
- updateId: promotedUpdate.id,
87
- } as const satisfies UpdatePromoteResult;
88
- });