@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
@@ -1,22 +0,0 @@
1
- import { Data } from "effect";
2
-
3
- import { makeCommandErrorHandler } from "../../lib/command-errors";
4
- import { resolveNamedResourceId as resolveNamedResourceIdBase } from "../../lib/resolve-named-resource";
5
-
6
- export class UpdateCommandError extends Data.TaggedError("UpdateCommandError")<{
7
- readonly message: string;
8
- }> {}
9
-
10
- export const handleUpdateCommandErrors = makeCommandErrorHandler({
11
- UpdateCommandError: 2,
12
- BuildProfileError: 2,
13
- RuntimeVersionError: 2,
14
- UpdateRollbackError: 2,
15
- UpdatePromoteError: 2,
16
- });
17
-
18
- export const resolveNamedResourceId = (params: {
19
- readonly items: readonly { readonly id: string; readonly name: string }[];
20
- readonly kind: string;
21
- readonly name: string;
22
- }) => resolveNamedResourceIdBase(params, (message) => new UpdateCommandError({ message }));
@@ -1,22 +0,0 @@
1
- import { Command } from "@effect/cli";
2
- import { Console } from "effect";
3
-
4
- import { deleteCommand } from "./delete";
5
- import { listCommand } from "./list";
6
- import { promoteCommand } from "./promote";
7
- import { publishCommand } from "./publish";
8
- import { rollbackCommand } from "./rollback";
9
- import { rolloutCommand } from "./rollout";
10
-
11
- export const updateCommand = Command.make("update", {}, () =>
12
- Console.log("Manage OTA updates. Run with --help for subcommands."),
13
- ).pipe(
14
- Command.withSubcommands([
15
- publishCommand,
16
- listCommand,
17
- deleteCommand,
18
- promoteCommand,
19
- rollbackCommand,
20
- rolloutCommand,
21
- ]),
22
- );
@@ -1,60 +0,0 @@
1
- import { Command, Options } from "@effect/cli";
2
- import { Console, Effect, Option } from "effect";
3
-
4
- import { readProjectId } from "../../lib/app-json";
5
- import { printTable } from "../../lib/output";
6
- import { apiClient } from "../../services/api-client";
7
- import { handleUpdateCommandErrors, resolveNamedResourceId } from "./helpers";
8
-
9
- const branch = Options.text("branch").pipe(Options.optional);
10
- const limit = Options.integer("limit").pipe(Options.withDefault(20));
11
-
12
- export const listCommand = Command.make("list", { branch, limit }, (opts) =>
13
- Effect.gen(function* () {
14
- const projectId = yield* readProjectId;
15
- const api = yield* apiClient;
16
- const { items: branches } = yield* api.branches.list({
17
- urlParams: { projectId, page: 1, limit: 1000 },
18
- });
19
-
20
- const branchId = yield* Option.match(opts.branch, {
21
- onNone: () => Effect.succeed(undefined as string | undefined),
22
- onSome: (branchName) =>
23
- resolveNamedResourceId({
24
- items: branches,
25
- kind: "Branch",
26
- name: branchName,
27
- }),
28
- });
29
-
30
- const { items } = yield* api.updates.list({
31
- urlParams: {
32
- projectId,
33
- ...(branchId === undefined ? {} : { branchId }),
34
- page: 1,
35
- limit: opts.limit,
36
- },
37
- });
38
-
39
- if (items.length === 0) {
40
- yield* Console.log("No updates found.");
41
- return;
42
- }
43
-
44
- const branchNames = new Map(branches.map((item) => [item.id, item.name]));
45
-
46
- yield* printTable(
47
- ["Update ID", "Group", "Branch", "Platform", "Runtime", "Rollout", "Rollback", "Created"],
48
- items.map((item) => [
49
- item.id,
50
- item.groupId,
51
- branchNames.get(item.branchId) ?? item.branchId,
52
- item.platform,
53
- item.runtimeVersion,
54
- `${String(item.rolloutPercentage)}%`,
55
- item.isRollback ? "yes" : "no",
56
- item.createdAt,
57
- ]),
58
- );
59
- }).pipe(handleUpdateCommandErrors),
60
- );
@@ -1,30 +0,0 @@
1
- import { Args, Command, Options } from "@effect/cli";
2
- import { Console, Effect, Option } from "effect";
3
-
4
- import { runUpdatePromote } from "../../application/update-promote";
5
- import { handleUpdateCommandErrors } from "./helpers";
6
-
7
- const updateId = Args.text({ name: "updateId" });
8
- const channel = Options.text("channel");
9
- const manifestBodyFile = Options.text("manifest-body-file").pipe(Options.optional);
10
- const signatureFile = Options.text("signature-file").pipe(Options.optional);
11
- const certificateChainFile = Options.text("certificate-chain-file").pipe(Options.optional);
12
-
13
- export const promoteCommand = Command.make(
14
- "promote",
15
- { updateId, channel, manifestBodyFile, signatureFile, certificateChainFile },
16
- (opts) =>
17
- Effect.gen(function* () {
18
- const result = yield* runUpdatePromote({
19
- updateId: opts.updateId,
20
- channel: opts.channel,
21
- manifestBodyFile: Option.getOrUndefined(opts.manifestBodyFile),
22
- signatureFile: Option.getOrUndefined(opts.signatureFile),
23
- certificateChainFile: Option.getOrUndefined(opts.certificateChainFile),
24
- });
25
-
26
- yield* Console.log(
27
- `Promoted update ${result.sourceUpdateId} to channel "${result.channel}" as update ${result.updateId}.`,
28
- );
29
- }).pipe(handleUpdateCommandErrors),
30
- );
@@ -1,94 +0,0 @@
1
- import { Command, Options } from "@effect/cli";
2
- import { Console, Effect, Option } from "effect";
3
-
4
- import { exitWith } from "../../application/command-exit";
5
- import { runUpdatePublish } from "../../application/update-publish";
6
- import { rolloutPercentageOption } from "../../lib/cli-schemas";
7
- import { printTable } from "../../lib/output";
8
-
9
- const branch = Options.text("branch").pipe(Options.optional);
10
- const platform = Options.choice("platform", ["ios", "android", "all"] as const).pipe(
11
- Options.withDefault("all"),
12
- );
13
- const message = Options.text("message").pipe(Options.optional);
14
- const environment = Options.text("environment").pipe(Options.withDefault("production"));
15
- const auto = Options.boolean("auto").pipe(Options.withDefault(false));
16
- const clear = Options.boolean("clear");
17
- const rolloutPercentage = rolloutPercentageOption("rollout-percentage").pipe(Options.optional);
18
- const manifestBodyFile = Options.text("manifest-body-file").pipe(Options.optional);
19
- const signatureFile = Options.text("signature-file").pipe(Options.optional);
20
- const certificateChainFile = Options.text("certificate-chain-file").pipe(Options.optional);
21
- const manifestBodyFileIos = Options.text("manifest-body-file-ios").pipe(Options.optional);
22
- const signatureFileIos = Options.text("signature-file-ios").pipe(Options.optional);
23
- const certificateChainFileIos = Options.text("certificate-chain-file-ios").pipe(Options.optional);
24
- const manifestBodyFileAndroid = Options.text("manifest-body-file-android").pipe(Options.optional);
25
- const signatureFileAndroid = Options.text("signature-file-android").pipe(Options.optional);
26
- const certificateChainFileAndroid = Options.text("certificate-chain-file-android").pipe(
27
- Options.optional,
28
- );
29
-
30
- export const publishCommand = Command.make(
31
- "publish",
32
- {
33
- branch,
34
- platform,
35
- message,
36
- environment,
37
- auto,
38
- clear,
39
- rolloutPercentage,
40
- manifestBodyFile,
41
- signatureFile,
42
- certificateChainFile,
43
- manifestBodyFileIos,
44
- signatureFileIos,
45
- certificateChainFileIos,
46
- manifestBodyFileAndroid,
47
- signatureFileAndroid,
48
- certificateChainFileAndroid,
49
- },
50
- (opts) =>
51
- Effect.gen(function* () {
52
- const result = yield* runUpdatePublish({
53
- branch: Option.getOrUndefined(opts.branch),
54
- platform: opts.platform,
55
- message: Option.getOrUndefined(opts.message),
56
- auto: opts.auto,
57
- environment: opts.environment,
58
- clear: opts.clear,
59
- rolloutPercentage: Option.getOrUndefined(opts.rolloutPercentage),
60
- manifestBodyFile: Option.getOrUndefined(opts.manifestBodyFile),
61
- signatureFile: Option.getOrUndefined(opts.signatureFile),
62
- certificateChainFile: Option.getOrUndefined(opts.certificateChainFile),
63
- manifestBodyFileIos: Option.getOrUndefined(opts.manifestBodyFileIos),
64
- signatureFileIos: Option.getOrUndefined(opts.signatureFileIos),
65
- certificateChainFileIos: Option.getOrUndefined(opts.certificateChainFileIos),
66
- manifestBodyFileAndroid: Option.getOrUndefined(opts.manifestBodyFileAndroid),
67
- signatureFileAndroid: Option.getOrUndefined(opts.signatureFileAndroid),
68
- certificateChainFileAndroid: Option.getOrUndefined(opts.certificateChainFileAndroid),
69
- });
70
-
71
- yield* Console.log(`Published update group ${result.groupId} to branch "${result.branch}".`);
72
- yield* Console.log("");
73
- yield* printTable(
74
- ["Platform", "Update ID", "Runtime Version", "Uploaded", "Reused"],
75
- result.results.map((entry) => [
76
- entry.platform,
77
- entry.updateId,
78
- entry.runtimeVersion,
79
- String(entry.uploadedAssets),
80
- String(entry.deduplicatedAssets),
81
- ]),
82
- );
83
- }).pipe(
84
- Effect.catchTags({
85
- AuthRequiredError: (error) => exitWith(3, error.message),
86
- ProjectNotLinkedError: (error) => exitWith(4, error.message),
87
- BuildProfileError: (error) => exitWith(2, error.message),
88
- RuntimeVersionError: (error) => exitWith(2, error.message),
89
- EnvExportError: (error) => exitWith(7, error.message),
90
- BuildFailedError: (error) => exitWith(6, error.message),
91
- UpdatePublishError: (error) => exitWith(7, error.message),
92
- }),
93
- ),
94
- );
@@ -1,42 +0,0 @@
1
- import { Command, Options } from "@effect/cli";
2
- import { Console, Effect, Option } from "effect";
3
-
4
- import { runUpdateRollback } from "../../application/update-rollback";
5
- import { printTable } from "../../lib/output";
6
- import { handleUpdateCommandErrors } from "./helpers";
7
-
8
- const branch = Options.text("branch");
9
- const platform = Options.choice("platform", ["ios", "android", "all"] as const).pipe(
10
- Options.withDefault("all"),
11
- );
12
- const message = Options.text("message").pipe(Options.optional);
13
- const commitTime = Options.text("commit-time").pipe(Options.optional);
14
- const directiveBodyFile = Options.text("directive-body-file").pipe(Options.optional);
15
- const signatureFile = Options.text("signature-file").pipe(Options.optional);
16
- const certificateChainFile = Options.text("certificate-chain-file").pipe(Options.optional);
17
-
18
- export const rollbackCommand = Command.make(
19
- "rollback",
20
- { branch, platform, message, commitTime, directiveBodyFile, signatureFile, certificateChainFile },
21
- (opts) =>
22
- Effect.gen(function* () {
23
- const result = yield* runUpdateRollback({
24
- branch: opts.branch,
25
- platform: opts.platform,
26
- message: Option.getOrUndefined(opts.message),
27
- commitTime: Option.getOrUndefined(opts.commitTime),
28
- directiveBodyFile: Option.getOrUndefined(opts.directiveBodyFile),
29
- signatureFile: Option.getOrUndefined(opts.signatureFile),
30
- certificateChainFile: Option.getOrUndefined(opts.certificateChainFile),
31
- });
32
-
33
- yield* Console.log(
34
- `Created rollback group ${result.groupId} on branch "${result.branch}" at ${result.commitTime}.`,
35
- );
36
- yield* Console.log("");
37
- yield* printTable(
38
- ["Platform", "Update ID", "Runtime Version"],
39
- result.results.map((entry) => [entry.platform, entry.updateId, entry.runtimeVersion]),
40
- );
41
- }).pipe(handleUpdateCommandErrors),
42
- );
@@ -1,17 +0,0 @@
1
- import { Args, Command } from "@effect/cli";
2
- import { Console, Effect } from "effect";
3
-
4
- import { apiClient } from "../../../services/api-client";
5
- import { handleUpdateCommandErrors } from "../helpers";
6
-
7
- const updateId = Args.text({ name: "updateId" });
8
-
9
- export const completeCommand = Command.make("complete", { updateId }, (opts) =>
10
- Effect.gen(function* () {
11
- const api = yield* apiClient;
12
- const result = yield* api.updates.completeRollout({ path: { id: opts.updateId } });
13
- yield* Console.log(
14
- `Completed rollout for ${opts.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`,
15
- );
16
- }).pipe(handleUpdateCommandErrors),
17
- );
@@ -1,10 +0,0 @@
1
- import { Command } from "@effect/cli";
2
- import { Console } from "effect";
3
-
4
- import { completeCommand } from "./complete";
5
- import { revertCommand } from "./revert";
6
- import { setCommand } from "./set";
7
-
8
- export const rolloutCommand = Command.make("rollout", {}, () =>
9
- Console.log("Manage per-update rollouts. Run with --help for subcommands."),
10
- ).pipe(Command.withSubcommands([setCommand, completeCommand, revertCommand]));
@@ -1,17 +0,0 @@
1
- import { Args, Command } from "@effect/cli";
2
- import { Console, Effect } from "effect";
3
-
4
- import { apiClient } from "../../../services/api-client";
5
- import { handleUpdateCommandErrors } from "../helpers";
6
-
7
- const updateId = Args.text({ name: "updateId" });
8
-
9
- export const revertCommand = Command.make("revert", { updateId }, (opts) =>
10
- Effect.gen(function* () {
11
- const api = yield* apiClient;
12
- const result = yield* api.updates.revertRollout({ path: { id: opts.updateId } });
13
- yield* Console.log(
14
- `Reverted rollout for ${opts.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`,
15
- );
16
- }).pipe(handleUpdateCommandErrors),
17
- );
@@ -1,23 +0,0 @@
1
- import { Args, Command } from "@effect/cli";
2
- import { Console, Effect } from "effect";
3
-
4
- import { rolloutPercentageOption } from "../../../lib/cli-schemas";
5
- import { apiClient } from "../../../services/api-client";
6
- import { handleUpdateCommandErrors } from "../helpers";
7
-
8
- const updateId = Args.text({ name: "updateId" });
9
- const percentage = rolloutPercentageOption("percentage");
10
-
11
- export const setCommand = Command.make("set", { updateId, percentage }, (opts) =>
12
- Effect.gen(function* () {
13
- const api = yield* apiClient;
14
- const result = yield* api.updates.editRollout({
15
- path: { id: opts.updateId },
16
- payload: { percentage: opts.percentage },
17
- });
18
-
19
- yield* Console.log(
20
- `Updated rollout for ${opts.updateId} to ${String(result.rolloutPercentage)}%.`,
21
- );
22
- }).pipe(handleUpdateCommandErrors),
23
- );
package/src/index.ts DELETED
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import process from "node:process";
4
-
5
- import { Command } from "@effect/cli";
6
- import { BunRuntime } from "@effect/platform-bun";
7
- import { Console, Effect } from "effect";
8
-
9
- import { CliLive } from "./app-layer";
10
- import { analyticsCommand } from "./commands/analytics";
11
- import { auditLogsCommand } from "./commands/audit-logs";
12
- import { branchesCommand } from "./commands/branches";
13
- import { buildCommand } from "./commands/build";
14
- import { buildsCommand } from "./commands/builds";
15
- import { channelsCommand } from "./commands/channels";
16
- import { credentialsCommand } from "./commands/credentials";
17
- import { envCommand } from "./commands/env";
18
- import { fingerprintCommand } from "./commands/fingerprint";
19
- import { initCommand } from "./commands/init";
20
- import { loginCommand } from "./commands/login";
21
- import { logoutCommand } from "./commands/logout";
22
- import { projectsCommand } from "./commands/projects";
23
- import { statusCommand } from "./commands/status";
24
- import { updateCommand } from "./commands/update";
25
-
26
- const command = Command.make("better-update", {}, () =>
27
- Console.log("better-update CLI - Run with --help to see available commands"),
28
- ).pipe(
29
- Command.withSubcommands([
30
- loginCommand,
31
- logoutCommand,
32
- initCommand,
33
- statusCommand,
34
- projectsCommand,
35
- branchesCommand,
36
- channelsCommand,
37
- buildCommand,
38
- buildsCommand,
39
- credentialsCommand,
40
- envCommand,
41
- fingerprintCommand,
42
- updateCommand,
43
- analyticsCommand,
44
- auditLogsCommand,
45
- ]),
46
- );
47
-
48
- const cli = Command.run(command, {
49
- name: "better-update",
50
- version: "0.1.0",
51
- });
52
-
53
- cli(process.argv).pipe(Effect.provide(CliLive), BunRuntime.runMain);
@@ -1,114 +0,0 @@
1
- import { CommandExecutor } from "@effect/platform";
2
- import { it } from "@effect/vitest";
3
- import { Effect, Exit } from "effect";
4
-
5
- import { generateAndroidKeystore, renderDistinguishedName } from "./android-keystore";
6
- import { BuildFailedError } from "./exit-codes";
7
- import { failureError } from "./test-utils";
8
-
9
- const makeStubExecutor = (
10
- exitCode: (command: unknown) => Effect.Effect<CommandExecutor.ExitCode, unknown>,
11
- ): CommandExecutor.CommandExecutor =>
12
- ({
13
- [CommandExecutor.TypeId]: CommandExecutor.TypeId,
14
- exitCode,
15
- }) as unknown as CommandExecutor.CommandExecutor;
16
-
17
- const provideStubExecutor = (
18
- exitCode: (command: unknown) => Effect.Effect<CommandExecutor.ExitCode, unknown>,
19
- ) => Effect.provideService(CommandExecutor.CommandExecutor, makeStubExecutor(exitCode));
20
-
21
- describe("android keystore helpers", () => {
22
- it("renderDistinguishedName formats CN and O", () => {
23
- expect(
24
- renderDistinguishedName({
25
- commonName: "Jane Doe",
26
- organization: "Acme Inc",
27
- }),
28
- ).toBe("CN=Jane Doe, O=Acme Inc");
29
- });
30
-
31
- it.effect("generateAndroidKeystore runs keytool with expected arguments", () =>
32
- Effect.gen(function* () {
33
- let executedCommand: Record<string, unknown> | undefined;
34
-
35
- yield* generateAndroidKeystore({
36
- outputPath: "/tmp/release.keystore",
37
- keyAlias: "release-key",
38
- storePassword: "store-pass",
39
- keyPassword: "key-pass",
40
- commonName: "Jane Doe",
41
- organization: "Acme Inc",
42
- }).pipe(
43
- provideStubExecutor((command) => {
44
- executedCommand = command as Record<string, unknown>;
45
- return Effect.succeed(CommandExecutor.ExitCode(0));
46
- }),
47
- );
48
-
49
- expect(executedCommand?.["command"]).toBe("keytool");
50
- expect(executedCommand?.["args"]).toStrictEqual(
51
- expect.arrayContaining([
52
- "-genkeypair",
53
- "-keystore",
54
- "/tmp/release.keystore",
55
- "-alias",
56
- "release-key",
57
- "-storepass",
58
- "store-pass",
59
- "-keypass",
60
- "key-pass",
61
- "-dname",
62
- "CN=Jane Doe, O=Acme Inc",
63
- "-noprompt",
64
- ]),
65
- );
66
- }),
67
- );
68
-
69
- it.effect("generateAndroidKeystore fails with BuildFailedError on non-zero exit", () =>
70
- Effect.gen(function* () {
71
- const exit = yield* generateAndroidKeystore({
72
- outputPath: "/tmp/release.keystore",
73
- keyAlias: "release-key",
74
- storePassword: "store-pass",
75
- keyPassword: "key-pass",
76
- commonName: "Jane Doe",
77
- organization: "Acme Inc",
78
- }).pipe(
79
- provideStubExecutor(() => Effect.succeed(CommandExecutor.ExitCode(23))),
80
- Effect.exit,
81
- );
82
-
83
- expect(Exit.isFailure(exit)).toBe(true);
84
- if (Exit.isFailure(exit)) {
85
- const error = failureError(exit);
86
- expect(error).toBeInstanceOf(BuildFailedError);
87
- expect(error!.message).toContain("exited with code 23");
88
- }
89
- }),
90
- );
91
-
92
- it.effect("generateAndroidKeystore fails with BuildFailedError when spawning fails", () =>
93
- Effect.gen(function* () {
94
- const exit = yield* generateAndroidKeystore({
95
- outputPath: "/tmp/release.keystore",
96
- keyAlias: "release-key",
97
- storePassword: "store-pass",
98
- keyPassword: "key-pass",
99
- commonName: "Jane Doe",
100
- organization: "Acme Inc",
101
- }).pipe(
102
- provideStubExecutor(() => Effect.fail(new Error("spawn failed"))),
103
- Effect.exit,
104
- );
105
-
106
- expect(Exit.isFailure(exit)).toBe(true);
107
- if (Exit.isFailure(exit)) {
108
- const error = failureError(exit);
109
- expect(error).toBeInstanceOf(BuildFailedError);
110
- expect(error!.message).toContain("failed to spawn");
111
- }
112
- }),
113
- );
114
- });
@@ -1,76 +0,0 @@
1
- import { Command } from "@effect/platform";
2
- import { Effect } from "effect";
3
-
4
- import type { CommandExecutor } from "@effect/platform";
5
-
6
- import { BuildFailedError } from "./exit-codes";
7
-
8
- const DEFAULT_KEYSTORE_VALIDITY_DAYS = 10_000;
9
-
10
- export interface GenerateAndroidKeystoreInput {
11
- readonly outputPath: string;
12
- readonly keyAlias: string;
13
- readonly storePassword: string;
14
- readonly keyPassword: string;
15
- readonly commonName: string;
16
- readonly organization: string;
17
- readonly validityDays?: number;
18
- }
19
-
20
- export const renderDistinguishedName = (params: {
21
- readonly commonName: string;
22
- readonly organization: string;
23
- }): string => `CN=${params.commonName}, O=${params.organization}`;
24
-
25
- export const generateAndroidKeystore = (
26
- input: GenerateAndroidKeystoreInput,
27
- ): Effect.Effect<void, BuildFailedError, CommandExecutor.CommandExecutor> =>
28
- Command.exitCode(
29
- Command.make(
30
- "keytool",
31
- "-genkeypair",
32
- "-v",
33
- "-storetype",
34
- "JKS",
35
- "-keystore",
36
- input.outputPath,
37
- "-alias",
38
- input.keyAlias,
39
- "-keyalg",
40
- "RSA",
41
- "-keysize",
42
- "2048",
43
- "-validity",
44
- String(input.validityDays ?? DEFAULT_KEYSTORE_VALIDITY_DAYS),
45
- "-storepass",
46
- input.storePassword,
47
- "-keypass",
48
- input.keyPassword,
49
- "-dname",
50
- renderDistinguishedName({
51
- commonName: input.commonName,
52
- organization: input.organization,
53
- }),
54
- "-noprompt",
55
- ).pipe(Command.stdout("inherit"), Command.stderr("inherit")),
56
- ).pipe(
57
- Effect.mapError(
58
- (cause) =>
59
- new BuildFailedError({
60
- step: "generate android keystore",
61
- exitCode: 1,
62
- message: `generate android keystore failed to spawn: ${String(cause)}`,
63
- }),
64
- ),
65
- Effect.flatMap((code) =>
66
- code === 0
67
- ? Effect.void
68
- : Effect.fail(
69
- new BuildFailedError({
70
- step: "generate android keystore",
71
- exitCode: code,
72
- message: `generate android keystore exited with code ${code}`,
73
- }),
74
- ),
75
- ),
76
- );